@financica/scrada-client 0.1.0 → 0.2.1
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.d.ts +9 -1
- package/dist/index.js +14 -2
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
package/dist/index.d.ts
CHANGED
|
@@ -227,7 +227,15 @@ declare class ScradaApiClient {
|
|
|
227
227
|
getInboundDocument(companyId: string, documentId: string): Promise<ScradaInboundDocumentResponse>;
|
|
228
228
|
getInboundDocumentPdf(companyId: string, documentId: string): Promise<ScradaInboundPdfResponse>;
|
|
229
229
|
confirmInboundDocument(companyId: string, documentId: string): Promise<void>;
|
|
230
|
-
|
|
230
|
+
/**
|
|
231
|
+
* @param options.idempotencyKey Sent as `Idempotency-Key`. A transient
|
|
232
|
+
* network-error retry with the same key is collapsed by Scrada, so the
|
|
233
|
+
* recipient's Peppol endpoint won't receive the same invoice twice. The
|
|
234
|
+
* caller should pass a deterministic key (typically the invoice ID).
|
|
235
|
+
*/
|
|
236
|
+
sendOutboundSalesInvoice(companyId: string, payload: PeppolOnlyInvoice, options?: {
|
|
237
|
+
idempotencyKey?: string;
|
|
238
|
+
}): Promise<string>;
|
|
231
239
|
getOutboundDocumentInfo(companyId: string, documentId: string): Promise<ScradaOutboundDocumentInfo>;
|
|
232
240
|
lookupPeppolParticipant(companyId: string, scheme: string, id: string): Promise<ScradaPeppolLookupResponse>;
|
|
233
241
|
lookupPeppolParty(companyId: string, payload: Record<string, unknown>): Promise<ScradaPeppolLookupResponse>;
|
package/dist/index.js
CHANGED
|
@@ -247,11 +247,23 @@ var ScradaApiClient = class {
|
|
|
247
247
|
});
|
|
248
248
|
}
|
|
249
249
|
// ── Outbound documents ──────────────────────────────────────────────
|
|
250
|
-
|
|
250
|
+
/**
|
|
251
|
+
* @param options.idempotencyKey Sent as `Idempotency-Key`. A transient
|
|
252
|
+
* network-error retry with the same key is collapsed by Scrada, so the
|
|
253
|
+
* recipient's Peppol endpoint won't receive the same invoice twice. The
|
|
254
|
+
* caller should pass a deterministic key (typically the invoice ID).
|
|
255
|
+
*/
|
|
256
|
+
async sendOutboundSalesInvoice(companyId, payload, options) {
|
|
257
|
+
const headers = {
|
|
258
|
+
"Content-Type": "application/json"
|
|
259
|
+
};
|
|
260
|
+
if (options?.idempotencyKey) {
|
|
261
|
+
headers["Idempotency-Key"] = options.idempotencyKey;
|
|
262
|
+
}
|
|
251
263
|
const response = await this.request({
|
|
252
264
|
path: `/company/${companyId}/peppol/outbound/salesInvoice`,
|
|
253
265
|
method: "POST",
|
|
254
|
-
headers
|
|
266
|
+
headers,
|
|
255
267
|
body: JSON.stringify(payload)
|
|
256
268
|
});
|
|
257
269
|
return normalizeDocumentId(response);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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"]}
|
|
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\t/**\n\t * @param options.idempotencyKey Sent as `Idempotency-Key`. A transient\n\t * network-error retry with the same key is collapsed by Scrada, so the\n\t * recipient's Peppol endpoint won't receive the same invoice twice. The\n\t * caller should pass a deterministic key (typically the invoice ID).\n\t */\n\tasync sendOutboundSalesInvoice(\n\t\tcompanyId: string,\n\t\tpayload: PeppolOnlyInvoice,\n\t\toptions?: { idempotencyKey?: string },\n\t): Promise<string> {\n\t\tconst headers: Record<string, string> = {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t};\n\t\tif (options?.idempotencyKey) {\n\t\t\theaders[\"Idempotency-Key\"] = options.idempotencyKey;\n\t\t}\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,\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
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@financica/scrada-client",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "TypeScript HTTP client for the Scrada Peppol API (https://api.scrada.be/v1).",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/financica/scrada-client.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": "https://github.com/financica/scrada-client/issues",
|
|
11
|
+
"homepage": "https://github.com/financica/scrada-client#readme",
|
|
6
12
|
"type": "module",
|
|
7
13
|
"exports": {
|
|
8
14
|
".": {
|