Files
binect-js/src/clients/documents.ts
tegwick b9aebb42f1 Add Binect SDK implementation, Explorer, and test suite
SDK (@binect/js):
- BinectClient with domain sub-clients (documents, sendings, accounts,
  attachments, invoices)
- HTTP Basic Auth, native fetch only (no runtime dependencies)
- TypeScript types matching Binect API vocabulary
- Status predicates and polling helpers in helpers.ts
- Structured error handling (BinectApiError, BinectAuthError)

Explorer:
- Standalone browser-based API explorer (explorer/index.html)
- Interactive testing without code

Tests:
- Unit tests for client, types, errors, helpers, http
- E2E tests for upload/delete and send/cancel workflows

Also includes:
- Architecture Decision Records (ADRs)
- Example DIN 5008 letter PDFs for testing
- API specification research notes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 23:10:34 +01:00

353 lines
9.9 KiB
TypeScript

import type { HttpClient } from '../http.js';
import type {
Document,
DocumentUploadOptions,
DocumentUploadRequestBody,
DocumentTransformation,
CoverPageOptions,
DocumentAttribute,
ListResponse,
PaginationOptions,
} from '../types.js';
/**
* Client for document-related API operations.
* Handles document upload, retrieval, modification, and preview.
*/
export class DocumentsClient {
constructor(private readonly http: HttpClient) {}
/**
* Upload a new document (letter or serial letter).
* POST /documents
*
* @param options - Document upload options including base64-encoded PDF content
* @returns The created document with validation results
*/
async upload(options: DocumentUploadOptions): Promise<Document> {
// Transform user-friendly options to API request body format
const requestBody: DocumentUploadRequestBody = {
content: {
filename: options.filename,
content: options.content,
},
};
// Add options if any are specified
if (options.simplex !== undefined || options.color !== undefined ||
options.envelope !== undefined || options.franking !== undefined ||
options.productionCountry !== undefined) {
requestBody.options = {
simplex: options.simplex,
color: options.color,
envelope: options.envelope,
franking: options.franking,
productionCountry: options.productionCountry,
};
}
// Add attributes if specified
if (options.attributes) {
requestBody.attributes = options.attributes;
}
// Add split params if specified
if (options.splitToken !== undefined || options.pagesPerLetter !== undefined) {
requestBody.splitParams = {
splitToken: options.splitToken,
splitAfterNumberOfPages: options.pagesPerLetter,
};
}
// Add response format if specified
if (options.responseFormat !== undefined) {
requestBody.responseFormat = options.responseFormat;
}
return this.http.request<Document>({
method: 'POST',
path: '/documents',
body: requestBody,
});
}
/**
* List all shippable documents (status 2).
* GET /documents
*
* @param pagination - Optional pagination parameters
* @returns List of shippable documents
*/
async list(pagination?: PaginationOptions): Promise<ListResponse<Document>> {
return this.http.request<ListResponse<Document>>({
method: 'GET',
path: '/documents',
query: pagination,
});
}
/**
* List all documents with errors (status 7).
* GET /documents/errors
*
* @param pagination - Optional pagination parameters
* @returns List of erroneous documents
*/
async listErrors(pagination?: PaginationOptions): Promise<ListResponse<Document>> {
return this.http.request<ListResponse<Document>>({
method: 'GET',
path: '/documents/errors',
query: pagination,
});
}
/**
* Get a specific document by ID.
* GET /documents/{documentID}
*
* @param documentId - The document ID
* @returns The document details
*/
async get(documentId: string): Promise<Document> {
return this.http.request<Document>({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}`,
});
}
/**
* Delete a document.
* DELETE /documents/{documentID}
*
* @param documentId - The document ID to delete
*/
async delete(documentId: string): Promise<void> {
await this.http.request<void>({
method: 'DELETE',
path: `/documents/${encodeURIComponent(documentId)}`,
});
}
/**
* Get all attributes for a document.
* GET /documents/{documentID}/attributes
*
* @param documentId - The document ID
* @returns Key-value attributes
*/
async getAttributes(documentId: string): Promise<Record<string, string>> {
return this.http.request<Record<string, string>>({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}/attributes`,
});
}
/**
* Set attributes for a document.
* POST /documents/{documentID}/attributes
*
* @param documentId - The document ID
* @param attributes - Array of key-value attributes to set
*/
async setAttributes(documentId: string, attributes: DocumentAttribute[]): Promise<void> {
await this.http.request<void>({
method: 'POST',
path: `/documents/${encodeURIComponent(documentId)}/attributes`,
body: attributes,
});
}
/**
* Get a specific attribute value.
* GET /documents/{documentID}/attributes/{key}
*
* @param documentId - The document ID
* @param key - The attribute key
* @returns The attribute value
*/
async getAttribute(documentId: string, key: string): Promise<string> {
return this.http.request<string>({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}/attributes/${encodeURIComponent(key)}`,
});
}
/**
* Update a specific attribute value.
* PUT /documents/{documentID}/attributes/{key}
*
* @param documentId - The document ID
* @param key - The attribute key
* @param value - The new attribute value
*/
async updateAttribute(documentId: string, key: string, value: string): Promise<void> {
await this.http.request<void>({
method: 'PUT',
path: `/documents/${encodeURIComponent(documentId)}/attributes/${encodeURIComponent(key)}`,
body: { value },
});
}
/**
* Delete a specific attribute.
* DELETE /documents/{documentID}/attributes/{key}
*
* @param documentId - The document ID
* @param key - The attribute key to delete
*/
async deleteAttribute(documentId: string, key: string): Promise<void> {
await this.http.request<void>({
method: 'DELETE',
path: `/documents/${encodeURIComponent(documentId)}/attributes/${encodeURIComponent(key)}`,
});
}
/**
* Apply transformations (scaling/offset) to a document.
* PUT /documents/{documentID}/transformations
*
* @param documentId - The document ID
* @param transformation - Transformation parameters
* @returns The updated document
*/
async applyTransformation(
documentId: string,
transformation: DocumentTransformation
): Promise<Document> {
return this.http.request<Document>({
method: 'PUT',
path: `/documents/${encodeURIComponent(documentId)}/transformations`,
body: transformation,
});
}
/**
* Revert transformations to original document.
* DELETE /documents/{documentID}/transformations
*
* @param documentId - The document ID
* @returns The updated document
*/
async revertTransformation(documentId: string): Promise<Document> {
return this.http.request<Document>({
method: 'DELETE',
path: `/documents/${encodeURIComponent(documentId)}/transformations`,
});
}
/**
* Add a cover page to a document.
* PUT /documents/{documentID}/coverpage
*
* @param documentId - The document ID
* @param options - Cover page options with base64-encoded PDF
* @returns The updated document
*/
async addCoverPage(documentId: string, options: CoverPageOptions): Promise<Document> {
return this.http.request<Document>({
method: 'PUT',
path: `/documents/${encodeURIComponent(documentId)}/coverpage`,
body: options,
});
}
/**
* Remove the cover page from a document.
* DELETE /documents/{documentID}/coverpage
*
* @param documentId - The document ID
* @returns The updated document
*/
async removeCoverPage(documentId: string): Promise<Document> {
return this.http.request<Document>({
method: 'DELETE',
path: `/documents/${encodeURIComponent(documentId)}/coverpage`,
});
}
/**
* Get PDF preview of a document.
* GET /documents/{documentID}/pdf
*
* @param documentId - The document ID
* @returns Response containing PDF data
*/
async getPdf(documentId: string): Promise<Response> {
return this.http.requestRaw({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}/pdf`,
});
}
/**
* Get PNG preview of a document (first page).
* GET /documents/{documentID}/png
*
* @param documentId - The document ID
* @returns Response containing PNG data
*/
async getPng(documentId: string): Promise<Response> {
return this.http.requestRaw({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}/png`,
});
}
/**
* Get attachments for a document.
* GET /documents/{documentID}/attachments
*
* @param documentId - The document ID
* @returns Array of attachment IDs
*/
async getAttachments(documentId: string): Promise<string[]> {
return this.http.request<string[]>({
method: 'GET',
path: `/documents/${encodeURIComponent(documentId)}/attachments`,
});
}
/**
* Add an attachment to a document.
* POST /documents/{documentID}/attachments
*
* @param documentId - The document ID
* @param attachmentId - The attachment ID to add
*/
async addAttachment(documentId: string, attachmentId: string): Promise<void> {
await this.http.request<void>({
method: 'POST',
path: `/documents/${encodeURIComponent(documentId)}/attachments`,
body: { attachmentId },
});
}
/**
* Update attachments for a document (replace all).
* PATCH /documents/{documentID}/attachments
*
* @param documentId - The document ID
* @param attachmentIds - Array of attachment IDs
*/
async updateAttachments(documentId: string, attachmentIds: string[]): Promise<void> {
await this.http.request<void>({
method: 'PATCH',
path: `/documents/${encodeURIComponent(documentId)}/attachments`,
body: { attachmentIds },
});
}
/**
* Remove all attachments from a document.
* DELETE /documents/{documentID}/attachments
*
* @param documentId - The document ID
*/
async removeAttachments(documentId: string): Promise<void> {
await this.http.request<void>({
method: 'DELETE',
path: `/documents/${encodeURIComponent(documentId)}/attachments`,
});
}
}