ksef-client-ts 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,58 +2,18 @@
2
2
 
3
3
  TypeScript client for the Polish National e-Invoice System (KSeF) API v2.
4
4
 
5
- **[Documentation](https://flopsstuff.github.io/ksef-client-ts)** · **[NPM](https://www.npmjs.com/package/ksef-client-ts)**
6
-
7
- ## Features
8
-
9
- - **Complete API coverage** — KSeF API v2.6.0, types aligned with the official OpenAPI spec
10
- - **Offline invoice mode** — full lifecycle for all 4 KSeF offline modes with QR KOD I + KOD II signing, deadline tracking, local storage, and technical correction
11
- - **Full-featured CLI** — `ksef` with 15 command groups for auth, sessions, invoices, offline, batch upload, export, and more
12
- - **High-level workflows** — auth, online/batch sessions, invoice export — full lifecycle in a single call
13
- - **Built-in cryptography** — AES-256-CBC, RSA-OAEP, ECDH, XAdES-B signatures, self-signed certs (Node crypto)
14
- - **External signing** — HSM, EPUAP, and smart card authentication via callback-based signing
15
- - **Automatic token management** — AuthManager: token injection, 401 refresh with dedup
16
- - **Opt-in circuit breaker** — pauses outgoing requests for a short cooldown window after consecutive upstream failures
17
- - **Streaming batch uploads** — constant-memory batch upload via Web Streams API with ZIP bomb protection
18
- - **Incremental export** — HWM-based paginated export with file-based state persistence
19
- - **Multiple document structures** — FA, PEF, PEF_KOR, FA_RR with typed FormCode constants and UPO parsing
20
- - **Invoice XML serialization (FA2/FA3/PEF/PEF_KOR)** — build XSD-compliant invoice XML from typed TypeScript objects with correct element ordering (including the FA3 per-VAT-rate interleave) and namespace injection; `ksef invoice build` exposes the same pipeline to shell workflows with JSON/YAML input and optional XSD validation
21
- - **Invoice XML validation** — three-level client-side validation (well-formedness, XSD schema via Zod, NIP/PESEL checksums, future date rejection) with auto-detection for all 6 invoice types
22
- - **Typed errors with RFC 7807 Problem Details** — `KSeFError` hierarchy with dedicated classes for 400/401/403/410/429 carrying structured diagnostic context; exhaustive dispatch via the `KSeFApiProblem` union and `assertNever`; fluent request builders
23
- - **Comprehensive test coverage** — unit + E2E tests across HTTP, crypto, services, workflows; CI on every change
24
- - **Interactive setup wizard** — `ksef setup` guides through environment selection, authentication, and token generation in one flow
25
- - **Zero HTTP dependencies** — native `fetch` (Node 18+); dual ESM/CJS via tsup
26
-
27
- Requires **Node.js 18+**.
5
+ **[Documentation](https://flopsstuff.github.io/ksef-client-ts)** · **[GitHub](https://github.com/Flopsstuff/ksef-client-ts)**
28
6
 
29
7
  ## Install
30
8
 
31
- Install CLI globally:
32
-
33
- ```bash
34
- npm i -g ksef-client-ts
35
- ksef --help
36
- ```
37
-
38
- Install in a project:
39
-
40
9
  ```bash
41
- # Choose one package manager:
42
- # npm
43
- npm i ksef-client-ts
44
- # Yarn
45
- yarn add ksef-client-ts
46
- # pnpm
47
- pnpm add ksef-client-ts
10
+ npm i ksef-client-ts # library
11
+ npm i -g ksef-client-ts # ksef CLI
48
12
  ```
49
13
 
50
- For local development, clone and build:
14
+ Requires **Node.js 18+**. Dual ESM/CJS output via tsup.
51
15
 
52
- ```bash
53
- git clone https://github.com/Flopsstuff/ksef-client-ts.git
54
- cd ksef-client-ts
55
- yarn install && yarn build
56
- ```
16
+ ## Quick start
57
17
 
58
18
  ```ts
59
19
  import { KSeFClient } from 'ksef-client-ts';
@@ -62,7 +22,7 @@ const client = new KSeFClient({ environment: 'TEST' });
62
22
  await client.crypto.init();
63
23
 
64
24
  const challenge = await client.auth.getChallenge();
65
- // ... authenticate, open session, send invoices
25
+ // ... authenticate, open a session, send invoices
66
26
  ```
67
27
 
68
28
  ```bash
@@ -70,38 +30,19 @@ ksef auth login --token "$KSEF_TOKEN" --nip "$KSEF_NIP"
70
30
  ksef session open # 1. Open online session (required)
71
31
  ksef invoice send invoice.xml # 2. Send invoice
72
32
  ksef session invoices # 3. Verify invoice status
73
- ksef session close # 4. Close session (optional)
74
33
  ```
75
34
 
76
- See the [documentation](https://flopsstuff.github.io/ksef-client-ts) for full usage, [API reference](https://flopsstuff.github.io/ksef-client-ts/api-reference), and [CLI reference](https://flopsstuff.github.io/ksef-client-ts/cli).
77
-
78
- ## Development
35
+ Node-only helpers (filesystem storage, native XSD validation) are available from the `ksef-client-ts/node` entry point.
79
36
 
80
- ```bash
81
- yarn install # Install dependencies (yarn 4.x via Corepack)
82
- yarn build # Build ESM + CJS + DTS via tsup
83
- yarn lint # Type-check (tsc --noEmit)
84
- yarn test # Run all tests (vitest)
85
- ```
37
+ ## Documentation
86
38
 
87
- ## Related
39
+ The full feature list, guides, API and CLI reference, and the v0.10 migration guide live in the **[documentation](https://flopsstuff.github.io/ksef-client-ts)** and the **[GitHub README](https://github.com/Flopsstuff/ksef-client-ts)**:
88
40
 
89
- - [KSeF Web Portal](https://ap.ksef.mf.gov.pl/web/) — official KSeF web application (token management, permissions, invoices)
90
- - [KSeF official docs](https://github.com/CIRFMF/ksef-docs) — official documentation (Polish)
91
- - [KSeF docs translated](https://flopsstuff.github.io/ksef-docs/) — translated documentation (EN/RU/UK), [source on GitHub](https://github.com/Flopsstuff/ksef-docs)
92
- - [ksef-client-csharp](https://github.com/CIRFMF/ksef-client-csharp) — official C# reference client
93
- - [ksef-client-java](https://github.com/CIRFMF/ksef-client-java) — official Java reference client
41
+ - [Quick start](https://flopsstuff.github.io/ksef-client-ts/quick-start)
42
+ - [API reference](https://flopsstuff.github.io/ksef-client-ts/api-reference)
43
+ - [CLI reference](https://flopsstuff.github.io/ksef-client-ts/cli)
44
+ - [Migration guide (v0.10)](https://flopsstuff.github.io/ksef-client-ts/migration-v0.10)
94
45
 
95
46
  ## License
96
47
 
97
48
  [MIT](LICENSE)
98
-
99
- ---
100
-
101
- [![Tests](https://github.com/Flopsstuff/ksef-client-ts/actions/workflows/ci.yml/badge.svg)](https://github.com/Flopsstuff/ksef-client-ts/actions/workflows/ci.yml)
102
- ![Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/Fl0p/1558034ac67a11548c7f8f0c05e8d4c0/raw/ksef-client-ts-coverage.json&cacheSeconds=300)
103
- ![NPM Version](https://img.shields.io/npm/v/ksef-client-ts?color=green)
104
- ![NPM Downloads](https://img.shields.io/npm/dm/ksef-client-ts)
105
- ![GitHub Stars](https://img.shields.io/github/stars/Flopsstuff/ksef-client-ts)
106
- ![GitHub Forks](https://img.shields.io/github/forks/Flopsstuff/ksef-client-ts)
107
- ![GitHub License](https://img.shields.io/github/license/Flopsstuff/ksef-client-ts)
package/dist/cli.js CHANGED
@@ -238,18 +238,21 @@ var init_ksef_bad_request_error = __esm({
238
238
  });
239
239
 
240
240
  // src/errors/ksef-auth-status-error.ts
241
+ var KSeFAuthStatusError;
241
242
  var init_ksef_auth_status_error = __esm({
242
243
  "src/errors/ksef-auth-status-error.ts"() {
243
244
  "use strict";
244
245
  init_ksef_error();
245
- }
246
- });
247
-
248
- // src/errors/ksef-session-expired-error.ts
249
- var init_ksef_session_expired_error = __esm({
250
- "src/errors/ksef-session-expired-error.ts"() {
251
- "use strict";
252
- init_ksef_error();
246
+ KSeFAuthStatusError = class extends KSeFError {
247
+ referenceNumber;
248
+ statusDescription;
249
+ constructor(message, referenceNumber, statusDescription) {
250
+ super(message);
251
+ this.name = "KSeFAuthStatusError";
252
+ this.referenceNumber = referenceNumber;
253
+ this.statusDescription = statusDescription;
254
+ }
255
+ };
253
256
  }
254
257
  });
255
258
 
@@ -397,13 +400,6 @@ var init_ksef_xsd_validation_error = __esm({
397
400
  }
398
401
  });
399
402
 
400
- // src/errors/assert-never.ts
401
- var init_assert_never = __esm({
402
- "src/errors/assert-never.ts"() {
403
- "use strict";
404
- }
405
- });
406
-
407
403
  // src/errors/index.ts
408
404
  var init_errors = __esm({
409
405
  "src/errors/index.ts"() {
@@ -415,15 +411,7 @@ var init_errors = __esm({
415
411
  init_ksef_forbidden_error();
416
412
  init_ksef_gone_error();
417
413
  init_ksef_bad_request_error();
418
- init_ksef_auth_status_error();
419
- init_ksef_session_expired_error();
420
414
  init_ksef_validation_error();
421
- init_ksef_batch_timeout_error();
422
- init_ksef_unknown_public_key_error();
423
- init_ksef_circuit_open_error();
424
- init_ksef_xsd_validation_error();
425
- init_error_codes();
426
- init_assert_never();
427
415
  }
428
416
  });
429
417
 
@@ -480,7 +468,6 @@ var init_options = __esm({
480
468
  var init_config = __esm({
481
469
  "src/config/index.ts"() {
482
470
  "use strict";
483
- init_environments();
484
471
  init_options();
485
472
  }
486
473
  });
@@ -2095,6 +2082,44 @@ var init_tokens = __esm({
2095
2082
  }
2096
2083
  });
2097
2084
 
2085
+ // src/validation/patterns.ts
2086
+ function isValidNip(value) {
2087
+ if (!Nip.test(value)) return false;
2088
+ let sum = 0;
2089
+ for (let i = 0; i < 9; i++) {
2090
+ sum += Number(value[i]) * NIP_WEIGHTS[i];
2091
+ }
2092
+ const checksum = sum % 11;
2093
+ return checksum !== 10 && checksum === Number(value[9]);
2094
+ }
2095
+ function isValidPesel(value) {
2096
+ if (!Pesel.test(value)) return false;
2097
+ let sum = 0;
2098
+ for (let i = 0; i < 10; i++) {
2099
+ sum += Number(value[i]) * PESEL_WEIGHTS[i];
2100
+ }
2101
+ const checksum = (10 - sum % 10) % 10;
2102
+ return checksum === Number(value[10]);
2103
+ }
2104
+ function isValidCertificateSerialNumber(value) {
2105
+ return CertificateSerialNumber.test(value);
2106
+ }
2107
+ var NIP_PATTERN_CORE, VAT_UE_PATTERN_CORE, Nip, VatUe, NipVatUe, Pesel, CertificateSerialNumber, NIP_WEIGHTS, PESEL_WEIGHTS;
2108
+ var init_patterns = __esm({
2109
+ "src/validation/patterns.ts"() {
2110
+ "use strict";
2111
+ NIP_PATTERN_CORE = "[1-9]((\\d[1-9])|([1-9]\\d))\\d{7}";
2112
+ VAT_UE_PATTERN_CORE = "(ATU\\d{8}|BE[01]{1}\\d{9}|BG\\d{9,10}|CY\\d{8}[A-Z]|CZ\\d{8,10}|DE\\d{9}|DK\\d{8}|EE\\d{9}|EL\\d{9}|ES([A-Z]\\d{8}|\\d{8}[A-Z]|[A-Z]\\d{7}[A-Z])|FI\\d{8}|FR[A-Z0-9]{2}\\d{9}|HR\\d{11}|HU\\d{8}|IE(\\d{7}[A-Z]{2}|\\d[A-Z0-9+*]\\d{5}[A-Z])|IT\\d{11}|LT(\\d{9}|\\d{12})|LU\\d{8}|LV\\d{11}|MT\\d{8}|NL[A-Z0-9+*]{12}|PT\\d{9}|RO\\d{2,10}|SE\\d{12}|SI\\d{8}|SK\\d{10}|XI((\\d{9}|\\d{12})|(GD|HA)\\d{3}))";
2113
+ Nip = new RegExp(`^${NIP_PATTERN_CORE}$`);
2114
+ VatUe = new RegExp(`^${VAT_UE_PATTERN_CORE}$`);
2115
+ NipVatUe = new RegExp(`^${NIP_PATTERN_CORE}-${VAT_UE_PATTERN_CORE}$`);
2116
+ Pesel = /^\d{2}(?:0[1-9]|1[0-2]|2[1-9]|3[0-2]|4[1-9]|5[0-2]|6[1-9]|7[0-2]|8[1-9]|9[0-2])\d{7}$/;
2117
+ CertificateSerialNumber = /^[0-9A-F]{16}$/;
2118
+ NIP_WEIGHTS = [6, 5, 7, 2, 3, 4, 5, 6, 7];
2119
+ PESEL_WEIGHTS = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
2120
+ }
2121
+ });
2122
+
2098
2123
  // src/services/certificates.ts
2099
2124
  var CertificateApiService;
2100
2125
  var init_certificates = __esm({
@@ -2102,6 +2127,8 @@ var init_certificates = __esm({
2102
2127
  "use strict";
2103
2128
  init_rest_request();
2104
2129
  init_routes();
2130
+ init_ksef_validation_error();
2131
+ init_patterns();
2105
2132
  CertificateApiService = class {
2106
2133
  restClient;
2107
2134
  constructor(restClient) {
@@ -2128,6 +2155,14 @@ var init_certificates = __esm({
2128
2155
  return response.body;
2129
2156
  }
2130
2157
  async retrieve(request) {
2158
+ for (const serial of request.certificateSerialNumbers ?? []) {
2159
+ if (!isValidCertificateSerialNumber(serial)) {
2160
+ throw KSeFValidationError.fromField(
2161
+ "certificateSerialNumbers",
2162
+ `Invalid certificate serial number "${serial}": must be exactly 16 uppercase hex characters (^[0-9A-F]{16}$)`
2163
+ );
2164
+ }
2165
+ }
2131
2166
  const req = RestRequest.post(Routes.Certificates.retrieve).body(request);
2132
2167
  const response = await this.restClient.execute(req);
2133
2168
  return response.body;
@@ -2793,6 +2828,7 @@ var init_verification_link_service = __esm({
2793
2828
  constructor(baseQrUrl) {
2794
2829
  this.baseQrUrl = baseQrUrl;
2795
2830
  }
2831
+ baseQrUrl;
2796
2832
  /**
2797
2833
  * Build invoice verification URL (Code I).
2798
2834
  * Format: {baseQrUrl}/invoice/{NIP}/{DD-MM-YYYY}/{hash_base64url}
@@ -3103,6 +3139,7 @@ var init_offline_invoice_workflow = __esm({
3103
3139
  constructor(qrService) {
3104
3140
  this.qrService = qrService;
3105
3141
  }
3142
+ qrService;
3106
3143
  async generate(input, options) {
3107
3144
  if (!input.invoiceXml || input.invoiceXml.trim().length === 0) {
3108
3145
  throw new Error("invoiceXml must not be empty");
@@ -4650,11 +4687,6 @@ var init_xml = __esm({
4650
4687
  "src/xml/index.ts"() {
4651
4688
  "use strict";
4652
4689
  init_upo_parser();
4653
- init_invoice_field_extractor();
4654
- init_xml_engine();
4655
- init_order_map();
4656
- init_faktura_builder();
4657
- init_pef_builder();
4658
4690
  init_invoice_serializer();
4659
4691
  }
4660
4692
  });
@@ -6132,11 +6164,6 @@ var SCHEMA_TYPES, NAMESPACE_MAP;
6132
6164
  var init_schemas = __esm({
6133
6165
  "src/validation/schemas/index.ts"() {
6134
6166
  "use strict";
6135
- init_fa3();
6136
- init_fa2();
6137
- init_fa_rr1();
6138
- init_pef3();
6139
- init_pef_kor3();
6140
6167
  SCHEMA_TYPES = ["FA3", "FA2", "FA_RR1", "PEF3", "PEF_KOR3"];
6141
6168
  NAMESPACE_MAP = {
6142
6169
  "http://crd.gov.pl/wzor/2025/06/25/13775/": "FA3",
@@ -6334,40 +6361,6 @@ var init_char_validity = __esm({
6334
6361
  }
6335
6362
  });
6336
6363
 
6337
- // src/validation/patterns.ts
6338
- function isValidNip(value) {
6339
- if (!Nip.test(value)) return false;
6340
- let sum = 0;
6341
- for (let i = 0; i < 9; i++) {
6342
- sum += Number(value[i]) * NIP_WEIGHTS[i];
6343
- }
6344
- const checksum = sum % 11;
6345
- return checksum !== 10 && checksum === Number(value[9]);
6346
- }
6347
- function isValidPesel(value) {
6348
- if (!Pesel.test(value)) return false;
6349
- let sum = 0;
6350
- for (let i = 0; i < 10; i++) {
6351
- sum += Number(value[i]) * PESEL_WEIGHTS[i];
6352
- }
6353
- const checksum = (10 - sum % 10) % 10;
6354
- return checksum === Number(value[10]);
6355
- }
6356
- var NIP_PATTERN_CORE, VAT_UE_PATTERN_CORE, Nip, VatUe, NipVatUe, Pesel, NIP_WEIGHTS, PESEL_WEIGHTS;
6357
- var init_patterns = __esm({
6358
- "src/validation/patterns.ts"() {
6359
- "use strict";
6360
- NIP_PATTERN_CORE = "[1-9]((\\d[1-9])|([1-9]\\d))\\d{7}";
6361
- VAT_UE_PATTERN_CORE = "(ATU\\d{8}|BE[01]{1}\\d{9}|BG\\d{9,10}|CY\\d{8}[A-Z]|CZ\\d{8,10}|DE\\d{9}|DK\\d{8}|EE\\d{9}|EL\\d{9}|ES([A-Z]\\d{8}|\\d{8}[A-Z]|[A-Z]\\d{7}[A-Z])|FI\\d{8}|FR[A-Z0-9]{2}\\d{9}|HR\\d{11}|HU\\d{8}|IE(\\d{7}[A-Z]{2}|\\d[A-Z0-9+*]\\d{5}[A-Z])|IT\\d{11}|LT(\\d{9}|\\d{12})|LU\\d{8}|LV\\d{11}|MT\\d{8}|NL[A-Z0-9+*]{12}|PT\\d{9}|RO\\d{2,10}|SE\\d{12}|SI\\d{8}|SK\\d{10}|XI((\\d{9}|\\d{12})|(GD|HA)\\d{3}))";
6362
- Nip = new RegExp(`^${NIP_PATTERN_CORE}$`);
6363
- VatUe = new RegExp(`^${VAT_UE_PATTERN_CORE}$`);
6364
- NipVatUe = new RegExp(`^${NIP_PATTERN_CORE}-${VAT_UE_PATTERN_CORE}$`);
6365
- Pesel = /^\d{2}(?:0[1-9]|1[0-2]|2[1-9]|3[0-2]|4[1-9]|5[0-2]|6[1-9]|7[0-2]|8[1-9]|9[0-2])\d{7}$/;
6366
- NIP_WEIGHTS = [6, 5, 7, 2, 3, 4, 5, 6, 7];
6367
- PESEL_WEIGHTS = [1, 3, 7, 9, 1, 3, 7, 9, 1, 3];
6368
- }
6369
- });
6370
-
6371
6364
  // src/validation/invoice-validator.ts
6372
6365
  var invoice_validator_exports = {};
6373
6366
  __export(invoice_validator_exports, {
@@ -7286,8 +7279,34 @@ function clearCredentials() {
7286
7279
 
7287
7280
  // src/workflows/auth-workflow.ts
7288
7281
  init_polling();
7289
- init_auth_xml_builder();
7290
7282
  init_with_key_rotation_retry();
7283
+ init_ksef_auth_status_error();
7284
+ var AUTH_STATUS_SUCCESS = 200;
7285
+ var AUTH_STATUS_IN_PROGRESS = 100;
7286
+ async function awaitAuthentication(client, referenceNumber, authToken, pollOptions) {
7287
+ const final = await pollUntil(
7288
+ () => client.auth.getAuthStatus(referenceNumber, authToken),
7289
+ (s) => s.status.code !== AUTH_STATUS_IN_PROGRESS,
7290
+ { ...pollOptions, description: `auth ${referenceNumber}` }
7291
+ );
7292
+ if (final.status.code !== AUTH_STATUS_SUCCESS) {
7293
+ const details = final.status.details?.length ? ` (${final.status.details.join("; ")})` : "";
7294
+ throw new KSeFAuthStatusError(
7295
+ `Authentication failed with status ${final.status.code}: ${final.status.description}${details}`,
7296
+ referenceNumber,
7297
+ final.status.description
7298
+ );
7299
+ }
7300
+ const tokens = await client.auth.getAccessToken(authToken);
7301
+ client.authManager.setAccessToken(tokens.accessToken.token);
7302
+ client.authManager.setRefreshToken(tokens.refreshToken.token);
7303
+ return {
7304
+ accessToken: tokens.accessToken.token,
7305
+ accessTokenValidUntil: tokens.accessToken.validUntil,
7306
+ refreshToken: tokens.refreshToken.token,
7307
+ refreshTokenValidUntil: tokens.refreshToken.validUntil
7308
+ };
7309
+ }
7291
7310
  async function authenticateWithToken(client, options) {
7292
7311
  const challenge2 = await client.auth.getChallenge();
7293
7312
  await client.crypto.init();
@@ -7302,20 +7321,7 @@ async function authenticateWithToken(client, options) {
7302
7321
  });
7303
7322
  });
7304
7323
  const authToken = submitResult.authenticationToken.token;
7305
- await pollUntil(
7306
- () => client.auth.getAuthStatus(submitResult.referenceNumber, authToken),
7307
- (s) => s.status.code !== 100,
7308
- { ...options.pollOptions, description: `auth ${submitResult.referenceNumber}` }
7309
- );
7310
- const tokens = await client.auth.getAccessToken(authToken);
7311
- client.authManager.setAccessToken(tokens.accessToken.token);
7312
- client.authManager.setRefreshToken(tokens.refreshToken.token);
7313
- return {
7314
- accessToken: tokens.accessToken.token,
7315
- accessTokenValidUntil: tokens.accessToken.validUntil,
7316
- refreshToken: tokens.refreshToken.token,
7317
- refreshTokenValidUntil: tokens.refreshToken.validUntil
7318
- };
7324
+ return awaitAuthentication(client, submitResult.referenceNumber, authToken, options.pollOptions);
7319
7325
  }
7320
7326
 
7321
7327
  // src/cli/session-recovery.ts
@@ -8321,6 +8327,7 @@ var FileHwmStore = class {
8321
8327
  constructor(filePath) {
8322
8328
  this.filePath = filePath;
8323
8329
  }
8330
+ filePath;
8324
8331
  async load() {
8325
8332
  try {
8326
8333
  const data = await fs7.readFile(this.filePath, "utf-8");
@@ -8338,8 +8345,6 @@ var FileHwmStore = class {
8338
8345
  };
8339
8346
 
8340
8347
  // src/workflows/invoice-export-workflow.ts
8341
- init_zip();
8342
- init_targz();
8343
8348
  init_polling();
8344
8349
  init_with_key_rotation_retry();
8345
8350