@beliq/sdk 0.1.1 → 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/README.md +65 -5
- package/dist/constants-BZuNzqbV.d.cts +85 -0
- package/dist/constants-BZuNzqbV.d.ts +85 -0
- package/dist/helpers.cjs +30 -1
- package/dist/helpers.d.cts +6 -2
- package/dist/helpers.d.ts +6 -2
- package/dist/helpers.js +26 -1
- package/dist/index.cjs +161 -18
- package/dist/index.d.cts +806 -30
- package/dist/index.d.ts +806 -30
- package/dist/index.js +154 -17
- package/package.json +12 -6
- package/dist/constants-bWOXCmJD.d.cts +0 -22
- package/dist/constants-bWOXCmJD.d.ts +0 -22
package/dist/index.cjs
CHANGED
|
@@ -23,12 +23,18 @@ __export(index_exports, {
|
|
|
23
23
|
Beliq: () => Beliq,
|
|
24
24
|
BeliqApiError: () => BeliqApiError,
|
|
25
25
|
DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
|
|
26
|
+
DEFAULT_MAX_RETRIES: () => DEFAULT_MAX_RETRIES,
|
|
27
|
+
DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
|
|
26
28
|
LIVE_CONVERT_SOURCE_FORMATS: () => LIVE_CONVERT_SOURCE_FORMATS,
|
|
27
29
|
LIVE_CONVERT_TARGET_FORMATS: () => LIVE_CONVERT_TARGET_FORMATS,
|
|
30
|
+
LIVE_GENERATE_PRESETS: () => LIVE_GENERATE_PRESETS,
|
|
28
31
|
LIVE_GENERATE_STANDARDS: () => LIVE_GENERATE_STANDARDS,
|
|
29
32
|
LIVE_PARSE_FORMATS: () => LIVE_PARSE_FORMATS,
|
|
30
33
|
LIVE_PROFILES: () => LIVE_PROFILES,
|
|
31
|
-
|
|
34
|
+
LIVE_PROFILES_BY_STANDARD: () => LIVE_PROFILES_BY_STANDARD,
|
|
35
|
+
LIVE_VALIDATE_FORMATS: () => LIVE_VALIDATE_FORMATS,
|
|
36
|
+
isProfileAllowedForStandard: () => isProfileAllowedForStandard,
|
|
37
|
+
profilesForStandard: () => profilesForStandard
|
|
32
38
|
});
|
|
33
39
|
module.exports = __toCommonJS(index_exports);
|
|
34
40
|
|
|
@@ -68,6 +74,18 @@ function toBytes(input) {
|
|
|
68
74
|
function decodeUtf8(bytes) {
|
|
69
75
|
return new TextDecoder("utf-8").decode(bytes);
|
|
70
76
|
}
|
|
77
|
+
function decodeBase64(input) {
|
|
78
|
+
const b64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
79
|
+
if (typeof atob === "function") {
|
|
80
|
+
const bin = atob(b64);
|
|
81
|
+
const out = new Uint8Array(bin.length);
|
|
82
|
+
for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
const maybeBuffer = globalThis.Buffer;
|
|
86
|
+
if (maybeBuffer) return new Uint8Array(maybeBuffer.from(b64, "base64"));
|
|
87
|
+
throw new Error("beliq: no base64 decoder available in this runtime");
|
|
88
|
+
}
|
|
71
89
|
var PDF_MAGIC = [37, 80, 68, 70, 45];
|
|
72
90
|
function sniffContentType(bytes) {
|
|
73
91
|
const isPdf = bytes.length >= PDF_MAGIC.length && PDF_MAGIC.every((b, i) => bytes[i] === b);
|
|
@@ -106,7 +124,8 @@ function buildRequest(params) {
|
|
|
106
124
|
path: "/v1/generate",
|
|
107
125
|
jsonBody: merged,
|
|
108
126
|
contentType: "application/json",
|
|
109
|
-
|
|
127
|
+
// JSON mode returns the seal envelope; binary mode returns the raw document body.
|
|
128
|
+
...params.sealed ? { accept: "application/json", outputKind: "json" } : { outputKind: "binary" }
|
|
110
129
|
};
|
|
111
130
|
}
|
|
112
131
|
case "validate": {
|
|
@@ -190,8 +209,30 @@ function errorFromResponse(status, bytes) {
|
|
|
190
209
|
|
|
191
210
|
// src/constants.ts
|
|
192
211
|
var DEFAULT_BASE_URL = "https://api.beliq.eu";
|
|
212
|
+
var DEFAULT_TIMEOUT_MS = 9e4;
|
|
213
|
+
var DEFAULT_MAX_RETRIES = 3;
|
|
193
214
|
var LIVE_GENERATE_STANDARDS = ["xrechnung", "zugferd", "facturx", "peppol-bis"];
|
|
215
|
+
var LIVE_GENERATE_PRESETS = [
|
|
216
|
+
{ id: "xrechnung", label: "XRechnung", standard: "xrechnung", output: "xml" },
|
|
217
|
+
{ id: "factur-x", label: "Factur-X", standard: "facturx", output: "pdf", facturxProfile: "en16931" },
|
|
218
|
+
{ id: "zugferd", label: "ZUGFeRD", standard: "zugferd", output: "pdf" },
|
|
219
|
+
{ id: "peppol-bis", label: "Peppol BIS 3.0", standard: "peppol-bis", output: "xml" },
|
|
220
|
+
{ id: "nlcius", label: "NLCIUS", standard: "peppol-bis", output: "xml", profile: "netherlands-nlcius" }
|
|
221
|
+
];
|
|
194
222
|
var LIVE_PROFILES = ["basicwl", "en16931", "extended", "extended-ctc-fr"];
|
|
223
|
+
var LIVE_PROFILES_BY_STANDARD = {
|
|
224
|
+
xrechnung: ["xrechnung"],
|
|
225
|
+
"peppol-bis": ["peppol", "romania-ro-cius", "netherlands-nlcius"],
|
|
226
|
+
zugferd: ["basicwl", "en16931", "extended"],
|
|
227
|
+
facturx: ["basicwl", "en16931", "extended", "extended-ctc-fr"]
|
|
228
|
+
};
|
|
229
|
+
function profilesForStandard(standard) {
|
|
230
|
+
return LIVE_PROFILES_BY_STANDARD[standard] ?? [];
|
|
231
|
+
}
|
|
232
|
+
function isProfileAllowedForStandard(standard, profile) {
|
|
233
|
+
const allowed = profilesForStandard(standard);
|
|
234
|
+
return allowed.length === 0 || allowed.includes(profile);
|
|
235
|
+
}
|
|
195
236
|
var LIVE_VALIDATE_FORMATS = ["auto", "cii", "ubl"];
|
|
196
237
|
var LIVE_PARSE_FORMATS = ["auto", "cii", "ubl"];
|
|
197
238
|
var LIVE_CONVERT_SOURCE_FORMATS = [
|
|
@@ -213,6 +254,9 @@ var LIVE_CONVERT_TARGET_FORMATS = [
|
|
|
213
254
|
];
|
|
214
255
|
|
|
215
256
|
// src/transport.ts
|
|
257
|
+
var RETRYABLE_STATUSES = /* @__PURE__ */ new Set([429, 502, 503]);
|
|
258
|
+
var MAX_RETRY_AFTER_MS = 3e4;
|
|
259
|
+
var BACKOFF_BASE_MS = 500;
|
|
216
260
|
function authHeaders(config) {
|
|
217
261
|
return config.auth === "bearer" ? { Authorization: `Bearer ${config.apiKey}` } : { "X-API-Key": config.apiKey };
|
|
218
262
|
}
|
|
@@ -226,26 +270,86 @@ function buildUrl(baseUrl, path, query) {
|
|
|
226
270
|
const qs = params.toString();
|
|
227
271
|
return qs ? `${baseUrl}${path}?${qs}` : `${baseUrl}${path}`;
|
|
228
272
|
}
|
|
273
|
+
function retryDelayMs(res, attempt) {
|
|
274
|
+
const header = res?.headers.get("retry-after");
|
|
275
|
+
if (header) {
|
|
276
|
+
const seconds = Number(header);
|
|
277
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
278
|
+
return Math.min(seconds * 1e3, MAX_RETRY_AFTER_MS) + Math.random() * 250;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const backoff = BACKOFF_BASE_MS * 2 ** attempt;
|
|
282
|
+
return backoff + Math.random() * backoff;
|
|
283
|
+
}
|
|
284
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
229
285
|
async function send(config, req) {
|
|
230
286
|
const headers = { ...authHeaders(config) };
|
|
231
287
|
if (req.contentType) headers["Content-Type"] = req.contentType;
|
|
288
|
+
if (req.accept) headers["Accept"] = req.accept;
|
|
232
289
|
const body = req.jsonBody !== void 0 ? JSON.stringify(req.jsonBody) : req.rawBody;
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
290
|
+
const url = buildUrl(config.baseUrl, req.path, req.query);
|
|
291
|
+
let lastError;
|
|
292
|
+
for (let attempt = 0; attempt <= config.maxRetries; attempt++) {
|
|
293
|
+
const controller = new AbortController();
|
|
294
|
+
const timer = config.timeoutMs > 0 ? setTimeout(() => controller.abort(), config.timeoutMs) : void 0;
|
|
295
|
+
let res;
|
|
296
|
+
try {
|
|
297
|
+
res = await config.fetchImpl(url, {
|
|
298
|
+
method: req.method,
|
|
299
|
+
headers,
|
|
300
|
+
body,
|
|
301
|
+
signal: controller.signal
|
|
302
|
+
});
|
|
303
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
304
|
+
if (res.ok) return { status: res.status, headers: res.headers, bytes };
|
|
305
|
+
lastError = errorFromResponse(res.status, bytes);
|
|
306
|
+
if (!RETRYABLE_STATUSES.has(res.status) || attempt === config.maxRetries) {
|
|
307
|
+
throw lastError;
|
|
308
|
+
}
|
|
309
|
+
} catch (err) {
|
|
310
|
+
if (err instanceof BeliqApiError && !RETRYABLE_STATUSES.has(err.status)) throw err;
|
|
311
|
+
if (controller.signal.aborted) {
|
|
312
|
+
throw new BeliqApiError(
|
|
313
|
+
`beliq: request to ${req.path} exceeded ${config.timeoutMs}ms`,
|
|
314
|
+
{ status: 0, code: "TIMEOUT" }
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
lastError = err;
|
|
318
|
+
if (attempt === config.maxRetries) throw err;
|
|
319
|
+
} finally {
|
|
320
|
+
if (timer) clearTimeout(timer);
|
|
321
|
+
}
|
|
322
|
+
await sleep(retryDelayMs(res, attempt));
|
|
323
|
+
}
|
|
324
|
+
throw lastError;
|
|
241
325
|
}
|
|
242
326
|
|
|
243
327
|
// src/client.ts
|
|
328
|
+
var TEST_KEY_PREFIX = "blq_test_";
|
|
329
|
+
function livemodeHeader(headers) {
|
|
330
|
+
const raw = headers.get("x-beliq-livemode");
|
|
331
|
+
if (raw === "true") return true;
|
|
332
|
+
if (raw === "false") return false;
|
|
333
|
+
return void 0;
|
|
334
|
+
}
|
|
335
|
+
function parseRulesetArtifacts(headers) {
|
|
336
|
+
const raw = headers.get("x-ruleset-artifacts");
|
|
337
|
+
if (!raw) return void 0;
|
|
338
|
+
try {
|
|
339
|
+
const parsed = JSON.parse(raw);
|
|
340
|
+
return Array.isArray(parsed) ? parsed : void 0;
|
|
341
|
+
} catch {
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
244
345
|
function generateMeta(headers) {
|
|
245
346
|
return {
|
|
246
347
|
schematronVersion: headers.get("x-schematron-version") ?? void 0,
|
|
247
348
|
pdfKind: headers.get("x-pdf-kind") ?? void 0,
|
|
248
|
-
outputEnvelope: headers.get("x-output-envelope") ?? void 0
|
|
349
|
+
outputEnvelope: headers.get("x-output-envelope") ?? void 0,
|
|
350
|
+
rulesetSha256: headers.get("x-ruleset-sha256") ?? void 0,
|
|
351
|
+
rulesetArtifacts: parseRulesetArtifacts(headers),
|
|
352
|
+
livemode: livemodeHeader(headers)
|
|
249
353
|
};
|
|
250
354
|
}
|
|
251
355
|
function convertMeta(headers) {
|
|
@@ -265,22 +369,32 @@ function convertMeta(headers) {
|
|
|
265
369
|
profileDetected: headers.get("x-profile-detected") ?? void 0,
|
|
266
370
|
lostElementsCount: countRaw != null ? Number(countRaw) : void 0,
|
|
267
371
|
lostElements,
|
|
268
|
-
conversionTools: headers.get("x-conversion-tools") ?? void 0
|
|
372
|
+
conversionTools: headers.get("x-conversion-tools") ?? void 0,
|
|
373
|
+
livemode: livemodeHeader(headers)
|
|
269
374
|
};
|
|
270
375
|
}
|
|
271
376
|
var Beliq = class {
|
|
272
377
|
#config;
|
|
378
|
+
/**
|
|
379
|
+
* True for a live key, false for a blq_test_ sandbox key, derived from the key
|
|
380
|
+
* prefix (the same rule the server applies). Available before any request; the
|
|
381
|
+
* per-response x-beliq-livemode header is surfaced on generate/convert meta.
|
|
382
|
+
*/
|
|
383
|
+
livemode;
|
|
273
384
|
constructor(options) {
|
|
274
385
|
if (!options?.apiKey) throw new Error("beliq: apiKey is required");
|
|
275
386
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
276
387
|
if (typeof fetchImpl !== "function") {
|
|
277
388
|
throw new Error("beliq: no global fetch available; pass options.fetch");
|
|
278
389
|
}
|
|
390
|
+
this.livemode = !options.apiKey.startsWith(TEST_KEY_PREFIX);
|
|
279
391
|
this.#config = {
|
|
280
392
|
apiKey: options.apiKey,
|
|
281
393
|
baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
282
394
|
auth: options.auth ?? "header",
|
|
283
|
-
fetchImpl
|
|
395
|
+
fetchImpl,
|
|
396
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
397
|
+
maxRetries: options.maxRetries ?? DEFAULT_MAX_RETRIES
|
|
284
398
|
};
|
|
285
399
|
}
|
|
286
400
|
/** Account, plan, and quota context for the key. Does not consume quota. */
|
|
@@ -289,14 +403,37 @@ var Beliq = class {
|
|
|
289
403
|
}
|
|
290
404
|
/** Generate a compliant e-invoice from an EN 16931 object. */
|
|
291
405
|
async generate(input) {
|
|
292
|
-
const res = await send(
|
|
406
|
+
const res = await send(
|
|
407
|
+
this.#config,
|
|
408
|
+
buildRequest({ operation: "generate", ...input, sealed: input.seal })
|
|
409
|
+
);
|
|
410
|
+
const meta = generateMeta(res.headers);
|
|
411
|
+
if (input.seal) {
|
|
412
|
+
const envelope = parseEnvelope(res.bytes);
|
|
413
|
+
if (envelope?.success === false) throw errorFromResponse(res.status, res.bytes);
|
|
414
|
+
const data = envelope?.data;
|
|
415
|
+
if (!data || data.output === void 0) {
|
|
416
|
+
throw new BeliqApiError("beliq: seal response was not a JSON envelope", {
|
|
417
|
+
status: res.status
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
const bytes = decodeBase64(data.output);
|
|
421
|
+
const contentType2 = data.contentType ?? "application/octet-stream";
|
|
422
|
+
return {
|
|
423
|
+
contentType: contentType2,
|
|
424
|
+
bytes,
|
|
425
|
+
xml: contentType2.includes("xml") ? decodeUtf8(bytes) : void 0,
|
|
426
|
+
sha256: data.sha256,
|
|
427
|
+
validationResult: data.validationResult,
|
|
428
|
+
meta
|
|
429
|
+
};
|
|
430
|
+
}
|
|
293
431
|
const contentType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
294
|
-
const isXml = contentType.includes("xml");
|
|
295
432
|
return {
|
|
296
433
|
contentType,
|
|
297
434
|
bytes: res.bytes,
|
|
298
|
-
xml:
|
|
299
|
-
meta
|
|
435
|
+
xml: contentType.includes("xml") ? decodeUtf8(res.bytes) : void 0,
|
|
436
|
+
meta
|
|
300
437
|
};
|
|
301
438
|
}
|
|
302
439
|
/** Validate a document against authority-pinned rules. */
|
|
@@ -358,10 +495,16 @@ var Beliq = class {
|
|
|
358
495
|
Beliq,
|
|
359
496
|
BeliqApiError,
|
|
360
497
|
DEFAULT_BASE_URL,
|
|
498
|
+
DEFAULT_MAX_RETRIES,
|
|
499
|
+
DEFAULT_TIMEOUT_MS,
|
|
361
500
|
LIVE_CONVERT_SOURCE_FORMATS,
|
|
362
501
|
LIVE_CONVERT_TARGET_FORMATS,
|
|
502
|
+
LIVE_GENERATE_PRESETS,
|
|
363
503
|
LIVE_GENERATE_STANDARDS,
|
|
364
504
|
LIVE_PARSE_FORMATS,
|
|
365
505
|
LIVE_PROFILES,
|
|
366
|
-
|
|
506
|
+
LIVE_PROFILES_BY_STANDARD,
|
|
507
|
+
LIVE_VALIDATE_FORMATS,
|
|
508
|
+
isProfileAllowedForStandard,
|
|
509
|
+
profilesForStandard
|
|
367
510
|
});
|