ksef-client-ts 0.5.2 → 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.js CHANGED
@@ -298,21 +298,21 @@ var init_rest_request = __esm({
298
298
  _query = [];
299
299
  _presigned = false;
300
300
  _skipAuthRetry = false;
301
- constructor(method, path) {
301
+ constructor(method, path2) {
302
302
  this.method = method;
303
- this.path = path;
303
+ this.path = path2;
304
304
  }
305
- static get(path) {
306
- return new _RestRequest("GET", path);
305
+ static get(path2) {
306
+ return new _RestRequest("GET", path2);
307
307
  }
308
- static post(path) {
309
- return new _RestRequest("POST", path);
308
+ static post(path2) {
309
+ return new _RestRequest("POST", path2);
310
310
  }
311
- static put(path) {
312
- return new _RestRequest("PUT", path);
311
+ static put(path2) {
312
+ return new _RestRequest("PUT", path2);
313
313
  }
314
- static delete(path) {
315
- return new _RestRequest("DELETE", path);
314
+ static delete(path2) {
315
+ return new _RestRequest("DELETE", path2);
316
316
  }
317
317
  body(data) {
318
318
  this._body = data;
@@ -632,9 +632,9 @@ var init_rest_client = __esm({
632
632
  return response;
633
633
  }
634
634
  buildUrl(request) {
635
- const path = this.routeBuilder.build(request.path);
635
+ const path2 = this.routeBuilder.build(request.path);
636
636
  const base = this.options.baseUrl;
637
- const url = new URL(`${base}${path}`);
637
+ const url = new URL(`${base}${path2}`);
638
638
  const query = request.getQuery();
639
639
  for (const [key, value] of query) {
640
640
  url.searchParams.append(key, value);
@@ -955,7 +955,9 @@ function isValidVatUe(value) {
955
955
  return VatUe.test(value);
956
956
  }
957
957
  function isValidNipVatUe(value) {
958
- return NipVatUe.test(value);
958
+ if (!NipVatUe.test(value)) return false;
959
+ const nip = value.split("-")[0];
960
+ return isValidNip(nip);
959
961
  }
960
962
  function isValidInternalId(value) {
961
963
  return InternalId.test(value);
@@ -2645,11 +2647,11 @@ async function validateSchema(xml, options, _parsed) {
2645
2647
  const prefix = rootElement ? `/${rootElement}/` : "/";
2646
2648
  const validationErrors = result.error.issues.map((issue) => {
2647
2649
  const zodPath = issue.path.join("/");
2648
- const path = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2650
+ const path2 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2649
2651
  return {
2650
2652
  code: mapZodErrorCode(issue),
2651
2653
  message: issue.message,
2652
- path
2654
+ path: path2
2653
2655
  };
2654
2656
  });
2655
2657
  return { valid: false, schemaType, errors: validationErrors };
@@ -2689,9 +2691,9 @@ function validateBusinessRules(xml, _parsed) {
2689
2691
  collectDateErrors(object, rootElement, errors);
2690
2692
  return { valid: errors.length === 0, schemaType, errors };
2691
2693
  }
2692
- function collectNipPeselErrors(obj, path, errors) {
2694
+ function collectNipPeselErrors(obj, path2, errors) {
2693
2695
  for (const [key, value] of Object.entries(obj)) {
2694
- const currentPath = path ? `${path}/${key}` : key;
2696
+ const currentPath = path2 ? `${path2}/${key}` : key;
2695
2697
  if (key === "NIP" && typeof value === "string") {
2696
2698
  if (!isValidNip(value)) {
2697
2699
  errors.push({
@@ -2770,6 +2772,88 @@ var init_invoice_validator = __esm({
2770
2772
  }
2771
2773
  });
2772
2774
 
2775
+ // src/models/document-structures/types.ts
2776
+ var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
2777
+ var init_types = __esm({
2778
+ "src/models/document-structures/types.ts"() {
2779
+ "use strict";
2780
+ SystemCode = {
2781
+ FA_2: "FA (2)",
2782
+ FA_3: "FA (3)",
2783
+ PEF_3: "PEF (3)",
2784
+ PEF_KOR_3: "PEF_KOR (3)",
2785
+ FA_RR_1: "FA_RR (1)"
2786
+ };
2787
+ FORM_CODES = {
2788
+ FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
2789
+ FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
2790
+ PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
2791
+ PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
2792
+ FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
2793
+ FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
2794
+ FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
2795
+ };
2796
+ DEFAULT_FORM_CODE = FORM_CODES.FA_3;
2797
+ INVOICE_TYPES_BY_SYSTEM_CODE = {
2798
+ [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2799
+ [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2800
+ [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
2801
+ [SystemCode.PEF_KOR_3]: ["KorPef"],
2802
+ [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
2803
+ };
2804
+ FORM_CODE_KEYS = {
2805
+ FA2: FORM_CODES.FA_2,
2806
+ FA3: FORM_CODES.FA_3,
2807
+ PEF3: FORM_CODES.PEF_3,
2808
+ PEFKOR3: FORM_CODES.PEF_KOR_3,
2809
+ FARR1: FORM_CODES.FA_RR_1
2810
+ };
2811
+ }
2812
+ });
2813
+
2814
+ // src/models/document-structures/helpers.ts
2815
+ function getFormCode(systemCode) {
2816
+ return SYSTEM_CODE_TO_FORM_CODE[systemCode];
2817
+ }
2818
+ function parseFormCode(raw) {
2819
+ const match = ALL_FORM_CODES.find(
2820
+ (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
2821
+ );
2822
+ return match ?? raw;
2823
+ }
2824
+ function validateFormCodeForSession(formCode, sessionType) {
2825
+ if (sessionType === "online") return true;
2826
+ return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
2827
+ }
2828
+ var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
2829
+ var init_helpers = __esm({
2830
+ "src/models/document-structures/helpers.ts"() {
2831
+ "use strict";
2832
+ init_types();
2833
+ SYSTEM_CODE_TO_FORM_CODE = {
2834
+ [SystemCode.FA_2]: FORM_CODES.FA_2,
2835
+ [SystemCode.FA_3]: FORM_CODES.FA_3,
2836
+ [SystemCode.PEF_3]: FORM_CODES.PEF_3,
2837
+ [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
2838
+ [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
2839
+ };
2840
+ ALL_FORM_CODES = Object.values(FORM_CODES);
2841
+ BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
2842
+ SystemCode.PEF_3,
2843
+ SystemCode.PEF_KOR_3
2844
+ ]);
2845
+ }
2846
+ });
2847
+
2848
+ // src/models/document-structures/index.ts
2849
+ var init_document_structures = __esm({
2850
+ "src/models/document-structures/index.ts"() {
2851
+ "use strict";
2852
+ init_types();
2853
+ init_helpers();
2854
+ }
2855
+ });
2856
+
2773
2857
  // src/services/auth.ts
2774
2858
  var AuthService;
2775
2859
  var init_auth = __esm({
@@ -2885,6 +2969,32 @@ var init_online_session = __esm({
2885
2969
  }
2886
2970
  });
2887
2971
 
2972
+ // src/utils/concurrency.ts
2973
+ async function runWithConcurrency(tasks, parallelism) {
2974
+ if (tasks.length === 0) return;
2975
+ const limit = Math.max(1, Math.min(parallelism, tasks.length));
2976
+ const controller = new AbortController();
2977
+ let index = 0;
2978
+ const workers = Array.from({ length: limit }, async () => {
2979
+ while (index < tasks.length && !controller.signal.aborted) {
2980
+ const current = index;
2981
+ index += 1;
2982
+ try {
2983
+ await tasks[current](controller.signal);
2984
+ } catch (err) {
2985
+ controller.abort();
2986
+ throw err;
2987
+ }
2988
+ }
2989
+ });
2990
+ await Promise.all(workers);
2991
+ }
2992
+ var init_concurrency = __esm({
2993
+ "src/utils/concurrency.ts"() {
2994
+ "use strict";
2995
+ }
2996
+ });
2997
+
2888
2998
  // src/services/batch-session.ts
2889
2999
  var BatchSessionService;
2890
3000
  var init_batch_session = __esm({
@@ -2893,6 +3003,7 @@ var init_batch_session = __esm({
2893
3003
  init_ksef_feature();
2894
3004
  init_rest_request();
2895
3005
  init_routes();
3006
+ init_concurrency();
2896
3007
  BatchSessionService = class {
2897
3008
  restClient;
2898
3009
  constructor(restClient) {
@@ -2906,12 +3017,10 @@ var init_batch_session = __esm({
2906
3017
  const response = await this.restClient.execute(req);
2907
3018
  return response.body;
2908
3019
  }
2909
- async sendParts(openResponse, parts) {
2910
- const uploadRequests = openResponse.partUploadRequests;
2911
- const tasks = parts.map(async (part) => {
2912
- const uploadReq = uploadRequests.find(
2913
- (r) => r.ordinalNumber === part.ordinalNumber
2914
- );
3020
+ async sendParts(openResponse, parts, parallelism) {
3021
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3022
+ const tasks = parts.map((part) => async (signal) => {
3023
+ const uploadReq = uploadMap.get(part.ordinalNumber);
2915
3024
  if (!uploadReq) {
2916
3025
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
2917
3026
  }
@@ -2919,25 +3028,38 @@ var init_batch_session = __esm({
2919
3028
  for (const [k, v] of Object.entries(uploadReq.headers)) {
2920
3029
  if (v != null) headers[k] = v;
2921
3030
  }
2922
- await fetch(uploadReq.url, {
3031
+ const resp = await fetch(uploadReq.url, {
2923
3032
  method: uploadReq.method,
2924
3033
  headers,
2925
- body: part.data
3034
+ body: part.data,
3035
+ signal
2926
3036
  });
3037
+ if (!resp.ok) {
3038
+ const body = await resp.text().catch(() => "");
3039
+ throw new Error(
3040
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3041
+ );
3042
+ }
2927
3043
  });
2928
- await Promise.all(tasks);
3044
+ if (parallelism !== void 0) {
3045
+ await runWithConcurrency(tasks, parallelism);
3046
+ } else {
3047
+ const ac = new AbortController();
3048
+ await Promise.all(tasks.map((t) => t(ac.signal).catch((err) => {
3049
+ ac.abort();
3050
+ throw err;
3051
+ })));
3052
+ }
2929
3053
  }
2930
3054
  /**
2931
- * Upload parts sequentially (not in parallel) because each part uses a
2932
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
2933
- * backpressure issues and exceed memory limits for large payloads.
3055
+ * Upload parts using streaming bodies (`duplex: 'half'`).
3056
+ * By default uploads sequentially to avoid backpressure issues.
3057
+ * Pass `parallelism` to enable bounded concurrent uploads.
2934
3058
  */
2935
- async sendPartsWithStream(openResponse, parts) {
2936
- const uploadRequests = openResponse.partUploadRequests;
2937
- for (const part of parts) {
2938
- const uploadReq = uploadRequests.find(
2939
- (r) => r.ordinalNumber === part.ordinalNumber
2940
- );
3059
+ async sendPartsWithStream(openResponse, parts, parallelism) {
3060
+ const uploadMap = new Map(openResponse.partUploadRequests.map((r) => [r.ordinalNumber, r]));
3061
+ const uploadPart = async (part, signal) => {
3062
+ const uploadReq = uploadMap.get(part.ordinalNumber);
2941
3063
  if (!uploadReq) {
2942
3064
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
2943
3065
  }
@@ -2949,11 +3071,23 @@ var init_batch_session = __esm({
2949
3071
  method: uploadReq.method,
2950
3072
  headers,
2951
3073
  body: part.dataStream,
3074
+ signal,
2952
3075
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
2953
3076
  duplex: "half"
2954
3077
  });
2955
3078
  if (!resp.ok) {
2956
- throw new Error(`Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
3079
+ const body = await resp.text().catch(() => "");
3080
+ throw new Error(
3081
+ `Upload failed for part ${part.ordinalNumber}: HTTP ${resp.status}${body ? ` \u2014 ${body}` : ""}`
3082
+ );
3083
+ }
3084
+ };
3085
+ if (parallelism !== void 0) {
3086
+ await runWithConcurrency(parts.map((p) => (signal) => uploadPart(p, signal)), parallelism);
3087
+ } else {
3088
+ const { signal } = new AbortController();
3089
+ for (const part of parts) {
3090
+ await uploadPart(part, signal);
2957
3091
  }
2958
3092
  }
2959
3093
  }
@@ -3066,7 +3200,10 @@ var init_invoice_download = __esm({
3066
3200
  async getInvoice(ksefNumber) {
3067
3201
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
3068
3202
  const response = await this.restClient.executeRaw(req);
3069
- return new TextDecoder().decode(response.body);
3203
+ return {
3204
+ xml: new TextDecoder().decode(response.body),
3205
+ hash: response.headers.get("x-ms-meta-hash") ?? void 0
3206
+ };
3070
3207
  }
3071
3208
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
3072
3209
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -3335,20 +3472,20 @@ var init_lighthouse = __esm({
3335
3472
  this.lighthouseUrl = options.lighthouseUrl;
3336
3473
  this.timeout = options.timeout;
3337
3474
  }
3338
- async fetchJson(path) {
3475
+ async fetchJson(path2) {
3339
3476
  if (!this.lighthouseUrl) {
3340
3477
  throw new KSeFError(
3341
3478
  "Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
3342
3479
  );
3343
3480
  }
3344
- const response = await fetch(`${this.lighthouseUrl}${path}`, {
3481
+ const response = await fetch(`${this.lighthouseUrl}${path2}`, {
3345
3482
  headers: { Accept: "application/json" },
3346
3483
  signal: AbortSignal.timeout(this.timeout)
3347
3484
  });
3348
3485
  if (!response.ok) {
3349
3486
  const body = await response.text();
3350
3487
  throw new KSeFError(
3351
- `Lighthouse ${path} failed: HTTP ${response.status} \u2014 ${body}`
3488
+ `Lighthouse ${path2} failed: HTTP ${response.status} \u2014 ${body}`
3352
3489
  );
3353
3490
  }
3354
3491
  return await response.json();
@@ -4328,6 +4465,458 @@ var init_zip = __esm({
4328
4465
  }
4329
4466
  });
4330
4467
 
4468
+ // src/offline/holidays.ts
4469
+ function toDateKey(d) {
4470
+ return d.toISOString().slice(0, 10);
4471
+ }
4472
+ function dateKey(year, month, day) {
4473
+ return toDateKey(new Date(Date.UTC(year, month - 1, day)));
4474
+ }
4475
+ function addDays(d, n) {
4476
+ const result = new Date(d);
4477
+ result.setUTCDate(result.getUTCDate() + n);
4478
+ return result;
4479
+ }
4480
+ function computeEasterSunday(year) {
4481
+ const a = year % 19;
4482
+ const b = Math.floor(year / 100);
4483
+ const c = year % 100;
4484
+ const d = Math.floor(b / 4);
4485
+ const e = b % 4;
4486
+ const f = Math.floor((b + 8) / 25);
4487
+ const g = Math.floor((b - f + 1) / 3);
4488
+ const h = (19 * a + b - d - g + 15) % 30;
4489
+ const i = Math.floor(c / 4);
4490
+ const k = c % 4;
4491
+ const l = (32 + 2 * e + 2 * i - h - k) % 7;
4492
+ const m = Math.floor((a + 11 * h + 22 * l) / 451);
4493
+ const month = Math.floor((h + l - 7 * m + 114) / 31);
4494
+ const day = (h + l - 7 * m + 114) % 31 + 1;
4495
+ return new Date(Date.UTC(year, month - 1, day));
4496
+ }
4497
+ function getPolishHolidays(year) {
4498
+ const cached = holidayCache.get(year);
4499
+ if (cached) return cached;
4500
+ const easter = computeEasterSunday(year);
4501
+ const holidays = /* @__PURE__ */ new Set([
4502
+ // Fixed
4503
+ dateKey(year, 1, 1),
4504
+ dateKey(year, 1, 6),
4505
+ dateKey(year, 5, 1),
4506
+ dateKey(year, 5, 3),
4507
+ dateKey(year, 8, 15),
4508
+ dateKey(year, 11, 1),
4509
+ dateKey(year, 11, 11),
4510
+ dateKey(year, 12, 25),
4511
+ dateKey(year, 12, 26),
4512
+ // Wigilia — added by Dz.U. 2024 poz. 1965, effective from 2025
4513
+ ...year >= 2025 ? [dateKey(year, 12, 24)] : [],
4514
+ // Moveable
4515
+ toDateKey(easter),
4516
+ toDateKey(addDays(easter, 1)),
4517
+ toDateKey(addDays(easter, 49)),
4518
+ toDateKey(addDays(easter, 60))
4519
+ ]);
4520
+ holidayCache.set(year, holidays);
4521
+ return holidays;
4522
+ }
4523
+ function isPolishHoliday(date) {
4524
+ return getPolishHolidays(date.getUTCFullYear()).has(toDateKey(date));
4525
+ }
4526
+ var holidayCache;
4527
+ var init_holidays = __esm({
4528
+ "src/offline/holidays.ts"() {
4529
+ "use strict";
4530
+ holidayCache = /* @__PURE__ */ new Map();
4531
+ }
4532
+ });
4533
+
4534
+ // src/offline/deadline.ts
4535
+ function getDefaultReason(mode) {
4536
+ switch (mode) {
4537
+ case "offline24":
4538
+ return "PLANNED";
4539
+ case "offline":
4540
+ return "SYSTEM_UNAVAILABLE";
4541
+ case "awaryjny":
4542
+ return "EMERGENCY";
4543
+ case "awaria_calkowita":
4544
+ return "TOTAL_FAILURE";
4545
+ }
4546
+ }
4547
+ function nextBusinessDay(from) {
4548
+ const d = new Date(from);
4549
+ d.setUTCDate(d.getUTCDate() + 1);
4550
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
4551
+ d.setUTCDate(d.getUTCDate() + 1);
4552
+ }
4553
+ return d;
4554
+ }
4555
+ function addBusinessDays(from, days) {
4556
+ if (!Number.isInteger(days) || days < 0) {
4557
+ throw new Error(`days must be a non-negative integer, got ${days}`);
4558
+ }
4559
+ const d = new Date(from);
4560
+ let remaining = days;
4561
+ while (remaining > 0) {
4562
+ d.setUTCDate(d.getUTCDate() + 1);
4563
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
4564
+ remaining--;
4565
+ }
4566
+ }
4567
+ return d;
4568
+ }
4569
+ function endOfDay(d) {
4570
+ const result = new Date(d);
4571
+ result.setUTCHours(23, 59, 59, 999);
4572
+ return result;
4573
+ }
4574
+ function getMaintenanceEndFallback(mw) {
4575
+ if (mw.endTime) {
4576
+ return new Date(mw.endTime);
4577
+ }
4578
+ const start = new Date(mw.startTime);
4579
+ start.setUTCDate(start.getUTCDate() + 7);
4580
+ return start;
4581
+ }
4582
+ function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
4583
+ const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
4584
+ switch (mode) {
4585
+ case "offline24": {
4586
+ return endOfDay(nextBusinessDay(date));
4587
+ }
4588
+ case "offline": {
4589
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4590
+ return endOfDay(nextBusinessDay(base));
4591
+ }
4592
+ case "awaryjny": {
4593
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4594
+ return endOfDay(addBusinessDays(base, 7));
4595
+ }
4596
+ case "awaria_calkowita": {
4597
+ return new Date(FAR_FUTURE);
4598
+ }
4599
+ }
4600
+ }
4601
+ function extendDeadlineForMaintenance(currentDeadline, maintenanceWindow, mode = "awaryjny") {
4602
+ const current = typeof currentDeadline === "string" ? new Date(currentDeadline) : new Date(currentDeadline);
4603
+ const mwEnd = getMaintenanceEndFallback(maintenanceWindow);
4604
+ if (mwEnd.getTime() > current.getTime()) {
4605
+ switch (mode) {
4606
+ case "offline24":
4607
+ case "offline":
4608
+ return endOfDay(nextBusinessDay(mwEnd));
4609
+ case "awaryjny":
4610
+ return endOfDay(addBusinessDays(mwEnd, 7));
4611
+ case "awaria_calkowita":
4612
+ return new Date(FAR_FUTURE);
4613
+ }
4614
+ }
4615
+ return current;
4616
+ }
4617
+ function isExpired(submitBy) {
4618
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4619
+ return Date.now() > deadline.getTime();
4620
+ }
4621
+ function getTimeUntilDeadline(submitBy) {
4622
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4623
+ return Math.max(0, deadline.getTime() - Date.now());
4624
+ }
4625
+ var FAR_FUTURE;
4626
+ var init_deadline = __esm({
4627
+ "src/offline/deadline.ts"() {
4628
+ "use strict";
4629
+ init_holidays();
4630
+ FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4631
+ }
4632
+ });
4633
+
4634
+ // src/workflows/offline-invoice-workflow.ts
4635
+ import crypto7 from "crypto";
4636
+ var OfflineInvoiceWorkflow;
4637
+ var init_offline_invoice_workflow = __esm({
4638
+ "src/workflows/offline-invoice-workflow.ts"() {
4639
+ "use strict";
4640
+ init_ksef_api_error();
4641
+ init_deadline();
4642
+ init_document_structures();
4643
+ OfflineInvoiceWorkflow = class {
4644
+ constructor(qrService) {
4645
+ this.qrService = qrService;
4646
+ }
4647
+ async generate(input, options) {
4648
+ if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
4649
+ throw new Error("invoiceXml must not be empty");
4650
+ }
4651
+ if (!input.invoiceNumber) {
4652
+ throw new Error("invoiceNumber is required");
4653
+ }
4654
+ if (!input.sellerNip) {
4655
+ throw new Error("sellerNip is required");
4656
+ }
4657
+ const mode = options?.mode ?? "offline24";
4658
+ const reason = getDefaultReason(mode);
4659
+ const invoiceHashBase64 = crypto7.createHash("sha256").update(input.invoiceXml).digest("base64");
4660
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4661
+ input.sellerNip,
4662
+ input.invoiceDate,
4663
+ invoiceHashBase64
4664
+ );
4665
+ let kod2Url;
4666
+ if (options?.certificate) {
4667
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4668
+ input.sellerIdentifier.type,
4669
+ input.sellerIdentifier.value,
4670
+ input.sellerNip,
4671
+ options.certificate.certificateSerial,
4672
+ invoiceHashBase64,
4673
+ options.certificate.privateKeyPem
4674
+ );
4675
+ }
4676
+ const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4677
+ const metadata = {
4678
+ id: crypto7.randomUUID(),
4679
+ mode,
4680
+ reason,
4681
+ status: "GENERATED",
4682
+ invoiceNumber: input.invoiceNumber,
4683
+ invoiceDate: input.invoiceDate,
4684
+ invoiceXml: input.invoiceXml,
4685
+ sellerNip: input.sellerNip,
4686
+ sellerIdentifier: input.sellerIdentifier,
4687
+ buyerIdentifier: input.buyerIdentifier,
4688
+ totalAmount: input.totalAmount,
4689
+ currency: input.currency,
4690
+ kod1Url,
4691
+ kod2Url,
4692
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4693
+ submitBy,
4694
+ maintenanceWindowId: options?.maintenanceWindow?.id
4695
+ };
4696
+ if (options?.storage) {
4697
+ await options.storage.save(metadata);
4698
+ }
4699
+ return metadata;
4700
+ }
4701
+ async submit(client, options) {
4702
+ const { storage, checkExpiry = true } = options;
4703
+ let invoices;
4704
+ if (options.invoiceIds) {
4705
+ invoices = [];
4706
+ const notFound = [];
4707
+ for (const id of options.invoiceIds) {
4708
+ const inv = await storage.get(id);
4709
+ if (inv) {
4710
+ invoices.push(inv);
4711
+ } else {
4712
+ notFound.push(id);
4713
+ }
4714
+ }
4715
+ if (notFound.length > 0) {
4716
+ throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
4717
+ }
4718
+ } else {
4719
+ invoices = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
4720
+ }
4721
+ const result = {
4722
+ total: invoices.length,
4723
+ submitted: 0,
4724
+ accepted: 0,
4725
+ rejected: 0,
4726
+ failed: 0,
4727
+ expired: 0,
4728
+ results: []
4729
+ };
4730
+ if (invoices.length === 0) return result;
4731
+ const pending = [];
4732
+ for (const inv of invoices) {
4733
+ if (checkExpiry && isExpired(inv.submitBy)) {
4734
+ await storage.update(inv.id, { status: "EXPIRED" });
4735
+ result.expired++;
4736
+ result.results.push({
4737
+ invoiceId: inv.id,
4738
+ invoiceNumber: inv.invoiceNumber,
4739
+ status: "EXPIRED"
4740
+ });
4741
+ } else {
4742
+ pending.push(inv);
4743
+ }
4744
+ }
4745
+ if (pending.length === 0) return result;
4746
+ await client.crypto.init();
4747
+ const encData = client.crypto.getEncryptionData();
4748
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4749
+ const openResp = await client.onlineSession.openSession(
4750
+ { formCode, encryption: encData.encryptionInfo }
4751
+ );
4752
+ const sessionRef = openResp.referenceNumber;
4753
+ try {
4754
+ for (const inv of pending) {
4755
+ await storage.update(inv.id, { status: "QUEUED" });
4756
+ try {
4757
+ const data = new TextEncoder().encode(inv.invoiceXml);
4758
+ const plainMeta = client.crypto.getFileMetadata(data);
4759
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4760
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4761
+ await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
4762
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4763
+ invoiceHash: plainMeta.hashSHA,
4764
+ invoiceSize: plainMeta.fileSize,
4765
+ encryptedInvoiceHash: encMeta.hashSHA,
4766
+ encryptedInvoiceSize: encMeta.fileSize,
4767
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4768
+ offlineMode: true
4769
+ });
4770
+ await storage.update(inv.id, {
4771
+ status: "ACCEPTED",
4772
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4773
+ ksefReferenceNumber: resp.referenceNumber
4774
+ });
4775
+ result.submitted++;
4776
+ result.accepted++;
4777
+ result.results.push({
4778
+ invoiceId: inv.id,
4779
+ invoiceNumber: inv.invoiceNumber,
4780
+ status: "ACCEPTED",
4781
+ ksefReferenceNumber: resp.referenceNumber
4782
+ });
4783
+ } catch (err) {
4784
+ const message = err instanceof Error ? err.message : String(err);
4785
+ if (err instanceof KSeFApiError) {
4786
+ await storage.update(inv.id, {
4787
+ status: "REJECTED",
4788
+ error: { code: err.statusCode, message }
4789
+ });
4790
+ result.submitted++;
4791
+ result.rejected++;
4792
+ result.results.push({
4793
+ invoiceId: inv.id,
4794
+ invoiceNumber: inv.invoiceNumber,
4795
+ status: "REJECTED",
4796
+ error: { code: err.statusCode, message }
4797
+ });
4798
+ } else {
4799
+ await storage.update(inv.id, {
4800
+ status: "QUEUED",
4801
+ error: { code: 0, message }
4802
+ });
4803
+ result.failed++;
4804
+ result.results.push({
4805
+ invoiceId: inv.id,
4806
+ invoiceNumber: inv.invoiceNumber,
4807
+ status: "QUEUED",
4808
+ error: { code: 0, message }
4809
+ });
4810
+ }
4811
+ }
4812
+ }
4813
+ } finally {
4814
+ try {
4815
+ await client.onlineSession.closeSession(sessionRef);
4816
+ } catch {
4817
+ }
4818
+ }
4819
+ return result;
4820
+ }
4821
+ // TODO(perf): Each correction opens a separate KSeF session.
4822
+ // For batch corrections, consider a correctBatch() sharing one session (like submit()).
4823
+ async correct(client, options) {
4824
+ const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
4825
+ const original = await storage.get(rejectedInvoiceId);
4826
+ if (!original) {
4827
+ throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
4828
+ }
4829
+ if (original.status !== "REJECTED") {
4830
+ throw new Error(
4831
+ original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4832
+ );
4833
+ }
4834
+ const originalHash = crypto7.createHash("sha256").update(original.invoiceXml).digest("base64");
4835
+ await client.crypto.init();
4836
+ const encData = client.crypto.getEncryptionData();
4837
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4838
+ const openResp = await client.onlineSession.openSession(
4839
+ { formCode, encryption: encData.encryptionInfo }
4840
+ );
4841
+ const sessionRef = openResp.referenceNumber;
4842
+ try {
4843
+ const data = new TextEncoder().encode(correctedInvoiceXml);
4844
+ const plainMeta = client.crypto.getFileMetadata(data);
4845
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4846
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4847
+ const correctionId = crypto7.randomUUID();
4848
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4849
+ const correctedHashBase64 = crypto7.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4850
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4851
+ original.sellerNip,
4852
+ original.invoiceDate,
4853
+ correctedHashBase64
4854
+ );
4855
+ let kod2Url;
4856
+ if (options.certificate) {
4857
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4858
+ original.sellerIdentifier.type,
4859
+ original.sellerIdentifier.value,
4860
+ original.sellerNip,
4861
+ options.certificate.certificateSerial,
4862
+ correctedHashBase64,
4863
+ options.certificate.privateKeyPem
4864
+ );
4865
+ }
4866
+ const correctionMetadata = {
4867
+ id: correctionId,
4868
+ mode: original.mode,
4869
+ reason: original.reason,
4870
+ status: "SUBMITTED",
4871
+ invoiceNumber: original.invoiceNumber,
4872
+ invoiceDate: original.invoiceDate,
4873
+ invoiceXml: correctedInvoiceXml,
4874
+ sellerNip: original.sellerNip,
4875
+ sellerIdentifier: original.sellerIdentifier,
4876
+ buyerIdentifier: original.buyerIdentifier,
4877
+ kod1Url,
4878
+ kod2Url,
4879
+ generatedAt: submittedAt,
4880
+ submitBy: original.submitBy,
4881
+ submittedAt,
4882
+ correctedInvoiceId: rejectedInvoiceId
4883
+ };
4884
+ await storage.save(correctionMetadata);
4885
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4886
+ invoiceHash: plainMeta.hashSHA,
4887
+ invoiceSize: plainMeta.fileSize,
4888
+ encryptedInvoiceHash: encMeta.hashSHA,
4889
+ encryptedInvoiceSize: encMeta.fileSize,
4890
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4891
+ offlineMode: true,
4892
+ hashOfCorrectedInvoice: originalHash
4893
+ });
4894
+ await storage.update(correctionId, {
4895
+ status: "ACCEPTED",
4896
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4897
+ ksefReferenceNumber: resp.referenceNumber
4898
+ });
4899
+ await storage.update(rejectedInvoiceId, {
4900
+ status: "CORRECTED",
4901
+ correctedBy: correctionId
4902
+ });
4903
+ return {
4904
+ invoiceId: correctionId,
4905
+ invoiceNumber: correctionMetadata.invoiceNumber,
4906
+ status: "ACCEPTED",
4907
+ ksefReferenceNumber: resp.referenceNumber
4908
+ };
4909
+ } finally {
4910
+ try {
4911
+ await client.onlineSession.closeSession(sessionRef);
4912
+ } catch {
4913
+ }
4914
+ }
4915
+ }
4916
+ };
4917
+ }
4918
+ });
4919
+
4331
4920
  // src/client.ts
4332
4921
  var client_exports = {};
4333
4922
  __export(client_exports, {
@@ -4393,6 +4982,7 @@ var init_client = __esm({
4393
4982
  init_cryptography_service();
4394
4983
  init_verification_link_service();
4395
4984
  init_auth_xml_builder();
4985
+ init_offline_invoice_workflow();
4396
4986
  KSeFClient = class {
4397
4987
  auth;
4398
4988
  activeSessions;
@@ -4411,6 +5001,8 @@ var init_client = __esm({
4411
5001
  qr;
4412
5002
  options;
4413
5003
  authManager;
5004
+ _baseRestClientConfig;
5005
+ _offline;
4414
5006
  constructor(options) {
4415
5007
  this.options = resolveOptions(options);
4416
5008
  const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
@@ -4421,6 +5013,8 @@ var init_client = __esm({
4421
5013
  });
4422
5014
  this.authManager = authManager;
4423
5015
  const restClientConfig = buildRestClientConfig(options, authManager);
5016
+ const { authManager: _am, ...baseConfig } = restClientConfig;
5017
+ this._baseRestClientConfig = baseConfig;
4424
5018
  const restClient = new RestClient(this.options, restClientConfig);
4425
5019
  const fetcher = new CertificateFetcher(restClient);
4426
5020
  this.crypto = new CryptographyService(fetcher);
@@ -4439,6 +5033,16 @@ var init_client = __esm({
4439
5033
  this.testData = new TestDataService(restClient, this.options.environmentName);
4440
5034
  this.qr = new VerificationLinkService(this.options.baseQrUrl);
4441
5035
  }
5036
+ get offline() {
5037
+ if (!this._offline) {
5038
+ this._offline = new OfflineInvoiceWorkflow(this.qr);
5039
+ }
5040
+ return this._offline;
5041
+ }
5042
+ /** @internal Create a RestClient with a different AuthManager, preserving transport/retry/rateLimit config. */
5043
+ createScopedRestClient(authManager) {
5044
+ return new RestClient(this.options, { ...this._baseRestClientConfig, authManager });
5045
+ }
4442
5046
  async loginWithToken(token, nip) {
4443
5047
  const challenge = await this.auth.getChallenge();
4444
5048
  await this.crypto.init();
@@ -4522,65 +5126,8 @@ init_xml_to_object();
4522
5126
  init_schema_registry();
4523
5127
  init_invoice_validator();
4524
5128
 
4525
- // src/models/document-structures/types.ts
4526
- var SystemCode = {
4527
- FA_2: "FA (2)",
4528
- FA_3: "FA (3)",
4529
- PEF_3: "PEF (3)",
4530
- PEF_KOR_3: "PEF_KOR (3)",
4531
- FA_RR_1: "FA_RR (1)"
4532
- };
4533
- var FORM_CODES = {
4534
- FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
4535
- FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
4536
- PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
4537
- PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
4538
- FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
4539
- FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
4540
- FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
4541
- };
4542
- var DEFAULT_FORM_CODE = FORM_CODES.FA_3;
4543
- var INVOICE_TYPES_BY_SYSTEM_CODE = {
4544
- [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4545
- [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4546
- [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
4547
- [SystemCode.PEF_KOR_3]: ["KorPef"],
4548
- [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
4549
- };
4550
- var FORM_CODE_KEYS = {
4551
- FA2: FORM_CODES.FA_2,
4552
- FA3: FORM_CODES.FA_3,
4553
- PEF3: FORM_CODES.PEF_3,
4554
- PEFKOR3: FORM_CODES.PEF_KOR_3,
4555
- FARR1: FORM_CODES.FA_RR_1
4556
- };
4557
-
4558
- // src/models/document-structures/helpers.ts
4559
- var SYSTEM_CODE_TO_FORM_CODE = {
4560
- [SystemCode.FA_2]: FORM_CODES.FA_2,
4561
- [SystemCode.FA_3]: FORM_CODES.FA_3,
4562
- [SystemCode.PEF_3]: FORM_CODES.PEF_3,
4563
- [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
4564
- [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
4565
- };
4566
- var ALL_FORM_CODES = Object.values(FORM_CODES);
4567
- var BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
4568
- SystemCode.PEF_3,
4569
- SystemCode.PEF_KOR_3
4570
- ]);
4571
- function getFormCode(systemCode) {
4572
- return SYSTEM_CODE_TO_FORM_CODE[systemCode];
4573
- }
4574
- function parseFormCode(raw) {
4575
- const match = ALL_FORM_CODES.find(
4576
- (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
4577
- );
4578
- return match ?? raw;
4579
- }
4580
- function validateFormCodeForSession(formCode, sessionType) {
4581
- if (sessionType === "online") return true;
4582
- return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
4583
- }
5129
+ // src/models/index.ts
5130
+ init_document_structures();
4584
5131
 
4585
5132
  // src/services/index.ts
4586
5133
  init_auth();
@@ -5315,6 +5862,18 @@ function parseKSeFTokenContext(token) {
5315
5862
  };
5316
5863
  }
5317
5864
 
5865
+ // src/utils/hash.ts
5866
+ import crypto6 from "crypto";
5867
+ function sha256Base642(data) {
5868
+ return crypto6.createHash("sha256").update(data).digest("base64");
5869
+ }
5870
+ function verifyHash(data, expectedHash) {
5871
+ return sha256Base642(data) === expectedHash;
5872
+ }
5873
+
5874
+ // src/utils/index.ts
5875
+ init_concurrency();
5876
+
5318
5877
  // src/workflows/polling.ts
5319
5878
  async function pollUntil(action, condition, options) {
5320
5879
  const intervalMs = options?.intervalMs ?? 2e3;
@@ -5332,6 +5891,13 @@ async function pollUntil(action, condition, options) {
5332
5891
  );
5333
5892
  }
5334
5893
 
5894
+ // src/workflows/online-session-workflow.ts
5895
+ init_ksef_session_expired_error();
5896
+ init_auth_manager();
5897
+ init_online_session();
5898
+ init_session_status();
5899
+ init_document_structures();
5900
+
5335
5901
  // src/xml/upo-parser.ts
5336
5902
  init_ksef_validation_error();
5337
5903
  import { XMLParser } from "fast-xml-parser";
@@ -5450,21 +6016,54 @@ function parseUpoXml(xml) {
5450
6016
  };
5451
6017
  }
5452
6018
 
6019
+ // src/xml/invoice-field-extractor.ts
6020
+ import { XMLParser as XMLParser2 } from "fast-xml-parser";
6021
+ var invoiceParser = new XMLParser2({
6022
+ ignoreAttributes: false,
6023
+ attributeNamePrefix: "@_",
6024
+ parseTagValue: false,
6025
+ parseAttributeValue: false,
6026
+ removeNSPrefix: true,
6027
+ trimValues: false
6028
+ });
6029
+ function extractInvoiceFields(xml) {
6030
+ const parsed = invoiceParser.parse(xml);
6031
+ const root = parsed?.Faktura;
6032
+ if (!root || typeof root !== "object") {
6033
+ throw new Error(
6034
+ "Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
6035
+ );
6036
+ }
6037
+ if (root.Fa && typeof root.Fa === "object") {
6038
+ const invoiceDate = nonEmptyString(root.Fa.P_1);
6039
+ const invoiceNumber = nonEmptyString(root.Fa.P_2);
6040
+ if (invoiceDate && invoiceNumber) {
6041
+ return { invoiceNumber, invoiceDate };
6042
+ }
6043
+ }
6044
+ if (root.FakturaRR && typeof root.FakturaRR === "object") {
6045
+ const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
6046
+ const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
6047
+ if (invoiceDate && invoiceNumber) {
6048
+ return { invoiceNumber, invoiceDate };
6049
+ }
6050
+ }
6051
+ throw new Error(
6052
+ "Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
6053
+ );
6054
+ }
6055
+ function nonEmptyString(value) {
6056
+ return typeof value === "string" && value.length > 0 ? value : void 0;
6057
+ }
6058
+
5453
6059
  // src/workflows/online-session-workflow.ts
5454
6060
  init_invoice_validator();
5455
6061
  init_ksef_validation_error();
5456
- async function openOnlineSession(client, options) {
5457
- await client.crypto.init();
5458
- const encData = client.crypto.getEncryptionData();
5459
- const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
5460
- const openResp = await client.onlineSession.openSession(
5461
- { formCode, encryption: encData.encryptionInfo },
5462
- options?.upoVersion
5463
- );
5464
- const sessionRef = openResp.referenceNumber;
6062
+ function buildSessionHandle(params) {
6063
+ const { deps, sessionRef, validUntil, cipherKey, cipherIv, formCode, validate: validate2 } = params;
5465
6064
  async function fetchUpo(pollOpts) {
5466
6065
  const result = await pollUntil(
5467
- () => client.sessionStatus.getSessionStatus(sessionRef),
6066
+ () => deps.sessionStatus.getSessionStatus(sessionRef),
5468
6067
  (s) => s.status.code === 200 || s.status.code >= 400,
5469
6068
  { ...pollOpts, description: `UPO for session ${sessionRef}` }
5470
6069
  );
@@ -5480,9 +6079,9 @@ async function openOnlineSession(client, options) {
5480
6079
  }
5481
6080
  return {
5482
6081
  sessionRef,
5483
- validUntil: openResp.validUntil,
6082
+ validUntil,
5484
6083
  async sendInvoice(invoiceXml) {
5485
- if (options?.validate) {
6084
+ if (validate2) {
5486
6085
  const xmlStr = typeof invoiceXml === "string" ? invoiceXml : new TextDecoder().decode(invoiceXml);
5487
6086
  const vResult = await validate(xmlStr);
5488
6087
  if (!vResult.valid) {
@@ -5493,10 +6092,10 @@ async function openOnlineSession(client, options) {
5493
6092
  }
5494
6093
  }
5495
6094
  const data = typeof invoiceXml === "string" ? new TextEncoder().encode(invoiceXml) : invoiceXml;
5496
- const plainMeta = client.crypto.getFileMetadata(data);
5497
- const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
5498
- const encMeta = client.crypto.getFileMetadata(encrypted);
5499
- const resp = await client.onlineSession.sendInvoice(sessionRef, {
6095
+ const plainMeta = deps.crypto.getFileMetadata(data);
6096
+ const encrypted = deps.crypto.encryptAES256(data, cipherKey, cipherIv);
6097
+ const encMeta = deps.crypto.getFileMetadata(encrypted);
6098
+ const resp = await deps.onlineSession.sendInvoice(sessionRef, {
5500
6099
  invoiceHash: plainMeta.hashSHA,
5501
6100
  invoiceSize: plainMeta.fileSize,
5502
6101
  encryptedInvoiceHash: encMeta.hashSHA,
@@ -5506,7 +6105,7 @@ async function openOnlineSession(client, options) {
5506
6105
  return resp.referenceNumber;
5507
6106
  },
5508
6107
  async close() {
5509
- await client.onlineSession.closeSession(sessionRef);
6108
+ await deps.onlineSession.closeSession(sessionRef);
5510
6109
  },
5511
6110
  async waitForUpo(pollOpts) {
5512
6111
  return fetchUpo(pollOpts);
@@ -5515,13 +6114,75 @@ async function openOnlineSession(client, options) {
5515
6114
  const upoInfo = await fetchUpo(pollOpts);
5516
6115
  const parsed = [];
5517
6116
  for (const page of upoInfo.pages) {
5518
- const result = await client.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6117
+ const result = await deps.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
5519
6118
  parsed.push(parseUpoXml(result.upo));
5520
6119
  }
5521
6120
  return { ...upoInfo, parsed };
6121
+ },
6122
+ getState() {
6123
+ const token = deps.getAccessToken();
6124
+ if (!token) {
6125
+ throw new Error("Cannot serialize session state: no access token available");
6126
+ }
6127
+ return {
6128
+ referenceNumber: sessionRef,
6129
+ aesKey: Buffer.from(cipherKey).toString("base64"),
6130
+ iv: Buffer.from(cipherIv).toString("base64"),
6131
+ accessToken: token,
6132
+ formCode,
6133
+ validUntil,
6134
+ ...validate2 ? { validate: validate2 } : {}
6135
+ };
5522
6136
  }
5523
6137
  };
5524
6138
  }
6139
+ async function openOnlineSession(client, options) {
6140
+ await client.crypto.init();
6141
+ const encData = client.crypto.getEncryptionData();
6142
+ const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
6143
+ const openResp = await client.onlineSession.openSession(
6144
+ { formCode, encryption: encData.encryptionInfo },
6145
+ options?.upoVersion
6146
+ );
6147
+ return buildSessionHandle({
6148
+ deps: {
6149
+ crypto: client.crypto,
6150
+ onlineSession: client.onlineSession,
6151
+ sessionStatus: client.sessionStatus,
6152
+ getAccessToken: () => client.authManager.getAccessToken()
6153
+ },
6154
+ sessionRef: openResp.referenceNumber,
6155
+ validUntil: openResp.validUntil,
6156
+ cipherKey: encData.cipherKey,
6157
+ cipherIv: encData.cipherIv,
6158
+ formCode,
6159
+ validate: options?.validate
6160
+ });
6161
+ }
6162
+ function resumeOnlineSession(client, state, options) {
6163
+ const expiry = new Date(state.validUntil);
6164
+ if (expiry.getTime() <= Date.now()) {
6165
+ throw new KSeFSessionExpiredError(
6166
+ `Cannot resume session: expired at ${state.validUntil}`
6167
+ );
6168
+ }
6169
+ const scopedAuth = new DefaultAuthManager(() => Promise.resolve(null), state.accessToken);
6170
+ const scopedRestClient = client.createScopedRestClient(scopedAuth);
6171
+ return buildSessionHandle({
6172
+ deps: {
6173
+ crypto: client.crypto,
6174
+ onlineSession: new OnlineSessionService(scopedRestClient),
6175
+ sessionStatus: new SessionStatusService(scopedRestClient),
6176
+ getAccessToken: () => scopedAuth.getAccessToken()
6177
+ },
6178
+ sessionRef: state.referenceNumber,
6179
+ validUntil: state.validUntil,
6180
+ cipherKey: new Uint8Array(Buffer.from(state.aesKey, "base64")),
6181
+ cipherIv: new Uint8Array(Buffer.from(state.iv, "base64")),
6182
+ formCode: state.formCode,
6183
+ validate: options?.validate ?? state.validate
6184
+ });
6185
+ }
5525
6186
  async function openSendAndClose(client, invoices, options) {
5526
6187
  const handle = await openOnlineSession(client, options);
5527
6188
  for (const inv of invoices) {
@@ -5532,7 +6193,11 @@ async function openSendAndClose(client, invoices, options) {
5532
6193
  }
5533
6194
 
5534
6195
  // src/workflows/batch-session-workflow.ts
6196
+ init_document_structures();
5535
6197
  async function uploadBatch(client, zipData, options) {
6198
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6199
+ throw new Error("parallelism must be a positive integer");
6200
+ }
5536
6201
  await client.crypto.init();
5537
6202
  if (options?.validate) {
5538
6203
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -5575,7 +6240,7 @@ async function uploadBatch(client, zipData, options) {
5575
6240
  },
5576
6241
  ordinalNumber: i + 1
5577
6242
  }));
5578
- await client.batchSession.sendParts(openResp, sendingParts);
6243
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
5579
6244
  await client.batchSession.closeSession(openResp.referenceNumber);
5580
6245
  const result = await pollUntil(
5581
6246
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5596,6 +6261,9 @@ async function uploadBatch(client, zipData, options) {
5596
6261
  };
5597
6262
  }
5598
6263
  async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
6264
+ if (options?.parallelism !== void 0 && (!Number.isInteger(options.parallelism) || options.parallelism < 1)) {
6265
+ throw new Error("parallelism must be a positive integer");
6266
+ }
5599
6267
  await client.crypto.init();
5600
6268
  const encData = client.crypto.getEncryptionData();
5601
6269
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -5617,7 +6285,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
5617
6285
  },
5618
6286
  options?.upoVersion
5619
6287
  );
5620
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
6288
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
5621
6289
  await client.batchSession.closeSession(openResp.referenceNumber);
5622
6290
  const result = await pollUntil(
5623
6291
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5692,6 +6360,7 @@ async function doExport(client, filters, options) {
5692
6360
  url: p.url,
5693
6361
  method: p.method,
5694
6362
  partSize: p.partSize,
6363
+ partHash: p.partHash,
5695
6364
  encryptedPartSize: p.encryptedPartSize,
5696
6365
  encryptedPartHash: p.encryptedPartHash,
5697
6366
  expirationDate: p.expirationDate
@@ -5717,6 +6386,9 @@ async function exportAndDownload(client, filters, options) {
5717
6386
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
5718
6387
  }
5719
6388
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6389
+ if (options?.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6390
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6391
+ }
5720
6392
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
5721
6393
  decryptedParts.push(decrypted);
5722
6394
  }
@@ -5789,6 +6461,9 @@ async function incrementalExportAndDownload(client, options) {
5789
6461
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
5790
6462
  }
5791
6463
  const encryptedData = new Uint8Array(await resp.arrayBuffer());
6464
+ if (options.verifyHash !== false && !verifyHash(encryptedData, part.encryptedPartHash)) {
6465
+ throw new Error(`Hash mismatch for export part ${part.ordinalNumber}`);
6466
+ }
5792
6467
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
5793
6468
  decryptedParts.push(decrypted);
5794
6469
  }
@@ -5952,7 +6627,141 @@ async function authenticateWithPkcs12(client, options) {
5952
6627
  });
5953
6628
  }
5954
6629
 
6630
+ // src/offline/index.ts
6631
+ init_deadline();
6632
+ init_holidays();
6633
+
6634
+ // src/offline/storage.ts
6635
+ function matchesFilter(invoice, filter) {
6636
+ if (filter.status !== void 0) {
6637
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
6638
+ if (!statuses.includes(invoice.status)) return false;
6639
+ }
6640
+ if (filter.mode !== void 0 && invoice.mode !== filter.mode) return false;
6641
+ if (filter.sellerNip !== void 0 && invoice.sellerNip !== filter.sellerNip) return false;
6642
+ if (filter.expiringBefore !== void 0) {
6643
+ const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
6644
+ if (new Date(invoice.submitBy).getTime() >= cutoff) return false;
6645
+ }
6646
+ return true;
6647
+ }
6648
+ var InMemoryOfflineInvoiceStorage = class {
6649
+ store = /* @__PURE__ */ new Map();
6650
+ async save(invoice) {
6651
+ this.store.set(invoice.id, JSON.parse(JSON.stringify(invoice)));
6652
+ }
6653
+ async get(id) {
6654
+ const entry = this.store.get(id);
6655
+ return entry ? JSON.parse(JSON.stringify(entry)) : null;
6656
+ }
6657
+ async list(filter) {
6658
+ const all = [...this.store.values()];
6659
+ if (!filter) return all.map((i) => JSON.parse(JSON.stringify(i)));
6660
+ return all.filter((i) => matchesFilter(i, filter)).map((i) => JSON.parse(JSON.stringify(i)));
6661
+ }
6662
+ async update(id, updates) {
6663
+ const existing = this.store.get(id);
6664
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6665
+ this.store.set(id, JSON.parse(JSON.stringify({ ...existing, ...updates })));
6666
+ }
6667
+ async delete(id) {
6668
+ this.store.delete(id);
6669
+ }
6670
+ };
6671
+
6672
+ // src/offline/file-storage.ts
6673
+ import * as fs2 from "fs/promises";
6674
+ import * as path from "path";
6675
+ import * as os from "os";
6676
+ function resolveDir(dir) {
6677
+ if (dir === "~" || dir.startsWith("~/")) {
6678
+ return path.join(os.homedir(), dir.slice(1));
6679
+ }
6680
+ return dir;
6681
+ }
6682
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6683
+ function validateId(id) {
6684
+ if (!UUID_RE.test(id)) {
6685
+ throw new Error(`Invalid invoice ID: ${id}`);
6686
+ }
6687
+ }
6688
+ var FileOfflineInvoiceStorage = class {
6689
+ dir;
6690
+ constructor(directory) {
6691
+ this.dir = resolveDir(directory ?? "~/.ksef/offline");
6692
+ }
6693
+ async ensureDir() {
6694
+ await fs2.mkdir(this.dir, { recursive: true });
6695
+ }
6696
+ filePath(id) {
6697
+ validateId(id);
6698
+ return path.join(this.dir, `${id}.json`);
6699
+ }
6700
+ async save(invoice) {
6701
+ await this.ensureDir();
6702
+ const file = this.filePath(invoice.id);
6703
+ const tmp = `${file}.tmp`;
6704
+ await fs2.writeFile(tmp, JSON.stringify(invoice, null, 2));
6705
+ await fs2.rename(tmp, file);
6706
+ }
6707
+ async get(id) {
6708
+ const file = this.filePath(id);
6709
+ try {
6710
+ return JSON.parse(await fs2.readFile(file, "utf-8"));
6711
+ } catch (err) {
6712
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6713
+ return null;
6714
+ }
6715
+ console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
6716
+ return null;
6717
+ }
6718
+ }
6719
+ async list(filter) {
6720
+ let files;
6721
+ try {
6722
+ files = (await fs2.readdir(this.dir)).filter((f) => f.endsWith(".json"));
6723
+ } catch {
6724
+ return [];
6725
+ }
6726
+ const results = [];
6727
+ for (const file of files) {
6728
+ try {
6729
+ const data = JSON.parse(
6730
+ await fs2.readFile(path.join(this.dir, file), "utf-8")
6731
+ );
6732
+ if (!filter || matchesFilter(data, filter)) {
6733
+ results.push(data);
6734
+ }
6735
+ } catch (err) {
6736
+ console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
6737
+ }
6738
+ }
6739
+ return results;
6740
+ }
6741
+ /**
6742
+ * Update invoice metadata (read-modify-write).
6743
+ *
6744
+ * Note: No file locking — concurrent updates to the same ID may cause
6745
+ * lost writes. Acceptable for CLI (single process). Library consumers
6746
+ * running parallel operations should use external locking.
6747
+ */
6748
+ async update(id, updates) {
6749
+ const existing = await this.get(id);
6750
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6751
+ await this.save({ ...existing, ...updates });
6752
+ }
6753
+ async delete(id) {
6754
+ const file = this.filePath(id);
6755
+ try {
6756
+ await fs2.unlink(file);
6757
+ } catch (e) {
6758
+ if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
6759
+ }
6760
+ }
6761
+ };
6762
+
5955
6763
  // src/index.ts
6764
+ init_offline_invoice_workflow();
5956
6765
  init_client();
5957
6766
  export {
5958
6767
  ActiveSessionsService,
@@ -5982,8 +6791,10 @@ export {
5982
6791
  FORM_CODES,
5983
6792
  FORM_CODE_KEYS,
5984
6793
  FileHwmStore,
6794
+ FileOfflineInvoiceStorage,
5985
6795
  INVOICE_TYPES_BY_SYSTEM_CODE,
5986
6796
  InMemoryHwmStore,
6797
+ InMemoryOfflineInvoiceStorage,
5987
6798
  InternalId,
5988
6799
  InvoiceDownloadService,
5989
6800
  InvoiceQueryFilterBuilder,
@@ -6007,6 +6818,7 @@ export {
6007
6818
  LimitsService,
6008
6819
  Nip,
6009
6820
  NipVatUe,
6821
+ OfflineInvoiceWorkflow,
6010
6822
  OnlineSessionService,
6011
6823
  PERMISSION_DESCRIPTION_MAX_LENGTH,
6012
6824
  PERMISSION_DESCRIPTION_MIN_LENGTH,
@@ -6036,6 +6848,7 @@ export {
6036
6848
  UpoVersion,
6037
6849
  VatUe,
6038
6850
  VerificationLinkService,
6851
+ addBusinessDays,
6039
6852
  authenticateWithCertificate,
6040
6853
  authenticateWithExternalSignature,
6041
6854
  authenticateWithPkcs12,
@@ -6043,6 +6856,7 @@ export {
6043
6856
  batchValidationDetails,
6044
6857
  buildUnsignedAuthTokenRequestXml,
6045
6858
  calculateBackoff,
6859
+ calculateOfflineDeadline,
6046
6860
  createZip,
6047
6861
  decodeJwtPayload,
6048
6862
  deduplicateByKsefNumber,
@@ -6052,9 +6866,16 @@ export {
6052
6866
  defaultTransport,
6053
6867
  exportAndDownload,
6054
6868
  exportInvoices,
6869
+ extendDeadlineForMaintenance,
6870
+ extractInvoiceFields,
6871
+ getDefaultReason,
6055
6872
  getEffectiveStartDate,
6056
6873
  getFormCode,
6874
+ getPolishHolidays,
6875
+ getTimeUntilDeadline,
6057
6876
  incrementalExportAndDownload,
6877
+ isExpired,
6878
+ isPolishHoliday,
6058
6879
  isRetryableError,
6059
6880
  isRetryableStatus,
6060
6881
  isValidBase64,
@@ -6072,6 +6893,7 @@ export {
6072
6893
  isValidReferenceNumber,
6073
6894
  isValidSha256Base64,
6074
6895
  isValidVatUe,
6896
+ nextBusinessDay,
6075
6897
  openOnlineSession,
6076
6898
  openSendAndClose,
6077
6899
  parseFormCode,
@@ -6080,6 +6902,9 @@ export {
6080
6902
  parseUpoXml,
6081
6903
  pollUntil,
6082
6904
  resolveOptions,
6905
+ resumeOnlineSession,
6906
+ runWithConcurrency,
6907
+ sha256Base642 as sha256Base64,
6083
6908
  sleep,
6084
6909
  unzip,
6085
6910
  updateContinuationPoint,
@@ -6094,6 +6919,7 @@ export {
6094
6919
  validatePresignedUrl,
6095
6920
  validateSchema,
6096
6921
  validateWellFormedness,
6922
+ verifyHash,
6097
6923
  xmlToObject
6098
6924
  };
6099
6925
  //# sourceMappingURL=index.js.map