@beliq/sdk 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -169,6 +169,8 @@ function errorFromResponse(status, bytes) {
169
169
 
170
170
  // src/constants.ts
171
171
  var DEFAULT_BASE_URL = "https://api.beliq.eu";
172
+ var DEFAULT_TIMEOUT_MS = 9e4;
173
+ var DEFAULT_MAX_RETRIES = 3;
172
174
  var LIVE_GENERATE_STANDARDS = ["xrechnung", "zugferd", "facturx", "peppol-bis"];
173
175
  var LIVE_GENERATE_PRESETS = [
174
176
  { id: "xrechnung", label: "XRechnung", standard: "xrechnung", output: "xml" },
@@ -178,6 +180,19 @@ var LIVE_GENERATE_PRESETS = [
178
180
  { id: "nlcius", label: "NLCIUS", standard: "peppol-bis", output: "xml", profile: "netherlands-nlcius" }
179
181
  ];
180
182
  var LIVE_PROFILES = ["basicwl", "en16931", "extended", "extended-ctc-fr"];
183
+ var LIVE_PROFILES_BY_STANDARD = {
184
+ xrechnung: ["xrechnung"],
185
+ "peppol-bis": ["peppol", "romania-ro-cius", "netherlands-nlcius"],
186
+ zugferd: ["basicwl", "en16931", "extended"],
187
+ facturx: ["basicwl", "en16931", "extended", "extended-ctc-fr"]
188
+ };
189
+ function profilesForStandard(standard) {
190
+ return LIVE_PROFILES_BY_STANDARD[standard] ?? [];
191
+ }
192
+ function isProfileAllowedForStandard(standard, profile) {
193
+ const allowed = profilesForStandard(standard);
194
+ return allowed.length === 0 || allowed.includes(profile);
195
+ }
181
196
  var LIVE_VALIDATE_FORMATS = ["auto", "cii", "ubl"];
182
197
  var LIVE_PARSE_FORMATS = ["auto", "cii", "ubl"];
183
198
  var LIVE_CONVERT_SOURCE_FORMATS = [
@@ -199,6 +214,9 @@ var LIVE_CONVERT_TARGET_FORMATS = [
199
214
  ];
200
215
 
201
216
  // src/transport.ts
217
+ var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503]);
218
+ var MAX_RETRY_AFTER_MS = 3e4;
219
+ var BACKOFF_BASE_MS = 500;
202
220
  function authHeaders(config) {
203
221
  return config.auth === "bearer" ? { Authorization: `Bearer ${config.apiKey}` } : { "X-API-Key": config.apiKey };
204
222
  }
@@ -212,19 +230,58 @@ function buildUrl(baseUrl, path, query) {
212
230
  const qs = params.toString();
213
231
  return qs ? `${baseUrl}${path}?${qs}` : `${baseUrl}${path}`;
214
232
  }
233
+ function retryDelayMs(res, attempt) {
234
+ const header = res?.headers.get("retry-after");
235
+ if (header) {
236
+ const seconds = Number(header);
237
+ if (Number.isFinite(seconds) && seconds >= 0) {
238
+ return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS) + Math.random() * 250;
239
+ }
240
+ }
241
+ const backoff = BACKOFF_BASE_MS * 2 ** attempt;
242
+ return backoff + Math.random() * backoff;
243
+ }
244
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
215
245
  async function send(config, req) {
216
246
  const headers = { ...authHeaders(config) };
217
247
  if (req.contentType) headers["Content-Type"] = req.contentType;
218
248
  if (req.accept) headers["Accept"] = req.accept;
219
249
  const body = req.jsonBody !== void 0 ? JSON.stringify(req.jsonBody) : req.rawBody;
220
- const res = await config.fetchImpl(buildUrl(config.baseUrl, req.path, req.query), {
221
- method: req.method,
222
- headers,
223
- body
224
- });
225
- const bytes = new Uint8Array(await res.arrayBuffer());
226
- if (!res.ok) throw errorFromResponse(res.status, bytes);
227
- return { status: res.status, headers: res.headers, bytes };
250
+ const url = buildUrl(config.baseUrl, req.path, req.query);
251
+ let lastError;
252
+ for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
253
+ const controller = new AbortController();
254
+ const timer = config.timeoutMs > 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : void 0;
255
+ let res;
256
+ try {
257
+ res = await config.fetchImpl(url, {
258
+ method: req.method,
259
+ headers,
260
+ body,
261
+ signal: controller.signal
262
+ });
263
+ const bytes = new Uint8Array(await res.arrayBuffer());
264
+ if (res.ok) return { status: res.status, headers: res.headers, bytes };
265
+ lastError = errorFromResponse(res.status, bytes);
266
+ if (!RETRYABLE_STATUSES.has(res.status) || attempt === config.maxRetries) {
267
+ throw lastError;
268
+ }
269
+ } catch (err) {
270
+ if (err instanceof BeliqApiError && !RETRYABLE_STATUSES.has(err.status)) throw err;
271
+ if (controller.signal.aborted) {
272
+ throw new BeliqApiError(
273
+ `beliq: request to ${req.path} exceeded ${config.timeoutMs}ms`,
274
+ { status: 0, code: "TIMEOUT" }
275
+ );
276
+ }
277
+ lastError = err;
278
+ if (attempt === config.maxRetries) throw err;
279
+ } finally {
280
+ if (timer) clearTimeout(timer);
281
+ }
282
+ await sleep(retryDelayMs(res, attempt));
283
+ }
284
+ throw lastError;
228
285
  }
229
286
 
230
287
  // src/client.ts
@@ -295,7 +352,9 @@ var Beliq = class {
295
352
  apiKey: options.apiKey,
296
353
  baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
297
354
  auth: options.auth ?? "header",
298
- fetchImpl
355
+ fetchImpl,
356
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
357
+ maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES
299
358
  };
300
359
  }
301
360
  /** Account, plan, and quota context for the key. Does not consume quota. */
@@ -395,11 +454,16 @@ export {
395
454
  Beliq,
396
455
  BeliqApiError,
397
456
  DEFAULT_BASE_URL,
457
+ DEFAULT_MAX_RETRIES,
458
+ DEFAULT_TIMEOUT_MS,
398
459
  LIVE_CONVERT_SOURCE_FORMATS,
399
460
  LIVE_CONVERT_TARGET_FORMATS,
400
461
  LIVE_GENERATE_PRESETS,
401
462
  LIVE_GENERATE_STANDARDS,
402
463
  LIVE_PARSE_FORMATS,
403
464
  LIVE_PROFILES,
404
- LIVE_VALIDATE_FORMATS
465
+ LIVE_PROFILES_BY_STANDARD,
466
+ LIVE_VALIDATE_FORMATS,
467
+ isProfileAllowedForStandard,
468
+ profilesForStandard
405
469
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beliq/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Official beliq SDK: generate, validate, parse, and convert EU-compliant e-invoices (XRechnung, ZUGFeRD, Factur-X, Peppol BIS) against authority-pinned, drift-checked rules.",
5
5
  "keywords": [
6
6
  "beliq",
@@ -75,17 +75,22 @@
75
75
  "spec:drift": "node scripts/check-live-drift.mjs",
76
76
  "test": "vitest run --exclude test/integration.test.ts",
77
77
  "test:integration": "vitest run test/integration.test.ts",
78
- "prepublishOnly": "npm run build"
78
+ "prepublishOnly": "npm run build",
79
+ "check:profiles": "node --experimental-strip-types scripts/check-profile-drift.mjs"
79
80
  },
80
81
  "dependencies": {},
82
+ "overrides": {
83
+ "esbuild": "^0.28.1",
84
+ "js-yaml": "^4.3.1"
85
+ },
81
86
  "devDependencies": {
87
+ "@eslint/js": "^10.0.0",
82
88
  "@types/node": "^20.14.0",
83
- "@typescript-eslint/eslint-plugin": "^7.18.0",
84
- "@typescript-eslint/parser": "^7.18.0",
85
- "eslint": "^8.57.0",
89
+ "eslint": "^10.0.0",
86
90
  "openapi-typescript": "^7.4.0",
87
91
  "tsup": "^8.3.0",
88
92
  "typescript": "^5.5.4",
89
- "vitest": "^2.1.8"
93
+ "typescript-eslint": "^8.0.0",
94
+ "vitest": "^4.0.0"
90
95
  }
91
96
  }
@@ -1,40 +0,0 @@
1
- type PlainObject = Record<string, unknown>;
2
- declare function isPlainObject(value: unknown): value is PlainObject;
3
- /** Deep-merge `source` into `target` (source wins). Arrays and scalars overwrite. */
4
- declare function mergeDeep(target: PlainObject, source: PlainObject): PlainObject;
5
- /** Drop undefined/empty entries so optional query params are omitted, not sent blank. */
6
- declare function compactQuery(query: PlainObject): PlainObject;
7
- /** Document bytes a caller may pass to validate/parse/convert. */
8
- type DocumentInput = string | Uint8Array | ArrayBuffer | ArrayBufferView;
9
- declare function toBytes(input: DocumentInput): Uint8Array;
10
- declare function decodeUtf8(bytes: Uint8Array): string;
11
- /** Sniff `application/pdf` vs `application/xml` from the leading bytes. */
12
- declare function sniffContentType(bytes: Uint8Array): string;
13
-
14
- declare const DEFAULT_BASE_URL = "https://api.beliq.eu";
15
- declare const LIVE_GENERATE_STANDARDS: readonly ["xrechnung", "zugferd", "facturx", "peppol-bis"];
16
- /** A named generate target: the API `standard` plus the `profile`/`facturxProfile`/`output` it needs. */
17
- interface GeneratePreset {
18
- id: string;
19
- label: string;
20
- standard: (typeof LIVE_GENERATE_STANDARDS)[number];
21
- output: 'xml' | 'pdf';
22
- /** API `profile`; omitted lets the engine pick the standard's default. */
23
- profile?: string;
24
- /** API `facturxProfile`; Factur-X / ZUGFeRD only. */
25
- facturxProfile?: string;
26
- }
27
- /**
28
- * Named generate targets surfaced to end users (connector dropdowns), mirroring
29
- * the public set on beliq.eu's own generator. NLCIUS is a Peppol BIS profile,
30
- * not a standalone standard, so it is reachable here rather than through
31
- * LIVE_GENERATE_STANDARDS or the Factur-X-only LIVE_PROFILES.
32
- */
33
- declare const LIVE_GENERATE_PRESETS: readonly GeneratePreset[];
34
- declare const LIVE_PROFILES: readonly ["basicwl", "en16931", "extended", "extended-ctc-fr"];
35
- declare const LIVE_VALIDATE_FORMATS: readonly ["auto", "cii", "ubl"];
36
- declare const LIVE_PARSE_FORMATS: readonly ["auto", "cii", "ubl"];
37
- declare const LIVE_CONVERT_SOURCE_FORMATS: readonly ["auto", "cii", "ubl", "zugferd", "facturx", "xrechnung", "peppol-bis"];
38
- declare const LIVE_CONVERT_TARGET_FORMATS: readonly ["cii", "ubl", "zugferd", "facturx", "xrechnung", "peppol-bis"];
39
-
40
- export { DEFAULT_BASE_URL as D, type GeneratePreset as G, LIVE_CONVERT_SOURCE_FORMATS as L, type PlainObject as P, type DocumentInput as a, LIVE_CONVERT_TARGET_FORMATS as b, LIVE_GENERATE_PRESETS as c, LIVE_GENERATE_STANDARDS as d, LIVE_PARSE_FORMATS as e, LIVE_PROFILES as f, LIVE_VALIDATE_FORMATS as g, compactQuery as h, decodeUtf8 as i, isPlainObject as j, mergeDeep as m, sniffContentType as s, toBytes as t };
@@ -1,40 +0,0 @@
1
- type PlainObject = Record<string, unknown>;
2
- declare function isPlainObject(value: unknown): value is PlainObject;
3
- /** Deep-merge `source` into `target` (source wins). Arrays and scalars overwrite. */
4
- declare function mergeDeep(target: PlainObject, source: PlainObject): PlainObject;
5
- /** Drop undefined/empty entries so optional query params are omitted, not sent blank. */
6
- declare function compactQuery(query: PlainObject): PlainObject;
7
- /** Document bytes a caller may pass to validate/parse/convert. */
8
- type DocumentInput = string | Uint8Array | ArrayBuffer | ArrayBufferView;
9
- declare function toBytes(input: DocumentInput): Uint8Array;
10
- declare function decodeUtf8(bytes: Uint8Array): string;
11
- /** Sniff `application/pdf` vs `application/xml` from the leading bytes. */
12
- declare function sniffContentType(bytes: Uint8Array): string;
13
-
14
- declare const DEFAULT_BASE_URL = "https://api.beliq.eu";
15
- declare const LIVE_GENERATE_STANDARDS: readonly ["xrechnung", "zugferd", "facturx", "peppol-bis"];
16
- /** A named generate target: the API `standard` plus the `profile`/`facturxProfile`/`output` it needs. */
17
- interface GeneratePreset {
18
- id: string;
19
- label: string;
20
- standard: (typeof LIVE_GENERATE_STANDARDS)[number];
21
- output: 'xml' | 'pdf';
22
- /** API `profile`; omitted lets the engine pick the standard's default. */
23
- profile?: string;
24
- /** API `facturxProfile`; Factur-X / ZUGFeRD only. */
25
- facturxProfile?: string;
26
- }
27
- /**
28
- * Named generate targets surfaced to end users (connector dropdowns), mirroring
29
- * the public set on beliq.eu's own generator. NLCIUS is a Peppol BIS profile,
30
- * not a standalone standard, so it is reachable here rather than through
31
- * LIVE_GENERATE_STANDARDS or the Factur-X-only LIVE_PROFILES.
32
- */
33
- declare const LIVE_GENERATE_PRESETS: readonly GeneratePreset[];
34
- declare const LIVE_PROFILES: readonly ["basicwl", "en16931", "extended", "extended-ctc-fr"];
35
- declare const LIVE_VALIDATE_FORMATS: readonly ["auto", "cii", "ubl"];
36
- declare const LIVE_PARSE_FORMATS: readonly ["auto", "cii", "ubl"];
37
- declare const LIVE_CONVERT_SOURCE_FORMATS: readonly ["auto", "cii", "ubl", "zugferd", "facturx", "xrechnung", "peppol-bis"];
38
- declare const LIVE_CONVERT_TARGET_FORMATS: readonly ["cii", "ubl", "zugferd", "facturx", "xrechnung", "peppol-bis"];
39
-
40
- export { DEFAULT_BASE_URL as D, type GeneratePreset as G, LIVE_CONVERT_SOURCE_FORMATS as L, type PlainObject as P, type DocumentInput as a, LIVE_CONVERT_TARGET_FORMATS as b, LIVE_GENERATE_PRESETS as c, LIVE_GENERATE_STANDARDS as d, LIVE_PARSE_FORMATS as e, LIVE_PROFILES as f, LIVE_VALIDATE_FORMATS as g, compactQuery as h, decodeUtf8 as i, isPlainObject as j, mergeDeep as m, sniffContentType as s, toBytes as t };