@msflib/react-documents¶
Standalone documents module built on TanStack Query.
Install¶
pnpm add @msflib/react-documents
Peer deps¶
- react >= 18
- @tanstack/react-query >= 5
Usage¶
Wrap your app with the module Provider inside your QueryClientProvider.
In Next.js App Router, Provider must be in a client component (
"use client").
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { DocumentsProvider, useDocuments } from '@msflib/react-documents';
const queryClient = new QueryClient();
<QueryClientProvider client={queryClient}>
<DocumentsProvider
options={{ requireAuth: true, isAuthenticated: true, jobsParams: { limit: 20 } }}
>
<DocumentsPanel />
</DocumentsProvider>
</QueryClientProvider>;
function DocumentsPanel() {
const { documents, ingestionStatus, ingestionJobs, uploadDocument, promoteDocument } =
useDocuments();
async function upload(file: File) {
const formData = new FormData();
formData.append('file', file, file.name);
await uploadDocument(formData);
}
return null;
}
Upload → promote flow¶
Uploading a document queues it for ingestion. A record starts private to the uploader (or scoped to a conversation) until it is promoted into the shared workspace corpus.
import { useDocuments } from '@msflib/react-documents';
function UploadForm({ conversationId }: { conversationId: string }) {
const { uploadDocument, promoteDocument } = useDocuments();
async function handleUpload(file: File) {
const formData = new FormData();
formData.append('file', file, file.name);
formData.append('conversation_id', conversationId);
formData.append('is_private', 'true');
const { record_id } = await uploadDocument(formData);
// Later, once the document should be visible workspace-wide:
await promoteDocument(record_id, { clear_private: true });
}
return null;
}
Listing, inspecting and removing documents¶
documents (from the Provider's default list query) reflects
options.listParams (conversation_id, sub_thread_id, status,
include_deleted, limit, offset). Use listDocuments to refetch with
different params, getDocument/useDocument to fetch a single record,
deleteDocument to soft-delete, and downloadDocument to fetch the file
content as a Blob.
import { useDocuments, useDocument } from '@msflib/react-documents';
function DocumentsList() {
const { documents, listDocuments, deleteDocument, downloadDocument } =
useDocuments();
async function refreshFailed() {
await listDocuments({ status: 'failed' });
}
async function removeAndDownload(recordId: number) {
await deleteDocument(recordId);
const blob = await downloadDocument(recordId);
// e.g. trigger a browser download via URL.createObjectURL(blob)
}
return null;
}
function DocumentDetail({ recordId }: { recordId: number }) {
const { data: document } = useDocument(recordId);
return null;
}
Reindexing¶
reindexDocuments re-enqueues in-scope documents for ingestion — useful
after switching a workspace's vector store backend, so already-indexed
documents get re-resolved and re-ingested against the new backend. Pass
purge_old_profile_id to also purge the scope from the previous backend
once re-enqueueing succeeds.
import { useDocuments } from '@msflib/react-documents';
function VectorStoreSwitch() {
const { reindexDocuments } = useDocuments();
async function onSwitch(oldProfileId: number) {
const { scanned, enqueued, errors } = await reindexDocuments({
purge_old_profile_id: oldProfileId,
});
}
return null;
}
Ingestion monitoring¶
ingestionStatus summarizes queue depth and record counts by state.
ingestionJobs lists individual ingestion jobs (with retry/error detail) and
can be filtered by status.
import { useDocuments, useDocumentIngestionJobs } from '@msflib/react-documents';
function IngestionQueue() {
const { ingestionStatus, runWorker } = useDocuments();
const { data } = useDocumentIngestionJobs({ status: 'failed', limit: 50 });
return null;
}
runWorker triggers a batch of pending ingestion jobs to be processed
(useful for manually kicking the worker in admin tooling); it is not
workspace-scoped.
Endpoints¶
Configure paths through configureApplication({ endpoints: { documents } }).
| Key | Default path | Methods / routes |
|---|---|---|
documents |
/documents |
base path used for scoping/matching; GET (list) |
upload |
/documents/upload |
POST (multipart/form-data) |
worker |
/documents/worker/run |
POST |
reindex |
/documents/reindex |
POST |
ingestionStatus |
/documents/ingestion/status |
GET |
ingestionJobs |
/documents/ingestion/jobs |
GET |
Additional routes built off documents:
- ${documents}/{record_id} — GET (fetch one), DELETE (remove)
- ${documents}/{record_id}/download — GET (raw file content)
- ${documents}/{record_id}/promote — POST
Notes:
- listDocuments/documents, getDocument/useDocument, deleteDocument,
downloadDocument, uploadDocument, reindexDocuments,
getIngestionStatus, and getIngestionJobs are workspace-scoped (they
send the workspace header).
- promoteDocument and runWorker are not workspace-scoped, matching the
backend API contract.
- downloadDocument bypasses the JSON API client and fetches the file
directly (with the same auth/workspace headers) to return a Blob.