@beliq/sdk 0.1.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/LICENSE +21 -0
- package/README.md +113 -0
- package/dist/constants-bWOXCmJD.d.cts +22 -0
- package/dist/constants-bWOXCmJD.d.ts +22 -0
- package/dist/helpers.cjs +200 -0
- package/dist/helpers.d.cts +44 -0
- package/dist/helpers.d.ts +44 -0
- package/dist/helpers.js +160 -0
- package/dist/index.cjs +367 -0
- package/dist/index.d.cts +985 -0
- package/dist/index.d.ts +985 -0
- package/dist/index.js +332 -0
- package/package.json +90 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
// src/internal.ts
|
|
2
|
+
function isPlainObject(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function mergeDeep(target, source) {
|
|
6
|
+
const out = { ...target };
|
|
7
|
+
for (const [key, value] of Object.entries(source)) {
|
|
8
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
9
|
+
if (isPlainObject(value) && isPlainObject(out[key])) {
|
|
10
|
+
out[key] = mergeDeep(out[key], value);
|
|
11
|
+
} else {
|
|
12
|
+
out[key] = value;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
function compactQuery(query) {
|
|
18
|
+
const out = {};
|
|
19
|
+
for (const [key, value] of Object.entries(query)) {
|
|
20
|
+
if (value === void 0 || value === "") continue;
|
|
21
|
+
out[key] = value;
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function toBytes(input) {
|
|
26
|
+
if (typeof input === "string") return new TextEncoder().encode(input);
|
|
27
|
+
if (input instanceof Uint8Array) return input;
|
|
28
|
+
if (input instanceof ArrayBuffer) return new Uint8Array(input);
|
|
29
|
+
if (ArrayBuffer.isView(input)) {
|
|
30
|
+
return new Uint8Array(input.buffer, input.byteOffset, input.byteLength);
|
|
31
|
+
}
|
|
32
|
+
throw new TypeError("beliq: document must be a string, Uint8Array, ArrayBuffer, or typed array");
|
|
33
|
+
}
|
|
34
|
+
function decodeUtf8(bytes) {
|
|
35
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
36
|
+
}
|
|
37
|
+
var PDF_MAGIC = [37, 80, 68, 70, 45];
|
|
38
|
+
function sniffContentType(bytes) {
|
|
39
|
+
const isPdf = bytes.length >= PDF_MAGIC.length && PDF_MAGIC.every((b, i) => bytes[i] === b);
|
|
40
|
+
return isPdf ? "application/pdf" : "application/xml";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/buildRequest.ts
|
|
44
|
+
function isFacturxFamily(standardOrFormat) {
|
|
45
|
+
return standardOrFormat === "facturx" || standardOrFormat === "zugferd";
|
|
46
|
+
}
|
|
47
|
+
function withAdvancedQuery(query, advanced) {
|
|
48
|
+
const base = compactQuery(query);
|
|
49
|
+
const merged = advanced && Object.keys(advanced).length > 0 ? mergeDeep(base, advanced) : base;
|
|
50
|
+
return merged;
|
|
51
|
+
}
|
|
52
|
+
function buildRequest(params) {
|
|
53
|
+
switch (params.operation) {
|
|
54
|
+
case "me":
|
|
55
|
+
return { method: "GET", path: "/v1/me", outputKind: "json" };
|
|
56
|
+
case "generate": {
|
|
57
|
+
const body = {
|
|
58
|
+
standard: params.standard,
|
|
59
|
+
output: params.output ?? "xml",
|
|
60
|
+
invoice: params.invoice ?? {}
|
|
61
|
+
};
|
|
62
|
+
if (params.profile) body.profile = params.profile;
|
|
63
|
+
if (params.facturxProfile && isFacturxFamily(params.standard)) {
|
|
64
|
+
body.facturxProfile = params.facturxProfile;
|
|
65
|
+
}
|
|
66
|
+
if (typeof params.verify === "boolean") body.verify = params.verify;
|
|
67
|
+
if (params.template) body.template = params.template;
|
|
68
|
+
if (params.pdfTemplateId) body.pdfTemplateId = params.pdfTemplateId;
|
|
69
|
+
const merged = params.advanced && Object.keys(params.advanced).length > 0 ? mergeDeep(body, params.advanced) : body;
|
|
70
|
+
return {
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: "/v1/generate",
|
|
73
|
+
jsonBody: merged,
|
|
74
|
+
contentType: "application/json",
|
|
75
|
+
outputKind: "binary"
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
case "validate": {
|
|
79
|
+
const query = { format: params.validateFormat };
|
|
80
|
+
if (typeof params.franceCtc === "boolean") query.franceCtc = params.franceCtc;
|
|
81
|
+
return {
|
|
82
|
+
method: "POST",
|
|
83
|
+
path: "/v1/validate",
|
|
84
|
+
query: withAdvancedQuery(query, params.advanced),
|
|
85
|
+
rawBody: params.rawBody,
|
|
86
|
+
contentType: params.rawContentType ?? "application/xml",
|
|
87
|
+
outputKind: "json"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
case "parse":
|
|
91
|
+
return {
|
|
92
|
+
method: "POST",
|
|
93
|
+
path: "/v1/parse",
|
|
94
|
+
query: withAdvancedQuery({ format: params.parseFormat }, params.advanced),
|
|
95
|
+
rawBody: params.rawBody,
|
|
96
|
+
contentType: params.rawContentType ?? "application/xml",
|
|
97
|
+
outputKind: "json"
|
|
98
|
+
};
|
|
99
|
+
case "convert": {
|
|
100
|
+
const query = {
|
|
101
|
+
sourceFormat: params.sourceFormat,
|
|
102
|
+
targetFormat: params.targetFormat
|
|
103
|
+
};
|
|
104
|
+
if (params.targetProfile && isFacturxFamily(params.targetFormat)) {
|
|
105
|
+
query.targetProfile = params.targetProfile;
|
|
106
|
+
}
|
|
107
|
+
if (typeof params.dropFranceCtcOverlay === "boolean") {
|
|
108
|
+
query.dropFranceCtcOverlay = params.dropFranceCtcOverlay;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
method: "POST",
|
|
112
|
+
path: "/v1/convert",
|
|
113
|
+
query: withAdvancedQuery(query, params.advanced),
|
|
114
|
+
rawBody: params.rawBody,
|
|
115
|
+
contentType: params.rawContentType ?? "application/xml",
|
|
116
|
+
outputKind: "binary"
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/errors.ts
|
|
123
|
+
var BeliqApiError = class extends Error {
|
|
124
|
+
code;
|
|
125
|
+
status;
|
|
126
|
+
details;
|
|
127
|
+
constructor(message, opts) {
|
|
128
|
+
super(message);
|
|
129
|
+
this.name = "BeliqApiError";
|
|
130
|
+
this.code = opts.code;
|
|
131
|
+
this.status = opts.status;
|
|
132
|
+
this.details = opts.details;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function parseEnvelope(bytes) {
|
|
136
|
+
let text;
|
|
137
|
+
try {
|
|
138
|
+
text = decodeUtf8(bytes);
|
|
139
|
+
} catch {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
if (!text.trim()) return null;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(text);
|
|
145
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
146
|
+
} catch {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
function errorFromResponse(status, bytes) {
|
|
151
|
+
const envelope = parseEnvelope(bytes);
|
|
152
|
+
const err = envelope?.error;
|
|
153
|
+
const message = err?.message ?? envelope?.message ?? `beliq request failed with status ${status}`;
|
|
154
|
+
return new BeliqApiError(message, { code: err?.code, status, details: err?.details });
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/constants.ts
|
|
158
|
+
var DEFAULT_BASE_URL = "https://api.beliq.eu";
|
|
159
|
+
var LIVE_GENERATE_STANDARDS = ["xrechnung", "zugferd", "facturx", "peppol-bis"];
|
|
160
|
+
var LIVE_PROFILES = ["basicwl", "en16931", "extended", "extended-ctc-fr"];
|
|
161
|
+
var LIVE_VALIDATE_FORMATS = ["auto", "cii", "ubl"];
|
|
162
|
+
var LIVE_PARSE_FORMATS = ["auto", "cii", "ubl"];
|
|
163
|
+
var LIVE_CONVERT_SOURCE_FORMATS = [
|
|
164
|
+
"auto",
|
|
165
|
+
"cii",
|
|
166
|
+
"ubl",
|
|
167
|
+
"zugferd",
|
|
168
|
+
"facturx",
|
|
169
|
+
"xrechnung",
|
|
170
|
+
"peppol-bis"
|
|
171
|
+
];
|
|
172
|
+
var LIVE_CONVERT_TARGET_FORMATS = [
|
|
173
|
+
"cii",
|
|
174
|
+
"ubl",
|
|
175
|
+
"zugferd",
|
|
176
|
+
"facturx",
|
|
177
|
+
"xrechnung",
|
|
178
|
+
"peppol-bis"
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
// src/transport.ts
|
|
182
|
+
function authHeaders(config) {
|
|
183
|
+
return config.auth === "bearer" ? { Authorization: `Bearer ${config.apiKey}` } : { "X-API-Key": config.apiKey };
|
|
184
|
+
}
|
|
185
|
+
function buildUrl(baseUrl, path, query) {
|
|
186
|
+
if (!query) return `${baseUrl}${path}`;
|
|
187
|
+
const params = new URLSearchParams();
|
|
188
|
+
for (const [key, value] of Object.entries(query)) {
|
|
189
|
+
if (value === void 0) continue;
|
|
190
|
+
params.set(key, String(value));
|
|
191
|
+
}
|
|
192
|
+
const qs = params.toString();
|
|
193
|
+
return qs ? `${baseUrl}${path}?${qs}` : `${baseUrl}${path}`;
|
|
194
|
+
}
|
|
195
|
+
async function send(config, req) {
|
|
196
|
+
const headers = { ...authHeaders(config) };
|
|
197
|
+
if (req.contentType) headers["Content-Type"] = req.contentType;
|
|
198
|
+
const body = req.jsonBody !== void 0 ? JSON.stringify(req.jsonBody) : req.rawBody;
|
|
199
|
+
const res = await config.fetchImpl(buildUrl(config.baseUrl, req.path, req.query), {
|
|
200
|
+
method: req.method,
|
|
201
|
+
headers,
|
|
202
|
+
body
|
|
203
|
+
});
|
|
204
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
205
|
+
if (!res.ok) throw errorFromResponse(res.status, bytes);
|
|
206
|
+
return { status: res.status, headers: res.headers, bytes };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/client.ts
|
|
210
|
+
function generateMeta(headers) {
|
|
211
|
+
return {
|
|
212
|
+
schematronVersion: headers.get("x-schematron-version") ?? void 0,
|
|
213
|
+
pdfKind: headers.get("x-pdf-kind") ?? void 0,
|
|
214
|
+
outputEnvelope: headers.get("x-output-envelope") ?? void 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function convertMeta(headers) {
|
|
218
|
+
let lostElements;
|
|
219
|
+
const lostRaw = headers.get("x-lost-elements");
|
|
220
|
+
if (lostRaw) {
|
|
221
|
+
try {
|
|
222
|
+
const parsed = JSON.parse(lostRaw);
|
|
223
|
+
if (Array.isArray(parsed)) lostElements = parsed.map(String);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const countRaw = headers.get("x-lost-elements-count");
|
|
228
|
+
return {
|
|
229
|
+
sourceFormat: headers.get("x-source-format") ?? void 0,
|
|
230
|
+
targetFormat: headers.get("x-target-format") ?? void 0,
|
|
231
|
+
profileDetected: headers.get("x-profile-detected") ?? void 0,
|
|
232
|
+
lostElementsCount: countRaw != null ? Number(countRaw) : void 0,
|
|
233
|
+
lostElements,
|
|
234
|
+
conversionTools: headers.get("x-conversion-tools") ?? void 0
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
var Beliq = class {
|
|
238
|
+
#config;
|
|
239
|
+
constructor(options) {
|
|
240
|
+
if (!options?.apiKey) throw new Error("beliq: apiKey is required");
|
|
241
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
242
|
+
if (typeof fetchImpl !== "function") {
|
|
243
|
+
throw new Error("beliq: no global fetch available; pass options.fetch");
|
|
244
|
+
}
|
|
245
|
+
this.#config = {
|
|
246
|
+
apiKey: options.apiKey,
|
|
247
|
+
baseUrl: (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
248
|
+
auth: options.auth ?? "header",
|
|
249
|
+
fetchImpl
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** Account, plan, and quota context for the key. Does not consume quota. */
|
|
253
|
+
async me() {
|
|
254
|
+
return this.#json({ operation: "me" });
|
|
255
|
+
}
|
|
256
|
+
/** Generate a compliant e-invoice from an EN 16931 object. */
|
|
257
|
+
async generate(input) {
|
|
258
|
+
const res = await send(this.#config, buildRequest({ operation: "generate", ...input }));
|
|
259
|
+
const contentType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
260
|
+
const isXml = contentType.includes("xml");
|
|
261
|
+
return {
|
|
262
|
+
contentType,
|
|
263
|
+
bytes: res.bytes,
|
|
264
|
+
xml: isXml ? decodeUtf8(res.bytes) : void 0,
|
|
265
|
+
meta: generateMeta(res.headers)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** Validate a document against authority-pinned rules. */
|
|
269
|
+
async validate(document, options = {}) {
|
|
270
|
+
return this.#json({
|
|
271
|
+
operation: "validate",
|
|
272
|
+
...this.#rawParams(document, options),
|
|
273
|
+
validateFormat: options.format,
|
|
274
|
+
franceCtc: options.franceCtc
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/** Parse a document into a structured EN 16931 invoice. */
|
|
278
|
+
async parse(document, options = {}) {
|
|
279
|
+
return this.#json({
|
|
280
|
+
operation: "parse",
|
|
281
|
+
...this.#rawParams(document, options),
|
|
282
|
+
parseFormat: options.format
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
/** Convert a document between EN 16931 formats. */
|
|
286
|
+
async convert(document, options) {
|
|
287
|
+
const res = await send(
|
|
288
|
+
this.#config,
|
|
289
|
+
buildRequest({
|
|
290
|
+
operation: "convert",
|
|
291
|
+
...this.#rawParams(document, options),
|
|
292
|
+
sourceFormat: options.sourceFormat,
|
|
293
|
+
targetFormat: options.targetFormat,
|
|
294
|
+
targetProfile: options.targetProfile,
|
|
295
|
+
dropFranceCtcOverlay: options.dropFranceCtcOverlay
|
|
296
|
+
})
|
|
297
|
+
);
|
|
298
|
+
return {
|
|
299
|
+
contentType: res.headers.get("content-type") ?? "application/octet-stream",
|
|
300
|
+
bytes: res.bytes,
|
|
301
|
+
meta: convertMeta(res.headers)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
#rawParams(document, options) {
|
|
305
|
+
const rawBody = toBytes(document);
|
|
306
|
+
return {
|
|
307
|
+
rawBody,
|
|
308
|
+
rawContentType: options.contentType ?? sniffContentType(rawBody),
|
|
309
|
+
advanced: options.advanced
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async #json(params) {
|
|
313
|
+
const res = await send(this.#config, buildRequest(params));
|
|
314
|
+
const envelope = parseEnvelope(res.bytes);
|
|
315
|
+
if (envelope?.success === false) throw errorFromResponse(res.status, res.bytes);
|
|
316
|
+
if (!envelope || envelope.data === void 0) {
|
|
317
|
+
throw new BeliqApiError("beliq: response was not a JSON envelope", { status: res.status });
|
|
318
|
+
}
|
|
319
|
+
return envelope.data;
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
export {
|
|
323
|
+
Beliq,
|
|
324
|
+
BeliqApiError,
|
|
325
|
+
DEFAULT_BASE_URL,
|
|
326
|
+
LIVE_CONVERT_SOURCE_FORMATS,
|
|
327
|
+
LIVE_CONVERT_TARGET_FORMATS,
|
|
328
|
+
LIVE_GENERATE_STANDARDS,
|
|
329
|
+
LIVE_PARSE_FORMATS,
|
|
330
|
+
LIVE_PROFILES,
|
|
331
|
+
LIVE_VALIDATE_FORMATS
|
|
332
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@beliq/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"beliq",
|
|
7
|
+
"e-invoice",
|
|
8
|
+
"einvoice",
|
|
9
|
+
"xrechnung",
|
|
10
|
+
"zugferd",
|
|
11
|
+
"factur-x",
|
|
12
|
+
"peppol",
|
|
13
|
+
"en16931",
|
|
14
|
+
"compliance",
|
|
15
|
+
"sdk"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"homepage": "https://beliq.eu",
|
|
19
|
+
"author": {
|
|
20
|
+
"name": "beliq",
|
|
21
|
+
"email": "hello@beliq.eu"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/beliq-eu/beliq-sdk-node.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/beliq-eu/beliq-sdk-node/issues"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/index.d.ts",
|
|
38
|
+
"default": "./dist/index.js"
|
|
39
|
+
},
|
|
40
|
+
"require": {
|
|
41
|
+
"types": "./dist/index.d.cts",
|
|
42
|
+
"default": "./dist/index.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"./helpers": {
|
|
46
|
+
"import": {
|
|
47
|
+
"types": "./dist/helpers.d.ts",
|
|
48
|
+
"default": "./dist/helpers.js"
|
|
49
|
+
},
|
|
50
|
+
"require": {
|
|
51
|
+
"types": "./dist/helpers.d.cts",
|
|
52
|
+
"default": "./dist/helpers.cjs"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist"
|
|
58
|
+
],
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public",
|
|
61
|
+
"provenance": true
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=20.15"
|
|
65
|
+
},
|
|
66
|
+
"scripts": {
|
|
67
|
+
"clean": "rm -rf dist",
|
|
68
|
+
"build": "npm run clean && tsup",
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"lint": "eslint src test",
|
|
71
|
+
"lint:fix": "eslint src test --fix",
|
|
72
|
+
"sync:spec": "node scripts/sync-spec.mjs",
|
|
73
|
+
"gen:types": "node scripts/gen-types.mjs",
|
|
74
|
+
"openapi:check": "node scripts/gen-types.mjs --check",
|
|
75
|
+
"test": "vitest run --exclude test/integration.test.ts",
|
|
76
|
+
"test:integration": "vitest run test/integration.test.ts",
|
|
77
|
+
"prepublishOnly": "npm run build"
|
|
78
|
+
},
|
|
79
|
+
"dependencies": {},
|
|
80
|
+
"devDependencies": {
|
|
81
|
+
"@types/node": "^20.14.0",
|
|
82
|
+
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
83
|
+
"@typescript-eslint/parser": "^7.18.0",
|
|
84
|
+
"eslint": "^8.57.0",
|
|
85
|
+
"openapi-typescript": "^7.4.0",
|
|
86
|
+
"tsup": "^8.3.0",
|
|
87
|
+
"typescript": "^5.5.4",
|
|
88
|
+
"vitest": "^2.1.8"
|
|
89
|
+
}
|
|
90
|
+
}
|