/** * Documents resource * * CRUD operations for documents. File upload/download operations are handled separately. * All documents are created with pending_upload status. */ import type { AtomicBatchResult, ConfirmUploadInput, CreateDocumentInput, DocumentFilter, DocumentSort, ListOptions, OcrStructuredResult, PaginatedResponse, PartialBatchResult, PresignedDownloadUrl, PresignedUploadUrl, UpdateDocumentInput, UpsertDocumentInput, UpsertResult } from "@artu-ai/shared"; import { Document, type DocumentData } from "../models/Document"; import { type ProgressCallback, type UploadableFile } from "../utils/upload"; import { BaseResource } from "./base"; export interface BatchOptions { atomic?: boolean; } /** * Result of creating a document. * * - With contentType: Returns document + presigned upload URL * - Without contentType: Returns document only (no-file document) */ export interface CreateDocumentResult { /** The created document */ document: Document; /** Presigned URL for uploading the file (only for file uploads) */ upload?: PresignedUploadUrl; } /** * Input for uploading a document file. * Combines document metadata with upload options. */ export type UploadDocumentInput = Omit & { /** Optional content type override. If not provided, will be inferred from file. */ contentType?: string; }; /** * Options for the upload operation. */ export interface UploadOptions { /** * Progress callback, called with a value from 0 to 1. * Only works in browser environments. */ onProgress?: ProgressCallback; /** * AbortSignal for cancelling the upload. * @example * const controller = new AbortController(); * sdk.documents.upload(file, data, { signal: controller.signal }); * // Later: controller.abort(); */ signal?: AbortSignal; /** * Upload timeout in milliseconds. * @default 300000 (5 minutes) */ timeout?: number; } /** * Documents resource for managing document records. * * @example * ```typescript * // Create a document record * const doc = await sdk.documents.create({ * clientId: "client_123", * scope: "MX", * type: "ine_front", * category: "identity", * }); * * // List documents * const docs = await sdk.documents.list({ filter: { clientId: "client_123" } }); * * // Update document metadata * await sdk.documents.update(doc.id, { metadata: { verified: true } }); * ``` */ export declare class DocumentsResource extends BaseResource { /** * Creates a new document record and returns a presigned POST upload URL. * * Documents are created with status "pending_upload". * Use the returned presigned URL to upload the file directly to storage. * After upload, call `confirmUpload()` to transition status to "uploaded". * * **Important:** The upload uses POST with FormData (not PUT). This allows * enforcing a 25 MB file size limit at the storage level. * * @param data - Document data * @returns The created document and presigned upload URL with form fields * * @example * ```typescript * // Create document and get upload URL * const { document, upload } = await compliance.documents.create({ * clientId: "client-123", * scope: "MX", * type: "ine_front", * }); * * // Upload file directly via PUT * const uploadResponse = await fetch(upload.url, { * method: "PUT", * body: file, * headers: { "Content-Type": file.type }, * }); * * if (!uploadResponse.ok) { * throw new Error(`Upload failed: ${uploadResponse.status}`); * } * * // Confirm upload with file metadata * await compliance.documents.confirmUpload({ * id: document.id, * filename: file.name, * contentType: file.type, * size: file.size, * }); * ``` */ create(data: CreateDocumentInput): Promise; /** * Confirms that a file has been uploaded to storage. * * Call this after uploading a file via the presigned URL returned by `create()`. * This transitions the document status from "pending_upload" to "uploaded". * * @param data - Upload confirmation data * @returns The updated document */ confirmUpload(data: ConfirmUploadInput): Promise; /** * Uploads a file and creates a document in one operation. * * This is a convenience method that combines: * 1. Creating a document record with presigned URL * 2. Uploading the file directly to storage * 3. Confirming the upload * * For more control over the upload process, use `create()`, manual upload, * and `confirmUpload()` separately. * * @param file - The file to upload (File, Blob, or UploadableFile) * @param data - Document metadata (clientId, scope, type, etc.) * @param options - Upload options (progress callback, abort signal, timeout) * @returns The created and uploaded document * * @example * ```typescript * // Simple upload * const document = await sdk.documents.upload(file, { * clientId: "client-123", * scope: "MX", * type: "ine_front", * }); * * // With progress tracking * const document = await sdk.documents.upload(file, { * clientId: "client-123", * scope: "MX", * type: "ine_front", * }, { * onProgress: (progress) => { * console.log(`Upload: ${Math.round(progress * 100)}%`); * }, * }); * * // With abort support * const controller = new AbortController(); * const uploadPromise = sdk.documents.upload(file, data, { * signal: controller.signal, * }); * * // Cancel the upload * controller.abort(); * ``` * * @throws {FileTooLargeError} If file exceeds 25 MB limit * @throws {UploadError} If upload fails at any stage * @throws {UploadAbortedError} If upload is cancelled via AbortSignal * @throws {UploadTimeoutError} If upload times out */ upload(file: File | Blob | UploadableFile, data: UploadDocumentInput, options?: UploadOptions): Promise; /** * Regenerates a presigned upload URL for a document record that is * stuck in `pending_upload` after a failed upload attempt. * * The record retains its original `storagePath` and `contentType`, so * the new URL is signed against the same path with the same content-type * restriction. After re-uploading, call `confirmUpload()` as usual. * * @param id - Document ID (must be in `pending_upload` status) * @returns Fresh presigned upload URL with expiration */ getUploadUrl(id: string): Promise; /** * Retries a failed upload for a document record stuck in `pending_upload`. * * Combines: * 1. Regenerating the presigned upload URL (`getUploadUrl`) * 2. Uploading the file directly to storage * 3. Confirming the upload * * Use this when the original `upload()` call failed at the storage PUT * step (network error, S3 throttling, etc.) but the document record was * created successfully. * * @param id - Document ID (must be in `pending_upload` status) * @param file - The file to upload (must match the original contentType) * @param options - Upload options (progress callback, abort signal, timeout) * @returns The confirmed document record * * @throws {UploadError} If upload fails at any stage * @throws {UploadAbortedError} If upload is cancelled via AbortSignal * @throws {UploadTimeoutError} If upload times out */ retryUpload(id: string, file: File | Blob | UploadableFile, options?: UploadOptions): Promise; /** * Gets a presigned URL for downloading/viewing a document. * * The URL allows direct access to the file in storage without going through the backend. * Use this for displaying documents in the UI or downloading them. * * @param id - Document ID * @returns Presigned download URL with expiration * @throws If document doesn't exist, is in pending_upload status, or has no storage path * * @example * ```typescript * // Get download URL for viewing * const { url, expiresAt } = await compliance.documents.getDownloadUrl(docId); * * // Display in browser * window.open(url, '_blank'); * * // Or use in an img tag * Document * ``` */ getDownloadUrl(id: string): Promise; /** * Retrieves a document by ID * * @param id - Document ID * @returns The document */ retrieve(id: string): Promise; /** * Retrieves a document by metadata key/value. */ retrieveByMetadata(key: string, value: string): Promise; /** * Retrieves a document by externalId */ retrieveByExternalId(externalId: string): Promise; /** * Updates a document's metadata, expiration, or fields. * * Note: Status cannot be updated directly - it's managed by the system. * * @param id - Document ID * @param data - Fields to update * @returns The updated document */ update(id: string, data: UpdateDocumentInput): Promise; /** * Soft-deletes a document * * @param id - Document ID */ delete(id: string): Promise; /** * Submit an uploaded document for officer review. * Transitions: uploaded → pending_review. * * @param id - Document ID * @returns The updated document */ submit(id: string): Promise; /** * Verify a pending_review document. * Transitions: pending_review → verified. * * @param id - Document ID * @returns The updated document */ verify(id: string): Promise; /** * Reject a pending_review document with a reason. * Transitions: pending_review → rejected. * * @param id - Document ID * @param input - Rejection details * @returns The updated document */ reject(id: string, input: { reason: string; }): Promise; /** * Extract structured fields from a document via the OCR pipeline. * Status transitions: uploaded → processing → pending_review on success. * * @param documentId - Document ID * @returns Structured OCR extraction result */ extract(documentId: string): Promise; /** * Lists documents with optional filtering and pagination. * * @param options - List options with filter, cursor, and limit * @returns Paginated list of documents */ list(options?: ListOptions): Promise>; /** * Lists all documents for a specific client with pagination. * * @param clientId - Client ID * @param options - Pagination options * @returns Paginated list of documents */ listByClient(clientId: string, options?: { cursor?: string; limit?: number; }): Promise>; /** * Counts documents matching the given filter. * * @param filter - Optional filter criteria * @returns The count of matching documents */ count(filter?: DocumentFilter): Promise; /** * Async iterator for documents with optional filtering. * * @param options - Filter options * @yields Documents one at a time */ iterate(options?: ListOptions): AsyncGenerator; /** * Creates multiple document records in a batch. * * @param documents - Array of document creation data * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed */ createMany(documents: CreateDocumentInput[], options?: { atomic?: true; }): Promise>; createMany(documents: CreateDocumentInput[], options: { atomic: false; }): Promise>; /** * Updates multiple documents in a batch. * * @param updates - Array of { id, data } objects * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed */ updateMany(updates: { id: string; data: UpdateDocumentInput; }[], options?: { atomic?: true; }): Promise>; updateMany(updates: { id: string; data: UpdateDocumentInput; }[], options: { atomic: false; }): Promise>; /** * Creates or updates a document by externalId. * * If a document with the given externalId exists, it will be updated. * Otherwise, a new document will be created. * * @param data - Document upsert data (must include externalId and clientId) * @returns The created or updated document with action indicator */ upsertByExternalId(data: UpsertDocumentInput): Promise>; /** * Creates or updates multiple documents by externalId in a single request. * * @param documents - Array of document upsert data (each must include externalId and clientId) * @param options - Batch options (atomic: true by default) * @returns Atomic result with data array, or partial result with succeeded/failed */ upsertManyByExternalId(documents: UpsertDocumentInput[], options?: { atomic?: true; }): Promise>>; upsertManyByExternalId(documents: UpsertDocumentInput[], options: { atomic: false; }): Promise>>; /** * Gets document count for a client * * @param clientId - Client ID * @returns Count of documents */ countByClient(clientId: string): Promise; /** * Checks if a document with the given type exists for a client * * @param clientId - Client ID * @param type - Document type * @returns True if document exists and is available */ hasDocumentType(clientId: string, type: string): Promise; /** * Instantiates a Document model from raw data. * Override in subclasses to return scope-specific models. */ protected instantiate(data: DocumentData): Document; }