@beliq/sdk 0.1.0 → 0.2.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
@@ -34,6 +34,18 @@ function toBytes(input) {
34
34
  function decodeUtf8(bytes) {
35
35
  return new TextDecoder("utf-8").decode(bytes);
36
36
  }
37
+ function decodeBase64(input) {
38
+ const b64 = input.replace(/-/g, "+").replace(/_/g, "/");
39
+ if (typeof atob === "function") {
40
+ const bin = atob(b64);
41
+ const out = new Uint8Array(bin.length);
42
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
43
+ return out;
44
+ }
45
+ const maybeBuffer = globalThis.Buffer;
46
+ if (maybeBuffer) return new Uint8Array(maybeBuffer.from(b64, "base64"));
47
+ throw new Error("beliq: no base64 decoder available in this runtime");
48
+ }
37
49
  var PDF_MAGIC = [37, 80, 68, 70, 45];
38
50
  function sniffContentType(bytes) {
39
51
  const isPdf = bytes.length >= PDF_MAGIC.length && PDF_MAGIC.every((b, i) => bytes[i] === b);
@@ -72,7 +84,8 @@ function buildRequest(params) {
72
84
  path: "/v1/generate",
73
85
  jsonBody: merged,
74
86
  contentType: "application/json",
75
- outputKind: "binary"
87
+ // JSON mode returns the seal envelope; binary mode returns the raw document body.
88
+ ...params.sealed ? { accept: "application/json", outputKind: "json" } : { outputKind: "binary" }
76
89
  };
77
90
  }
78
91
  case "validate": {
@@ -157,6 +170,13 @@ function errorFromResponse(status, bytes) {
157
170
  // src/constants.ts
158
171
  var DEFAULT_BASE_URL = "https://api.beliq.eu";
159
172
  var LIVE_GENERATE_STANDARDS = ["xrechnung", "zugferd", "facturx", "peppol-bis"];
173
+ var LIVE_GENERATE_PRESETS = [
174
+ { id: "xrechnung", label: "XRechnung", standard: "xrechnung", output: "xml" },
175
+ { id: "factur-x", label: "Factur-X", standard: "facturx", output: "pdf", facturxProfile: "en16931" },
176
+ { id: "zugferd", label: "ZUGFeRD", standard: "zugferd", output: "pdf" },
177
+ { id: "peppol-bis", label: "Peppol BIS 3.0", standard: "peppol-bis", output: "xml" },
178
+ { id: "nlcius", label: "NLCIUS", standard: "peppol-bis", output: "xml", profile: "netherlands-nlcius" }
179
+ ];
160
180
  var LIVE_PROFILES = ["basicwl", "en16931", "extended", "extended-ctc-fr"];
161
181
  var LIVE_VALIDATE_FORMATS = ["auto", "cii", "ubl"];
162
182
  var LIVE_PARSE_FORMATS = ["auto", "cii", "ubl"];
@@ -195,6 +215,7 @@ function buildUrl(baseUrl, path, query) {
195
215
  async function send(config, req) {
196
216
  const headers = { ...authHeaders(config) };
197
217
  if (req.contentType) headers["Content-Type"] = req.contentType;
218
+ if (req.accept) headers["Accept"] = req.accept;
198
219
  const body = req.jsonBody !== void 0 ? JSON.stringify(req.jsonBody) : req.rawBody;
199
220
  const res = await config.fetchImpl(buildUrl(config.baseUrl, req.path, req.query), {
200
221
  method: req.method,
@@ -207,11 +228,31 @@ async function send(config, req) {
207
228
  }
208
229
 
209
230
  // src/client.ts
231
+ var TEST_KEY_PREFIX = "blq_test_";
232
+ function livemodeHeader(headers) {
233
+ const raw = headers.get("x-beliq-livemode");
234
+ if (raw === "true") return true;
235
+ if (raw === "false") return false;
236
+ return void 0;
237
+ }
238
+ function parseRulesetArtifacts(headers) {
239
+ const raw = headers.get("x-ruleset-artifacts");
240
+ if (!raw) return void 0;
241
+ try {
242
+ const parsed = JSON.parse(raw);
243
+ return Array.isArray(parsed) ? parsed : void 0;
244
+ } catch {
245
+ return void 0;
246
+ }
247
+ }
210
248
  function generateMeta(headers) {
211
249
  return {
212
250
  schematronVersion: headers.get("x-schematron-version") ?? void 0,
213
251
  pdfKind: headers.get("x-pdf-kind") ?? void 0,
214
- outputEnvelope: headers.get("x-output-envelope") ?? void 0
252
+ outputEnvelope: headers.get("x-output-envelope") ?? void 0,
253
+ rulesetSha256: headers.get("x-ruleset-sha256") ?? void 0,
254
+ rulesetArtifacts: parseRulesetArtifacts(headers),
255
+ livemode: livemodeHeader(headers)
215
256
  };
216
257
  }
217
258
  function convertMeta(headers) {
@@ -231,17 +272,25 @@ function convertMeta(headers) {
231
272
  profileDetected: headers.get("x-profile-detected") ?? void 0,
232
273
  lostElementsCount: countRaw != null ? Number(countRaw) : void 0,
233
274
  lostElements,
234
- conversionTools: headers.get("x-conversion-tools") ?? void 0
275
+ conversionTools: headers.get("x-conversion-tools") ?? void 0,
276
+ livemode: livemodeHeader(headers)
235
277
  };
236
278
  }
237
279
  var Beliq = class {
238
280
  #config;
281
+ /**
282
+ * True for a live key, false for a blq_test_ sandbox key, derived from the key
283
+ * prefix (the same rule the server applies). Available before any request; the
284
+ * per-response x-beliq-livemode header is surfaced on generate/convert meta.
285
+ */
286
+ livemode;
239
287
  constructor(options) {
240
288
  if (!options?.apiKey) throw new Error("beliq: apiKey is required");
241
289
  const fetchImpl = options.fetch ?? globalThis.fetch;
242
290
  if (typeof fetchImpl !== "function") {
243
291
  throw new Error("beliq: no global fetch available; pass options.fetch");
244
292
  }
293
+ this.livemode = !options.apiKey.startsWith(TEST_KEY_PREFIX);
245
294
  this.#config = {
246
295
  apiKey: options.apiKey,
247
296
  baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
@@ -255,14 +304,37 @@ var Beliq = class {
255
304
  }
256
305
  /** Generate a compliant e-invoice from an EN 16931 object. */
257
306
  async generate(input) {
258
- const res = await send(this.#config, buildRequest({ operation: "generate", ...input }));
307
+ const res = await send(
308
+ this.#config,
309
+ buildRequest({ operation: "generate", ...input, sealed: input.seal })
310
+ );
311
+ const meta = generateMeta(res.headers);
312
+ if (input.seal) {
313
+ const envelope = parseEnvelope(res.bytes);
314
+ if (envelope?.success === false) throw errorFromResponse(res.status, res.bytes);
315
+ const data = envelope?.data;
316
+ if (!data || data.output === void 0) {
317
+ throw new BeliqApiError("beliq: seal response was not a JSON envelope", {
318
+ status: res.status
319
+ });
320
+ }
321
+ const bytes = decodeBase64(data.output);
322
+ const contentType2 = data.contentType ?? "application/octet-stream";
323
+ return {
324
+ contentType: contentType2,
325
+ bytes,
326
+ xml: contentType2.includes("xml") ? decodeUtf8(bytes) : void 0,
327
+ sha256: data.sha256,
328
+ validationResult: data.validationResult,
329
+ meta
330
+ };
331
+ }
259
332
  const contentType = res.headers.get("content-type") ?? "application/octet-stream";
260
- const isXml = contentType.includes("xml");
261
333
  return {
262
334
  contentType,
263
335
  bytes: res.bytes,
264
- xml: isXml ? decodeUtf8(res.bytes) : void 0,
265
- meta: generateMeta(res.headers)
336
+ xml: contentType.includes("xml") ? decodeUtf8(res.bytes) : void 0,
337
+ meta
266
338
  };
267
339
  }
268
340
  /** Validate a document against authority-pinned rules. */
@@ -325,6 +397,7 @@ export {
325
397
  DEFAULT_BASE_URL,
326
398
  LIVE_CONVERT_SOURCE_FORMATS,
327
399
  LIVE_CONVERT_TARGET_FORMATS,
400
+ LIVE_GENERATE_PRESETS,
328
401
  LIVE_GENERATE_STANDARDS,
329
402
  LIVE_PARSE_FORMATS,
330
403
  LIVE_PROFILES,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@beliq/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.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",
@@ -72,6 +72,7 @@
72
72
  "sync:spec": "node scripts/sync-spec.mjs",
73
73
  "gen:types": "node scripts/gen-types.mjs",
74
74
  "openapi:check": "node scripts/gen-types.mjs --check",
75
+ "spec:drift": "node scripts/check-live-drift.mjs",
75
76
  "test": "vitest run --exclude test/integration.test.ts",
76
77
  "test:integration": "vitest run test/integration.test.ts",
77
78
  "prepublishOnly": "npm run build"