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.cjs CHANGED
@@ -320,21 +320,21 @@ var init_rest_request = __esm({
320
320
  _query = [];
321
321
  _presigned = false;
322
322
  _skipAuthRetry = false;
323
- constructor(method, path) {
323
+ constructor(method, path2) {
324
324
  this.method = method;
325
- this.path = path;
325
+ this.path = path2;
326
326
  }
327
- static get(path) {
328
- return new _RestRequest("GET", path);
327
+ static get(path2) {
328
+ return new _RestRequest("GET", path2);
329
329
  }
330
- static post(path) {
331
- return new _RestRequest("POST", path);
330
+ static post(path2) {
331
+ return new _RestRequest("POST", path2);
332
332
  }
333
- static put(path) {
334
- return new _RestRequest("PUT", path);
333
+ static put(path2) {
334
+ return new _RestRequest("PUT", path2);
335
335
  }
336
- static delete(path) {
337
- return new _RestRequest("DELETE", path);
336
+ static delete(path2) {
337
+ return new _RestRequest("DELETE", path2);
338
338
  }
339
339
  body(data) {
340
340
  this._body = data;
@@ -654,9 +654,9 @@ var init_rest_client = __esm({
654
654
  return response;
655
655
  }
656
656
  buildUrl(request) {
657
- const path = this.routeBuilder.build(request.path);
657
+ const path2 = this.routeBuilder.build(request.path);
658
658
  const base = this.options.baseUrl;
659
- const url = new URL(`${base}${path}`);
659
+ const url = new URL(`${base}${path2}`);
660
660
  const query = request.getQuery();
661
661
  for (const [key, value] of query) {
662
662
  url.searchParams.append(key, value);
@@ -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);
@@ -2668,11 +2670,11 @@ async function validateSchema(xml, options, _parsed) {
2668
2670
  const prefix = rootElement ? `/${rootElement}/` : "/";
2669
2671
  const validationErrors = result.error.issues.map((issue) => {
2670
2672
  const zodPath = issue.path.join("/");
2671
- const path = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2673
+ const path2 = zodPath ? `${prefix}${zodPath}` : rootElement ? `/${rootElement}` : void 0;
2672
2674
  return {
2673
2675
  code: mapZodErrorCode(issue),
2674
2676
  message: issue.message,
2675
- path
2677
+ path: path2
2676
2678
  };
2677
2679
  });
2678
2680
  return { valid: false, schemaType, errors: validationErrors };
@@ -2712,9 +2714,9 @@ function validateBusinessRules(xml, _parsed) {
2712
2714
  collectDateErrors(object, rootElement, errors);
2713
2715
  return { valid: errors.length === 0, schemaType, errors };
2714
2716
  }
2715
- function collectNipPeselErrors(obj, path, errors) {
2717
+ function collectNipPeselErrors(obj, path2, errors) {
2716
2718
  for (const [key, value] of Object.entries(obj)) {
2717
- const currentPath = path ? `${path}/${key}` : key;
2719
+ const currentPath = path2 ? `${path2}/${key}` : key;
2718
2720
  if (key === "NIP" && typeof value === "string") {
2719
2721
  if (!isValidNip(value)) {
2720
2722
  errors.push({
@@ -2793,6 +2795,88 @@ var init_invoice_validator = __esm({
2793
2795
  }
2794
2796
  });
2795
2797
 
2798
+ // src/models/document-structures/types.ts
2799
+ var SystemCode, FORM_CODES, DEFAULT_FORM_CODE, INVOICE_TYPES_BY_SYSTEM_CODE, FORM_CODE_KEYS;
2800
+ var init_types = __esm({
2801
+ "src/models/document-structures/types.ts"() {
2802
+ "use strict";
2803
+ SystemCode = {
2804
+ FA_2: "FA (2)",
2805
+ FA_3: "FA (3)",
2806
+ PEF_3: "PEF (3)",
2807
+ PEF_KOR_3: "PEF_KOR (3)",
2808
+ FA_RR_1: "FA_RR (1)"
2809
+ };
2810
+ FORM_CODES = {
2811
+ FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
2812
+ FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
2813
+ PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
2814
+ PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
2815
+ FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
2816
+ FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
2817
+ FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
2818
+ };
2819
+ DEFAULT_FORM_CODE = FORM_CODES.FA_3;
2820
+ INVOICE_TYPES_BY_SYSTEM_CODE = {
2821
+ [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2822
+ [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
2823
+ [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
2824
+ [SystemCode.PEF_KOR_3]: ["KorPef"],
2825
+ [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
2826
+ };
2827
+ FORM_CODE_KEYS = {
2828
+ FA2: FORM_CODES.FA_2,
2829
+ FA3: FORM_CODES.FA_3,
2830
+ PEF3: FORM_CODES.PEF_3,
2831
+ PEFKOR3: FORM_CODES.PEF_KOR_3,
2832
+ FARR1: FORM_CODES.FA_RR_1
2833
+ };
2834
+ }
2835
+ });
2836
+
2837
+ // src/models/document-structures/helpers.ts
2838
+ function getFormCode(systemCode) {
2839
+ return SYSTEM_CODE_TO_FORM_CODE[systemCode];
2840
+ }
2841
+ function parseFormCode(raw) {
2842
+ const match = ALL_FORM_CODES.find(
2843
+ (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
2844
+ );
2845
+ return match ?? raw;
2846
+ }
2847
+ function validateFormCodeForSession(formCode, sessionType) {
2848
+ if (sessionType === "online") return true;
2849
+ return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
2850
+ }
2851
+ var SYSTEM_CODE_TO_FORM_CODE, ALL_FORM_CODES, BATCH_DISALLOWED_SYSTEM_CODES;
2852
+ var init_helpers = __esm({
2853
+ "src/models/document-structures/helpers.ts"() {
2854
+ "use strict";
2855
+ init_types();
2856
+ SYSTEM_CODE_TO_FORM_CODE = {
2857
+ [SystemCode.FA_2]: FORM_CODES.FA_2,
2858
+ [SystemCode.FA_3]: FORM_CODES.FA_3,
2859
+ [SystemCode.PEF_3]: FORM_CODES.PEF_3,
2860
+ [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
2861
+ [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
2862
+ };
2863
+ ALL_FORM_CODES = Object.values(FORM_CODES);
2864
+ BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
2865
+ SystemCode.PEF_3,
2866
+ SystemCode.PEF_KOR_3
2867
+ ]);
2868
+ }
2869
+ });
2870
+
2871
+ // src/models/document-structures/index.ts
2872
+ var init_document_structures = __esm({
2873
+ "src/models/document-structures/index.ts"() {
2874
+ "use strict";
2875
+ init_types();
2876
+ init_helpers();
2877
+ }
2878
+ });
2879
+
2796
2880
  // src/services/auth.ts
2797
2881
  var AuthService;
2798
2882
  var init_auth = __esm({
@@ -2908,6 +2992,32 @@ var init_online_session = __esm({
2908
2992
  }
2909
2993
  });
2910
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
+
2911
3021
  // src/services/batch-session.ts
2912
3022
  var BatchSessionService;
2913
3023
  var init_batch_session = __esm({
@@ -2916,6 +3026,7 @@ var init_batch_session = __esm({
2916
3026
  init_ksef_feature();
2917
3027
  init_rest_request();
2918
3028
  init_routes();
3029
+ init_concurrency();
2919
3030
  BatchSessionService = class {
2920
3031
  restClient;
2921
3032
  constructor(restClient) {
@@ -2929,12 +3040,10 @@ var init_batch_session = __esm({
2929
3040
  const response = await this.restClient.execute(req);
2930
3041
  return response.body;
2931
3042
  }
2932
- async sendParts(openResponse, parts) {
2933
- const uploadRequests = openResponse.partUploadRequests;
2934
- const tasks = parts.map(async (part) => {
2935
- const uploadReq = uploadRequests.find(
2936
- (r) => r.ordinalNumber === part.ordinalNumber
2937
- );
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);
2938
3047
  if (!uploadReq) {
2939
3048
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
2940
3049
  }
@@ -2942,25 +3051,38 @@ var init_batch_session = __esm({
2942
3051
  for (const [k, v] of Object.entries(uploadReq.headers)) {
2943
3052
  if (v != null) headers[k] = v;
2944
3053
  }
2945
- await fetch(uploadReq.url, {
3054
+ const resp = await fetch(uploadReq.url, {
2946
3055
  method: uploadReq.method,
2947
3056
  headers,
2948
- body: part.data
3057
+ body: part.data,
3058
+ signal
2949
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
+ }
2950
3066
  });
2951
- 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
+ }
2952
3076
  }
2953
3077
  /**
2954
- * Upload parts sequentially (not in parallel) because each part uses a
2955
- * streaming body (`duplex: 'half'`). Parallel streaming uploads can cause
2956
- * 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.
2957
3081
  */
2958
- async sendPartsWithStream(openResponse, parts) {
2959
- const uploadRequests = openResponse.partUploadRequests;
2960
- for (const part of parts) {
2961
- const uploadReq = uploadRequests.find(
2962
- (r) => r.ordinalNumber === part.ordinalNumber
2963
- );
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);
2964
3086
  if (!uploadReq) {
2965
3087
  throw new Error(`No upload request found for part ${part.ordinalNumber}`);
2966
3088
  }
@@ -2972,11 +3094,23 @@ var init_batch_session = __esm({
2972
3094
  method: uploadReq.method,
2973
3095
  headers,
2974
3096
  body: part.dataStream,
3097
+ signal,
2975
3098
  // @ts-expect-error -- Node 18+ undici supports duplex for streaming body
2976
3099
  duplex: "half"
2977
3100
  });
2978
3101
  if (!resp.ok) {
2979
- 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);
2980
3114
  }
2981
3115
  }
2982
3116
  }
@@ -3089,7 +3223,10 @@ var init_invoice_download = __esm({
3089
3223
  async getInvoice(ksefNumber) {
3090
3224
  const req = RestRequest.get(Routes.Invoices.byKsefNumber(ksefNumber));
3091
3225
  const response = await this.restClient.executeRaw(req);
3092
- 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
+ };
3093
3230
  }
3094
3231
  async queryInvoiceMetadata(filters, pageOffset, pageSize, sortOrder) {
3095
3232
  const req = RestRequest.post(Routes.Invoices.queryMetadata).body(filters);
@@ -3358,20 +3495,20 @@ var init_lighthouse = __esm({
3358
3495
  this.lighthouseUrl = options.lighthouseUrl;
3359
3496
  this.timeout = options.timeout;
3360
3497
  }
3361
- async fetchJson(path) {
3498
+ async fetchJson(path2) {
3362
3499
  if (!this.lighthouseUrl) {
3363
3500
  throw new KSeFError(
3364
3501
  "Lighthouse API is not available for the DEMO environment. Use TEST or PROD instead."
3365
3502
  );
3366
3503
  }
3367
- const response = await fetch(`${this.lighthouseUrl}${path}`, {
3504
+ const response = await fetch(`${this.lighthouseUrl}${path2}`, {
3368
3505
  headers: { Accept: "application/json" },
3369
3506
  signal: AbortSignal.timeout(this.timeout)
3370
3507
  });
3371
3508
  if (!response.ok) {
3372
3509
  const body = await response.text();
3373
3510
  throw new KSeFError(
3374
- `Lighthouse ${path} failed: HTTP ${response.status} \u2014 ${body}`
3511
+ `Lighthouse ${path2} failed: HTTP ${response.status} \u2014 ${body}`
3375
3512
  );
3376
3513
  }
3377
3514
  return await response.json();
@@ -4351,6 +4488,458 @@ var init_zip = __esm({
4351
4488
  }
4352
4489
  });
4353
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
+
4557
+ // src/offline/deadline.ts
4558
+ function getDefaultReason(mode) {
4559
+ switch (mode) {
4560
+ case "offline24":
4561
+ return "PLANNED";
4562
+ case "offline":
4563
+ return "SYSTEM_UNAVAILABLE";
4564
+ case "awaryjny":
4565
+ return "EMERGENCY";
4566
+ case "awaria_calkowita":
4567
+ return "TOTAL_FAILURE";
4568
+ }
4569
+ }
4570
+ function nextBusinessDay(from) {
4571
+ const d = new Date(from);
4572
+ d.setUTCDate(d.getUTCDate() + 1);
4573
+ while (d.getUTCDay() === 0 || d.getUTCDay() === 6 || isPolishHoliday(d)) {
4574
+ d.setUTCDate(d.getUTCDate() + 1);
4575
+ }
4576
+ return d;
4577
+ }
4578
+ function addBusinessDays(from, days) {
4579
+ if (!Number.isInteger(days) || days < 0) {
4580
+ throw new Error(`days must be a non-negative integer, got ${days}`);
4581
+ }
4582
+ const d = new Date(from);
4583
+ let remaining = days;
4584
+ while (remaining > 0) {
4585
+ d.setUTCDate(d.getUTCDate() + 1);
4586
+ if (d.getUTCDay() !== 0 && d.getUTCDay() !== 6 && !isPolishHoliday(d)) {
4587
+ remaining--;
4588
+ }
4589
+ }
4590
+ return d;
4591
+ }
4592
+ function endOfDay(d) {
4593
+ const result = new Date(d);
4594
+ result.setUTCHours(23, 59, 59, 999);
4595
+ return result;
4596
+ }
4597
+ function getMaintenanceEndFallback(mw) {
4598
+ if (mw.endTime) {
4599
+ return new Date(mw.endTime);
4600
+ }
4601
+ const start = new Date(mw.startTime);
4602
+ start.setUTCDate(start.getUTCDate() + 7);
4603
+ return start;
4604
+ }
4605
+ function calculateOfflineDeadline(mode, invoiceDate, maintenanceWindow) {
4606
+ const date = typeof invoiceDate === "string" ? new Date(invoiceDate) : invoiceDate;
4607
+ switch (mode) {
4608
+ case "offline24": {
4609
+ return endOfDay(nextBusinessDay(date));
4610
+ }
4611
+ case "offline": {
4612
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4613
+ return endOfDay(nextBusinessDay(base));
4614
+ }
4615
+ case "awaryjny": {
4616
+ const base = maintenanceWindow ? getMaintenanceEndFallback(maintenanceWindow) : date;
4617
+ return endOfDay(addBusinessDays(base, 7));
4618
+ }
4619
+ case "awaria_calkowita": {
4620
+ return new Date(FAR_FUTURE);
4621
+ }
4622
+ }
4623
+ }
4624
+ function extendDeadlineForMaintenance(currentDeadline, maintenanceWindow, mode = "awaryjny") {
4625
+ const current = typeof currentDeadline === "string" ? new Date(currentDeadline) : new Date(currentDeadline);
4626
+ const mwEnd = getMaintenanceEndFallback(maintenanceWindow);
4627
+ if (mwEnd.getTime() > current.getTime()) {
4628
+ switch (mode) {
4629
+ case "offline24":
4630
+ case "offline":
4631
+ return endOfDay(nextBusinessDay(mwEnd));
4632
+ case "awaryjny":
4633
+ return endOfDay(addBusinessDays(mwEnd, 7));
4634
+ case "awaria_calkowita":
4635
+ return new Date(FAR_FUTURE);
4636
+ }
4637
+ }
4638
+ return current;
4639
+ }
4640
+ function isExpired(submitBy) {
4641
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4642
+ return Date.now() > deadline.getTime();
4643
+ }
4644
+ function getTimeUntilDeadline(submitBy) {
4645
+ const deadline = typeof submitBy === "string" ? new Date(submitBy) : submitBy;
4646
+ return Math.max(0, deadline.getTime() - Date.now());
4647
+ }
4648
+ var FAR_FUTURE;
4649
+ var init_deadline = __esm({
4650
+ "src/offline/deadline.ts"() {
4651
+ "use strict";
4652
+ init_holidays();
4653
+ FAR_FUTURE = /* @__PURE__ */ new Date("9999-12-31T23:59:59Z");
4654
+ }
4655
+ });
4656
+
4657
+ // src/workflows/offline-invoice-workflow.ts
4658
+ var import_node_crypto3, OfflineInvoiceWorkflow;
4659
+ var init_offline_invoice_workflow = __esm({
4660
+ "src/workflows/offline-invoice-workflow.ts"() {
4661
+ "use strict";
4662
+ import_node_crypto3 = __toESM(require("crypto"), 1);
4663
+ init_ksef_api_error();
4664
+ init_deadline();
4665
+ init_document_structures();
4666
+ OfflineInvoiceWorkflow = class {
4667
+ constructor(qrService) {
4668
+ this.qrService = qrService;
4669
+ }
4670
+ async generate(input, options) {
4671
+ if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
4672
+ throw new Error("invoiceXml must not be empty");
4673
+ }
4674
+ if (!input.invoiceNumber) {
4675
+ throw new Error("invoiceNumber is required");
4676
+ }
4677
+ if (!input.sellerNip) {
4678
+ throw new Error("sellerNip is required");
4679
+ }
4680
+ const mode = options?.mode ?? "offline24";
4681
+ const reason = getDefaultReason(mode);
4682
+ const invoiceHashBase64 = import_node_crypto3.default.createHash("sha256").update(input.invoiceXml).digest("base64");
4683
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4684
+ input.sellerNip,
4685
+ input.invoiceDate,
4686
+ invoiceHashBase64
4687
+ );
4688
+ let kod2Url;
4689
+ if (options?.certificate) {
4690
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4691
+ input.sellerIdentifier.type,
4692
+ input.sellerIdentifier.value,
4693
+ input.sellerNip,
4694
+ options.certificate.certificateSerial,
4695
+ invoiceHashBase64,
4696
+ options.certificate.privateKeyPem
4697
+ );
4698
+ }
4699
+ const submitBy = options?.customDeadline ? typeof options.customDeadline === "string" ? options.customDeadline : options.customDeadline.toISOString() : calculateOfflineDeadline(mode, input.invoiceDate, options?.maintenanceWindow).toISOString();
4700
+ const metadata = {
4701
+ id: import_node_crypto3.default.randomUUID(),
4702
+ mode,
4703
+ reason,
4704
+ status: "GENERATED",
4705
+ invoiceNumber: input.invoiceNumber,
4706
+ invoiceDate: input.invoiceDate,
4707
+ invoiceXml: input.invoiceXml,
4708
+ sellerNip: input.sellerNip,
4709
+ sellerIdentifier: input.sellerIdentifier,
4710
+ buyerIdentifier: input.buyerIdentifier,
4711
+ totalAmount: input.totalAmount,
4712
+ currency: input.currency,
4713
+ kod1Url,
4714
+ kod2Url,
4715
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
4716
+ submitBy,
4717
+ maintenanceWindowId: options?.maintenanceWindow?.id
4718
+ };
4719
+ if (options?.storage) {
4720
+ await options.storage.save(metadata);
4721
+ }
4722
+ return metadata;
4723
+ }
4724
+ async submit(client, options) {
4725
+ const { storage, checkExpiry = true } = options;
4726
+ let invoices;
4727
+ if (options.invoiceIds) {
4728
+ invoices = [];
4729
+ const notFound = [];
4730
+ for (const id of options.invoiceIds) {
4731
+ const inv = await storage.get(id);
4732
+ if (inv) {
4733
+ invoices.push(inv);
4734
+ } else {
4735
+ notFound.push(id);
4736
+ }
4737
+ }
4738
+ if (notFound.length > 0) {
4739
+ throw new Error(`Offline invoice(s) not found: ${notFound.join(", ")}`);
4740
+ }
4741
+ } else {
4742
+ invoices = await storage.list({ status: ["GENERATED", "QUEUED", "SUBMITTED"] });
4743
+ }
4744
+ const result = {
4745
+ total: invoices.length,
4746
+ submitted: 0,
4747
+ accepted: 0,
4748
+ rejected: 0,
4749
+ failed: 0,
4750
+ expired: 0,
4751
+ results: []
4752
+ };
4753
+ if (invoices.length === 0) return result;
4754
+ const pending = [];
4755
+ for (const inv of invoices) {
4756
+ if (checkExpiry && isExpired(inv.submitBy)) {
4757
+ await storage.update(inv.id, { status: "EXPIRED" });
4758
+ result.expired++;
4759
+ result.results.push({
4760
+ invoiceId: inv.id,
4761
+ invoiceNumber: inv.invoiceNumber,
4762
+ status: "EXPIRED"
4763
+ });
4764
+ } else {
4765
+ pending.push(inv);
4766
+ }
4767
+ }
4768
+ if (pending.length === 0) return result;
4769
+ await client.crypto.init();
4770
+ const encData = client.crypto.getEncryptionData();
4771
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4772
+ const openResp = await client.onlineSession.openSession(
4773
+ { formCode, encryption: encData.encryptionInfo }
4774
+ );
4775
+ const sessionRef = openResp.referenceNumber;
4776
+ try {
4777
+ for (const inv of pending) {
4778
+ await storage.update(inv.id, { status: "QUEUED" });
4779
+ try {
4780
+ const data = new TextEncoder().encode(inv.invoiceXml);
4781
+ const plainMeta = client.crypto.getFileMetadata(data);
4782
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4783
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4784
+ await storage.update(inv.id, { status: "SUBMITTED", submittedAt: (/* @__PURE__ */ new Date()).toISOString() });
4785
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4786
+ invoiceHash: plainMeta.hashSHA,
4787
+ invoiceSize: plainMeta.fileSize,
4788
+ encryptedInvoiceHash: encMeta.hashSHA,
4789
+ encryptedInvoiceSize: encMeta.fileSize,
4790
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4791
+ offlineMode: true
4792
+ });
4793
+ await storage.update(inv.id, {
4794
+ status: "ACCEPTED",
4795
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4796
+ ksefReferenceNumber: resp.referenceNumber
4797
+ });
4798
+ result.submitted++;
4799
+ result.accepted++;
4800
+ result.results.push({
4801
+ invoiceId: inv.id,
4802
+ invoiceNumber: inv.invoiceNumber,
4803
+ status: "ACCEPTED",
4804
+ ksefReferenceNumber: resp.referenceNumber
4805
+ });
4806
+ } catch (err) {
4807
+ const message = err instanceof Error ? err.message : String(err);
4808
+ if (err instanceof KSeFApiError) {
4809
+ await storage.update(inv.id, {
4810
+ status: "REJECTED",
4811
+ error: { code: err.statusCode, message }
4812
+ });
4813
+ result.submitted++;
4814
+ result.rejected++;
4815
+ result.results.push({
4816
+ invoiceId: inv.id,
4817
+ invoiceNumber: inv.invoiceNumber,
4818
+ status: "REJECTED",
4819
+ error: { code: err.statusCode, message }
4820
+ });
4821
+ } else {
4822
+ await storage.update(inv.id, {
4823
+ status: "QUEUED",
4824
+ error: { code: 0, message }
4825
+ });
4826
+ result.failed++;
4827
+ result.results.push({
4828
+ invoiceId: inv.id,
4829
+ invoiceNumber: inv.invoiceNumber,
4830
+ status: "QUEUED",
4831
+ error: { code: 0, message }
4832
+ });
4833
+ }
4834
+ }
4835
+ }
4836
+ } finally {
4837
+ try {
4838
+ await client.onlineSession.closeSession(sessionRef);
4839
+ } catch {
4840
+ }
4841
+ }
4842
+ return result;
4843
+ }
4844
+ // TODO(perf): Each correction opens a separate KSeF session.
4845
+ // For batch corrections, consider a correctBatch() sharing one session (like submit()).
4846
+ async correct(client, options) {
4847
+ const { storage, rejectedInvoiceId, correctedInvoiceXml } = options;
4848
+ const original = await storage.get(rejectedInvoiceId);
4849
+ if (!original) {
4850
+ throw new Error(`Offline invoice not found: ${rejectedInvoiceId}`);
4851
+ }
4852
+ if (original.status !== "REJECTED") {
4853
+ throw new Error(
4854
+ original.status === "CORRECTED" ? `Invoice ${rejectedInvoiceId} has already been corrected (by ${original.correctedBy})` : `Only rejected invoices can be corrected (current status: ${original.status})`
4855
+ );
4856
+ }
4857
+ const originalHash = import_node_crypto3.default.createHash("sha256").update(original.invoiceXml).digest("base64");
4858
+ await client.crypto.init();
4859
+ const encData = client.crypto.getEncryptionData();
4860
+ const formCode = options.formCode ?? DEFAULT_FORM_CODE;
4861
+ const openResp = await client.onlineSession.openSession(
4862
+ { formCode, encryption: encData.encryptionInfo }
4863
+ );
4864
+ const sessionRef = openResp.referenceNumber;
4865
+ try {
4866
+ const data = new TextEncoder().encode(correctedInvoiceXml);
4867
+ const plainMeta = client.crypto.getFileMetadata(data);
4868
+ const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
4869
+ const encMeta = client.crypto.getFileMetadata(encrypted);
4870
+ const correctionId = import_node_crypto3.default.randomUUID();
4871
+ const submittedAt = (/* @__PURE__ */ new Date()).toISOString();
4872
+ const correctedHashBase64 = import_node_crypto3.default.createHash("sha256").update(correctedInvoiceXml).digest("base64");
4873
+ const kod1Url = this.qrService.buildInvoiceVerificationUrl(
4874
+ original.sellerNip,
4875
+ original.invoiceDate,
4876
+ correctedHashBase64
4877
+ );
4878
+ let kod2Url;
4879
+ if (options.certificate) {
4880
+ kod2Url = this.qrService.buildCertificateVerificationUrl(
4881
+ original.sellerIdentifier.type,
4882
+ original.sellerIdentifier.value,
4883
+ original.sellerNip,
4884
+ options.certificate.certificateSerial,
4885
+ correctedHashBase64,
4886
+ options.certificate.privateKeyPem
4887
+ );
4888
+ }
4889
+ const correctionMetadata = {
4890
+ id: correctionId,
4891
+ mode: original.mode,
4892
+ reason: original.reason,
4893
+ status: "SUBMITTED",
4894
+ invoiceNumber: original.invoiceNumber,
4895
+ invoiceDate: original.invoiceDate,
4896
+ invoiceXml: correctedInvoiceXml,
4897
+ sellerNip: original.sellerNip,
4898
+ sellerIdentifier: original.sellerIdentifier,
4899
+ buyerIdentifier: original.buyerIdentifier,
4900
+ kod1Url,
4901
+ kod2Url,
4902
+ generatedAt: submittedAt,
4903
+ submitBy: original.submitBy,
4904
+ submittedAt,
4905
+ correctedInvoiceId: rejectedInvoiceId
4906
+ };
4907
+ await storage.save(correctionMetadata);
4908
+ const resp = await client.onlineSession.sendInvoice(sessionRef, {
4909
+ invoiceHash: plainMeta.hashSHA,
4910
+ invoiceSize: plainMeta.fileSize,
4911
+ encryptedInvoiceHash: encMeta.hashSHA,
4912
+ encryptedInvoiceSize: encMeta.fileSize,
4913
+ encryptedInvoiceContent: Buffer.from(encrypted).toString("base64"),
4914
+ offlineMode: true,
4915
+ hashOfCorrectedInvoice: originalHash
4916
+ });
4917
+ await storage.update(correctionId, {
4918
+ status: "ACCEPTED",
4919
+ acceptedAt: (/* @__PURE__ */ new Date()).toISOString(),
4920
+ ksefReferenceNumber: resp.referenceNumber
4921
+ });
4922
+ await storage.update(rejectedInvoiceId, {
4923
+ status: "CORRECTED",
4924
+ correctedBy: correctionId
4925
+ });
4926
+ return {
4927
+ invoiceId: correctionId,
4928
+ invoiceNumber: correctionMetadata.invoiceNumber,
4929
+ status: "ACCEPTED",
4930
+ ksefReferenceNumber: resp.referenceNumber
4931
+ };
4932
+ } finally {
4933
+ try {
4934
+ await client.onlineSession.closeSession(sessionRef);
4935
+ } catch {
4936
+ }
4937
+ }
4938
+ }
4939
+ };
4940
+ }
4941
+ });
4942
+
4354
4943
  // src/client.ts
4355
4944
  var client_exports = {};
4356
4945
  __export(client_exports, {
@@ -4416,6 +5005,7 @@ var init_client = __esm({
4416
5005
  init_cryptography_service();
4417
5006
  init_verification_link_service();
4418
5007
  init_auth_xml_builder();
5008
+ init_offline_invoice_workflow();
4419
5009
  KSeFClient = class {
4420
5010
  auth;
4421
5011
  activeSessions;
@@ -4434,6 +5024,8 @@ var init_client = __esm({
4434
5024
  qr;
4435
5025
  options;
4436
5026
  authManager;
5027
+ _baseRestClientConfig;
5028
+ _offline;
4437
5029
  constructor(options) {
4438
5030
  this.options = resolveOptions(options);
4439
5031
  const authManager = options?.authManager ?? new DefaultAuthManager(async () => {
@@ -4444,6 +5036,8 @@ var init_client = __esm({
4444
5036
  });
4445
5037
  this.authManager = authManager;
4446
5038
  const restClientConfig = buildRestClientConfig(options, authManager);
5039
+ const { authManager: _am, ...baseConfig } = restClientConfig;
5040
+ this._baseRestClientConfig = baseConfig;
4447
5041
  const restClient = new RestClient(this.options, restClientConfig);
4448
5042
  const fetcher = new CertificateFetcher(restClient);
4449
5043
  this.crypto = new CryptographyService(fetcher);
@@ -4462,6 +5056,16 @@ var init_client = __esm({
4462
5056
  this.testData = new TestDataService(restClient, this.options.environmentName);
4463
5057
  this.qr = new VerificationLinkService(this.options.baseQrUrl);
4464
5058
  }
5059
+ get offline() {
5060
+ if (!this._offline) {
5061
+ this._offline = new OfflineInvoiceWorkflow(this.qr);
5062
+ }
5063
+ return this._offline;
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
+ }
4465
5069
  async loginWithToken(token, nip) {
4466
5070
  const challenge = await this.auth.getChallenge();
4467
5071
  await this.crypto.init();
@@ -4542,8 +5146,10 @@ __export(index_exports, {
4542
5146
  FORM_CODES: () => FORM_CODES,
4543
5147
  FORM_CODE_KEYS: () => FORM_CODE_KEYS,
4544
5148
  FileHwmStore: () => FileHwmStore,
5149
+ FileOfflineInvoiceStorage: () => FileOfflineInvoiceStorage,
4545
5150
  INVOICE_TYPES_BY_SYSTEM_CODE: () => INVOICE_TYPES_BY_SYSTEM_CODE,
4546
5151
  InMemoryHwmStore: () => InMemoryHwmStore,
5152
+ InMemoryOfflineInvoiceStorage: () => InMemoryOfflineInvoiceStorage,
4547
5153
  InternalId: () => InternalId,
4548
5154
  InvoiceDownloadService: () => InvoiceDownloadService,
4549
5155
  InvoiceQueryFilterBuilder: () => InvoiceQueryFilterBuilder,
@@ -4567,6 +5173,7 @@ __export(index_exports, {
4567
5173
  LimitsService: () => LimitsService,
4568
5174
  Nip: () => Nip,
4569
5175
  NipVatUe: () => NipVatUe,
5176
+ OfflineInvoiceWorkflow: () => OfflineInvoiceWorkflow,
4570
5177
  OnlineSessionService: () => OnlineSessionService,
4571
5178
  PERMISSION_DESCRIPTION_MAX_LENGTH: () => PERMISSION_DESCRIPTION_MAX_LENGTH,
4572
5179
  PERMISSION_DESCRIPTION_MIN_LENGTH: () => PERMISSION_DESCRIPTION_MIN_LENGTH,
@@ -4596,6 +5203,7 @@ __export(index_exports, {
4596
5203
  UpoVersion: () => UpoVersion,
4597
5204
  VatUe: () => VatUe,
4598
5205
  VerificationLinkService: () => VerificationLinkService,
5206
+ addBusinessDays: () => addBusinessDays,
4599
5207
  authenticateWithCertificate: () => authenticateWithCertificate,
4600
5208
  authenticateWithExternalSignature: () => authenticateWithExternalSignature,
4601
5209
  authenticateWithPkcs12: () => authenticateWithPkcs12,
@@ -4603,6 +5211,7 @@ __export(index_exports, {
4603
5211
  batchValidationDetails: () => batchValidationDetails,
4604
5212
  buildUnsignedAuthTokenRequestXml: () => buildUnsignedAuthTokenRequestXml,
4605
5213
  calculateBackoff: () => calculateBackoff,
5214
+ calculateOfflineDeadline: () => calculateOfflineDeadline,
4606
5215
  createZip: () => createZip,
4607
5216
  decodeJwtPayload: () => decodeJwtPayload,
4608
5217
  deduplicateByKsefNumber: () => deduplicateByKsefNumber,
@@ -4612,9 +5221,16 @@ __export(index_exports, {
4612
5221
  defaultTransport: () => defaultTransport,
4613
5222
  exportAndDownload: () => exportAndDownload,
4614
5223
  exportInvoices: () => exportInvoices,
5224
+ extendDeadlineForMaintenance: () => extendDeadlineForMaintenance,
5225
+ extractInvoiceFields: () => extractInvoiceFields,
5226
+ getDefaultReason: () => getDefaultReason,
4615
5227
  getEffectiveStartDate: () => getEffectiveStartDate,
4616
5228
  getFormCode: () => getFormCode,
5229
+ getPolishHolidays: () => getPolishHolidays,
5230
+ getTimeUntilDeadline: () => getTimeUntilDeadline,
4617
5231
  incrementalExportAndDownload: () => incrementalExportAndDownload,
5232
+ isExpired: () => isExpired,
5233
+ isPolishHoliday: () => isPolishHoliday,
4618
5234
  isRetryableError: () => isRetryableError,
4619
5235
  isRetryableStatus: () => isRetryableStatus,
4620
5236
  isValidBase64: () => isValidBase64,
@@ -4632,6 +5248,7 @@ __export(index_exports, {
4632
5248
  isValidReferenceNumber: () => isValidReferenceNumber,
4633
5249
  isValidSha256Base64: () => isValidSha256Base64,
4634
5250
  isValidVatUe: () => isValidVatUe,
5251
+ nextBusinessDay: () => nextBusinessDay,
4635
5252
  openOnlineSession: () => openOnlineSession,
4636
5253
  openSendAndClose: () => openSendAndClose,
4637
5254
  parseFormCode: () => parseFormCode,
@@ -4640,6 +5257,9 @@ __export(index_exports, {
4640
5257
  parseUpoXml: () => parseUpoXml,
4641
5258
  pollUntil: () => pollUntil,
4642
5259
  resolveOptions: () => resolveOptions,
5260
+ resumeOnlineSession: () => resumeOnlineSession,
5261
+ runWithConcurrency: () => runWithConcurrency,
5262
+ sha256Base64: () => sha256Base642,
4643
5263
  sleep: () => sleep,
4644
5264
  unzip: () => unzip,
4645
5265
  updateContinuationPoint: () => updateContinuationPoint,
@@ -4654,6 +5274,7 @@ __export(index_exports, {
4654
5274
  validatePresignedUrl: () => validatePresignedUrl,
4655
5275
  validateSchema: () => validateSchema,
4656
5276
  validateWellFormedness: () => validateWellFormedness,
5277
+ verifyHash: () => verifyHash,
4657
5278
  xmlToObject: () => xmlToObject
4658
5279
  });
4659
5280
  module.exports = __toCommonJS(index_exports);
@@ -4689,65 +5310,8 @@ init_xml_to_object();
4689
5310
  init_schema_registry();
4690
5311
  init_invoice_validator();
4691
5312
 
4692
- // src/models/document-structures/types.ts
4693
- var SystemCode = {
4694
- FA_2: "FA (2)",
4695
- FA_3: "FA (3)",
4696
- PEF_3: "PEF (3)",
4697
- PEF_KOR_3: "PEF_KOR (3)",
4698
- FA_RR_1: "FA_RR (1)"
4699
- };
4700
- var FORM_CODES = {
4701
- FA_2: { systemCode: "FA (2)", schemaVersion: "1-0E", value: "FA" },
4702
- FA_3: { systemCode: "FA (3)", schemaVersion: "1-0E", value: "FA" },
4703
- PEF_3: { systemCode: "PEF (3)", schemaVersion: "2-1", value: "PEF" },
4704
- PEF_KOR_3: { systemCode: "PEF_KOR (3)", schemaVersion: "2-1", value: "PEF" },
4705
- FA_RR_1_LEGACY: { systemCode: "FA_RR (1)", schemaVersion: "1-0E", value: "RR" },
4706
- FA_RR_1_TRANSITION: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "RR" },
4707
- FA_RR_1: { systemCode: "FA_RR (1)", schemaVersion: "1-1E", value: "FA_RR" }
4708
- };
4709
- var DEFAULT_FORM_CODE = FORM_CODES.FA_3;
4710
- var INVOICE_TYPES_BY_SYSTEM_CODE = {
4711
- [SystemCode.FA_2]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4712
- [SystemCode.FA_3]: ["Vat", "Zal", "Kor", "Roz", "Upr", "KorZal", "KorRoz"],
4713
- [SystemCode.PEF_3]: ["VatPef", "VatPefSp", "KorPef"],
4714
- [SystemCode.PEF_KOR_3]: ["KorPef"],
4715
- [SystemCode.FA_RR_1]: ["VatRr", "KorVatRr"]
4716
- };
4717
- var FORM_CODE_KEYS = {
4718
- FA2: FORM_CODES.FA_2,
4719
- FA3: FORM_CODES.FA_3,
4720
- PEF3: FORM_CODES.PEF_3,
4721
- PEFKOR3: FORM_CODES.PEF_KOR_3,
4722
- FARR1: FORM_CODES.FA_RR_1
4723
- };
4724
-
4725
- // src/models/document-structures/helpers.ts
4726
- var SYSTEM_CODE_TO_FORM_CODE = {
4727
- [SystemCode.FA_2]: FORM_CODES.FA_2,
4728
- [SystemCode.FA_3]: FORM_CODES.FA_3,
4729
- [SystemCode.PEF_3]: FORM_CODES.PEF_3,
4730
- [SystemCode.PEF_KOR_3]: FORM_CODES.PEF_KOR_3,
4731
- [SystemCode.FA_RR_1]: FORM_CODES.FA_RR_1
4732
- };
4733
- var ALL_FORM_CODES = Object.values(FORM_CODES);
4734
- var BATCH_DISALLOWED_SYSTEM_CODES = /* @__PURE__ */ new Set([
4735
- SystemCode.PEF_3,
4736
- SystemCode.PEF_KOR_3
4737
- ]);
4738
- function getFormCode(systemCode) {
4739
- return SYSTEM_CODE_TO_FORM_CODE[systemCode];
4740
- }
4741
- function parseFormCode(raw) {
4742
- const match = ALL_FORM_CODES.find(
4743
- (fc) => fc.systemCode === raw.systemCode && fc.schemaVersion === raw.schemaVersion && fc.value === raw.value
4744
- );
4745
- return match ?? raw;
4746
- }
4747
- function validateFormCodeForSession(formCode, sessionType) {
4748
- if (sessionType === "online") return true;
4749
- return !BATCH_DISALLOWED_SYSTEM_CODES.has(formCode.systemCode);
4750
- }
5313
+ // src/models/index.ts
5314
+ init_document_structures();
4751
5315
 
4752
5316
  // src/services/index.ts
4753
5317
  init_auth();
@@ -5482,6 +6046,18 @@ function parseKSeFTokenContext(token) {
5482
6046
  };
5483
6047
  }
5484
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
+
5485
6061
  // src/workflows/polling.ts
5486
6062
  async function pollUntil(action, condition, options) {
5487
6063
  const intervalMs = options?.intervalMs ?? 2e3;
@@ -5499,6 +6075,13 @@ async function pollUntil(action, condition, options) {
5499
6075
  );
5500
6076
  }
5501
6077
 
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();
6083
+ init_document_structures();
6084
+
5502
6085
  // src/xml/upo-parser.ts
5503
6086
  var import_fast_xml_parser = require("fast-xml-parser");
5504
6087
  init_ksef_validation_error();
@@ -5617,21 +6200,54 @@ function parseUpoXml(xml) {
5617
6200
  };
5618
6201
  }
5619
6202
 
6203
+ // src/xml/invoice-field-extractor.ts
6204
+ var import_fast_xml_parser2 = require("fast-xml-parser");
6205
+ var invoiceParser = new import_fast_xml_parser2.XMLParser({
6206
+ ignoreAttributes: false,
6207
+ attributeNamePrefix: "@_",
6208
+ parseTagValue: false,
6209
+ parseAttributeValue: false,
6210
+ removeNSPrefix: true,
6211
+ trimValues: false
6212
+ });
6213
+ function extractInvoiceFields(xml) {
6214
+ const parsed = invoiceParser.parse(xml);
6215
+ const root = parsed?.Faktura;
6216
+ if (!root || typeof root !== "object") {
6217
+ throw new Error(
6218
+ "Cannot extract invoice fields: missing <Faktura> root element. Ensure the XML is a valid KSeF invoice (FA_2, FA_3, or FA_RR)."
6219
+ );
6220
+ }
6221
+ if (root.Fa && typeof root.Fa === "object") {
6222
+ const invoiceDate = nonEmptyString(root.Fa.P_1);
6223
+ const invoiceNumber = nonEmptyString(root.Fa.P_2);
6224
+ if (invoiceDate && invoiceNumber) {
6225
+ return { invoiceNumber, invoiceDate };
6226
+ }
6227
+ }
6228
+ if (root.FakturaRR && typeof root.FakturaRR === "object") {
6229
+ const invoiceDate = nonEmptyString(root.FakturaRR.P_4B);
6230
+ const invoiceNumber = nonEmptyString(root.FakturaRR.P_4C);
6231
+ if (invoiceDate && invoiceNumber) {
6232
+ return { invoiceNumber, invoiceDate };
6233
+ }
6234
+ }
6235
+ throw new Error(
6236
+ "Cannot extract invoice number and date from XML. Expected <Fa><P_1>/<P_2> (FA format) or <FakturaRR><P_4B>/<P_4C> (RR format)."
6237
+ );
6238
+ }
6239
+ function nonEmptyString(value) {
6240
+ return typeof value === "string" && value.length > 0 ? value : void 0;
6241
+ }
6242
+
5620
6243
  // src/workflows/online-session-workflow.ts
5621
6244
  init_invoice_validator();
5622
6245
  init_ksef_validation_error();
5623
- async function openOnlineSession(client, options) {
5624
- await client.crypto.init();
5625
- const encData = client.crypto.getEncryptionData();
5626
- const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
5627
- const openResp = await client.onlineSession.openSession(
5628
- { formCode, encryption: encData.encryptionInfo },
5629
- options?.upoVersion
5630
- );
5631
- const sessionRef = openResp.referenceNumber;
6246
+ function buildSessionHandle(params) {
6247
+ const { deps, sessionRef, validUntil, cipherKey, cipherIv, formCode, validate: validate2 } = params;
5632
6248
  async function fetchUpo(pollOpts) {
5633
6249
  const result = await pollUntil(
5634
- () => client.sessionStatus.getSessionStatus(sessionRef),
6250
+ () => deps.sessionStatus.getSessionStatus(sessionRef),
5635
6251
  (s) => s.status.code === 200 || s.status.code >= 400,
5636
6252
  { ...pollOpts, description: `UPO for session ${sessionRef}` }
5637
6253
  );
@@ -5647,9 +6263,9 @@ async function openOnlineSession(client, options) {
5647
6263
  }
5648
6264
  return {
5649
6265
  sessionRef,
5650
- validUntil: openResp.validUntil,
6266
+ validUntil,
5651
6267
  async sendInvoice(invoiceXml) {
5652
- if (options?.validate) {
6268
+ if (validate2) {
5653
6269
  const xmlStr = typeof invoiceXml === "string" ? invoiceXml : new TextDecoder().decode(invoiceXml);
5654
6270
  const vResult = await validate(xmlStr);
5655
6271
  if (!vResult.valid) {
@@ -5660,10 +6276,10 @@ async function openOnlineSession(client, options) {
5660
6276
  }
5661
6277
  }
5662
6278
  const data = typeof invoiceXml === "string" ? new TextEncoder().encode(invoiceXml) : invoiceXml;
5663
- const plainMeta = client.crypto.getFileMetadata(data);
5664
- const encrypted = client.crypto.encryptAES256(data, encData.cipherKey, encData.cipherIv);
5665
- const encMeta = client.crypto.getFileMetadata(encrypted);
5666
- 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, {
5667
6283
  invoiceHash: plainMeta.hashSHA,
5668
6284
  invoiceSize: plainMeta.fileSize,
5669
6285
  encryptedInvoiceHash: encMeta.hashSHA,
@@ -5673,7 +6289,7 @@ async function openOnlineSession(client, options) {
5673
6289
  return resp.referenceNumber;
5674
6290
  },
5675
6291
  async close() {
5676
- await client.onlineSession.closeSession(sessionRef);
6292
+ await deps.onlineSession.closeSession(sessionRef);
5677
6293
  },
5678
6294
  async waitForUpo(pollOpts) {
5679
6295
  return fetchUpo(pollOpts);
@@ -5682,13 +6298,75 @@ async function openOnlineSession(client, options) {
5682
6298
  const upoInfo = await fetchUpo(pollOpts);
5683
6299
  const parsed = [];
5684
6300
  for (const page of upoInfo.pages) {
5685
- const result = await client.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
6301
+ const result = await deps.sessionStatus.getSessionUpo(sessionRef, page.referenceNumber);
5686
6302
  parsed.push(parseUpoXml(result.upo));
5687
6303
  }
5688
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
+ };
5689
6320
  }
5690
6321
  };
5691
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
+ }
5692
6370
  async function openSendAndClose(client, invoices, options) {
5693
6371
  const handle = await openOnlineSession(client, options);
5694
6372
  for (const inv of invoices) {
@@ -5699,7 +6377,11 @@ async function openSendAndClose(client, invoices, options) {
5699
6377
  }
5700
6378
 
5701
6379
  // src/workflows/batch-session-workflow.ts
6380
+ init_document_structures();
5702
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
+ }
5703
6385
  await client.crypto.init();
5704
6386
  if (options?.validate) {
5705
6387
  const { unzip: unzip2 } = await Promise.resolve().then(() => (init_zip(), zip_exports));
@@ -5742,7 +6424,7 @@ async function uploadBatch(client, zipData, options) {
5742
6424
  },
5743
6425
  ordinalNumber: i + 1
5744
6426
  }));
5745
- await client.batchSession.sendParts(openResp, sendingParts);
6427
+ await client.batchSession.sendParts(openResp, sendingParts, options?.parallelism);
5746
6428
  await client.batchSession.closeSession(openResp.referenceNumber);
5747
6429
  const result = await pollUntil(
5748
6430
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5763,6 +6445,9 @@ async function uploadBatch(client, zipData, options) {
5763
6445
  };
5764
6446
  }
5765
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
+ }
5766
6451
  await client.crypto.init();
5767
6452
  const encData = client.crypto.getEncryptionData();
5768
6453
  const formCode = options?.formCode ?? DEFAULT_FORM_CODE;
@@ -5784,7 +6469,7 @@ async function uploadBatchStream(client, zipStreamFactory, zipSize, options) {
5784
6469
  },
5785
6470
  options?.upoVersion
5786
6471
  );
5787
- await client.batchSession.sendPartsWithStream(openResp, streamParts);
6472
+ await client.batchSession.sendPartsWithStream(openResp, streamParts, options?.parallelism);
5788
6473
  await client.batchSession.closeSession(openResp.referenceNumber);
5789
6474
  const result = await pollUntil(
5790
6475
  () => client.sessionStatus.getSessionStatus(openResp.referenceNumber),
@@ -5859,6 +6544,7 @@ async function doExport(client, filters, options) {
5859
6544
  url: p.url,
5860
6545
  method: p.method,
5861
6546
  partSize: p.partSize,
6547
+ partHash: p.partHash,
5862
6548
  encryptedPartSize: p.encryptedPartSize,
5863
6549
  encryptedPartHash: p.encryptedPartHash,
5864
6550
  expirationDate: p.expirationDate
@@ -5884,6 +6570,9 @@ async function exportAndDownload(client, filters, options) {
5884
6570
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
5885
6571
  }
5886
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
+ }
5887
6576
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
5888
6577
  decryptedParts.push(decrypted);
5889
6578
  }
@@ -5956,6 +6645,9 @@ async function incrementalExportAndDownload(client, options) {
5956
6645
  throw new Error(`Download failed for part ${part.ordinalNumber}: HTTP ${resp.status}`);
5957
6646
  }
5958
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
+ }
5959
6651
  const decrypted = client.crypto.decryptAES256(encryptedData, encData.cipherKey, encData.cipherIv);
5960
6652
  decryptedParts.push(decrypted);
5961
6653
  }
@@ -6119,7 +6811,141 @@ async function authenticateWithPkcs12(client, options) {
6119
6811
  });
6120
6812
  }
6121
6813
 
6814
+ // src/offline/index.ts
6815
+ init_deadline();
6816
+ init_holidays();
6817
+
6818
+ // src/offline/storage.ts
6819
+ function matchesFilter(invoice, filter) {
6820
+ if (filter.status !== void 0) {
6821
+ const statuses = Array.isArray(filter.status) ? filter.status : [filter.status];
6822
+ if (!statuses.includes(invoice.status)) return false;
6823
+ }
6824
+ if (filter.mode !== void 0 && invoice.mode !== filter.mode) return false;
6825
+ if (filter.sellerNip !== void 0 && invoice.sellerNip !== filter.sellerNip) return false;
6826
+ if (filter.expiringBefore !== void 0) {
6827
+ const cutoff = typeof filter.expiringBefore === "string" ? new Date(filter.expiringBefore).getTime() : filter.expiringBefore.getTime();
6828
+ if (new Date(invoice.submitBy).getTime() >= cutoff) return false;
6829
+ }
6830
+ return true;
6831
+ }
6832
+ var InMemoryOfflineInvoiceStorage = class {
6833
+ store = /* @__PURE__ */ new Map();
6834
+ async save(invoice) {
6835
+ this.store.set(invoice.id, JSON.parse(JSON.stringify(invoice)));
6836
+ }
6837
+ async get(id) {
6838
+ const entry = this.store.get(id);
6839
+ return entry ? JSON.parse(JSON.stringify(entry)) : null;
6840
+ }
6841
+ async list(filter) {
6842
+ const all = [...this.store.values()];
6843
+ if (!filter) return all.map((i) => JSON.parse(JSON.stringify(i)));
6844
+ return all.filter((i) => matchesFilter(i, filter)).map((i) => JSON.parse(JSON.stringify(i)));
6845
+ }
6846
+ async update(id, updates) {
6847
+ const existing = this.store.get(id);
6848
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6849
+ this.store.set(id, JSON.parse(JSON.stringify({ ...existing, ...updates })));
6850
+ }
6851
+ async delete(id) {
6852
+ this.store.delete(id);
6853
+ }
6854
+ };
6855
+
6856
+ // src/offline/file-storage.ts
6857
+ var fs2 = __toESM(require("fs/promises"), 1);
6858
+ var path = __toESM(require("path"), 1);
6859
+ var os = __toESM(require("os"), 1);
6860
+ function resolveDir(dir) {
6861
+ if (dir === "~" || dir.startsWith("~/")) {
6862
+ return path.join(os.homedir(), dir.slice(1));
6863
+ }
6864
+ return dir;
6865
+ }
6866
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
6867
+ function validateId(id) {
6868
+ if (!UUID_RE.test(id)) {
6869
+ throw new Error(`Invalid invoice ID: ${id}`);
6870
+ }
6871
+ }
6872
+ var FileOfflineInvoiceStorage = class {
6873
+ dir;
6874
+ constructor(directory) {
6875
+ this.dir = resolveDir(directory ?? "~/.ksef/offline");
6876
+ }
6877
+ async ensureDir() {
6878
+ await fs2.mkdir(this.dir, { recursive: true });
6879
+ }
6880
+ filePath(id) {
6881
+ validateId(id);
6882
+ return path.join(this.dir, `${id}.json`);
6883
+ }
6884
+ async save(invoice) {
6885
+ await this.ensureDir();
6886
+ const file = this.filePath(invoice.id);
6887
+ const tmp = `${file}.tmp`;
6888
+ await fs2.writeFile(tmp, JSON.stringify(invoice, null, 2));
6889
+ await fs2.rename(tmp, file);
6890
+ }
6891
+ async get(id) {
6892
+ const file = this.filePath(id);
6893
+ try {
6894
+ return JSON.parse(await fs2.readFile(file, "utf-8"));
6895
+ } catch (err) {
6896
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
6897
+ return null;
6898
+ }
6899
+ console.warn(`Warning: Failed to read offline invoice ${id}: ${err instanceof Error ? err.message : String(err)}`);
6900
+ return null;
6901
+ }
6902
+ }
6903
+ async list(filter) {
6904
+ let files;
6905
+ try {
6906
+ files = (await fs2.readdir(this.dir)).filter((f) => f.endsWith(".json"));
6907
+ } catch {
6908
+ return [];
6909
+ }
6910
+ const results = [];
6911
+ for (const file of files) {
6912
+ try {
6913
+ const data = JSON.parse(
6914
+ await fs2.readFile(path.join(this.dir, file), "utf-8")
6915
+ );
6916
+ if (!filter || matchesFilter(data, filter)) {
6917
+ results.push(data);
6918
+ }
6919
+ } catch (err) {
6920
+ console.warn(`Warning: Skipping corrupt offline invoice file ${file}: ${err instanceof Error ? err.message : String(err)}`);
6921
+ }
6922
+ }
6923
+ return results;
6924
+ }
6925
+ /**
6926
+ * Update invoice metadata (read-modify-write).
6927
+ *
6928
+ * Note: No file locking — concurrent updates to the same ID may cause
6929
+ * lost writes. Acceptable for CLI (single process). Library consumers
6930
+ * running parallel operations should use external locking.
6931
+ */
6932
+ async update(id, updates) {
6933
+ const existing = await this.get(id);
6934
+ if (!existing) throw new Error(`Offline invoice not found: ${id}`);
6935
+ await this.save({ ...existing, ...updates });
6936
+ }
6937
+ async delete(id) {
6938
+ const file = this.filePath(id);
6939
+ try {
6940
+ await fs2.unlink(file);
6941
+ } catch (e) {
6942
+ if (e instanceof Error && "code" in e && e.code !== "ENOENT") throw e;
6943
+ }
6944
+ }
6945
+ };
6946
+
6122
6947
  // src/index.ts
6948
+ init_offline_invoice_workflow();
6123
6949
  init_client();
6124
6950
  // Annotate the CommonJS export names for ESM import in node:
6125
6951
  0 && (module.exports = {
@@ -6150,8 +6976,10 @@ init_client();
6150
6976
  FORM_CODES,
6151
6977
  FORM_CODE_KEYS,
6152
6978
  FileHwmStore,
6979
+ FileOfflineInvoiceStorage,
6153
6980
  INVOICE_TYPES_BY_SYSTEM_CODE,
6154
6981
  InMemoryHwmStore,
6982
+ InMemoryOfflineInvoiceStorage,
6155
6983
  InternalId,
6156
6984
  InvoiceDownloadService,
6157
6985
  InvoiceQueryFilterBuilder,
@@ -6175,6 +7003,7 @@ init_client();
6175
7003
  LimitsService,
6176
7004
  Nip,
6177
7005
  NipVatUe,
7006
+ OfflineInvoiceWorkflow,
6178
7007
  OnlineSessionService,
6179
7008
  PERMISSION_DESCRIPTION_MAX_LENGTH,
6180
7009
  PERMISSION_DESCRIPTION_MIN_LENGTH,
@@ -6204,6 +7033,7 @@ init_client();
6204
7033
  UpoVersion,
6205
7034
  VatUe,
6206
7035
  VerificationLinkService,
7036
+ addBusinessDays,
6207
7037
  authenticateWithCertificate,
6208
7038
  authenticateWithExternalSignature,
6209
7039
  authenticateWithPkcs12,
@@ -6211,6 +7041,7 @@ init_client();
6211
7041
  batchValidationDetails,
6212
7042
  buildUnsignedAuthTokenRequestXml,
6213
7043
  calculateBackoff,
7044
+ calculateOfflineDeadline,
6214
7045
  createZip,
6215
7046
  decodeJwtPayload,
6216
7047
  deduplicateByKsefNumber,
@@ -6220,9 +7051,16 @@ init_client();
6220
7051
  defaultTransport,
6221
7052
  exportAndDownload,
6222
7053
  exportInvoices,
7054
+ extendDeadlineForMaintenance,
7055
+ extractInvoiceFields,
7056
+ getDefaultReason,
6223
7057
  getEffectiveStartDate,
6224
7058
  getFormCode,
7059
+ getPolishHolidays,
7060
+ getTimeUntilDeadline,
6225
7061
  incrementalExportAndDownload,
7062
+ isExpired,
7063
+ isPolishHoliday,
6226
7064
  isRetryableError,
6227
7065
  isRetryableStatus,
6228
7066
  isValidBase64,
@@ -6240,6 +7078,7 @@ init_client();
6240
7078
  isValidReferenceNumber,
6241
7079
  isValidSha256Base64,
6242
7080
  isValidVatUe,
7081
+ nextBusinessDay,
6243
7082
  openOnlineSession,
6244
7083
  openSendAndClose,
6245
7084
  parseFormCode,
@@ -6248,6 +7087,9 @@ init_client();
6248
7087
  parseUpoXml,
6249
7088
  pollUntil,
6250
7089
  resolveOptions,
7090
+ resumeOnlineSession,
7091
+ runWithConcurrency,
7092
+ sha256Base64,
6251
7093
  sleep,
6252
7094
  unzip,
6253
7095
  updateContinuationPoint,
@@ -6262,6 +7104,7 @@ init_client();
6262
7104
  validatePresignedUrl,
6263
7105
  validateSchema,
6264
7106
  validateWellFormedness,
7107
+ verifyHash,
6265
7108
  xmlToObject
6266
7109
  });
6267
7110
  //# sourceMappingURL=index.cjs.map