@space-operator/ocr (0.1.2)
Installation
@space-operator:registry=npm install @space-operator/ocr@0.1.2"@space-operator/ocr": "0.1.2"About this package
@space-operator/ocr
Headless, server-side OCR engine with pluggable model providers. It ingests PDFs, runs chunked OCR jobs with text/vision correction, persists page results in SQLite, and exports markdown/DOCX — without any UI or HTTP layer.
- Pluggable providers:
OcrProvideris the contract; Mistral is the default viamistralProvider(). - Job queue: upload-once, bounded chunks, progress events, cooperative cancellation, per-document locking, and restart recovery.
- Review metadata: low-confidence words, OCR blocks, page confidence, dimensions, and verification results are persisted per page.
- Exports: markdown, DOCX with low-confidence words bolded, and a review DOCX listing unresolved verification issues. All strings are localizable.
Requirements
- Node.js 22.13 or newer (
node:sqlite) - A model provider API key (Mistral by default)
- Poppler (
pdftoppm) for vision correction and page previews
Install
npm install @space-operator/ocr
The package is published to a private Forgejo registry. Consumer projects need
a scoped .npmrc and a token:
@space-operator:registry=https://registry.spaceoperator.org/api/packages/amir/npm/
//registry.spaceoperator.org/api/packages/amir/npm/:_authToken=${FORGEJO_TOKEN}
Quick start
import { createOcrEngine, mistralProvider } from "@space-operator/ocr";
const engine = createOcrEngine({
dataDir: "./data/ocr", // ocr.sqlite, uploads/, and the page cache live here
provider: mistralProvider({ apiKey: process.env.MISTRAL_API_KEY! }),
// Optional: chunkSize, visionFixMaxPasses, maxUploadMb, maxPdfPages,
// pdftoppmPath, exportLabels, scheduler, logger, onEvent
});
const document = await engine.ingestPdf(pdfBytes, {
fileName: "report.pdf",
mimeType: "application/pdf",
});
const jobId = engine.enqueueJob({
documentId: document.id,
type: "ocr",
selectedPages: Array.from(
{ length: document.pageCount },
(_, index) => index,
),
});
// Standalone processes run the job on setImmediate. Next.js apps pass
// `scheduler: (work) => after(work)` to run it after the HTTP response.
await engine.runJob(jobId); // useful in tests and CLIs
const pages = engine.getPages(document.id);
const markdown = engine.exportMarkdown(document.id);
const docx = await engine.exportDocx(document.id);
Providers
The engine only talks to OcrProvider. Mistral ships as the default adapter:
import { mistralProvider } from "@space-operator/ocr";
mistralProvider({
apiKey: process.env.MISTRAL_API_KEY!,
ocrModel: "mistral-ocr-4-1",
fixModel: "mistral-small-latest",
visionFixModel: "mistral-medium-3-5",
lowConfidenceThreshold: 0.85,
defaultFixPrompt: "…",
correctionMaxTokens: 4_096,
timeoutMs: 90_000,
retryConfig: { strategy: "backoff", retryConnectionErrors: true, backoff: { … } },
});
Another provider implements the same contract and is passed through
createOcrEngine({ provider }):
type OcrProviderCapability =
| "managed-upload" | "remote-sources" | "structured-output"
| "text-fix" | "vision-fix" | "vision-verify";
interface OcrProvider {
readonly id: string;
readonly name: string;
readonly capabilities: readonly OcrProviderCapability[];
uploadDocument?(filePath: string, fileName: string, operation?): Promise<string>;
deleteDocument?(fileId: string, operation?): Promise<void>;
processPages(input: {
fileId?: string;
source?: OcrDocumentSource;
pages: number[];
ocr?: OcrRequestOptions;
signal?: AbortSignal;
}): Promise<OcrPageResult[]>;
fixText?(markdown: string, prompt: string, operation?): Promise<string>;
fixVision?(pageImageDataUrl: string, markdown: string, prompt: string, operation?): Promise<string>;
verifyVision?(pageImageDataUrl: string, markdown: string, prompt: string, operation?): Promise<VisionVerificationResult>;
dispose?(): Promise<void>;
}
Capability flags gate the runner: a prompt fix requires text-fix, a vision
fix requires vision-fix, and the verification loop only runs when
vision-verify is present. Providers without managed-upload receive the PDF
as a bytes source instead.
providerFromPipelineClient(client, info?) adapts any
@space-operator/mistral-ocr-pipeline client (or a test fake) to this
contract.
API
| Method | Purpose |
|---|---|
ingestPdf(bytes, { fileName, mimeType? }) |
Validate, store, and register a PDF |
enqueueJob({ documentId, type, selectedPages, prompt?, fixMode? }) |
Queue OCR/redo work |
cancelJob(jobId) / runJob(jobId, overrides?) |
Cooperative cancel / run inline |
listDocuments, getDocument, getDocumentDetail, getDocumentsStats |
Read models |
getPages, getPageRevisions, getJob, getJobsForDocument, listRecentJobs |
Page and job history |
updatePageMarkdown({ documentId, pageIndex, markdown }) |
Manual edit + revision |
exportMarkdown, exportDocx, exportReviewDocx |
Exports (string/Buffer) |
renderPageImage(documentId, pageIndex) |
Cached PNG for previews/overlays |
recoverInterruptedJobs() |
Fail interrupted jobs; call once at startup |
close() |
Close the store |
Events (onEvent) are best-effort and useful for progress UIs:
job.queued, job.running, job.progress, job.succeeded, job.failed,
job.cancelled, page.updated, document.updated. Polling the store works
too.
Env helper
ocrEngineOptionsFromEnv() reads conventional OCR_* / MISTRAL_*
variables. The package never reads the environment on its own.
Data layout
<dataDir>/
ocr.sqlite documents, pages, ocr_jobs, page_revisions
uploads/ stored source PDFs
uploads/.page-cache/ rendered page PNGs
The schema matches the Document Tools app, so existing databases open without a migration.
Operational notes
- Single process. Jobs run in-process; two instances sharing one
dataDirwould double-run work. Use one instance per data directory. - Node floor.
node:sqliterequires Node 22.13+ and may print an experimental warning. - Poppler. Vision correction and
renderPageImagefail withoutpdftoppm; setpdftoppmPathif it is not onPATH. - Cleanup. Uploaded provider files are deleted after success, failure, or cancellation.
Development
From the repository root:
npm run packages:build # build both packages
npm run packages:test # package test suites
npm run packages:pack # tarballs for token-free installs
Dependencies
Dependencies
| ID | Version |
|---|---|
| @space-operator/mistral-ocr-pipeline | ^0.4.0 |
| docx | ^9.6.1 |
| pdf-lib | ^1.17.1 |
Development dependencies
| ID | Version |
|---|---|
| @types/node | ^20 |
| typescript | ^5 |