@space-operator/ocr (0.1.2)

Published 2026-09-24 00:51:29 +00:00 by amir

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: OcrProvider is the contract; Mistral is the default via mistralProvider().
  • 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 dataDir would double-run work. Use one instance per data directory.
  • Node floor. node:sqlite requires Node 22.13+ and may print an experimental warning.
  • Poppler. Vision correction and renderPageImage fail without pdftoppm; set pdftoppmPath if it is not on PATH.
  • 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

Keywords

ocr document-ai pdf pipeline mistral
Details
npm
2026-09-24 00:51:29 +00:00
0
latest
39 KiB
Assets (1)
ocr-0.1.2.tgz 39 KiB
Versions (3) View all
0.1.2 2026-09-24
0.1.1 2026-09-23
0.1.0 2026-09-23