@financica/scrada-client 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/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # @financica/scrada-client
2
+
3
+ TypeScript HTTP client for the [Scrada](https://www.scrada.be/) Peppol API. Scrada is a Belgian Peppol access point that exposes a JSON API for sending and receiving Peppol BIS Billing 3.0 documents (invoices, credit notes) plus participant registration and lookup.
4
+
5
+ This package is the thin transport layer — it wraps `https://api.scrada.be/v1` and exposes typed request/response shapes. It does not build payloads from upstream sources like Stripe; for that, see [`@financica/scrada-stripe`](../scrada-stripe).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @financica/scrada-client
11
+ ```
12
+
13
+ Requires Node 18+ for the global `fetch`.
14
+
15
+ ## Usage
16
+
17
+ ```ts
18
+ import { ScradaApiClient } from "@financica/scrada-client";
19
+
20
+ const scrada = new ScradaApiClient({
21
+ apiKey: process.env.SCRADA_API_KEY!,
22
+ password: process.env.SCRADA_PASSWORD!,
23
+ });
24
+
25
+ const documentId = await scrada.sendOutboundSalesInvoice(companyId, invoice);
26
+ const info = await scrada.getOutboundDocumentInfo(companyId, documentId);
27
+ ```
28
+
29
+ Or construct it from environment variables:
30
+
31
+ ```ts
32
+ import { createScradaApiClientFromEnv } from "@financica/scrada-client";
33
+
34
+ // Reads SCRADA_API_KEY, SCRADA_PASSWORD, SCRADA_API_BASE_URL.
35
+ const scrada = createScradaApiClientFromEnv();
36
+ ```
37
+
38
+ ## Methods
39
+
40
+ | Method | HTTP | Path |
41
+ | --- | --- | --- |
42
+ | `registerCompany` | POST | `/company/{id}/peppol/register` |
43
+ | `deregisterCompany` | DELETE | `/company/{id}/peppol/deregister/{scheme}/{id}` |
44
+ | `getUnconfirmedInboundDocuments` | GET | `/company/{id}/peppol/inbound/document/unconfirmed` |
45
+ | `getInboundDocument` | GET | `/company/{id}/peppol/inbound/document/{docId}` |
46
+ | `getInboundDocumentPdf` | GET | `/company/{id}/peppol/inbound/document/{docId}/pdf` |
47
+ | `confirmInboundDocument` | PUT | `/company/{id}/peppol/inbound/document/{docId}/confirm` |
48
+ | `sendOutboundSalesInvoice` | POST | `/company/{id}/peppol/outbound/salesInvoice` |
49
+ | `getOutboundDocumentInfo` | GET | `/company/{id}/peppol/outbound/document/{docId}/info` |
50
+ | `lookupPeppolParticipant` | GET | `/company/{id}/peppol/lookup/{scheme}/{id}` |
51
+ | `lookupPeppolParty` | POST | `/company/{id}/peppol/lookup` |
52
+
53
+ ## Errors
54
+
55
+ All non-2xx responses surface as `ScradaApiError`:
56
+
57
+ ```ts
58
+ import { ScradaApiClient, ScradaApiError } from "@financica/scrada-client";
59
+
60
+ try {
61
+ await scrada.sendOutboundSalesInvoice(companyId, invoice);
62
+ } catch (err) {
63
+ if (err instanceof ScradaApiError) {
64
+ console.error(err.status, err.message, err.details);
65
+ }
66
+ throw err;
67
+ }
68
+ ```
69
+
70
+ The `message` is extracted from the response body using `summarizeScradaErrorDetails`, which walks Scrada's variable error shapes (`{message}`, `{errors: [{detail}]}`, `{modelState}`, `{defaultFormat}`, …) and joins the human-readable strings with ` | `. The full original body is preserved on `err.details`.
71
+
72
+ ## Types
73
+
74
+ - `PeppolOnlyInvoice` — the body shape for the outbound sales invoice and self-billing endpoints (mirrors `v1.PeppolOnlyInvoice` in the Scrada OpenAPI spec).
75
+ - `PeppolOnlyInvoiceParty` — supplier or customer; includes the `vatStatus` field that determines whether the supplier may charge VAT (1 = Subject, 2 = Not subject, 3 = Small business / franchise).
76
+ - `PeppolOnlyInvoiceLine`, `SalesInvoiceVatTotal`, `ScradaInvoiceAttachment`, `ScradaAddress`.
77
+ - `ScradaInboundDocumentSummary`, `ScradaInboundDocumentResponse`, `ScradaOutboundDocumentInfo`, `ScradaPeppolLookupResponse`.
78
+ - Enum-coded scalars: `CompanyVatStatus`, `CompanyInvoiceLineVatType`, `CompanyInvoiceTaxNumberType`.
79
+
80
+ ## Constants
81
+
82
+ `DEFAULT_SCRADA_API_BASE_URL`, `SCRADA_LANGUAGE_HEADER`, `SCRADA_ATTACHMENT_FILE_TYPE_INVOICE`, plus the default Peppol identifier scheme strings (`DEFAULT_PEPPOL_*`) used during participant registration.
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Mirrors v1.CompanyVatStatus.
3
+ *
4
+ * 1 — Subject to VAT (charges and remits VAT — the normal case)
5
+ * 2 — Not subject to VAT (no VAT registration at all)
6
+ * 3 — Small business / franchise exemption (has a VAT number but is exempt
7
+ * from collecting VAT, e.g. Belgian Article 56bis kleine onderneming)
8
+ *
9
+ * Scrada uses this on the supplier party to decide whether the supplier may
10
+ * charge VAT on the outbound document. When unset, Scrada appears to default
11
+ * to a non-VAT-collecting status, and any non-zero VAT line is rejected with
12
+ * "VAT difference left for 0% VAT".
13
+ */
14
+ type CompanyVatStatus = 1 | 2 | 3;
15
+ /**
16
+ * Mirrors v1.CompanyInvoiceLineVatType (subset commonly used).
17
+ *
18
+ * 1 — Standard rate
19
+ * 2 — Zero rate
20
+ * 3 — Exempt from tax
21
+ * 50 — Reverse charge
22
+ * 52 — 0% Clause 44 (Article 44)
23
+ *
24
+ * The full enum supports 1–10, 20–22, 50–54, 70–72 (export, ICD, OSS, etc.).
25
+ * Modeled as `number` rather than a literal union so the rarer codes don't
26
+ * require library updates to stay valid.
27
+ */
28
+ type CompanyInvoiceLineVatType = number;
29
+ /** Mirrors v1.CompanyInvoiceTaxNumberType. 1 = BE, 2 = NL, 3 = FR. */
30
+ type CompanyInvoiceTaxNumberType = 1 | 2 | 3;
31
+ interface ScradaAddress {
32
+ street: string | null;
33
+ streetNumber: string | null;
34
+ streetBox: string | null;
35
+ city: string | null;
36
+ zipCode: string | null;
37
+ countrySubentity: string | null;
38
+ countryCode: string | null;
39
+ }
40
+ interface PeppolOnlyInvoiceParty {
41
+ peppolID?: string | null;
42
+ code?: string | null;
43
+ languageCode?: string | null;
44
+ name: string;
45
+ address: ScradaAddress;
46
+ phone?: string | null;
47
+ email?: string | null;
48
+ invoiceEmail?: string | null;
49
+ contact?: string | null;
50
+ vatStatus?: CompanyVatStatus;
51
+ taxNumberType?: CompanyInvoiceTaxNumberType;
52
+ taxNumber?: string | null;
53
+ legalPersonRegister?: string | null;
54
+ vatNumber?: string | null;
55
+ glnNumber?: string | null;
56
+ }
57
+ interface PeppolOnlyInvoiceLine {
58
+ lineNumber: string;
59
+ itemCodeSeller?: string | null;
60
+ itemCodeBuyer?: string | null;
61
+ itemName: string;
62
+ itemOriginCountryCode?: string | null;
63
+ quantity: number;
64
+ /** Scrada's CompanyInvoiceLineUomType code (1 = piece). */
65
+ unitType: number;
66
+ itemExclVat?: number | null;
67
+ itemInclVat?: number | null;
68
+ vatType: CompanyInvoiceLineVatType;
69
+ vatPercentage: number;
70
+ totalDiscountExclVat?: number | null;
71
+ totalDiscountInclVat?: number | null;
72
+ totalExclVat?: number | null;
73
+ totalInclVat?: number | null;
74
+ invoicePeriodStartDate?: string | null;
75
+ invoicePeriodEndDate?: string | null;
76
+ }
77
+ interface SalesInvoiceVatTotal {
78
+ vatType: CompanyInvoiceLineVatType;
79
+ vatPercentage: number;
80
+ totalExclVat: number;
81
+ totalVat: number;
82
+ totalInclVat: number;
83
+ note?: string | null;
84
+ }
85
+ interface ScradaInvoiceAttachment {
86
+ filename: string;
87
+ fileType: number;
88
+ mimeType: string;
89
+ base64Data: string;
90
+ externalReference?: string;
91
+ }
92
+ /**
93
+ * Body for POST /v1/company/{companyID}/peppol/outbound/salesInvoice
94
+ * (and the self-billing equivalent).
95
+ */
96
+ interface PeppolOnlyInvoice {
97
+ number: string;
98
+ externalReference?: string | null;
99
+ creditInvoice?: boolean | null;
100
+ isInclVat?: boolean | null;
101
+ invoiceReference?: string | null;
102
+ invoiceDate: string;
103
+ invoiceExpiryDate?: string | null;
104
+ supplier: PeppolOnlyInvoiceParty;
105
+ customer: PeppolOnlyInvoiceParty;
106
+ totalExclVat: number;
107
+ totalInclVat: number;
108
+ totalVat: number;
109
+ currency?: string | null;
110
+ payableRoundingAmount?: number | null;
111
+ note?: string | null;
112
+ lines: PeppolOnlyInvoiceLine[];
113
+ vatTotals: SalesInvoiceVatTotal[];
114
+ paymentTerms?: string | null;
115
+ attachments?: ScradaInvoiceAttachment[] | null;
116
+ }
117
+ interface ScradaInboundDocumentSummary {
118
+ id: string;
119
+ internalNumber?: number;
120
+ peppolSenderScheme?: string;
121
+ peppolSenderID?: string;
122
+ peppolReceiverScheme?: string;
123
+ peppolReceiverID?: string;
124
+ peppolC1CountryCode?: string;
125
+ peppolC2Timestamp?: string;
126
+ peppolC2SeatID?: string;
127
+ peppolC2MessageID?: string;
128
+ peppolC3IncomingUniqueID?: string;
129
+ peppolC3MessageID?: string;
130
+ peppolC3Timestamp?: string;
131
+ peppolConversationID?: string;
132
+ peppolSbdhInstanceID?: string;
133
+ peppolProcessScheme?: string;
134
+ peppolProcessValue?: string;
135
+ peppolDocumentTypeScheme?: string;
136
+ peppolDocumentTypeValue?: string;
137
+ }
138
+ interface ScradaInboundUnconfirmedResponse {
139
+ results?: ScradaInboundDocumentSummary[];
140
+ __count?: number;
141
+ }
142
+ interface ScradaInboundDocumentResponse {
143
+ body: string;
144
+ contentType: string | null;
145
+ headers: Record<string, string>;
146
+ }
147
+ interface ScradaOutboundDocumentInfo {
148
+ id: string;
149
+ createdOn?: string;
150
+ externalReference?: string;
151
+ peppolSenderID?: string | null;
152
+ peppolReceiverID?: string | null;
153
+ peppolC1CountryCode?: string | null;
154
+ peppolC2Timestamp?: string | null;
155
+ peppolC2SeatID?: string | null;
156
+ peppolC2MessageID?: string | null;
157
+ peppolC3MessageID?: string | null;
158
+ peppolC3Timestamp?: string | null;
159
+ peppolC3SeatID?: string | null;
160
+ peppolConversationID?: string | null;
161
+ peppolSbdhInstanceID?: string | null;
162
+ peppolDocumentTypeScheme?: string | null;
163
+ peppolDocumentTypeValue?: string | null;
164
+ peppolProcessScheme?: string | null;
165
+ peppolProcessValue?: string | null;
166
+ salesInvoiceID?: string | null;
167
+ status: string;
168
+ attempt?: number;
169
+ errorMessage?: string;
170
+ }
171
+ interface ScradaPeppolLookupResponse {
172
+ registered?: boolean;
173
+ supportInvoice?: boolean;
174
+ supportCreditInvoice?: boolean;
175
+ supportSelfBillingInvoice?: boolean;
176
+ supportSelfBillingCreditInvoice?: boolean;
177
+ participantIdentifier?: {
178
+ scheme?: string;
179
+ id?: string;
180
+ } | null;
181
+ businessEntity?: {
182
+ name?: string;
183
+ languageCode?: string;
184
+ countryCode?: string;
185
+ } | null;
186
+ documentTypes?: Array<{
187
+ scheme?: string;
188
+ value?: string;
189
+ processIdentifier?: {
190
+ scheme?: string;
191
+ value?: string;
192
+ } | null;
193
+ }> | null;
194
+ }
195
+
196
+ interface ScradaApiClientOptions {
197
+ apiKey: string;
198
+ password: string;
199
+ baseUrl?: string;
200
+ /** Override the global `fetch` (e.g. for testing). */
201
+ fetch?: typeof fetch;
202
+ }
203
+ interface ScradaInboundPdfResponse {
204
+ arrayBuffer: ArrayBuffer;
205
+ contentType: string;
206
+ headers: Record<string, string>;
207
+ }
208
+ /**
209
+ * HTTP client for the Scrada Peppol API.
210
+ *
211
+ * https://api.scrada.be/v1
212
+ *
213
+ * Authenticates with API key + password sent as `X-API-KEY` / `X-PASSWORD`
214
+ * headers. All non-2xx responses surface as `ScradaApiError`.
215
+ */
216
+ declare class ScradaApiClient {
217
+ private readonly apiKey;
218
+ private readonly password;
219
+ private readonly baseUrl;
220
+ private readonly fetchImpl;
221
+ constructor(options: ScradaApiClientOptions);
222
+ private buildHeaders;
223
+ private request;
224
+ registerCompany(companyId: string, payload: Record<string, unknown>): Promise<string>;
225
+ deregisterCompany(companyId: string, participantIdentifierScheme: string, participantIdentifierValue: string): Promise<void>;
226
+ getUnconfirmedInboundDocuments(companyId: string): Promise<ScradaInboundUnconfirmedResponse>;
227
+ getInboundDocument(companyId: string, documentId: string): Promise<ScradaInboundDocumentResponse>;
228
+ getInboundDocumentPdf(companyId: string, documentId: string): Promise<ScradaInboundPdfResponse>;
229
+ confirmInboundDocument(companyId: string, documentId: string): Promise<void>;
230
+ sendOutboundSalesInvoice(companyId: string, payload: PeppolOnlyInvoice): Promise<string>;
231
+ getOutboundDocumentInfo(companyId: string, documentId: string): Promise<ScradaOutboundDocumentInfo>;
232
+ lookupPeppolParticipant(companyId: string, scheme: string, id: string): Promise<ScradaPeppolLookupResponse>;
233
+ lookupPeppolParty(companyId: string, payload: Record<string, unknown>): Promise<ScradaPeppolLookupResponse>;
234
+ }
235
+ /**
236
+ * Create a Scrada API client from the canonical environment variables.
237
+ * Throws when SCRADA_API_KEY or SCRADA_PASSWORD is missing.
238
+ */
239
+ declare const createScradaApiClientFromEnv: (env?: NodeJS.ProcessEnv) => ScradaApiClient;
240
+
241
+ declare const DEFAULT_SCRADA_API_BASE_URL = "https://api.scrada.be/v1";
242
+ /** Language sent in the Scrada `Language` request header (server may localize errors). */
243
+ declare const SCRADA_LANGUAGE_HEADER = "EN";
244
+ declare const DEFAULT_PEPPOL_SENDER_IDENTIFIER_SCHEME = "iso6523-actorid-upis";
245
+ /** ISO/IEC 6523 0208: Belgian enterprise number (KBO/BCE). */
246
+ declare const DEFAULT_PEPPOL_COMPANY_IDENTIFIER_SCHEME = "0208";
247
+ /** ISO/IEC 6523 9925: Belgian VAT number scheme. */
248
+ declare const DEFAULT_PEPPOL_VAT_IDENTIFIER_SCHEME = "9925";
249
+ declare const DEFAULT_PEPPOL_DOCUMENT_TYPE_SCHEME = "busdox-docid-qns";
250
+ declare const DEFAULT_PEPPOL_DOCUMENT_TYPE_VALUE = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1";
251
+ declare const DEFAULT_PEPPOL_PROCESS_SCHEME = "cenbii-procid-ubl";
252
+ declare const DEFAULT_PEPPOL_PROCESS_VALUE = "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0";
253
+ /** Scrada `attachment.fileType` value for the primary invoice/credit note PDF. */
254
+ declare const SCRADA_ATTACHMENT_FILE_TYPE_INVOICE = 1;
255
+
256
+ declare class ScradaApiError extends Error {
257
+ status: number;
258
+ details: unknown;
259
+ constructor(message: string, status: number, details: unknown);
260
+ }
261
+ /**
262
+ * Walks a Scrada error response body and pulls out the human-readable
263
+ * message strings. Returns `null` when nothing usable is found.
264
+ *
265
+ * Scrada returns errors in several shapes (string, `{message}`,
266
+ * `{errors: [{detail}]}`, `{modelState: {...}}`, etc.); this collapses
267
+ * them into a single ` | `-separated summary.
268
+ */
269
+ declare const summarizeScradaErrorDetails: (details: unknown) => string | null;
270
+ /**
271
+ * Converts a non-2xx Scrada response into a ScradaApiError, parsing the body
272
+ * as JSON when possible and extracting message strings via
273
+ * {@link summarizeScradaErrorDetails}.
274
+ */
275
+ declare const scradaApiErrorFromResponse: (response: Response) => Promise<ScradaApiError>;
276
+
277
+ export { type CompanyInvoiceLineVatType, type CompanyInvoiceTaxNumberType, type CompanyVatStatus, DEFAULT_PEPPOL_COMPANY_IDENTIFIER_SCHEME, DEFAULT_PEPPOL_DOCUMENT_TYPE_SCHEME, DEFAULT_PEPPOL_DOCUMENT_TYPE_VALUE, DEFAULT_PEPPOL_PROCESS_SCHEME, DEFAULT_PEPPOL_PROCESS_VALUE, DEFAULT_PEPPOL_SENDER_IDENTIFIER_SCHEME, DEFAULT_PEPPOL_VAT_IDENTIFIER_SCHEME, DEFAULT_SCRADA_API_BASE_URL, type PeppolOnlyInvoice, type PeppolOnlyInvoiceLine, type PeppolOnlyInvoiceParty, SCRADA_ATTACHMENT_FILE_TYPE_INVOICE, SCRADA_LANGUAGE_HEADER, type SalesInvoiceVatTotal, type ScradaAddress, ScradaApiClient, type ScradaApiClientOptions, ScradaApiError, type ScradaInboundDocumentResponse, type ScradaInboundDocumentSummary, type ScradaInboundPdfResponse, type ScradaInboundUnconfirmedResponse, type ScradaInvoiceAttachment, type ScradaOutboundDocumentInfo, type ScradaPeppolLookupResponse, createScradaApiClientFromEnv, scradaApiErrorFromResponse, summarizeScradaErrorDetails };
package/dist/index.js ADDED
@@ -0,0 +1,312 @@
1
+ // src/constants.ts
2
+ var DEFAULT_SCRADA_API_BASE_URL = "https://api.scrada.be/v1";
3
+ var SCRADA_LANGUAGE_HEADER = "EN";
4
+ var DEFAULT_PEPPOL_SENDER_IDENTIFIER_SCHEME = "iso6523-actorid-upis";
5
+ var DEFAULT_PEPPOL_COMPANY_IDENTIFIER_SCHEME = "0208";
6
+ var DEFAULT_PEPPOL_VAT_IDENTIFIER_SCHEME = "9925";
7
+ var DEFAULT_PEPPOL_DOCUMENT_TYPE_SCHEME = "busdox-docid-qns";
8
+ var DEFAULT_PEPPOL_DOCUMENT_TYPE_VALUE = "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1";
9
+ var DEFAULT_PEPPOL_PROCESS_SCHEME = "cenbii-procid-ubl";
10
+ var DEFAULT_PEPPOL_PROCESS_VALUE = "urn:fdc:peppol.eu:2017:poacc:billing:01:1.0";
11
+ var SCRADA_ATTACHMENT_FILE_TYPE_INVOICE = 1;
12
+
13
+ // src/errors.ts
14
+ var ScradaApiError = class extends Error {
15
+ status;
16
+ details;
17
+ constructor(message, status, details) {
18
+ super(message);
19
+ this.name = "ScradaApiError";
20
+ this.status = status;
21
+ this.details = details;
22
+ }
23
+ };
24
+ var tryParseJson = (value) => {
25
+ try {
26
+ return JSON.parse(value);
27
+ } catch {
28
+ return null;
29
+ }
30
+ };
31
+ var normalizeDetailMessage = (value) => value.trim().replace(/\s+/g, " ");
32
+ var collectDetailMessages = (value, collector, depth = 0) => {
33
+ if (depth > 4 || value === null || value === void 0) return;
34
+ if (typeof value === "string") {
35
+ const normalized = normalizeDetailMessage(value);
36
+ if (normalized.length > 0) collector.add(normalized);
37
+ return;
38
+ }
39
+ if (Array.isArray(value)) {
40
+ for (const entry of value) {
41
+ collectDetailMessages(entry, collector, depth + 1);
42
+ }
43
+ return;
44
+ }
45
+ if (typeof value !== "object") return;
46
+ const record = value;
47
+ const preferredFields = [
48
+ "defaultFormat",
49
+ "message",
50
+ "error",
51
+ "title",
52
+ "detail",
53
+ "description",
54
+ "reason"
55
+ ];
56
+ for (const field of preferredFields) {
57
+ const fieldValue = record[field];
58
+ if (typeof fieldValue === "string") {
59
+ const normalized = normalizeDetailMessage(fieldValue);
60
+ if (normalized.length > 0) collector.add(normalized);
61
+ }
62
+ }
63
+ const nestedFields = ["details", "errors", "validationErrors", "modelState"];
64
+ for (const field of nestedFields) {
65
+ if (field in record) {
66
+ collectDetailMessages(record[field], collector, depth + 1);
67
+ }
68
+ }
69
+ for (const [key, fieldValue] of Object.entries(record)) {
70
+ if (preferredFields.includes(key) || nestedFields.includes(key)) continue;
71
+ if (typeof fieldValue === "string") {
72
+ const normalized = normalizeDetailMessage(fieldValue);
73
+ if (normalized.length > 0 && /error|invalid|missing|required/i.test(key)) {
74
+ collector.add(normalized);
75
+ }
76
+ continue;
77
+ }
78
+ if (Array.isArray(fieldValue) || fieldValue && typeof fieldValue === "object") {
79
+ collectDetailMessages(fieldValue, collector, depth + 1);
80
+ }
81
+ }
82
+ };
83
+ var summarizeScradaErrorDetails = (details) => {
84
+ const collector = /* @__PURE__ */ new Set();
85
+ collectDetailMessages(details, collector);
86
+ if (collector.size === 0) return null;
87
+ return Array.from(collector).slice(0, 6).join(" | ");
88
+ };
89
+ var coerceErrorMessage = (details, fallback) => summarizeScradaErrorDetails(details) ?? fallback;
90
+ var scradaApiErrorFromResponse = async (response) => {
91
+ const textBody = await response.text();
92
+ const parsed = tryParseJson(textBody) ?? textBody;
93
+ return new ScradaApiError(
94
+ coerceErrorMessage(
95
+ parsed,
96
+ `Scrada request failed with status ${response.status}`
97
+ ),
98
+ response.status,
99
+ parsed
100
+ );
101
+ };
102
+
103
+ // src/client.ts
104
+ var joinUrl = (baseUrl, path) => {
105
+ const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
106
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
107
+ return `${normalizedBase}${normalizedPath}`;
108
+ };
109
+ var normalizeHeaders = (headers) => {
110
+ const result = {};
111
+ headers.forEach((value, key) => {
112
+ result[key.toLowerCase()] = value;
113
+ });
114
+ return result;
115
+ };
116
+ var normalizeDocumentId = (value) => {
117
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
118
+ if (value && typeof value === "object") {
119
+ const record = value;
120
+ if (typeof record.id === "string" && record.id.trim().length > 0) {
121
+ return record.id.trim();
122
+ }
123
+ if (typeof record.documentID === "string" && record.documentID.trim().length > 0) {
124
+ return record.documentID.trim();
125
+ }
126
+ }
127
+ throw new Error("Unable to resolve document ID from Scrada response");
128
+ };
129
+ var tryParseJson2 = (value) => {
130
+ try {
131
+ return JSON.parse(value);
132
+ } catch {
133
+ return null;
134
+ }
135
+ };
136
+ var ScradaApiClient = class {
137
+ apiKey;
138
+ password;
139
+ baseUrl;
140
+ fetchImpl;
141
+ constructor(options) {
142
+ this.apiKey = options.apiKey;
143
+ this.password = options.password;
144
+ this.baseUrl = options.baseUrl ?? DEFAULT_SCRADA_API_BASE_URL;
145
+ this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
146
+ }
147
+ buildHeaders(extra) {
148
+ const headers = new Headers({
149
+ "X-API-KEY": this.apiKey,
150
+ "X-PASSWORD": this.password,
151
+ Language: SCRADA_LANGUAGE_HEADER
152
+ });
153
+ if (extra) {
154
+ const extraHeaders = new Headers(extra);
155
+ extraHeaders.forEach((value, key) => {
156
+ headers.set(key, value);
157
+ });
158
+ }
159
+ return headers;
160
+ }
161
+ async request(params) {
162
+ const response = await this.fetchImpl(joinUrl(this.baseUrl, params.path), {
163
+ method: params.method ?? "GET",
164
+ headers: this.buildHeaders(params.headers),
165
+ body: params.body
166
+ });
167
+ if (!response.ok) {
168
+ throw await scradaApiErrorFromResponse(response);
169
+ }
170
+ const expect = params.expect ?? "json";
171
+ if (expect === "arrayBuffer") {
172
+ return await response.arrayBuffer();
173
+ }
174
+ if (expect === "text") {
175
+ return await response.text();
176
+ }
177
+ const textBody = await response.text();
178
+ if (!textBody) return null;
179
+ const parsed = tryParseJson2(textBody);
180
+ if (parsed !== null) return parsed;
181
+ return textBody;
182
+ }
183
+ // ── Peppol registration ─────────────────────────────────────────────
184
+ async registerCompany(companyId, payload) {
185
+ const response = await this.request({
186
+ path: `/company/${companyId}/peppol/register`,
187
+ method: "POST",
188
+ headers: { "Content-Type": "application/json" },
189
+ body: JSON.stringify(payload)
190
+ });
191
+ return normalizeDocumentId(response);
192
+ }
193
+ async deregisterCompany(companyId, participantIdentifierScheme, participantIdentifierValue) {
194
+ await this.request({
195
+ path: `/company/${companyId}/peppol/deregister/${encodeURIComponent(
196
+ participantIdentifierScheme
197
+ )}/${encodeURIComponent(participantIdentifierValue)}`,
198
+ method: "DELETE"
199
+ });
200
+ }
201
+ // ── Inbound documents ───────────────────────────────────────────────
202
+ async getUnconfirmedInboundDocuments(companyId) {
203
+ return this.request({
204
+ path: `/company/${companyId}/peppol/inbound/document/unconfirmed`
205
+ });
206
+ }
207
+ async getInboundDocument(companyId, documentId) {
208
+ const response = await this.fetchImpl(
209
+ joinUrl(
210
+ this.baseUrl,
211
+ `/company/${companyId}/peppol/inbound/document/${documentId}`
212
+ ),
213
+ {
214
+ method: "GET",
215
+ headers: this.buildHeaders()
216
+ }
217
+ );
218
+ if (!response.ok) throw await scradaApiErrorFromResponse(response);
219
+ return {
220
+ body: await response.text(),
221
+ contentType: response.headers.get("content-type"),
222
+ headers: normalizeHeaders(response.headers)
223
+ };
224
+ }
225
+ async getInboundDocumentPdf(companyId, documentId) {
226
+ const response = await this.fetchImpl(
227
+ joinUrl(
228
+ this.baseUrl,
229
+ `/company/${companyId}/peppol/inbound/document/${documentId}/pdf`
230
+ ),
231
+ {
232
+ method: "GET",
233
+ headers: this.buildHeaders()
234
+ }
235
+ );
236
+ if (!response.ok) throw await scradaApiErrorFromResponse(response);
237
+ return {
238
+ arrayBuffer: await response.arrayBuffer(),
239
+ contentType: response.headers.get("content-type") ?? "application/pdf",
240
+ headers: normalizeHeaders(response.headers)
241
+ };
242
+ }
243
+ async confirmInboundDocument(companyId, documentId) {
244
+ await this.request({
245
+ path: `/company/${companyId}/peppol/inbound/document/${documentId}/confirm`,
246
+ method: "PUT"
247
+ });
248
+ }
249
+ // ── Outbound documents ──────────────────────────────────────────────
250
+ async sendOutboundSalesInvoice(companyId, payload) {
251
+ const response = await this.request({
252
+ path: `/company/${companyId}/peppol/outbound/salesInvoice`,
253
+ method: "POST",
254
+ headers: { "Content-Type": "application/json" },
255
+ body: JSON.stringify(payload)
256
+ });
257
+ return normalizeDocumentId(response);
258
+ }
259
+ async getOutboundDocumentInfo(companyId, documentId) {
260
+ return this.request({
261
+ path: `/company/${companyId}/peppol/outbound/document/${documentId}/info`
262
+ });
263
+ }
264
+ // ── Peppol participant lookup ───────────────────────────────────────
265
+ async lookupPeppolParticipant(companyId, scheme, id) {
266
+ return this.request({
267
+ path: `/company/${companyId}/peppol/lookup/${encodeURIComponent(
268
+ scheme
269
+ )}/${encodeURIComponent(id)}`
270
+ });
271
+ }
272
+ async lookupPeppolParty(companyId, payload) {
273
+ return this.request({
274
+ path: `/company/${companyId}/peppol/lookup`,
275
+ method: "POST",
276
+ headers: { "Content-Type": "application/json" },
277
+ body: JSON.stringify(payload)
278
+ });
279
+ }
280
+ };
281
+ var createScradaApiClientFromEnv = (env = process.env) => {
282
+ const apiKey = env.SCRADA_API_KEY;
283
+ const password = env.SCRADA_PASSWORD;
284
+ if (!apiKey || !password) {
285
+ throw new Error(
286
+ "Scrada credentials are not configured. Set SCRADA_API_KEY and SCRADA_PASSWORD."
287
+ );
288
+ }
289
+ return new ScradaApiClient({
290
+ apiKey,
291
+ password,
292
+ baseUrl: env.SCRADA_API_BASE_URL
293
+ });
294
+ };
295
+ export {
296
+ DEFAULT_PEPPOL_COMPANY_IDENTIFIER_SCHEME,
297
+ DEFAULT_PEPPOL_DOCUMENT_TYPE_SCHEME,
298
+ DEFAULT_PEPPOL_DOCUMENT_TYPE_VALUE,
299
+ DEFAULT_PEPPOL_PROCESS_SCHEME,
300
+ DEFAULT_PEPPOL_PROCESS_VALUE,
301
+ DEFAULT_PEPPOL_SENDER_IDENTIFIER_SCHEME,
302
+ DEFAULT_PEPPOL_VAT_IDENTIFIER_SCHEME,
303
+ DEFAULT_SCRADA_API_BASE_URL,
304
+ SCRADA_ATTACHMENT_FILE_TYPE_INVOICE,
305
+ SCRADA_LANGUAGE_HEADER,
306
+ ScradaApiClient,
307
+ ScradaApiError,
308
+ createScradaApiClientFromEnv,
309
+ scradaApiErrorFromResponse,
310
+ summarizeScradaErrorDetails
311
+ };
312
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export const DEFAULT_SCRADA_API_BASE_URL = \"https://api.scrada.be/v1\";\n\n/** Language sent in the Scrada `Language` request header (server may localize errors). */\nexport const SCRADA_LANGUAGE_HEADER = \"EN\";\n\n// ── Peppol identifier scheme defaults ────────────────────────────────\n\nexport const DEFAULT_PEPPOL_SENDER_IDENTIFIER_SCHEME = \"iso6523-actorid-upis\";\n/** ISO/IEC 6523 0208: Belgian enterprise number (KBO/BCE). */\nexport const DEFAULT_PEPPOL_COMPANY_IDENTIFIER_SCHEME = \"0208\";\n/** ISO/IEC 6523 9925: Belgian VAT number scheme. */\nexport const DEFAULT_PEPPOL_VAT_IDENTIFIER_SCHEME = \"9925\";\nexport const DEFAULT_PEPPOL_DOCUMENT_TYPE_SCHEME = \"busdox-docid-qns\";\nexport const DEFAULT_PEPPOL_DOCUMENT_TYPE_VALUE =\n\t\"urn:oasis:names:specification:ubl:schema:xsd:Invoice-2::Invoice##urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0::2.1\";\nexport const DEFAULT_PEPPOL_PROCESS_SCHEME = \"cenbii-procid-ubl\";\nexport const DEFAULT_PEPPOL_PROCESS_VALUE =\n\t\"urn:fdc:peppol.eu:2017:poacc:billing:01:1.0\";\n\n// ── Scrada attachment file type codes ────────────────────────────────\n\n/** Scrada `attachment.fileType` value for the primary invoice/credit note PDF. */\nexport const SCRADA_ATTACHMENT_FILE_TYPE_INVOICE = 1;\n","export class ScradaApiError extends Error {\n\tstatus: number;\n\tdetails: unknown;\n\n\tconstructor(message: string, status: number, details: unknown) {\n\t\tsuper(message);\n\t\tthis.name = \"ScradaApiError\";\n\t\tthis.status = status;\n\t\tthis.details = details;\n\t}\n}\n\nconst tryParseJson = (value: string): unknown => {\n\ttry {\n\t\treturn JSON.parse(value) as unknown;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\nconst normalizeDetailMessage = (value: string) => value.trim().replace(/\\s+/g, \" \");\n\nconst collectDetailMessages = (value: unknown, collector: Set<string>, depth = 0) => {\n\tif (depth > 4 || value === null || value === undefined) return;\n\n\tif (typeof value === \"string\") {\n\t\tconst normalized = normalizeDetailMessage(value);\n\t\tif (normalized.length > 0) collector.add(normalized);\n\t\treturn;\n\t}\n\n\tif (Array.isArray(value)) {\n\t\tfor (const entry of value) {\n\t\t\tcollectDetailMessages(entry, collector, depth + 1);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (typeof value !== \"object\") return;\n\tconst record = value as Record<string, unknown>;\n\n\tconst preferredFields = [\n\t\t\"defaultFormat\",\n\t\t\"message\",\n\t\t\"error\",\n\t\t\"title\",\n\t\t\"detail\",\n\t\t\"description\",\n\t\t\"reason\",\n\t];\n\n\tfor (const field of preferredFields) {\n\t\tconst fieldValue = record[field];\n\t\tif (typeof fieldValue === \"string\") {\n\t\t\tconst normalized = normalizeDetailMessage(fieldValue);\n\t\t\tif (normalized.length > 0) collector.add(normalized);\n\t\t}\n\t}\n\n\tconst nestedFields = [\"details\", \"errors\", \"validationErrors\", \"modelState\"];\n\tfor (const field of nestedFields) {\n\t\tif (field in record) {\n\t\t\tcollectDetailMessages(record[field], collector, depth + 1);\n\t\t}\n\t}\n\n\tfor (const [key, fieldValue] of Object.entries(record)) {\n\t\tif (preferredFields.includes(key) || nestedFields.includes(key)) continue;\n\t\tif (typeof fieldValue === \"string\") {\n\t\t\tconst normalized = normalizeDetailMessage(fieldValue);\n\t\t\tif (normalized.length > 0 && /error|invalid|missing|required/i.test(key)) {\n\t\t\t\tcollector.add(normalized);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\tArray.isArray(fieldValue) ||\n\t\t\t(fieldValue && typeof fieldValue === \"object\")\n\t\t) {\n\t\t\tcollectDetailMessages(fieldValue, collector, depth + 1);\n\t\t}\n\t}\n};\n\n/**\n * Walks a Scrada error response body and pulls out the human-readable\n * message strings. Returns `null` when nothing usable is found.\n *\n * Scrada returns errors in several shapes (string, `{message}`,\n * `{errors: [{detail}]}`, `{modelState: {...}}`, etc.); this collapses\n * them into a single ` | `-separated summary.\n */\nexport const summarizeScradaErrorDetails = (details: unknown): string | null => {\n\tconst collector = new Set<string>();\n\tcollectDetailMessages(details, collector);\n\tif (collector.size === 0) return null;\n\treturn Array.from(collector).slice(0, 6).join(\" | \");\n};\n\nconst coerceErrorMessage = (details: unknown, fallback: string) =>\n\tsummarizeScradaErrorDetails(details) ?? fallback;\n\n/**\n * Converts a non-2xx Scrada response into a ScradaApiError, parsing the body\n * as JSON when possible and extracting message strings via\n * {@link summarizeScradaErrorDetails}.\n */\nexport const scradaApiErrorFromResponse = async (\n\tresponse: Response,\n): Promise<ScradaApiError> => {\n\tconst textBody = await response.text();\n\tconst parsed = tryParseJson(textBody) ?? textBody;\n\treturn new ScradaApiError(\n\t\tcoerceErrorMessage(\n\t\t\tparsed,\n\t\t\t`Scrada request failed with status ${response.status}`,\n\t\t),\n\t\tresponse.status,\n\t\tparsed,\n\t);\n};\n","import { DEFAULT_SCRADA_API_BASE_URL, SCRADA_LANGUAGE_HEADER } from \"./constants\";\nimport { scradaApiErrorFromResponse } from \"./errors\";\nimport type {\n\tPeppolOnlyInvoice,\n\tScradaInboundDocumentResponse,\n\tScradaInboundUnconfirmedResponse,\n\tScradaOutboundDocumentInfo,\n\tScradaPeppolLookupResponse,\n} from \"./types\";\n\ntype ScradaRequestMethod = \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\nexport interface ScradaApiClientOptions {\n\tapiKey: string;\n\tpassword: string;\n\tbaseUrl?: string;\n\t/** Override the global `fetch` (e.g. for testing). */\n\tfetch?: typeof fetch;\n}\n\nexport interface ScradaInboundPdfResponse {\n\tarrayBuffer: ArrayBuffer;\n\tcontentType: string;\n\theaders: Record<string, string>;\n}\n\nconst joinUrl = (baseUrl: string, path: string) => {\n\tconst normalizedBase = baseUrl.endsWith(\"/\") ? baseUrl.slice(0, -1) : baseUrl;\n\tconst normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n\treturn `${normalizedBase}${normalizedPath}`;\n};\n\nconst normalizeHeaders = (headers: Headers) => {\n\tconst result: Record<string, string> = {};\n\theaders.forEach((value, key) => {\n\t\tresult[key.toLowerCase()] = value;\n\t});\n\treturn result;\n};\n\nconst normalizeDocumentId = (value: unknown): string => {\n\tif (typeof value === \"string\" && value.trim().length > 0) return value.trim();\n\tif (value && typeof value === \"object\") {\n\t\tconst record = value as Record<string, unknown>;\n\t\tif (typeof record.id === \"string\" && record.id.trim().length > 0) {\n\t\t\treturn record.id.trim();\n\t\t}\n\t\tif (\n\t\t\ttypeof record.documentID === \"string\" &&\n\t\t\trecord.documentID.trim().length > 0\n\t\t) {\n\t\t\treturn record.documentID.trim();\n\t\t}\n\t}\n\tthrow new Error(\"Unable to resolve document ID from Scrada response\");\n};\n\nconst tryParseJson = (value: string): unknown => {\n\ttry {\n\t\treturn JSON.parse(value) as unknown;\n\t} catch {\n\t\treturn null;\n\t}\n};\n\n/**\n * HTTP client for the Scrada Peppol API.\n *\n * https://api.scrada.be/v1\n *\n * Authenticates with API key + password sent as `X-API-KEY` / `X-PASSWORD`\n * headers. All non-2xx responses surface as `ScradaApiError`.\n */\nexport class ScradaApiClient {\n\tprivate readonly apiKey: string;\n\tprivate readonly password: string;\n\tprivate readonly baseUrl: string;\n\tprivate readonly fetchImpl: typeof fetch;\n\n\tconstructor(options: ScradaApiClientOptions) {\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.password = options.password;\n\t\tthis.baseUrl = options.baseUrl ?? DEFAULT_SCRADA_API_BASE_URL;\n\t\tthis.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);\n\t}\n\n\tprivate buildHeaders(extra?: HeadersInit): Headers {\n\t\tconst headers = new Headers({\n\t\t\t\"X-API-KEY\": this.apiKey,\n\t\t\t\"X-PASSWORD\": this.password,\n\t\t\tLanguage: SCRADA_LANGUAGE_HEADER,\n\t\t});\n\t\tif (extra) {\n\t\t\tconst extraHeaders = new Headers(extra);\n\t\t\textraHeaders.forEach((value, key) => {\n\t\t\t\theaders.set(key, value);\n\t\t\t});\n\t\t}\n\t\treturn headers;\n\t}\n\n\tprivate async request<T>(params: {\n\t\tpath: string;\n\t\tmethod?: ScradaRequestMethod;\n\t\tbody?: BodyInit;\n\t\theaders?: HeadersInit;\n\t\texpect?: \"json\" | \"text\" | \"arrayBuffer\";\n\t}): Promise<T> {\n\t\tconst response = await this.fetchImpl(joinUrl(this.baseUrl, params.path), {\n\t\t\tmethod: params.method ?? \"GET\",\n\t\t\theaders: this.buildHeaders(params.headers),\n\t\t\tbody: params.body,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow await scradaApiErrorFromResponse(response);\n\t\t}\n\n\t\tconst expect = params.expect ?? \"json\";\n\t\tif (expect === \"arrayBuffer\") {\n\t\t\treturn (await response.arrayBuffer()) as T;\n\t\t}\n\t\tif (expect === \"text\") {\n\t\t\treturn (await response.text()) as T;\n\t\t}\n\n\t\tconst textBody = await response.text();\n\t\tif (!textBody) return null as T;\n\t\tconst parsed = tryParseJson(textBody);\n\t\tif (parsed !== null) return parsed as T;\n\t\treturn textBody as T;\n\t}\n\n\t// ── Peppol registration ─────────────────────────────────────────────\n\n\tasync registerCompany(\n\t\tcompanyId: string,\n\t\tpayload: Record<string, unknown>,\n\t): Promise<string> {\n\t\tconst response = await this.request<unknown>({\n\t\t\tpath: `/company/${companyId}/peppol/register`,\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(payload),\n\t\t});\n\t\treturn normalizeDocumentId(response);\n\t}\n\n\tasync deregisterCompany(\n\t\tcompanyId: string,\n\t\tparticipantIdentifierScheme: string,\n\t\tparticipantIdentifierValue: string,\n\t): Promise<void> {\n\t\tawait this.request<null>({\n\t\t\tpath: `/company/${companyId}/peppol/deregister/${encodeURIComponent(\n\t\t\t\tparticipantIdentifierScheme,\n\t\t\t)}/${encodeURIComponent(participantIdentifierValue)}`,\n\t\t\tmethod: \"DELETE\",\n\t\t});\n\t}\n\n\t// ── Inbound documents ───────────────────────────────────────────────\n\n\tasync getUnconfirmedInboundDocuments(\n\t\tcompanyId: string,\n\t): Promise<ScradaInboundUnconfirmedResponse> {\n\t\treturn this.request<ScradaInboundUnconfirmedResponse>({\n\t\t\tpath: `/company/${companyId}/peppol/inbound/document/unconfirmed`,\n\t\t});\n\t}\n\n\tasync getInboundDocument(\n\t\tcompanyId: string,\n\t\tdocumentId: string,\n\t): Promise<ScradaInboundDocumentResponse> {\n\t\tconst response = await this.fetchImpl(\n\t\t\tjoinUrl(\n\t\t\t\tthis.baseUrl,\n\t\t\t\t`/company/${companyId}/peppol/inbound/document/${documentId}`,\n\t\t\t),\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: this.buildHeaders(),\n\t\t\t},\n\t\t);\n\n\t\tif (!response.ok) throw await scradaApiErrorFromResponse(response);\n\n\t\treturn {\n\t\t\tbody: await response.text(),\n\t\t\tcontentType: response.headers.get(\"content-type\"),\n\t\t\theaders: normalizeHeaders(response.headers),\n\t\t};\n\t}\n\n\tasync getInboundDocumentPdf(\n\t\tcompanyId: string,\n\t\tdocumentId: string,\n\t): Promise<ScradaInboundPdfResponse> {\n\t\tconst response = await this.fetchImpl(\n\t\t\tjoinUrl(\n\t\t\t\tthis.baseUrl,\n\t\t\t\t`/company/${companyId}/peppol/inbound/document/${documentId}/pdf`,\n\t\t\t),\n\t\t\t{\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: this.buildHeaders(),\n\t\t\t},\n\t\t);\n\t\tif (!response.ok) throw await scradaApiErrorFromResponse(response);\n\n\t\treturn {\n\t\t\tarrayBuffer: await response.arrayBuffer(),\n\t\t\tcontentType: response.headers.get(\"content-type\") ?? \"application/pdf\",\n\t\t\theaders: normalizeHeaders(response.headers),\n\t\t};\n\t}\n\n\tasync confirmInboundDocument(companyId: string, documentId: string): Promise<void> {\n\t\tawait this.request<null>({\n\t\t\tpath: `/company/${companyId}/peppol/inbound/document/${documentId}/confirm`,\n\t\t\tmethod: \"PUT\",\n\t\t});\n\t}\n\n\t// ── Outbound documents ──────────────────────────────────────────────\n\n\tasync sendOutboundSalesInvoice(\n\t\tcompanyId: string,\n\t\tpayload: PeppolOnlyInvoice,\n\t): Promise<string> {\n\t\tconst response = await this.request<unknown>({\n\t\t\tpath: `/company/${companyId}/peppol/outbound/salesInvoice`,\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(payload),\n\t\t});\n\t\treturn normalizeDocumentId(response);\n\t}\n\n\tasync getOutboundDocumentInfo(\n\t\tcompanyId: string,\n\t\tdocumentId: string,\n\t): Promise<ScradaOutboundDocumentInfo> {\n\t\treturn this.request<ScradaOutboundDocumentInfo>({\n\t\t\tpath: `/company/${companyId}/peppol/outbound/document/${documentId}/info`,\n\t\t});\n\t}\n\n\t// ── Peppol participant lookup ───────────────────────────────────────\n\n\tasync lookupPeppolParticipant(\n\t\tcompanyId: string,\n\t\tscheme: string,\n\t\tid: string,\n\t): Promise<ScradaPeppolLookupResponse> {\n\t\treturn this.request<ScradaPeppolLookupResponse>({\n\t\t\tpath: `/company/${companyId}/peppol/lookup/${encodeURIComponent(\n\t\t\t\tscheme,\n\t\t\t)}/${encodeURIComponent(id)}`,\n\t\t});\n\t}\n\n\tasync lookupPeppolParty(\n\t\tcompanyId: string,\n\t\tpayload: Record<string, unknown>,\n\t): Promise<ScradaPeppolLookupResponse> {\n\t\treturn this.request<ScradaPeppolLookupResponse>({\n\t\t\tpath: `/company/${companyId}/peppol/lookup`,\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(payload),\n\t\t});\n\t}\n}\n\n/**\n * Create a Scrada API client from the canonical environment variables.\n * Throws when SCRADA_API_KEY or SCRADA_PASSWORD is missing.\n */\nexport const createScradaApiClientFromEnv = (\n\tenv: NodeJS.ProcessEnv = process.env,\n): ScradaApiClient => {\n\tconst apiKey = env.SCRADA_API_KEY;\n\tconst password = env.SCRADA_PASSWORD;\n\tif (!apiKey || !password) {\n\t\tthrow new Error(\n\t\t\t\"Scrada credentials are not configured. Set SCRADA_API_KEY and SCRADA_PASSWORD.\",\n\t\t);\n\t}\n\n\treturn new ScradaApiClient({\n\t\tapiKey,\n\t\tpassword,\n\t\tbaseUrl: env.SCRADA_API_BASE_URL,\n\t});\n};\n"],"mappings":"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","names":["tryParseJson"]}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@financica/scrada-client",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript HTTP client for the Scrada Peppol API (https://api.scrada.be/v1).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup",
20
+ "prepack": "bun run build",
21
+ "lint": "biome check .",
22
+ "format": "biome format --write .",
23
+ "type-check": "tsc --noEmit",
24
+ "test": "vitest",
25
+ "test:run": "vitest --run",
26
+ "ci": "bun run type-check && bun run lint && bun run test:run && bun run build"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "devDependencies": {
32
+ "@biomejs/biome": "^2.4.13",
33
+ "@types/node": "^22.0.0",
34
+ "tsup": "^8.0.0",
35
+ "typescript": "^5.9.3",
36
+ "vitest": "^4.0.18"
37
+ }
38
+ }