API Reference
Hooks, providers, and core APIs.
API Reference
Packages: `@fortemi/core` · `@fortemi/graph` · `@fortemi/react` Version: 2026.9.9
Table of Contents
- @fortemi/core
- Core Utilities
- Types
- Dataset Execution
- Event Bus
- Capability Manager
- Repositories
- NotesRepository
- SearchRepository
- TagsRepository
- CollectionsRepository
- LinksRepository
- SkosRepository
- AttachmentsRepository
- EmbeddingSetsRepository
- GraphRepository
- CommunitiesRepository
- Repository Types
- Tool Functions
- Job Queue
- Capabilities
- Migrations and Archive
- Canonical Records
- Knowledge Shards
- Service Worker
- Worker Utilities
- AIWG Index (@fortemi/core/aiwg-index)
- @fortemi/graph
- Graph Data Model
- Graph Helpers
- Render Pipeline
- Control Contract
- SVG Renderer
- GraphController
- @fortemi/react
- Dataset Workflow
- Provider
- Hooks
- Graph and community hooks
- Graph Views and Subpath Exports
@fortemi/core
Core Utilities
`VERSION`
const VERSION: string
The current package version string. Value: `'2026.9.9'`.
`generateId()`
function generateId(): string
Generates a UUIDv7 identifier. UUIDv7 values are time-ordered, making them suitable as primary keys in sorted indexes.
Returns: A UUIDv7 string, e.g. `'018f1e2d-3b4c-7a5d-8e9f-0a1b2c3d4e5f'`.
`computeHash(data)`
function computeHash(data: Uint8Array): string
Computes a SHA-256 digest of the provided binary data.
| Parameter | Type | Description |
|---|---|---|
| `data` | `Uint8Array` | Raw bytes to hash |
Returns: A prefixed hex string in the form `'sha256:<hex>'`.
`createPGliteInstance(persistence, archiveName)`
function createPGliteInstance(
persistence: PersistenceMode,
archiveName: string
): Promise<PGlite>
Creates and initializes a PGlite database instance using the specified storage backend.
| Parameter | Type | Description |
|---|---|---|
| `persistence` | `PersistenceMode` | Storage backend: `'opfs'`, `'idb'`, or `'memory'` |
| `archiveName` | `string` | Logical name for the archive; used to scope the on-disk path |
Returns: A `Promise` that resolves to an initialized `PGlite` instance.
`createFortemi(config)`
function createFortemi(config: FortemiConfig): FortemiCore
Lightweight factory for the non-React runtime shell. It creates the shared `TypedEventBus`, stores the provided config, and exposes a `destroy()` cleanup hook. Use `ArchiveManager.open()` to create the PGlite database before constructing repositories.
| Parameter | Type | Description |
|---|---|---|
| `config` | `FortemiConfig` | Configuration object (see FortemiConfig) |
Returns: A `FortemiCore` instance.
`createBlobStore(archiveName)`
function createBlobStore(archiveName: string): BlobStore
Creates a `BlobStore` backed by OPFS when available, falling back to IndexedDB. Used for binary attachment storage.
| Parameter | Type | Description |
|---|---|---|
| `archiveName` | `string` | Archive name used to scope the storage namespace |
Returns: A `BlobStore` instance.
`MemoryBlobStore`
class MemoryBlobStore implements BlobStore {
write(key: string, data: Uint8Array): Promise<void>
read(key: string): Promise<Uint8Array | null>
remove(key: string): Promise<void>
exists(key: string): Promise<boolean>
}
An in-memory implementation of `BlobStore` intended for use in test environments. Data does not persist between instantiations.
Types
`PersistenceMode`
type PersistenceMode = 'opfs' | 'idb' | 'memory'
Controls where PGlite stores data.
| Value | Description |
|---|---|
| `'opfs'` | Origin Private File System — best performance on supported browsers |
| `'idb'` | IndexedDB — broader compatibility fallback |
| `'memory'` | No persistence; suitable for tests |
`BlobStore`
interface BlobStore {
put(bytes: Uint8Array): Promise<string>
read(checksum: string): Promise<Uint8Array | null>
has(checksum: string): Promise<boolean>
delete?(checksum: string): Promise<boolean>
reconcile(liveChecksums: Iterable<string>, opts?: BlobReconcileOptions): Promise<BlobReconcileResult>
gc(opts?: BlobGcOptions): Promise<BlobGcResult>
diagnostics(): Promise<BlobStoreDiagnostics>
close(): Promise<void>
}
Content-addressed binary storage keyed by canonical `blake3:<hex>` checksums. Built-in stores implement the optional `delete` capability for rollback-safe shard sidecar promotion; custom stores without it remain API-compatible but cannot import sidecar bytes atomically.
`FortemiConfig`
interface FortemiConfig {
persistence: PersistenceMode
archiveName?: string
}
Configuration passed to `createFortemi`.
| Property | Type | Required | Description |
|---|---|---|---|
| `persistence` | `PersistenceMode` | Yes | Storage backend |
| `archiveName` | `string` | No | Archive identifier; defaults to a standard name when omitted |
`FortemiCore`
interface FortemiCore {
events: TypedEventBus
config: FortemiConfig
destroy(): void
}
The runtime shell returned by `createFortemi`. React apps usually get the richer `{ db, events, archiveManager, capabilityManager, blobStore }` surface from `FortemiProvider` instead.
Dataset Execution
The dataset APIs are versioned, storage-neutral contracts. Their descriptors, plans, receipts, and lineage records must be bound to the concrete runtime and evidence that produced them; method presence or a successful cache query is not a capability claim.
Capability negotiation
function validateDatasetExecutionDescriptor(
descriptor: DatasetExecutionCapabilityDescriptor,
): DatasetCapabilityDiagnostic[]
function negotiateDatasetExecutionCapabilities(
descriptor: DatasetExecutionCapabilityDescriptor,
request: DatasetCapabilityNegotiationRequest,
): DatasetCapabilityNegotiationResult
`fortemi.dataset-execution-capabilities/v1` distinguishes browser-local archives, static caches, portable shards, server processes, and remote persistence. Required capability/version/limit mismatches return `accepted: false`; optional mismatches return explicit degradation and fallback details. Negotiation is pure and performs no probe, network request, mutation, or fallback execution.
Ingest runs
class DatasetIngestExecutor {
constructor(store: DatasetIngestStore)
executeBatch(
plan: DatasetProcessingPlan,
batch: DatasetMutationBatch,
options?: ExecuteDatasetBatchOptions,
): Promise<DatasetRunReceipt>
resolveAmbiguousCommit(
plan: DatasetProcessingPlan,
batch: DatasetMutationBatch,
): Promise<DatasetRunReceipt | undefined>
}
class MemoryDatasetIngestStore implements DatasetIngestStore {}
`fortemi.dataset-ingest/v1` binds processing plans to source revisions, normalized configuration, transformation profiles, destination scope, rejection policy, and reconciliation limits. Each transaction commits record effects, redacted rejection accounting, checkpoint advancement, and its receipt together. Exact replay returns the stored receipt; an idempotency key reused for different canonical request content fails.
Evidence-bearing lineage
class DatasetLineageLedger {
constructor(options?: DatasetLineageLedgerOptions)
traverse(
request: LineageTraversalRequest,
policy: LineageAuthorizationPolicy,
): LineageTraversalResult
exportArchive(snapshot?: number): LineageLedgerArchive
project(
capabilities: LineageProjectionCapabilities,
snapshot?: number,
): LineageProjection
}
`fortemi.dataset-lineage/v1` is an append-only authority for entities, agents, activities, evidence revisions, assertions, and corrections. Traversal applies authorization before graph expansion, requires explicit depth/result/page bounds, and returns snapshot-bound cursors. External projections are regenerable and include explicit loss receipts; W3C PROV and OpenLineage are adapter targets rather than the native storage schema.
Materialization and retrieval
function negotiateDatasetMaterializationProfile(
request: DatasetProfileNegotiationRequest,
): DatasetProfileNegotiationResult
function executeDatasetMaterialization(
request: DatasetMaterializationRequest,
runtime: DatasetExecutionCapabilityDescriptor,
adapter: DatasetMaterializationAdapter,
authorize: DatasetRecordAuthorizer,
options?: {
fallbackProfiles?: DatasetMaterializationProfile[]
now?: () => string
},
): Promise<{
artifacts: DatasetMaterializationArtifact[]
receipt: DatasetMaterializationReceipt
}>
function executeDatasetRetrieval(
request: DatasetRetrievalRequest,
runtime: DatasetExecutionCapabilityDescriptor,
adapter: DatasetMaterializationAdapter,
receiptId: string,
fallbackProfiles?: DatasetMaterializationProfile[],
): Promise<DatasetRetrievalResponse>
`fortemi.dataset-materialization-profile/v1` defines composable canonical, lexical, vector, hybrid, embedding, graph, and community profiles. Receipts bind source/configuration digests, processing run, profile, implementation/model, freshness, privacy decisions, degradation, and measurements. This contract does not alter Knowledge Shard profiles or establish live-server parity.
See the architecture notes in `docs/architecture/dataset-*.md` and the schemas published from the package's `schemas` exports for the complete wire contracts.
Event Bus
`TypedEventBus`
class TypedEventBus {
on(event: FortemiEvent, handler: EventHandler): () => void
once(event: FortemiEvent, handler: EventHandler): () => void
emit(event: FortemiEvent, payload?: unknown): void
bridge(port: MessagePort): void
}
Typed publish/subscribe bus. All internal subsystems communicate through this bus. `bridge` connects a `MessagePort`, allowing events to be relayed across Worker boundaries.
| Method | Description |
|---|---|
| `on(event, handler)` | Subscribe to an event. Returns an unsubscribe function. |
| `once(event, handler)` | Subscribe for a single emission, then auto-unsubscribe. Returns an unsubscribe function. |
| `emit(event, payload?)` | Publish an event with an optional payload. |
| `bridge(port)` | Forward all events bidirectionally over a `MessagePort`. |
Event names:
| Event | When emitted |
|---|---|
| `note.created` | A note was successfully persisted |
| `note.updated` | A note's content or metadata changed |
| `note.deleted` | A note was soft-deleted |
| `note.restored` | A soft-deleted note was restored |
| `job.completed` | A background job finished successfully |
| `job.failed` | A background job terminated with an error |
| `capability.ready` | A capability finished loading and is available |
| `capability.disabled` | A capability was explicitly disabled |
| `capability.loading` | A capability loader started |
Capability Manager
`CapabilityManager`
class CapabilityManager {
enable(name: CapabilityName): Promise<void>
disable(name: CapabilityName): void
isReady(name: CapabilityName): boolean
getState(name: CapabilityName): CapabilityState
registerLoader(name: CapabilityName, fn: () => Promise<void>): void
getError(name: CapabilityName): Error | null
getProgress(name: CapabilityName): string | null
setProgress(name: CapabilityName, msg: string): void
listAll(): Array<{ name: CapabilityName; state: CapabilityState }>
}
Manages optional runtime capabilities (ML models, hardware features). Each capability has an independently tracked lifecycle.
| Method | Description |
|---|---|
| `enable(name)` | Invoke the registered loader for the capability. Resolves when ready. Emits `capability.loading` then `capability.ready`. |
| `disable(name)` | Unload a capability and mark it disabled. Emits `capability.disabled`. |
| `isReady(name)` | Returns `true` if the capability state is `'ready'`. |
| `getState(name)` | Returns the current `CapabilityState` for the capability. |
| `registerLoader(name, fn)` | Register the async function that initializes the capability. Must be called before `enable`. |
| `getError(name)` | Returns the `Error` that caused the last load failure, or `null`. |
| `getProgress(name)` | Returns the current progress message string, or `null`. |
| `setProgress(name, msg)` | Update the progress message during a long-running load. |
| `listAll()` | Returns an array of all registered capabilities and their states. |
`CapabilityName`
type CapabilityName = 'semantic' | 'llm' | 'audio' | 'vision' | 'pdf'
| Value | Description |
|---|---|
| `'semantic'` | Embedding model for semantic search |
| `'llm'` | Language model for title generation and tagging |
| `'audio'` | Audio transcription |
| `'vision'` | Image understanding |
| `'pdf'` | PDF text extraction |
`CapabilityState`
type CapabilityState = 'unloaded' | 'loading' | 'ready' | 'error' | 'disabled'
| Value | Description |
|---|---|
| `'unloaded'` | Loader registered but not yet started |
| `'loading'` | Loader is running |
| `'ready'` | Capability is available for use |
| `'error'` | Loader failed; inspect via `getError()` |
| `'disabled'` | Explicitly disabled via `disable()` |
Repositories
All repositories share a common constructor signature unless noted:
constructor(db: PGlite, events?: TypedEventBus)
`events` is optional. When provided, state-changing operations emit the corresponding events on the bus.
`NotesRepository`
class NotesRepository {
constructor(db: PGlite, events?: TypedEventBus)
create(input: NoteCreateInput): Promise<NoteFull>
get(id: string): Promise<NoteFull | null>
list(options?: NoteListOptions): Promise<PaginatedResult<NoteSummary>>
update(id: string, input: NoteUpdateInput): Promise<NoteFull>
delete(id: string): Promise<void>
restore(id: string): Promise<void>
star(id: string, starred: boolean): Promise<void>
pin(id: string, pinned: boolean): Promise<void>
archive(id: string, archived: boolean): Promise<void>
addTags(id: string, tags: string[]): Promise<void>
removeTags(id: string, tags: string[]): Promise<void>
getRevisions(id: string): Promise<NoteRevision[]>
getOriginalHistory(id: string): Promise<OriginalContentRevision[]>
}
| Method | Description |
|---|---|
| `create(input)` | Insert a new note. Emits `note.created`. |
| `get(id)` | Retrieve a single note by ID including full content and tags. Returns `null` if not found. |
| `list(options?)` | Retrieve a paginated, filtered list of note summaries. |
| `update(id, input)` | Apply partial updates to a note. Creates a revision snapshot. Emits `note.updated`. |
| `delete(id)` | Soft-delete a note. Emits `note.deleted`. |
| `restore(id)` | Undo a soft-delete. Emits `note.restored`. |
| `star(id, starred)` | Set the starred flag. |
| `pin(id, pinned)` | Set the pinned flag. |
| `archive(id, archived)` | Set the archived flag. |
| `addTags(id, tags)` | Append tags to a note without duplicates. |
| `removeTags(id, tags)` | Remove specific tags from a note. |
| `getRevisions(id)` | Return the ordered revision history for a note. |
| `getOriginalHistory(id)` | Return user-authored original history, newest version first. |
Native history reads include original version/user timestamps, revision parent, summary/rationale, generation/user-edit fields and current `last_revision_id`. `NoteFull.original.id` may be null; original identity is scoped by note owner.
`NoteFull.metadata` and native-backend `BackendNoteFull.metadata` expose note metadata separately from `current.ai_metadata`. `NoteCreateInput.metadata` and `NoteUpdateInput.metadata` accept JSON values, including null, arrays and scalar values. Explicit metadata writes and source imports remain independent of AI revision changes. Legacy current-metadata writers retain their prior projection until independent note metadata is authored. Registered metadata predicates use the note field. This does not complete public full-v1 restore/export acceptance. `OriginalContentRevision` is exported by Core and preserves scalar timestamp precision. The public `2.0.0/full-v1` dispatcher restores this native state; released and cross-runtime qualification remains tracked in #424.
`SearchRepository`
class SearchRepository {
constructor(db: PGlite, semanticAvailable?: boolean)
search(query: string, options?: SearchOptions, queryEmbedding?: number[]): Promise<SearchResponse>
semanticSearch(queryEmbedding: number[], options?: SearchOptions): Promise<SearchResponse>
hybridSearch(query: string, queryEmbedding: number[], options?: SearchOptions): Promise<SearchResponse>
}
| Method | Description |
|---|---|
| `search(query, options?, queryEmbedding?)` | Main entry point. Dispatches to text, semantic, or hybrid based on inputs. Empty query returns recent notes. Quoted phrases use `phraseto_tsquery`. |
| `semanticSearch(queryEmbedding, options?)` | Pure vector similarity search via pgvector cosine distance. |
| `hybridSearch(query, queryEmbedding, options?)` | Combines BM25 text ranking and vector similarity using Reciprocal Rank Fusion (k=60). |
Tenant, archive, deletion, and metadata predicates are applied before ranking for text, semantic, hybrid, and recent-note modes. Result locators expose source namespace, schema/import-run metadata, and an external-id hash; they do not return raw external source keys.
The `search()` method routing logic:
- Query text + embedding = hybrid (RRF fusion)
- Embedding only (empty query) = semantic (vector cosine)
- Query text without embedding = text (BM25 tsvector)
- Empty query, no embedding = recent notes (ordered by created_at DESC)
`buildNoteConditions()`
function buildNoteConditions(
options: Pick<SearchOptions, 'tags' | 'collection_id' | 'date_from' | 'date_to' | 'is_starred' | 'is_archived' | 'format' | 'source' | 'visibility'>,
startIdx: number,
includeDeleted?: boolean,
): { conditions: string[]; params: unknown[]; nextIdx: number }
Shared SQL condition builder used by both `SearchRepository` and `NotesRepository`. Generates parameterized WHERE clause conditions for all filter fields.
`TagsRepository`
class TagsRepository {
constructor(db: DatabaseClient)
addTag(noteId: string, tag: string): Promise<void>
removeTag(noteId: string, tag: string): Promise<void>
getTagsForNote(noteId: string): Promise<string[]>
getNotesForTag(tag: string): Promise<string[]>
listAllTags(): Promise<Array<{ tag: string; count: number }>>
listRecords(): Promise<TagRecord[]>
}
| Method | Description |
|---|---|
| `addTag` / `removeTag` | Add or remove a note membership. Adding declares the native tag atomically. |
| `getTagsForNote` / `getNotesForTag` | Query native membership in either direction. |
| `listAllTags()` | Return tags used by notes with usage counts. |
| `listRecords()` | Return declared tag records and exact creation timestamps, including unused tags. |
`CollectionsRepository`
class CollectionsRepository {
constructor(db: DatabaseClient)
create(input: { name: string; description?: string }): Promise<CollectionRow>
get(id: string): Promise<CollectionRow>
getRecord(id: string): Promise<CollectionRecord | null>
list(): Promise<CollectionRow[]>
update(id: string, input: { name?: string; description?: string }): Promise<CollectionRow>
delete(id: string): Promise<void>
assignNote(collectionId: string, noteId: string): Promise<void>
unassignNote(collectionId: string, noteId: string): Promise<void>
}
| Method | Description |
|---|---|
| `create(input)` | Create a new named collection. |
| `get(id)` | Retrieve an active collection by ID; throws if not found. |
| `getRecord(id)` | Read the full-v1 record with precise timestamp and imported snapshot count, or null if absent. |
| `list()` | List all collections. |
| `update(id, input)` | Update name or description. |
| `delete(id)` | Delete a collection. Does not delete member notes. |
| `assignNote(collectionId, noteId)` | Add membership; an existing membership is unchanged. |
| `unassignNote(collectionId, noteId)` | Remove membership. Membership mutations invalidate imported snapshot counts. |
`LinksRepository`
class LinksRepository {
constructor(db: DatabaseClient)
create(sourceNoteId: string, targetNoteId: string, linkType?: string): Promise<LinkRow>
get(id: string): Promise<LinkRow>
getRecord(id: string): Promise<LinkRecord | null>
listForNote(noteId: string): Promise<{ outbound: LinkRow[]; inbound: LinkRow[] }>
getBacklinks(noteId: string): Promise<string[]>
delete(id: string): Promise<void>
}
| Method | Description |
|---|---|
| `create(sourceNoteId, targetNoteId, linkType?)` | Create a note-target link, reusing an existing active pair/type. |
| `get(id)` | Retrieve a note-target link; throws if absent. |
| `getRecord(id)` | Read either target kind with full-v1 metadata/score/timestamp; rejects unrepresentable values such as a null score. |
| `listForNote` / `getBacklinks` | Read directional note-target relationships. |
| `delete(id)` | Soft-delete either target kind atomically. |
`TemplatesRepository`
class TemplatesRepository {
constructor(db: DatabaseClient)
create(input: TemplateCreateInput): Promise<TemplateRecord>
get(id: string): Promise<TemplateRecord>
list(): Promise<TemplateRecord[]>
update(id: string, input: Partial<TemplateCreateInput>): Promise<TemplateRecord>
delete(id: string): Promise<void>
}
`TemplateCreateInput` requires `name` and `content`; optional fields are `description`, `format`, `default_tags` and `collection_id`. Records preserve native identities, ordered default tags and precise timestamps. Invalid records reject before mutation. `get` throws when absent; deletion removes the record.
`SkosRepository`
class SkosRepository {
constructor(db: DatabaseClient)
createScheme(title: string, description?: string): Promise<SkosScheme>
createConcept(schemeId: string, prefLabel: string, options?: { altLabels?: string[]; definition?: string }): Promise<SkosConcept>
createRelation(sourceConceptId: string, targetConceptId: string, relationType: 'broader' | 'narrower' | 'related'): Promise<SkosRelation>
tagNote(noteId: string, conceptId: string): Promise<NoteSkosTag>
untagNote(noteId: string, conceptId: string): Promise<void>
conceptsForNote(noteId: string): Promise<SkosConcept[]>
listSchemes(): Promise<SkosScheme[]>
listConcepts(schemeId: string): Promise<SkosConcept[]>
getRelations(conceptId: string): Promise<SkosRelation[]>
deleteScheme(id: string): Promise<void>
deleteConcept(id: string): Promise<void>
getSchemeRecord(id: string): Promise<SkosSchemeRecord | null>
getConceptRecord(id: string): Promise<SkosConceptRecord | null>
getLabels(conceptId: string): Promise<SkosLabel[]>
getNotes(conceptId: string): Promise<SkosNote[]>
getMappings(conceptId: string): Promise<SkosMapping[]>
getSchemeMemberships(conceptId: string): Promise<SkosMembership[]>
getAssignments(noteId: string): Promise<NoteSkosAssignment[]>
listCollections(schemeId?: string): Promise<SkosCollection[]>
getCollectionMembers(collectionId: string): Promise<SkosCollectionMember[]>
}
Native records preserve rich scheme/concept fields, multilingual labels and notes, semantic relations, external mappings, memberships, assignments and collections. Rich record methods return timestamp strings with retained source precision; existing display methods keep their `Date` timestamp contract. Collection members sort by position, with null positions last. Scheme/concept record reads exclude soft-deleted rows.
Concept creation writes English labels, an optional definition and a primary scheme membership transactionally. Label/note edits update flattened display fields, which are projections rather than rich record authority. These native reads participate in public full-v1 native restoration; released qualification remains tracked in #424.
| Method | Description |
|---|---|
| `createScheme(title, description)` | Create a scheme; its initial notation is its generated ID. |
| `createConcept(schemeId, prefLabel, options)` | Create a concept and native labels, definition and membership. |
| `createRelation(sourceId, targetId, type)` | Assert a `'broader'`, `'narrower'`, or `'related'` relation. |
| `tagNote(noteId, conceptId)` | Attach a SKOS concept to a note idempotently. |
| `untagNote(noteId, conceptId)` | Remove a note-to-concept assignment. |
| `conceptsForNote(noteId)` | Read active SKOS concepts assigned to a note. |
| `getSchemeRecord(id)` | Retrieve a rich active scheme record by ID. |
| `getConceptRecord(id)` | Retrieve a rich active concept record by ID. |
| `listConcepts(schemeId)` | List all concepts belonging to a scheme. |
`ProvenanceRepository`
class ProvenanceRepository {
constructor(db: PGlite)
recordProvenance(
entityType: string,
entityId: string,
input: {
activity: string
agent: string | null
startedAt?: Date | string
endedAt?: Date | string | null
attributes?: unknown
}
): Promise<ProvenanceEdge>
forEntity(entityType: string, entityId: string): Promise<ProvenanceEdge[]>
getActivity(id: string): Promise<ProvenanceActivity | null>
activitiesForNote(noteId: string): Promise<ProvenanceActivity[]>
derivationsForRevision(revisionId: string): Promise<ProvenanceDerivation[]>
getNamedLocation(id: string): Promise<NamedLocation | null>
getLocation(id: string): Promise<ProvenanceLocation | null>
getDevice(id: string): Promise<ProvenanceDevice | null>
getCapture(id: string): Promise<ProvenanceCapture | null>
captureForNote(noteId: string): Promise<ProvenanceCapture | null>
}
First-class W3C PROV write/read surface over `provenance_edge`, so consumers do not need raw SQL to record lifecycle events.
The historical `ProvenanceEdge` name denotes a local activity. Rich derivations are separate `ProvenanceDerivation` records. Note reads include activities owned by that note's revisions. Agents may be null and metadata is arbitrary JSON; narrow `unknown` before object-field access. JSON strings are values, not encoded objects to parse a second time. `useNoteProvenance` retains the same values.
Capture records expose exact structured time ranges. Geometry getters emit EWKB from current native GeoJSON, retaining original byte order only for unchanged values. These APIs expose the native provenance restored by public `full-v1` import. Released producer/consumer qualification remains tracked in #424.
`AttachmentsRepository`
class AttachmentsRepository {
constructor(db: PGlite, blobStore: BlobStore)
attach(input: { noteId: string; filename: string; mimeType: string; data: Uint8Array }): Promise<AttachmentRow>
get(id: string): Promise<AttachmentRow | null>
getBlob(id: string): Promise<Uint8Array | null>
list(noteId: string): Promise<AttachmentRow[]>
delete(id: string): Promise<void>
}
Unlike other repositories, `AttachmentsRepository` takes a `BlobStore` instead of `TypedEventBus` as its second argument, because attachment data is stored outside the database.
| Method | Description |
|---|---|
| `attach(input)` | Write binary data to the blob store and record metadata in the database. |
| `get(id)` | Retrieve attachment metadata without the binary payload. |
| `getBlob(id)` | Retrieve the raw binary data for an attachment. |
| `list(noteId)` | List all attachments for a note (metadata only). |
| `delete(id)` | Remove metadata from the database and delete the blob. |
`EmbeddingSetsRepository`
class EmbeddingSetsRepository {
constructor(db: PGlite, events?: TypedEventBus)
getConfig(id: string): Promise<EmbeddingConfigRow>
listConfigs(): Promise<EmbeddingConfigRow[]>
listMembers(setId: string): Promise<EmbeddingMemberRow[]>
listEmbeddings(setId: string): Promise<EmbeddingRow[]>
create(input: EmbeddingSetCreateInput): Promise<EmbeddingSetRow>
ensureDefault(): Promise<EmbeddingSetRow>
get(id: string): Promise<EmbeddingSetRow>
list(): Promise<EmbeddingSetRow[]>
listDescriptors(): Promise<EmbeddingSetDescriptor[]>
createVirtualDefinition(input: VirtualEmbeddingSetDefinition): Promise<EmbeddingSetRow>
putEmbedding(input: EmbeddingSetEmbeddingInput): Promise<{ id: string }>
resolveSelector(selector: EmbeddingSetSelector): Promise<ResolvedEmbeddingSet>
}
Manages physical and virtual embedding sets. Selectors can target explicit sets, criteria-based sets, set operations, latest-compatible sets, snapshots, or fallback chains. Virtual definitions are durable metadata until materialized by application code.
Configuration reads include native provider, MRL and document-composition metadata. `listMembers` includes declared memberships even without a vector. `listEmbeddings` includes all chunks and metadata-only records with null vectors; selector resolution excludes rows without usable note vectors. `EmbeddingRow` exposes nullable owners/timestamps and explicit contract-fingerprint presence. `EmbeddingSetRow` also exposes stored index/refresh and agent metadata. These native reads expose public full-v1 restored state; released qualification remains tracked in #424.
Semantic and hybrid queries compare vectors of the query dimension, exclude null vectors and rank each note once using its best matching chunk. An explicit set selector retains all chunks in the source set chosen for each note. Native linking also requires the same set/model; 384- and 768-dimensional data can coexist.
`GraphRepository`
class GraphRepository {
constructor(db: PGlite)
buildLinkGraph(): Promise<CommunityGraph>
buildSimilarityGraph(embeddingSet: string | EmbeddingSetSelector, options?: SimilarityGraphOptions): Promise<CommunityGraph>
buildOrLoadSimilarityGraph(request: SimilarityGraphRequest): Promise<SimilarityGraphResult>
saveSimilarityGraphArtifact(input: {
graph: CommunityGraph
request: Required<Pick<SimilarityGraphRequest, 'selector' | 'k' | 'minSimilarity' | 'metric' | 'source'>>
resolved: ResolvedEmbeddingSet
cacheKey: SimilarityGraphCacheKey
freshness?: 'fresh' | 'stale' | 'unknown'
}): Promise<SimilarityGraphResult['graphSource']>
markSimilarityGraphStale(graphSourceId: string, reason: string): Promise<void>
getSourceRecord(graphSourceId: string): Promise<GraphSourceRecord | null>
getEdgeRecords(graphSourceId: string): Promise<GraphEdgeRecord[]>
loadGraphArtifact(graphSourceId: string, noteIds?: string[], communitySetId?: string): Promise<CommunityGraph>
}
Builds citation and embedding-similarity graphs, detects communities, and persists precomputed graph artifacts for shard export/import and UI reuse. Cached graph results include source metadata, freshness, and cache status.
Passing `communitySetId` loads that set's stored communities and assignments, including declared empty communities, without recomputing memberships. The set must belong to the requested graph source. Omitting it retains the existing computed-community behavior. Rich getters preserve nullable metadata, ranks, exact scalar timestamps and graph-scoped edge identities.
`CommunitiesRepository`
class CommunitiesRepository {
constructor(db: PGlite)
previewDynamicCommunity(filters: CommunityFilterDefinition): Promise<CommunityAssignmentView[]>
saveCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>
rerunDynamicCommunity(sourceId: string): Promise<CommunityAssignmentView[]>
listCommunitySources(): Promise<CommunitySourceDescriptor[]>
getCommunityAssignments(sourceId: string): Promise<CommunityAssignmentView[]>
listCommunitySummaries(sourceId: string): Promise<CommunitySummary[]>
getCommunitySet(sourceId: string): Promise<CommunitySetRecord | null>
getCommunityRecords(sourceId: string): Promise<CommunityRecord[]>
getAssignmentRecords(sourceId: string): Promise<CommunityAssignmentRecord[]>
}
Provides runtime-only dynamic community previews plus persisted dynamic snapshots and user-authored communities. Saved communities use the graph/community artifact tables so they can round-trip through Knowledge Shards.
Rich getters retain null/empty/value distinctions and the nested community array order, independently of display rank. Graph, set and community IDs are case-sensitive opaque strings. `saveCommunity` writes its source, set, child and assignments in one transaction. These native APIs do not by themselves complete public full-v1 restore/export acceptance; #424 remains the integration gate.
Repository Types
`NoteSummary`
interface NoteSummary {
id: string
title: string | null
snippet: string
tags: string[]
starred: boolean
pinned: boolean
archived: boolean
deletedAt: string | null
createdAt: string
updatedAt: string
}
Lightweight note projection used in list results.
`NoteFull`
interface NoteFull extends NoteSummary {
content: string
embedding: number[] | null
}
Full note including raw content and optional embedding vector.
`NoteCreateInput`
interface NoteCreateInput {
content: string
title?: string
tags?: string[]
}
`NoteUpdateInput`
interface NoteUpdateInput {
content?: string
title?: string
tags?: string[]
starred?: boolean
pinned?: boolean
archived?: boolean
}
All fields are optional; only provided fields are written.
`NoteListOptions`
interface NoteListOptions {
page?: number
pageSize?: number
tags?: string[]
starred?: boolean
pinned?: boolean
archived?: boolean
includeDeleted?: boolean
orderBy?: 'createdAt' | 'updatedAt' | 'title'
orderDir?: 'asc' | 'desc'
}
`PaginatedResult<T>`
interface PaginatedResult<T> {
items: T[]
total: number
page: number
pageSize: number
hasNext: boolean
}
`SearchResult`
interface SearchResult {
note: NoteSummary
score: number
highlights?: string[]
has_embedding?: boolean // whether the note has a vector embedding (for semantic search readiness)
}
`SearchFacets`
interface SearchFacets {
tags: { tag: string; count: number }[]
collections: { id: string; name: string; count: number }[]
}
Aggregate counts from the full (unpaginated) result set. Present on `SearchResponse` when `include_facets: true`.
`SearchResponse`
interface SearchResponse {
results: SearchResult[]
total: number
query: string
mode: 'text' | 'semantic' | 'hybrid'
semantic_available: boolean
limit: number
offset: number
facets?: SearchFacets
}
The `mode` field reflects the actual search mode used. `facets` is present when `include_facets: true` was requested.
`SearchOptions`
interface SearchOptions {
limit?: number // 1-100, default: 20
offset?: number // default: 0
tags?: string[] // filter: notes with ANY of these tags
collection_id?: string // filter: notes in this collection
date_from?: Date // filter: created on or after
date_to?: Date // filter: created on or before
is_starred?: boolean // filter: starred status
is_archived?: boolean // filter: archived status
format?: string // filter: 'markdown' | 'plain' | 'html'
source?: string // filter: 'user' | 'mcp' | 'import' | 'api'
visibility?: string // filter: 'private' | 'shared' | 'public'
tenant_id?: string // source-identity tenant scope
archive_id?: string | null // source-identity archive scope
metadataPredicates?: MetadataPredicate[] // indexed metadata filters
include_facets?: boolean // include tag/collection aggregate counts (default: false)
mode?: 'text' | 'semantic' | 'hybrid' | 'auto' // override search mode (default: 'auto')
}
All filters apply uniformly across text, semantic, hybrid, and recent-note search modes. Metadata predicates are bounded and allowlisted to indexed fields: `provider`, `model`, `role`, `event_kind`, `sensitivity`, and `import_run_id`. Unsupported paths fail clearly rather than running unbounded scans.
`SourceUpsertRepository`
class SourceUpsertRepository {
constructor(db: DatabaseClient, events?: TypedEventBus)
upsertBatch(items: SourceUpsertItem[], options?: SourceUpsertOptions): Promise<SourceUpsertBatchResult>
}
type SourceUpsertPolicy = 'replace' | 'version' | 'conflict'
type SourceUpsertOutcome = 'inserted' | 'unchanged' | 'versioned' | 'replaced' | 'conflict' | 'rejected'
Atomically imports source-addressed notes by tenant, archive, source namespace, and external id. Exact replays return `unchanged` without duplicating notes, revisions, jobs, source identities, or import runs. Changed content follows the per-item `policy`: replace current content, create a versioned revision, or report a conflict without mutation. Results include source-key hashes and content digests, not raw external ids.
`LifecyclePurgeRepository`
class LifecyclePurgeRepository {
constructor(db: DatabaseClient, events?: TypedEventBus, blobStore?: BlobStore)
preview(selector: PurgeSelector): Promise<PurgePreview>
begin(request: PurgeRequest): Promise<PurgeStatus>
status(operationId: string): Promise<PurgeStatus | null>
resume(operationId: string): Promise<PurgeStatus>
purge(selector: PurgeSelector, operationId: string): Promise<DeletionReceipt>
}
Provides authority-bound terminal purge for note ids or source-identity selectors. Preview counts match terminal receipt counts; begin removes note rows, revisions, links, tags, embeddings, graph edges, provenance edges, attachments, and source identity rows atomically. Configured BlobStore bytes are observable as `cleanup_pending` and removed idempotently by `resume`. Re-running the same operation returns the same content-free deletion receipt.
`NoteRevision`
interface NoteRevision {
id: string
noteId: string
content: string
title: string | null
createdAt: string
}
Immutable snapshot of a note's content at the time of an update.
`CollectionRow`
interface CollectionRow {
id: string
name: string
description: string | null
noteCount: number
createdAt: string
updatedAt: string
}
`LinkRow`
interface LinkRow {
id: string
sourceId: string
targetId: string
relation: string | null
confidence: number | null // similarity score (1 - cosine distance) for semantic links
updated_at: Date | null // last modification timestamp
createdAt: string
}
`AttachmentRow`
interface AttachmentRow {
id: string
noteId: string
filename: string
mimeType: string
size: number
createdAt: string
}
`AttachmentBlobRow`
interface AttachmentBlobRow extends AttachmentRow {
data: Uint8Array
}
Tool Functions
Tool functions accept plain input objects, validate them with Zod, and return structured results. `FortemiToolManifest` currently registers 10 bridge-visible Fortemi tools; `@fortemi/core` also exports `manageAttachments` as a direct helper because it requires both `db` and `blobStore`.
`captureKnowledge(db, events, input)`
function captureKnowledge(
db: PGlite,
events: TypedEventBus,
input:
| { action: 'create'; content: string; title?: string; tags?: string[] }
| { action: 'bulk_create'; notes: NoteCreateInput[] }
| { action: 'from_template'; templateId: string; variables: Record<string, string> }
): Promise<NoteFull | NoteFull[]>
Create one or more notes. `from_template` expands a stored template with variable substitution.
`manageNote(db, events, input)`
function manageNote(
db: PGlite,
events: TypedEventBus,
input:
| { action: 'update'; id: string } & NoteUpdateInput
| { action: 'delete'; id: string }
| { action: 'restore'; id: string }
| { action: 'archive'; id: string; archived: boolean }
| { action: 'star'; id: string; starred: boolean }
): Promise<NoteFull | void>
Mutate an existing note's state or metadata.
`searchTool(db, input)`
function searchTool(
db: PGlite,
input:
| { mode: 'text'; query: string; options?: SearchOptions }
| { mode: 'semantic'; embedding: number[]; options?: SearchOptions }
| { mode: 'hybrid'; query: string; embedding: number[]; options?: SearchOptions }
): Promise<SearchResponse>
Unified search entry point covering all three search modes.
`getNote(db, input)`
function getNote(
db: PGlite,
input: { id: string }
): Promise<NoteFull | null>
Retrieve a single note by ID.
`listNotes(db, input)`
function listNotes(
db: PGlite,
input: NoteListOptions
): Promise<PaginatedResult<NoteSummary>>
Retrieve a paginated, filtered note list.
`createRemoteBackend(config)`
function createRemoteBackend(config: {
baseUrl: string
id?: string
fetchImpl?: typeof fetch
headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)
authToken?: string
paths?: Partial<RemoteBackendPaths>
}): RemoteDataBackend
Creates the network-backed Fortemi server tier for `selectBackend`. The returned backend advertises `read`, `write`, `multiUser`, `semantic: 'server'`, `merge: false`, and `startupCost: 'network'`. These describe implemented dispatch, not authorization or provider availability for a particular server request.
A fourth seam adapter, `createRecordBackend`, serves the writable canonical record tier with no PGlite at all — see Canonical Records.
Readable `DataBackend` implementations expose `listNotes`, `getNote` and `search`, with optional full-note and relationship methods. Remote `getNoteFull` includes `provenanceGraph`, preserving server activities, edges, current chain and derived note IDs. Use `provenanceGraphOf(id)` for that graph. Remote `provenanceOf` throws `RemoteBackendError` with `kind: 'unsupported-operation'`; it cannot represent the server graph as local PGlite edges. PGlite and shard methods are unchanged.
Remote links include `direction` relative to the requested note and retain both endpoints. Remote concepts mark `altLabels` and `definition` in `unavailableFields`: their empty/null placeholders do not assert absence. `getNote`/`getNoteFull` return null only for a recognized note-not-found response. Authorization, transport, malformed response and enrichment errors throw `RemoteBackendError` with bounded status/problem metadata and no raw body text.
Remote `search(query, options?)` uses `q` and defaults to `mode: 'fts'` with a 20-hit limit. Supported modes are `fts`, `semantic`, and `hybrid`; the adapter admits integer limits 1-100. `tags` is an AND-filter encoded as one comma-separated value; empty, padded or comma-bearing tag values reject. Nonzero offset and nonempty source filters are unsupported and reject before dispatch. This endpoint does not provide offset pagination or facets. `totalKind: 'returned-hits'` makes the producer's count semantics explicit.
Search metadata preserves actual EnhancedSearchHit scores, snippets and optional chain/embedding fields. Timestamps are enriched with at most 100 sequential validated note-detail reads, preserving result order and reusing duplicate IDs. This is not an atomic snapshot: an enrichment failure fails the whole operation, and detail metadata can be newer than the search result. No empty timestamps or partial-success records are manufactured.
`RemoteSearchResult` includes `requestedMode`, `effectiveMode`, `degraded` and optional `degradation` (`code`, `effective_mode`). `semanticWithReport(query, k?)` retains explicit fallback results. The legacy array-returning `semantic(query, k?)` throws `RemoteBackendError` with `kind: 'degraded-search'` on fallback, before detail enrichment; it does not present FTS results as vector retrieval.
`manageNote(input)` accepts only these validated shapes:
| Action | Fields besides action | REST operation |
|---|---|---|
| `create` | `content`, optional `title`, `tags`, `source` | POST notes, revision mode none, empty pipeline |
| `update` | `note_id`, `content` and/or `tags` | PATCH note; content uses revision mode none |
| `star` / `unstar` | `note_id` | PATCH starred boolean |
| `archive` / `unarchive` | `note_id` | PATCH archived boolean |
| `delete` | `note_id` | DELETE note, body-free 204 |
| `restore` | `note_id` | POST note/restore with revision mode none |
Tag updates replace the tag set. Unknown actions/fields, including unsupported title/format/visibility updates, reject before dispatch as `invalid-request`. The result contains `action` and `note_id`; only PATCH responses include the projected `note`. Create/restore acknowledgements are identity-validated without an additional detail fetch. A failed transport or response validation does not prove that a dispatched mutation rolled back. Restore may queue server indexing work even with AI revision disabled. No automatic mutation retries are performed.
Legacy `paths.manageNote` and `paths.semantic` overrides reject as unsupported; use operation-specific `notes`, `note`, `restore` and `search` paths. A path override cannot translate a tool-intent or MCP envelope into a REST contract.
Qualification boundary: pinned producer fixtures and source tests do not establish released-consumer parity, successful vector retrieval or auth qualification. Those acceptance gates remain tracked by #417-421 and producer #1146.
`manageTags(db, input)`
function manageTags(
db: PGlite,
input:
| { action: 'list' }
| { action: 'frequency' }
| { action: 'suggest'; partial: string }
): Promise<string[] | Array<{ tag: string; count: number }>>
`manageCollections(db, input)`
function manageCollections(
db: PGlite,
input:
| { action: 'create'; name: string; description?: string }
| { action: 'get'; id: string }
| { action: 'list' }
| { action: 'update'; id: string; name?: string; description?: string }
| { action: 'delete'; id: string }
| { action: 'add_notes'; collectionId: string; noteIds: string[] }
| { action: 'remove_notes'; collectionId: string; noteIds: string[] }
): Promise<CollectionRow | CollectionRow[] | void>
`manageLinks(db, input)`
function manageLinks(
db: PGlite,
input:
| { action: 'create'; sourceId: string; targetId: string; relation?: string }
| { action: 'get'; id: string }
| { action: 'list'; noteId?: string }
| { action: 'delete'; id: string }
| { action: 'get_related'; noteId: string }
): Promise<LinkRow | LinkRow[] | NoteSummary[] | void>
`manageArchive(manager, input)`
function manageArchive(
manager: ArchiveManager,
input:
| { action: 'open'; name: string }
| { action: 'list' }
| { action: 'create'; name: string; persistence: PersistenceMode }
| { action: 'switch'; name: string }
| { action: 'delete'; name: string }
): Promise<unknown>
`manageCapabilities(manager, input)`
function manageCapabilities(
manager: CapabilityManager,
input:
| { action: 'enable'; name: CapabilityName }
| { action: 'disable'; name: CapabilityName }
| { action: 'status'; name?: CapabilityName }
): Promise<CapabilityState | Array<{ name: CapabilityName; state: CapabilityState }> | void>
`manageAttachments(db, blobStore, input)`
function manageAttachments(
db: PGlite,
blobStore: BlobStore,
input:
| { action: 'attach'; noteId: string; filename: string; mimeType: string; data: Uint8Array }
| { action: 'list'; noteId: string }
| { action: 'get'; id: string }
| { action: 'get_blob'; id: string }
| { action: 'delete'; id: string }
): Promise<AttachmentRow | AttachmentBlobRow | AttachmentRow[] | void>
Job Queue
The job queue processes background tasks (embedding generation, title generation, tagging, linking) asynchronously inside a Worker or on the main thread.
`JobQueueWorker`
class JobQueueWorker {
constructor(
db: PGlite,
events: TypedEventBus,
options: { pollIntervalMs?: number; batchSize?: number },
capabilityManager: CapabilityManager
)
registerHandler(jobType: JobType, handler: JobHandler): void
start(): void
stop(): void
processOnce(): Promise<number>
}
| Method | Description |
|---|---|
| `registerHandler(jobType, handler)` | Register an async function to handle a specific job type. |
| `start()` | Begin polling the job queue at the configured interval. |
| `stop()` | Stop polling. In-flight jobs complete before the worker halts. |
| `processOnce()` | Process one batch of pending jobs immediately. Returns the count processed. |
`enqueueJob(db, input)`
function enqueueJob(
db: PGlite,
input: { noteId: string; jobType: JobType; priority?: number; requiredCapability?: string | null }
): Promise<string>
Insert a job into the queue. Returns the job ID.
`enqueueNoteCreationJobs(db, noteId, hasTitle)`
function enqueueNoteCreationJobs(
db: PGlite,
noteId: string,
hasTitle: boolean
): Promise<void>
Convenience function that enqueues the standard set of post-creation jobs: embedding, concept tagging, linking, and optionally title generation (when `hasTitle` is `false`).
`enqueueFullWorkflow(db, noteId)`
async function enqueueFullWorkflow(db: PGlite, noteId: string): Promise<void>
Enqueues the complete 5-job pipeline for a note in correct dependency order: AI revision (1) → title generation (2) → embedding (3) → concept tagging (4) → linking (5).
`getJobQueueStatus(db, noteId?)`
function getJobQueueStatus(
db: PGlite,
noteId?: string
): Promise<Array<{ jobId: string; jobType: JobType; status: string; createdAt: string }>>
Return current queue status. Pass `noteId` to filter to jobs for a specific note.
Built-in Job Handlers
The following handler functions are registered on a `JobQueueWorker` to implement background processing. Each conforms to the `JobHandler` type.
| Export | Job Type | Requires Capability |
|---|---|---|
| `titleGenerationHandler` | `'title_generation'` | `'llm'` |
| `aiRevisionHandler` | `'ai_revision'` | `'llm'` |
| `conceptTaggingHandler` | `'concept_tagging'` | `'llm'` |
| `linkingHandler` | `'linking'` | `'llm'` |
| `embeddingGenerationHandler` | `'embedding'` | `'semantic'` |
`embeddingGenerationHandler` is exported from the capabilities module rather than the core job queue module.
`JobType`
type JobType =
| 'title_generation'
| 'ai_revision'
| 'embedding'
| 'concept_tagging'
| 'linking'
`JOB_PRIORITIES`
const JOB_PRIORITIES: Record<JobType, number>
Default numeric priority values for each job type. Lower numbers run first.
| Job Type | Priority |
|---|---|
| `ai_revision` | 1 |
| `title_generation` | 2 |
| `embedding` | 3 |
| `concept_tagging` | 4 |
| `linking` | 5 |
`JOB_CAPABILITIES`
const JOB_CAPABILITIES: Record<JobType, CapabilityName>
Maps each job type to the capability that must be ready before the job can run. If a pending job's required capability is not ready, the worker defers dispatch, keeps the job pending with a `requires capability '<name>' — not ready` message, and emits both `job.blocked` and `capability.required` events.
Capabilities
Utility functions for hardware detection, model selection, and ML function registration.
`detectGpuCapabilities()`
function detectGpuCapabilities(): Promise<GpuCapabilities>
Queries the WebGPU adapter (if available) to detect GPU memory and features.
Returns: A `GpuCapabilities` object with detected hardware details.
`estimateVramTier(caps)`
function estimateVramTier(caps: GpuCapabilities): VramTier
Maps detected GPU capabilities to a discrete VRAM tier used for model selection.
| Parameter | Type | Description |
|---|---|---|
| `caps` | `GpuCapabilities` | Output of `detectGpuCapabilities()` |
Returns: A `VramTier` value.
`selectLlmModel(tier, supportsF16?)`
function selectLlmModel(tier: VramTier, supportsF16?: boolean): string
Returns the recommended model identifier for the given hardware tier.
| Parameter | Type | Description |
|---|---|---|
| `tier` | `VramTier` | Hardware tier from `estimateVramTier` |
| `supportsF16` | `boolean` | Optional; prefer F16 quantization when `true` |
Returns: A model identifier string (e.g. `'smollm2-135m-instruct-q4_k_m'`).
`setEmbedFunction(fn)` / `getEmbedFunction()`
function setEmbedFunction(
fn: ((texts: string[], options?: { task?: InferenceTask; model?: string }) => Promise<number[][]>) | null,
): void
function getEmbedFunction(): ((texts: string[], options?: { task?: InferenceTask; model?: string }) => Promise<number[][]>) | null
Register or retrieve the active embedding function. The embedding function is called by `embeddingGenerationHandler` and `semanticSearch`. Must be set before semantic features are used.
`setLlmFunction(fn)` / `getLlmFunction()`
function setLlmFunction(
fn: ((prompt: string, options?: { maxTokens?: number; temperature?: number; task?: InferenceTask; model?: string }) => Promise<string>) | null,
): void
function getLlmFunction(): ((prompt: string, options?: { maxTokens?: number; temperature?: number; task?: InferenceTask; model?: string }) => Promise<string>) | null
Register or retrieve the active LLM inference function. Called by title generation, tagging, and revision handlers.
`registerSemanticCapability(manager)` / `unregisterSemanticCapability(manager)`
function registerSemanticCapability(manager: CapabilityManager): void
function unregisterSemanticCapability(manager: CapabilityManager): void
Attach or detach the default semantic capability loader from a `CapabilityManager` instance.
`registerSemanticCapabilityWorker(manager, port, options?)`
function registerSemanticCapabilityWorker(
manager: CapabilityManager,
port: EmbedTransportPort, // Worker | MessagePort
options?: EmbedWorkerOptions, // { timeoutMs?: number } — default 30000, 0 disables
): void
Register the semantic capability backed by an off-main-thread transport, so embedding model load and per-query inference never touch the main thread (#180). Core round-trips each `{ texts }` request to the host-owned worker and awaits `number[][]`. Pairs with `handleEmbedRequests(port, embed)` (worker side) and `createWorkerEmbedFunction(port, options?)` (the lower-level primitive). Additive and opt-in; the main-thread `registerSemanticCapability` path is unchanged. See Integration → Off-main-thread embedding transport.
`configureInferenceRuntime(options?)`
function configureInferenceRuntime(options?: {
providers?: ConfiguredInferenceProvider[]
routes?: Partial<Record<InferenceTask, ProviderRoutePolicy>>
activeProviderId?: string
bridgeHost?: FortemiBridgeHost
includeBridgeProviders?: boolean
discoverLocal?: boolean | DiscoveryOptions
embeddingTaskSelection?: {
largeDocumentChars?: number
largeDocumentChunks?: number
}
registry?: ProviderRegistry
events?: TypedEventBus
capabilityManager?: CapabilityManager
}): Promise<{
registry: ProviderRegistry
providers: InferenceProvider[]
routeValidation: ProviderRouteValidation[]
routeIssues: ProviderRouteValidationIssue[]
}>
Registers configured inference providers, adapts host bridge providers, optionally discovers local OpenAI-compatible servers, applies task routes, validates the configured routes, and wires `semantic`/`llm` capability loaders when a `CapabilityManager` is provided.
Supported task hints are `embedding.query`, `embedding.document`, `embedding.large-document`, `chat.general`, `chat.revision`, `chat.tagging`, `chat.linking`, and `vision.general`.
OpenAI-compatible `/models` listings and local discovery use the same lightweight model-name classifier for chat, embedding, and vision capability hints.
`embeddingTaskSelection` configures the built-in document embedding pipeline's threshold for switching from `embedding.document` to `embedding.large-document`.
`routeValidation` and `routeIssues` summarize configuration problems immediately after route application. They do not probe providers and do not include prompt, note, embedding, generated text, or key material.
Use the additive definition helpers when a deployment wants to compose provider packs, route presets, and environment-specific overrides:
function defineInferenceRuntime(config: InferenceRuntimeConfig): InferenceRuntimeConfig
function defineInferenceProvider(provider: InferenceProvider): ConfiguredInferenceProvider
function defineOpenAICompatibleProvider(config: OpenAIProviderConfig): ConfiguredInferenceProvider
function defineLegacyInferenceProvider(config: LegacyInferenceProviderConfig): ConfiguredInferenceProvider
function getConfiguredInferenceProviderId(config: ConfiguredInferenceProvider): string
function mergeInferenceRuntimeConfigs(
...configs: Array<InferenceRuntimeConfig | null | undefined>
): InferenceRuntimeConfig
`mergeInferenceRuntimeConfigs()` merges providers by configured provider ID with later fragments overriding earlier ones, merges route maps by task, merges `embeddingTaskSelection`, and lets later scalar options such as `activeProviderId`, `discoverLocal`, and `includeBridgeProviders` override earlier fragments.
`ProviderRegistry`
class ProviderRegistry {
add(provider: InferenceProvider): void
remove(id: string): void
setActive(id: string): void
setRoute(task: InferenceTask, policy: ProviderRoutePolicy): void
getRoute(task: InferenceTask): ProviderRoutePolicy | undefined
clearRoute(task: InferenceTask): void
clearRoutes(): void
previewRoute(
task: InferenceTask | undefined,
capability: keyof ProviderCapabilities,
requestModel?: string,
): ProviderRouteSelection
probeRoute(
task: InferenceTask | undefined,
capability: keyof ProviderCapabilities,
requestModel?: string,
): Promise<ProviderRouteProbeResult>
validateRoute(task: InferenceTask): ProviderRouteValidation
validateRoutes(): ProviderRouteValidation[]
embed(request: EmbedRequest): Promise<EmbedResponse>
complete(request: CompletionRequest): Promise<CompletionResponse>
stream(request: CompletionRequest): AsyncIterable<StreamChunk>
}
`ProviderRegistry` keeps the legacy `setEmbedFunction` and `setLlmFunction` bridges in sync while allowing task-specific provider/model selection.
`setRoute()` and `getRoute()` copy route policy arrays and nested requirement arrays at the registry boundary. Hosts can reuse or mutate their own config objects after applying routes without mutating the active registry route.
interface ProviderRoutePolicy {
providerIds?: string[]
tiers?: ProviderTier[]
model?: string
fallback?: boolean
requirements?: ProviderRouteRequirements
}
interface ProviderRouteRequirements {
privacyTiers?: Array<'local' | 'host-managed' | 'external'>
maxCostTier?: 'free' | 'low' | 'medium' | 'high'
minContextTokens?: number
minEmbeddingDimensions?: number
dataClass?: 'public' | 'private' | 'sensitive' | 'regulated'
maxInputChars?: number
}
Providers can expose optional runtime profile metadata:
interface ProviderProfile {
privacyTier?: 'local' | 'host-managed' | 'external'
costTier?: 'free' | 'low' | 'medium' | 'high'
maxInputChars?: number
embeddingDimensions?: number[]
dataClasses?: Array<'public' | 'private' | 'sensitive' | 'regulated'>
}
Route requirements are enforced before capability dispatch. If no configured provider satisfies the route, capability, and requirement set, the request rejects instead of falling back across that boundary.
Use `validateRoute()` or `validateRoutes()` to check configured routes without probing providers or sending inference payloads. Validation reports missing providers, unsupported capabilities, missing handlers, profile requirement failures, empty explicit chains, and routes with no eligible provider.
For `embed()` and `complete()`, routes with fallback enabled retry the next eligible provider when the selected provider throws. When `providerIds` is present, fallback is limited to that explicit ordered chain; it does not try unlisted registered providers. `provider.fallback` is emitted with provider IDs, error category, and error message only. Streaming requests resolve one provider up front and do not retry mid-stream.
Use the exported validators when building route configuration UI:
function providerSatisfiesRouteRequirements(
provider: InferenceProvider,
requirements: ProviderRouteRequirements | undefined,
capability: keyof ProviderCapabilities,
): boolean
function getProviderRouteRequirementIssue(
provider: InferenceProvider,
requirements: ProviderRouteRequirements | undefined,
capability: keyof ProviderCapabilities,
): string | undefined
`previewRoute()` resolves the provider/model that a task would use without sending inference payloads. `probeRoute()` resolves the same route and calls the selected provider's health probe.
Route configuration changes emit `provider.route.configured` and `provider.route.cleared` with task/provider/model metadata only. Each routed request emits `provider.route.selected` with provider ID/name, tier, capability, task, optional model, and whether a task route matched. Completed requests emit `provider.route.completed` with attempt count, fallback count, and latency. Failed requests emit `provider.route.failed` with attempt count, fallback count, latency, error category, and error message. These events never include prompts, input text, output text, vectors, or API keys.
`registerLlmCapability(manager)` / `unregisterLlmCapability(manager)`
function registerLlmCapability(manager: CapabilityManager): void
function unregisterLlmCapability(manager: CapabilityManager): void
Attach or detach the default LLM capability loader from a `CapabilityManager` instance.
`chunkText(content)`
function chunkText(content: string): string[]
Split a document into overlapping chunks suitable for embedding. Used internally before calling the embed function on long notes.
`selectEmbeddingTask(content, chunks, options?)`
function selectEmbeddingTask(
content: string,
chunks: string[],
options?: {
largeDocumentChars?: number
largeDocumentChunks?: number
},
): 'embedding.document' | 'embedding.large-document'
Returns `embedding.large-document` when content length or chunk count crosses the configured threshold. The default thresholds are exported as `DEFAULT_LARGE_DOCUMENT_CHARS` and `DEFAULT_LARGE_DOCUMENT_CHUNKS`.
Non-React hosts can configure the built-in embedding job directly:
function setEmbeddingTaskSelectionOptions(options?: {
largeDocumentChars?: number
largeDocumentChunks?: number
}): void
function getEmbeddingTaskSelectionOptions(): {
largeDocumentChars?: number
largeDocumentChunks?: number
}
`cosineSimilarity(a, b)`
function cosineSimilarity(a: number[], b: number[]): number
Compute the cosine similarity between two equal-length embedding vectors.
Returns: A float in the range `[-1, 1]`.
`suggestTags(embedding, tagEmbeddings)`
function suggestTags(
embedding: number[],
tagEmbeddings: Array<{ tag: string; embedding: number[] }>
): string[]
Return a ranked list of tags whose embeddings are most similar to the input embedding. Used by the concept tagging job handler.
Migrations and Archive
`MigrationRunner`
class MigrationRunner {
constructor(db: PGlite)
run(): Promise<void>
}
Applies all pending schema migrations in order. Safe to call on each startup; already-applied migrations are skipped.
`allMigrations`
const allMigrations: Migration[]
The ordered array of all schema migration definitions used by `MigrationRunner`.
`ArchiveManager`
class ArchiveManager {
constructor(persistence: PersistenceMode, events?: TypedEventBus)
open(name: string): Promise<PGlite>
list(): Promise<string[]>
create(name: string, persistence?: PersistenceMode): Promise<void>
switch(name: string): Promise<PGlite>
delete(name: string): Promise<void>
}
Manages multiple named archives (databases). Each archive is an independent PGlite instance.
| Method | Description |
|---|---|
| `open(name)` | Open an existing archive and return its `PGlite` instance. |
| `list()` | Return names of all known archives for the current persistence mode. |
| `create(name, persistence?)` | Create a new empty archive with optional override persistence mode. |
| `switch(name)` | Open a different archive, replacing the currently active instance. |
| `delete(name)` | Permanently delete an archive and all its data. |
| `adopt(backend, name?)` | Adopt an already-created backend without running migrations — e.g. a PGlite restored from a physical snapshot (the restored data dir already carries the schema + indexes). |
Physical DB snapshot — `dumpDbSnapshot` / `restoreDbSnapshot`
A snapshot is a binary dump of a populated PGlite data directory — schema + rows + indexes (including the HNSW vector index) — that restores in a single load with no migration, no shard import, and no client-side HNSW build. It is the fast, pre-indexed, single-version restore option, complementary to logical Knowledge Shards (portable + mergeable, but they pay the import + reindex cost on every load).
Distinct from `ArchiveManager`/`archiveName` (a named persistence store) and from Knowledge Shards (logical, mergeable interchange). "Snapshot" = the physical data-dir image.
// Build-time (Node), after the corpus is populated and the HNSW index built once:
const { data, meta } = await dumpDbSnapshot(db, { compression: 'gzip' })
// Write `data` to e.g. corpus.pgdata and `meta` to corpus.pgdata.meta.json (the sidecar).
// Browser restore — verifies the version stamp BEFORE loading, then no migration/import:
const pglite = await restoreDbSnapshot('/corpus/corpus.pgdata', { persistence: 'memory' })
| Export | Description |
|---|---|
| `dumpDbSnapshot(db, options?)` | Dump a `{ data, meta }` snapshot. `meta` stamps `{ pglite_version, pgvector_version, migration_head, created_at }`. |
| `restoreDbSnapshot(source, options?)` | Restore a `PGlite` from a snapshot (`{data,meta}`, a URL, or `{dataUrl, metaUrl?}`). Verifies the stamp first; throws `DbSnapshotVersionError` on mismatch (catch it to fall back to a shard import). |
| `verifyDbSnapshotMeta(meta, expected?)` | Pure compatibility check → `{ compatible, reasons, warnings }`. Hard gates: snapshot schema, migration head (exact), PGlite major.minor; pgvector is advisory. |
| `DbSnapshotVersionError` | Thrown when a snapshot is incompatible with this build; carries `reasons` + `meta`. |
| `SUPPORTED_PGLITE_VERSION` / `CURRENT_MIGRATION_HEAD` / `DB_SNAPSHOT_SCHEMA_VERSION` | The version values this build restores against. |
`createPGliteInstance(persistence, archiveName?, { loadDataDir })` accepts a snapshot blob directly for lower-level use. The React `FortemiProvider` exposes a `snapshotUrl` prop (main execution mode) as the turnkey path.
Canonical Records
The canonical record layer (ADR-013, `records/`) is the writable structured-record tier that exists independently of PGlite: records mirror the SQL rows one-to-one, every mutation commits atomically with a change-journal entry, and the PGlite tables become an optional, rebuildable projection. All exports below come from the `@fortemi/core` root.
`RecordStore` / `createRecordStore(options?)` / `MemoryRecordStore`
interface RecordStore {
get<C extends RecordCollectionName>(collection: C, id: string): Promise<RecordCollections[C] | null>
put<C extends RecordCollectionName>(collection: C, record: RecordCollections[C]): Promise<JournalEntry>
remove(collection: RecordCollectionName, id: string): Promise<JournalEntry>
applyBatch?(mutations: readonly RecordMutation[]): Promise<JournalEntry[]>
applyPurgeBatch?(
mutations: readonly RecordMutation[],
scrubJournal: readonly RecordJournalKey[],
): Promise<JournalEntry[]>
list<C extends RecordCollectionName>(collection: C, opts?: RecordListOptions): Promise<RecordCollections[C][]>
journalSince(sinceSeq: number, limit?: number): Promise<JournalEntry[]>
headSeq(): Promise<number>
readonly capabilities: RecordStoreCapabilities
close(): Promise<void>
}
The store contract plus the durable IndexedDB implementation (`createRecordStore` → `IdbRecordStore`, with schema versioning and a newer-schema guard) and the in-memory test double. Collections include canonical note/source records plus private lifecycle previews, operations, cleanup work, erasure targets, and content-free receipts. The built-in stores implement `applyBatch` and `applyPurgeBatch`; the latter removes prior deleted-record journal snapshots in the same transaction. Custom stores without that capability cannot claim terminal purge. Query capabilities remain explicit (`boundedTextScan: true`, `fullTextSearch: false`, `vectorSearch: false`, `sqlJoins: false`).
`CanonicalNotesRepository` / `CanonicalAttachmentsRepository`
DB-free note workflows (CRUD, soft-delete/restore, tags, links, nested collections, recent/by-tag queries, bounded text scan) and attachment workflows (bytes-first attach through the Bytecask `BlobStore`, dedupe, reference-only reads, manifest-derived `reconcileBlobs`/`gcBlobs`). `createCollection(name, description?, parentId?)` validates the optional parent before writing.
Source upsert and RecordStore lifecycle purge
RecordStore equivalents for source-addressed import and terminal purge use journal-compacting atomic mutation, preserve replay idempotence, expose `beginRecordStorePurge`, `recordStorePurgeStatus`, and `resumeRecordStorePurge`, and emit deletion receipts that omit content and raw external source ids. RecordStore Knowledge Shard export reports source identity mappings as typed loss because `record-v1` does not declare that operational mapping.
`createRecordBackend(store, options?)`
function createRecordBackend(store: RecordStore, options?: { id?: string }): DataBackend
Wraps a canonical `RecordStore` as a writable `DataBackend` for `selectBackend`. Advertises `read`, `write`, `merge`, `semantic: 'none'`, and `startupCost: 'instant'`; `manageNote` accepts the same Zod-validated input as the PGlite tool (update / delete / restore / archive / unarchive / star / unstar). Search is a bounded unranked scan, and `conceptsOf`/`provenanceOf` are absent (feature-detect via the optional methods) because the canonical tier does not persist SKOS or provenance records.
`exportShardFromRecords(store, options?)` / `importShardToRecords(store, data, options?)`
function exportShardFromRecords(store: RecordStore, options?: ExportOptions): Promise<Uint8Array>
function importShardToRecords(store: RecordStore, data: Uint8Array | ArrayBuffer, options?: ImportOptions): Promise<ImportResult>
Knowledge Shard handling with zero PGlite. Export honors `collectionId`/`tag` filters, `clusterNotesSize`, and the `includeBlobs` + `blobStore` byte sidecar. Import runs the same ADR-014 signature policy before any write, validates checksums, stages verified sidecar bytes, commits all record and journal mutations through `applyBatch`, and rolls newly promoted bytes back if that batch fails. Legacy unprofiled replace import reconciles tags, memberships, attachments, and note links for imported notes while preserving explicit null revisions and tombstone ordering. RecordStore advertises only the authority-owned `record-v1` subset; its import/export preserves collection hierarchy and reports profile-specific losses.
`projectNotes(db, store)` / `projectRecords(db, store)` / `dropNoteProjection(db)`
function projectNotes(db: DatabaseClient, store: RecordStore): Promise<NoteProjectionResult>
function projectRecords(db: DatabaseClient, store: RecordStore): Promise<RecordProjectionResult>
function dropNoteProjection(db: DatabaseClient): Promise<void>
Project canonical state into the optional PGlite tables. `projectNotes` covers the note tier (notes, originals, current revisions, tags, links, collections, memberships); `projectRecords` composes it with `projectAttachments` (migration 0017) for a full rebuild. Idempotent upserts, reconciliation of canonically hard-removed rows, and drop + rebuild with row-for-row parity — canonical records and Bytecask bytes are never modified. `projectAttachments` / `dropAttachmentProjection` are exported alongside.
Knowledge Shards
A Knowledge Shard is a gzip-compressed tar archive with a declared portability profile. Compatibility with the Rust/PostgreSQL Fortemi server is limited to profiles verified by the server-owned schema and cross-repository import/re-export fixtures; it is not implied for every component. All shard APIs are exported from the `@fortemi/core` root.
`exportShard(db, options?)`
function exportShard(db: DatabaseClient, options?: ExportOptions): Promise<Uint8Array>
interface ExportOptions {
profile?: 'core-v1' | 'full-v1'
schemaVersion?: '1.2.0' | '2.0.0'
includeEmbeddings?: boolean
collectionId?: string // export only notes in this collection
tag?: string // export only notes with this tag (e.g. 'app:research')
embeddingSetIds?: string[] // export only these sets + member/vector rows
includeMaterializedSelectors?: boolean
clusterNotesSize?: number // emit clustered notes/000.jsonl files for in-place readers
includeBlobs?: boolean // pack attachment bytes into a content-addressed blobs/<blake3-hex> sidecar
blobStore?: BlobStore // byte source for the sidecar; required when includeBlobs is set
}
Produces the `.shard` archive bytes. With `includeBlobs`, attachment bytes are read from `blobStore` by `content_hash` and packed as BLAKE3-addressed `blobs/<hex>` sidecar entries, making the shard self-contained; a blob the store cannot return is skipped (that attachment stays reference-only) rather than failing the export.
Named profiles require `exportShardWithReport`; `exportShard` rejects a named profile so capability and loss evidence cannot be discarded. The exact `2.0.0/full-v1` PGlite path produces an archive from current native state and requires a `BlobStore` for mandatory attachment bytes. The delivered local implementation receipt makes this exact-tuple path callable for producer and conformance use. It is not included in backend capability advertisements until the independent cross-repository receipt tracked by #382 is delivered. Schema 1.x `full-v1` is not accepted.
For live `2.0.0/full-v1`, select notes by a nonempty `tag` or `collectionId`; supplying both is rejected before producing an archive. `embeddingSetIds` narrows sets and their member/vector rows, not the note set; use it with a note selector to limit both. Empty selectors/lists are rejected. A nonmatching note selector produces no notes or attachment bytes. A nonmatching embedding selector produces no embedding sets but does not remove selected notes. Stored archival snapshots never participate in this dispatcher. Explicit archival export has no scope selectors and returns the stored logical files.
Scoped note exports include their declared tags, histories, revisions, attachment projections and mandatory bytes. Note links require both endpoints to be selected; URL links require their owning note, and provenance requires its selected note or revision. Graph edges and assignments are limited to selected notes and applicable embedding sets. Supporting collection ancestry, referenced graph/SKOS metadata, and shared templates remain profile dependencies, not additional selected notes. This is not an entire-database backup or an authorization boundary for shared metadata. Archival byte roundtrip is distinct from native restore (#424); released consumer qualification and suite NO-GO remain separately governed.
`importShard(db, data, options?)`
For `2.0.0/full-v1`, this dispatcher validates the complete archive and restores all 33 declared components to native tables in one transaction. Native repository CRUD is visible to subsequent exports; archival records cannot shadow it. `skip` preserves existing identities and their owned state, but permits new records to reference existing targets. `replace` reconciles selected owners; `error` rejects an existing identity. Required sidecars are verified and newly promoted bytes are compensated after transaction failure. Released producer/ consumer and platform qualification remains the separate #424 acceptance gate.
Native migration lineage follows record identity and survives ordinary edits. Empty imports and no-op skips do not overwrite existing lineage. An export with incompatible selected histories returns `incompatible-native-migration-lineage` instead of silently replacing metadata; a coherent scope retains exact optional manifest key presence. Native exports regenerate producer/time/checksums and do not carry an imported signature over changed bytes.
For intentionally archival operations, use the separately exported `importFullV1Snapshot(db, data, options?)` and `exportFullV1Snapshot(db, blobStore)` from `@fortemi/core`. The exact tuple must be `2.0.0/full-v1`; both return existing report-bearing result types. Snapshot import options are `conflictStrategy`, `blobStore`, `verifySignature`, and `trustStore` (`FullV1SnapshotImportOptions`). Required attachment bytes must be preserved. Malformed input returns a failure before storage mutation. The conflict unit is the persisted archive tuple: `skip` accepts identical archive bytes only, `replace` replaces that snapshot, and `error` rejects an existing snapshot. These operations do not merge native notes, index imported content, or include later native changes. Archival counts are in `component_counts`; native `counts.notes` remains zero. This separation does not close #424 or widen the historical cross-repository receipt.
function importShard(
db: DatabaseClient,
data: Uint8Array | ArrayBuffer,
options?: ImportOptions,
): Promise<ImportResult>
type ConflictStrategy = 'skip' | 'replace' | 'error'
interface ImportOptions {
conflictStrategy?: ConflictStrategy // default 'skip'
batchSize?: number // applied component rows between yields; default 250, zero disables yields
onProgress?: (progress: ImportProgress) => void
blobStore?: BlobStore // destination for hydrating sidecar attachment bytes
}
interface ImportResult {
success: boolean
counts: ImportCounts // per-component imported-row counts
skipped: Partial<ImportCounts>
warnings: string[]
errors: string[]
duration_ms: number
}
Native progress counts declared component rows, including skipped rows as done; nested attachments and communities are fields of their owning row. Callbacks are awaited. Invalid batch sizes and precommit callback failures return a failed import without committed state. The final `index` notification follows commit; its rejection retains `success: true` and adds a warning. Unsigned imports under `verifySignature: 'prefer'` also warn that publisher provenance was not verified.
Structured-error contract: a malformed manifest or component resolves to `{ success: false, errors: [...] }` — the promise does not reject. With `blobStore`, verified sidecar entries are promoted before the logical transaction; a failure rolls back newly promoted hashes and logical writes, while hashes that existed before import remain untouched. Custom stores without the optional `delete` capability fail before promotion. Without a blob store, attachments import as reference-only metadata. In legacy unprofiled `replace` mode, imported-note relationships converge to the archive and older live records cannot revive newer destination tombstones. These legacy semantics do not expand the named `core-v1` contract.
`openShard(source, options?)`
function openShard(source: ShardReaderSource, options?: OpenShardOptions): Promise<ShardReader>
interface OpenShardOptions {
baseUrl?: string
fetchImpl?: typeof fetch
semantic?: StaticSemanticProvider
maxCachedMatches?: number
maxComponentBytes?: number
}
interface ShardReader {
readonly manifest: ShardManifest
listNotes(options?: ShardListOptions): Promise<{ items: ShardReaderNote[]; total: number }>
getNote(id: string): Promise<ShardReaderNote | null>
getNoteFull(id: string): Promise<ShardNoteFull | null>
search(query: string, options?: ShardSearchOptions): Promise<ShardSearchResult>
semantic(query: string, k?: number): Promise<Array<{ note: ShardReaderNote; score: number }>>
linksOf(id: string): Promise<ShardLink[]>
conceptsOf(id: string): Promise<ShardSkosConcept[]>
relationsOf(conceptId: string): Promise<ShardSkosRelation[]>
provenanceOf(id: string): Promise<ShardProvenanceEdge[]>
close(): void
}
Read a shard in place — no database, no import. Components are fetched and checksum-validated lazily as each is first read (not up front), so opening is cheap even for large shards; clustered-note layouts (`clusterNotesSize` at export) fetch only the clusters they need. Throws when the shard's `min_reader_version` exceeds this build — fall back to `importShard`.
`createCosineSemanticProvider(options)`
function createCosineSemanticProvider(options: {
embedQuery: (query: string) => Promise<number[]> | number[]
vectorsFile?: string // default 'vectors.jsonl'
vectors?: VectorEntry[] // supply vectors directly instead of reading a file
}): StaticSemanticProvider
Brute-force cosine provider for `openShard`'s `semantic` option. Fine for small/demo corpora; supply a prebuilt-ANN `StaticSemanticProvider` for full-size corpora.
Prefetch / warm API
function prefetchShard(url: string, options?: PrefetchOptions): Promise<PrefetchResult>
function fromPrefetched(url: string): Uint8Array // throws if not prefetched
function isShardPrefetched(url: string): boolean
function getPrefetchedSha256(url: string): string | undefined
function clearPrefetchedShard(url?: string): void // omit url to clear all
`prefetchShard(url, { expectedSha256 })` downloads (optionally via Cache Storage) and verifies the whole-archive SHA-256, warming the bytes for a later `openShard(fromPrefetched(url))` or `importShard`.
Schema validation and low-level helpers
| Export | Purpose |
|---|---|
| `validateShardArchive` / `validateShardManifest` / `validateShardComponentRecord` / `assertShardComponentRecord` | Validate against the local `knowledge-shard.schema.json` copy; this is a structural gate, not server-compatibility evidence, until the copy has a pinned server receipt and cross-repository fixtures; returns `ShardSchemaValidationResult` |
| `getKnowledgeShardSchema()` | The parsed JSON Schema object |
| `packTarGz` / `unpackTarGz` | Tar + gzip primitives used by the pipelines |
| `sha256Hex` / `validateChecksums` | Component checksum helpers |
| `enforceSignaturePolicy` | The ADR-014 verify-before-persist gate shared by `importShard` and `importShardToRecords` |
| `noteToShard`, `noteFromShard`, `linkToShard`, `collectionToShard`, … | Per-entity field mappers between browser rows and server-parity shard JSON (one `xToShard`/`xFromShard` pair per component; see `shard/field-mapper.ts`) |
| `CURRENT_SHARD_VERSION` / `SHARD_FORMAT` | Format constants |
Service Worker
`registerServiceWorker(options)`
function registerServiceWorker(options?: {
scriptUrl?: string
scope?: string
}): Promise<ServiceWorkerRegistration>
Register the Fortemi service worker. The service worker intercepts requests to serve cached assets and route API-style requests to the in-process PGlite instance.
`createRoutes(db, events)`
function createRoutes(
db: PGlite,
events: TypedEventBus
): Route[]
Build the route table used by the service worker to dispatch incoming `fetch` events to the appropriate repository method.
`matchRoute(routes, request)`
function matchRoute(
routes: Route[],
request: Request
): RouteHandler | null
Find the handler for an incoming request by matching against the route table. Returns `null` if no route matches.
Worker Utilities
`PGliteWorkerClient`
class PGliteWorkerClient {
constructor(worker: Worker)
query<T>(sql: string, params?: unknown[]): Promise<T[]>
exec(sql: string): Promise<void>
transaction<T>(fn: (tx: TransactionProxy) => Promise<T>): Promise<T>
}
A `PGlite`-compatible client that proxies queries over a `Worker` message channel. Use this on the main thread when PGlite is running in a dedicated worker.
`TransactionProxy`
interface TransactionProxy {
query<T>(sql: string, params?: unknown[]): Promise<T[]>
exec(sql: string): Promise<void>
}
Handle passed to transaction callbacks in `PGliteWorkerClient.transaction`. Scoped to the in-flight transaction.
AIWG Index (`@fortemi/core/aiwg-index`)
Database-free tooling for AIWG Fortemi index exports (`aiwg.fortemi.index.export.v1`/`v2`): validate, query, chunk, embed, and project static index files without PGlite. Import from the subpath:
import { createAiwgIndexController, queryAiwgFortemiIndex } from '@fortemi/core/aiwg-index'
The schema authority is vendored at `packages/core/schemas/aiwg-fortemi-index-export.schema.json` (pinned with a provenance receipt).
This subpath is intentionally limited to dependency-free static-index helpers. Build pipelines that convert an AIWG export into a validated Knowledge Shard must import `aiwgFortemiIndexToKnowledgeShard` or `aiwgFortemiIndexToKnowledgeShardWithReport` from `@fortemi/core/aiwg-index-shard`. Full-runtime consumers may continue importing the converters from the top-level `@fortemi/core` entry. The archive-only entry emits the reversible schema 1.2.0 `core-v1` profile. The report-bearing entry emits exact `2.0.0/full-v1` only for lossless input and validates all 33 component files. Defaulted or unavailable AIWG concepts return `archive: null` plus typed loss entries. PGlite import/export support for that exact tuple does not imply unqualified server, RecordStore, or AIWG semantic parity.
`createAiwgIndexController(initialIndex?)`
function createAiwgIndexController(initialIndex?: AiwgFortemiIndexExport): AiwgIndexController
interface AiwgIndexController {
// Loading
loadIndex(value: unknown): AiwgFortemiIndexExport
loadChunkedIndex(manifest: unknown, loader: AiwgChunkedIndexLoader, options?: AiwgChunkedIndexLoadOptions): AiwgFortemiChunkManifest
getIndex(): AiwgFortemiIndexExport | null
getChunkedManifest(): AiwgFortemiChunkManifest | null
getSnapshot(): AiwgIndexControllerSnapshot
// Query
query(query?: string, options?: AiwgIndexQueryOptions): AiwgIndexQueryResult
queryChunked(query?: string, options?: AiwgChunkedIndexQueryOptions): Promise<AiwgChunkedIndexQueryResult>
getRecord(id: string): Promise<AiwgFortemiRecord> // resolves projected records via the detail loader
// Relationship traversal
neighbors(id: string, options?: AiwgRelationshipTraversalOptions): Promise<AiwgRelationshipTraversalResult>
relationshipQuery(options?: AiwgRelationshipQueryOptions): Promise<AiwgRelationshipTraversalResult>
relationshipSet(options: AiwgRelationshipSetOptions): Promise<AiwgRelationshipSetResult>
// Graph projection
toCommunityGraph(options?: AiwgIndexGraphOptions): CommunityGraph
toCommunityGraphChunked(options?: AiwgIndexGraphOptions & { onProgress?: (p: AiwgChunkedIndexProgress) => void }): Promise<CommunityGraph>
// Review workflow
setReviewDecision(input: AiwgReviewInput): AiwgReviewDecision
clearReviewDecision(itemId: string): void
createReviewDecisionExport(generatedAt?: string): AiwgReviewDecisionExport
// Lifecycle
clearChunkCache(): void
subscribe(listener: AiwgIndexControllerListener): () => void
}
Stateful controller over a whole or chunked index: load once, then query, traverse relationships, project to a `CommunityGraph`, and record review decisions. `subscribe` notifies on load/decision changes.
Query functions
function queryAiwgFortemiIndex(
index: AiwgFortemiIndexExport,
query?: string,
options?: AiwgIndexQueryOptions,
): AiwgIndexQueryResult
interface AiwgIndexQueryOptions {
types?: string[]
facets?: Record<string, string[]>
tags?: string[]
concepts?: string[]
privacy?: AiwgPrivacyClassification[] // 'private' | 'sanitized' | 'public'
relationshipTargetId?: string
limit?: number
offset?: number
rank?: boolean
snippets?: boolean
snippetLength?: number
weights?: Partial<AiwgIndexQueryWeights> // title/text/tag/concept/facet/id/source
includeMatches?: boolean
searchProfile?: 'default' | 'aiwg-discovery'
}
interface AiwgIndexQueryResult {
items: AiwgFortemiRecord[]
total: number
facets: Record<string, Record<string, number>>
rankedItems?: AiwgIndexQueryRankedItem[] // when rank: true — rank, snippet, matches
}
// Semantic and hybrid variants over a static embedding set
function queryAiwgSemanticIndex(index, embeddingSet, queryEmbedding: number[], options?): AiwgStaticSemanticResult[]
function queryAiwgHybridIndex(index, embeddingSet, query: string, queryEmbedding: number[], options?): AiwgStaticHybridResult[]
Chunked indexes
function buildAiwgChunkedIndex(index: AiwgFortemiIndexExport, options?: { partSize?: number /* default 500 */, ... }): AiwgChunkedIndexBuildResult
function createAiwgFetchChunkLoader(baseUrl?: string | URL): AiwgChunkedIndexLoader
function createAiwgFetchDetailLoader(baseUrl?: string | URL): AiwgChunkedIndexDetailLoader
`buildAiwgChunkedIndex` splits a whole index into a manifest + fixed-size parts (optionally search-projected records with per-id detail files) for static hosting. The fetch loaders resolve part/detail `href`s against `baseUrl`. v2 exports with `source.graph` are supported end-to-end.
Static embeddings
function buildAiwgStaticEmbeddingSet(
index: AiwgFortemiIndexExport,
options: BuildAiwgStaticEmbeddingSetOptions, // embed callback + granularity ('body' default)
): Promise<AiwgStaticEmbeddingSet>
function findAiwgStaticDuplicatePairs(index, embeddingSet, threshold = 0.9, options?): AiwgStaticDuplicatePair[]
Validators
Every validator has a `validate` form returning `{ valid, errors }` — total on hostile input, never throws — and an `assert` form that throws on invalid input:
| Validate (total) | Assert (throwing) | Target |
|---|---|---|
| `validateAiwgFortemiIndexExport` | `assertAiwgFortemiIndexExport` | Whole index export (v1/v2) |
| `validateAiwgFortemiChunkManifest` | `assertAiwgFortemiChunkManifest` | Chunk manifest |
| `validateAiwgFortemiChunkPart` | `assertAiwgFortemiChunkPart` | Chunk part (checked against source export version) |
| `validateAiwgStaticEmbeddingSet` | `assertAiwgStaticEmbeddingSet` | Static embedding set |
Projection and utilities
| Export | Purpose |
|---|---|
| `aiwgFortemiIndexToCommunityGraph(index, options?)` | Project records + relationships to a `@fortemi/graph` `CommunityGraph` (relationship weights, community strategy via `AiwgIndexGraphOptions`) |
| `filterAiwgRecordsByPrivacy(records, options?)` | Drop records above the allowed privacy classification (fails closed on unknown values) |
| `getAiwgFortemiFacets(items)` | Facet-name → value → count aggregation |
| `createAiwgReviewDecisionExport(source, decisions, generatedAt?)` | Serialize review decisions for round-trip to AIWG |
| `resolveAiwgFetchUrl` / `encodeAiwgDetailId` / `aiwgDetailHrefForId` | URL/id helpers used by the fetch loaders |
| `AIWG_SCAN_REQUIRED_FIELDS` / `DEFAULT_AIWG_DUPLICATE_SCAN_MAX_EMBEDDINGS` | Constants |
@fortemi/graph
Framework-agnostic graph tooling. The projection helpers operate on plain `CommunityGraph` data (structurally identical to what `@fortemi/core` produces), so a graph from `GraphRepository` or `aiwgFortemiIndexToCommunityGraph` drops straight in. `@fortemi/react`'s `GraphView` is built on these helpers, and JS-only hosts can use them to render their own SVG/canvas views without React or PGlite. All projection helpers are pure (no input mutation), deterministic, and database-free, so they tree-shake cleanly. The package depends on `@fortemi/core` for `GraphController` (the graph-source state machine), which reaches core's repositories.
pnpm add @fortemi/graph
Graph Data Model
interface GraphNode { id: string }
interface GraphEdge { source: string; target: string; weight: number; kind?: string }
interface GraphCommunity { id: string; nodes: string[] }
interface CommunityGraph {
nodes: GraphNode[]
edges: GraphEdge[]
communities: GraphCommunity[]
}
type GraphLayoutAlgorithm = 'force' | 'radial' | 'community' | 'manual'
interface PositionedGraphNode extends GraphNode {
x: number
y: number
degree: number
communityId?: string
}
interface PositionedGraph {
nodes: PositionedGraphNode[]
edges: GraphEdge[]
nodeIndex: Map<string, PositionedGraphNode>
}
interface GraphBounds {
minX: number; minY: number; maxX: number; maxY: number
width: number; height: number; centerX: number; centerY: number
}
interface ViewportTransform { scale: number; offsetX: number; offsetY: number }
`@fortemi/graph` re-exports `CommunityGraph` and its member types for hosts that do not depend on `@fortemi/core`. Community detection (`detectCommunities`) lives in `@fortemi/core`, which remains the base layer; this package only projects graphs it is given.
Graph Helpers
// Layout — deterministic 2D positions + per-node degree/community
interface LayoutOptions {
algorithm?: GraphLayoutAlgorithm
width?: number
height?: number
seed?: number // deterministic PRNG seed (`force`)
ticks?: number // settlement iterations (`force`)
nodeRadius?: NodeRadiusResolver // per-node render radius
linkDistance?: number // target edge length px (`force`)
linkStrength?: number // spring stiffness 0..1 (`force`)
chargeStrength?: number // repulsion magnitude (`force`)
collisionPadding?: number // extra collision spacing (`force`)
communityStrength?: number // pull toward community centroid (`force`)
boundsPadding?: number // min distance from canvas edges
pinned?: PositionMap // positions held fixed during settlement
initialPositions?: PositionMap // warm-start seed positions
}
function layoutCommunityGraph(
graph: CommunityGraph,
options?: LayoutOptions,
): PositionedGraph
// Filter — by community, edge kind, node allow-list, or predicate
interface GraphFilter {
communityIds?: string[]
edgeKinds?: string[]
nodeIds?: string[]
nodePredicate?: (node: GraphNode) => boolean
}
function filterCommunityGraph(graph: CommunityGraph | null | undefined, filter?: GraphFilter): CommunityGraph
// Sizing
function computeDegrees(graph: CommunityGraph): Map<string, number>
function nodeRadius(
degree: number,
options?: { base?: number; perDegree?: number; min?: number; max?: number },
): number
// Color — deterministic community → color (themeable palette)
const COMMUNITY_COLORS: readonly string[]
const UNASSIGNED_COMMUNITY_COLOR: string
function colorForCommunity(communityId: string | undefined, palette?: readonly string[], unassignedColor?: string): string
// Bounds / fit
function computeGraphBounds(nodes: ReadonlyArray<{ x: number; y: number }>): GraphBounds
function fitGraphToViewport(
bounds: GraphBounds,
viewport: { width: number; height: number },
options?: { padding?: number; minScale?: number; maxScale?: number },
): ViewportTransform
// Selection / neighborhood
function buildAdjacency(graph: CommunityGraph): Map<string, Set<string>>
function neighborsOf(graph: CommunityGraph, nodeId: string): Set<string>
function expandNeighborhood(
graph: CommunityGraph,
seeds: Iterable<string>,
options?: { depth?: number; includeSeeds?: boolean },
): Set<string>
function subgraphForNodes(graph: CommunityGraph, nodeIds: Iterable<string>): CommunityGraph
function neighborhoodSubgraph(graph: CommunityGraph, seeds: Iterable<string>, options?: { depth?: number; includeSeeds?: boolean }): CommunityGraph
// Static snapshots — reproducible JSON for JS-only hosts
interface GraphSnapshot {
version: number
graph: CommunityGraph
layout?: { algorithm: GraphLayoutAlgorithm; width: number; height: number }
generatedAt?: string
}
const GRAPH_SNAPSHOT_VERSION: number
function serializeGraphSnapshot(graph: CommunityGraph, options?: { layout?: GraphSnapshot['layout']; generatedAt?: string }): GraphSnapshot
function stringifyGraphSnapshot(graph: CommunityGraph, options?: { layout?: GraphSnapshot['layout']; generatedAt?: string }): string
function deserializeGraphSnapshot(input: GraphSnapshot | string): CommunityGraph
See `packages/graph/README.md` for a complete vanilla-JS host example that fetches a snapshot and renders it to SVG using these helpers.
Render Pipeline
Prepares a `CommunityGraph` for any renderer tier (SVG, Sigma 2D, 3D) as a `RenderGraph` — plain nodes/links with resolved colors, labels, and optionally baked positions.
// CommunityGraph → RenderGraph (colors + labels resolved, positions optional)
function mapCommunityGraph(graph: CommunityGraph, options?: MapCommunityGraphOptions): RenderGraph
// Layout + map in one step: positions baked in (x/y on every node)
function bakeRenderGraph(graph: CommunityGraph, options?: BakeRenderGraphOptions): RenderGraph
// Deterministic JSON (sorted nodes/links) for committing snapshots
function stringifyRenderGraph(graph: RenderGraph): string
// Load a snapshot from a URL, object, or factory; null on failure
function loadRenderSnapshot(
source: string | RenderGraph | (() => Promise<RenderGraph | null> | RenderGraph | null),
options?: { fetchImpl?: typeof fetch; requirePositions?: boolean }, // requirePositions default true
): Promise<RenderGraph | null>
// Guards and palette helpers
function isRenderGraph(value: unknown): value is RenderGraph
function hasBakedPositions(graph: RenderGraph): boolean
function communityRanks(graph: CommunityGraph): Map<string, number> // largest community = rank 0
const GREYSCALE_COMMUNITY_RAMP: readonly string[]
Control Contract
Every renderer tier honors the shared `GraphControlContract` — `algorithm`, `filters`, `selectedNodeId` + `onSelectNode`, `onNavigate`, `labelFor`, `colors` — so switching tiers means passing the same options object.
interface GraphControlFilters {
communityIds?: string[]
edgeKinds?: string[]
nodeIds?: string[]
minDegree?: number
}
// The one filter function every tier runs — visibility is identical across tiers
function applyControlFilters(graph: CommunityGraph | null | undefined, filters?: GraphControlFilters): CommunityGraph
// Shared legend data: { communityId, color, count }[]
function communityLegend(graph: CommunityGraph | null | undefined, colors?: readonly string[]): GraphLegendEntry[]
SVG Renderer
function renderCommunityGraph(
container: HTMLElement,
graph: CommunityGraph,
options?: GraphRenderOptions, // extends GraphControlContract
): GraphRenderHandle
interface GraphRenderOptions extends GraphControlContract {
layoutOptions?: Omit<LayoutOptions, 'algorithm' | 'width' | 'height'>
background?: string // default '#fafafa'
interactive?: boolean // default true (pan/zoom/click)
minScale?: number
maxScale?: number
}
interface GraphRenderHandle {
update(next: GraphRenderUpdate): void // patch graph/filters/algorithm/selection/labels
focus(nodeId: string | null): void
destroy(): void
readonly element: SVGSVGElement
}
Dependency-free DOM/SVG renderer for vanilla-JS hosts — no React required.
GraphController
class GraphController {
constructor(db: GraphControllerDb, options?: GraphControllerOptions)
getState(): GraphControllerState
subscribe(listener: GraphControllerListener): () => void
start(): Promise<void>
refresh(): Promise<void>
setMode(mode: GraphSourceMode): void
setEmbeddingSetSelector(selector: EmbeddingSetSelector): void
setCommunitySource(sourceId: string | null): void
setFilters(filters: CommunityFilterDefinition): void
recompute(): Promise<void>
previewDynamicCommunity(filters: CommunityFilterDefinition): Promise<void>
saveCurrentCommunity(input: CommunityCreateInput): Promise<CommunitySourceDescriptor>
}
Graph-source state machine: selects the source mode, loads/derives the `CommunityGraph` through core's repositories, and publishes `GraphControllerState` to subscribers. This is the package's one dependency on `@fortemi/core`; the React `useGraphController` hook wraps it.
@fortemi/react
Dataset Workflow
Import the dataset workflow from the dedicated subpath:
import {
DatasetConnectorForm,
DatasetLineageView,
DatasetWorkflowMachine,
useDatasetWorkflow,
} from '@fortemi/react/dataset'
const workflow = useDatasetWorkflow(connectorSchema, datasetApi)
`DatasetWorkflowMachine` and `useDatasetWorkflow` coordinate schema-driven configuration, staged checks, bounded side-effect-free previews, immutable plan approval, cancellation/retry, status, redacted rejections, and bounded lineage traversal. All I/O is delegated to the supplied `DatasetWorkflowApi`; the React package does not implement a second ingest or lineage backend.
`createMcpDatasetWorkflowApi(options)` provides the Fortemi Server execution adapter. It composes an application-supplied connector API and authority request builder with an injected MCP `callTool` transport. The adapter accepts only the `fortemi-server-mcp` live-remote-persistence runtime and receipt-validation revision `1.0.1`, previews the exact request before execution, validates receipt structure/digests/request bindings locally, and invokes `verify` separately before promoting committed or degraded work to verified completion. A terminal acknowledgement without that receipt remains non-successful. Because `manage_dataset_execution` is not a lineage traversal authority, callers must inject `lineage.traverseLineage` explicitly.
The contract authority remains Fortemi `v2026.9.10` at `306bae55a41eff7e4f8b8349304765322865e6cc`. Fortemi `v2026.9.11` has release source `280e60e23a1f4bb52a2859ea211dee9e45f672be`; commit `b5f4dbebca289ebfee0dca88d2766700cd4c85df` introduced its producer-side declaration for signed consumer commit `6718e9f930193a12b8aed27b61ebf9c47871ac44`. That metadata does not replace the authority or qualify a live deployment.
function createMcpDatasetWorkflowApi(options: DatasetMcpWorkflowOptions): DatasetWorkflowApi
function validateDatasetRunReceiptV101(
value: unknown,
prepared?: DatasetPreparedReceiptBinding,
): { valid: boolean; errors: string[] }
The adapter accepts either a standard single-text MCP result envelope or an already decoded object. It does not resolve credential references, serialize connector secrets into plans, or claim live qualification from fixture success.
The subpath exports `DatasetConnectorForm`, `DatasetCheckView`, `DatasetPreviewView`, `DatasetPlanReview`, `DatasetRunView`, `DatasetStatusView`, `DatasetRejectionsView`, `DatasetLineageView`, and `DatasetWorkflowActions`. Credential references and password-shaped properties render as empty write-only inputs. Destructive reconciliation at or above the plan threshold requires the operator to enter the plan ID before approval.
`datasetStatusStoryFixtures` and `datasetTerminalRunStories` provide stable UI states for canonical, derived, cached, stale, degraded, unverifiable, offline-cold/warm, rejected, cancelled, and failed scenarios.
Provider
`FortemiProvider`
function FortemiProvider(props: FortemiProviderProps): JSX.Element
Context provider that initializes a `FortemiCore` instance and makes it available to the component tree. Must wrap all components that use Fortemi hooks.
<FortemiProvider persistence="opfs" archiveName="my-notes">
<App />
</FortemiProvider>
`FortemiProviderProps`
interface FortemiProviderProps {
persistence: PersistenceMode
archiveName?: string
executionMode?: 'main' | 'worker'
createWorker?: () => Worker
snapshotUrl?: string
snapshotExpectations?: DbSnapshotExpectations
children: React.ReactNode
}
| Property | Type | Required | Description | |
|---|---|---|---|---|
| `persistence` | `PersistenceMode` | Yes | Storage backend for the underlying PGlite instance | |
| `archiveName` | `string` | No | Archive name; uses a default when omitted | |
| `executionMode` | `'main' \ | 'worker'` | No | Run PGlite on the main thread (default) or in a worker |
| `createWorker` | `() => Worker` | No | Worker factory for `executionMode: 'worker'` | |
| `snapshotUrl` | `string` | No | Restore from a physical data-dir snapshot (#187) instead of migrating an empty DB — fetches `<url>` + `<url>.meta.json`, verifies the stamp, then loads pre-indexed (no migration/import/HNSW build). Main mode only. | |
| `snapshotExpectations` | `DbSnapshotExpectations` | No | Override the snapshot version-compatibility expectations | |
| `children` | `React.ReactNode` | Yes | Component subtree |
`useFortemiContext()`
function useFortemiContext(): FortemiContextValue
Returns the `FortemiContextValue` from the nearest `FortemiProvider`. Throws if called outside a provider.
`FortemiContextValue`
interface FortemiContextValue {
db: PGlite
events: TypedEventBus
archiveManager: ArchiveManager
capabilityManager: CapabilityManager
blobStore: BlobStore
}
The raw runtime objects exposed by the provider. Use the typed hooks below for most UI work; access `FortemiContextValue` directly when calling repositories or tool functions manually.
Hooks
All hooks re-render when relevant data changes via the event bus. Initial data is fetched on mount.
`useNotes(options?)`
function useNotes(options?: NoteListOptions): {
notes: NoteSummary[]
total: number
hasNext: boolean
loading: boolean
error: Error | null
refetch: () => void
}
Subscribe to a paginated, filtered list of notes. Re-fetches automatically on `note.created`, `note.updated`, `note.deleted`, and `note.restored` events.
`useNote(id)`
function useNote(id: string): {
note: NoteFull | null
loading: boolean
error: Error | null
refetch: () => void
}
Fetch and subscribe to a single note. Re-fetches when the note is updated or restored.
`useSearch()`
function useSearch(): {
data: SearchResponse | null
loading: boolean
error: Error | null
search: (query: string, options?: SearchOptions) => Promise<SearchResponse>
clear: () => void
}
Automatically dispatches to the best available search mode. When semantic capability is ready, generates a query embedding and passes it to `SearchRepository.search()`, enabling hybrid search (text + vector). When semantic is not available, falls back to text-only search.
`useSearchHistory()`
function useSearchHistory(): {
history: string[]
addEntry: (query: string) => void
removeEntry: (query: string) => void
clearHistory: () => void
}
Persists search queries to `localStorage` (key: `fortemi:search-history`, max 50 entries). Deduplicates entries with most recent first. Survives archive switches.
`useSearchSuggestions(history?)`
function useSearchSuggestions(history?: string[]): {
suggestions: Array<{ text: string; source: 'vocabulary' | 'history' }>
loading: boolean
getSuggestions: (prefix: string) => void
clearSuggestions: () => void
refreshVocabulary: () => Promise<void>
}
Loads vocabulary from `ts_stat` (top 500 words by document frequency) on mount. Merges with search history for prefix-matched suggestions. Pass the `history` array from `useSearchHistory` for history-augmented suggestions.
`useCreateNote()`
function useCreateNote(): {
createNote: (input: NoteCreateInput) => Promise<NoteFull>
loading: boolean
error: Error | null
}
Returns a `createNote` function that constructs a `NotesRepository` from context and calls `NotesRepository.create()`. The repository emits `note.created` and queues post-creation jobs.
`useUpdateNote()`
function useUpdateNote(): {
updateNote: (id: string, input: NoteUpdateInput) => Promise<NoteFull>
loading: boolean
error: Error | null
}
`useDeleteNote()`
function useDeleteNote(): {
deleteNote: (id: string) => Promise<void>
restoreNote: (id: string) => Promise<void>
loading: boolean
error: Error | null
}
Provides both soft-delete and restore in a single hook.
`useTags()`
function useTags(): {
tags: string[]
frequency: Array<{ tag: string; count: number }>
loading: boolean
error: Error | null
suggest: (partial: string) => Promise<string[]>
}
Loads all tags and frequency counts on mount. `suggest` performs an on-demand prefix query.
`useCollections()`
function useCollections(): {
collections: CollectionRow[]
loading: boolean
error: Error | null
createCollection: (input: { name: string; description?: string }) => Promise<CollectionRow>
deleteCollection: (id: string) => Promise<void>
addNotes: (collectionId: string, noteIds: string[]) => Promise<void>
removeNotes: (collectionId: string, noteIds: string[]) => Promise<void>
}
`useJobQueue(pollMs?)`
function useJobQueue(pollMs?: number): {
jobs: Array<{ jobId: string; jobType: JobType; status: string; createdAt: string }>
loading: boolean
}
Polls `getJobQueueStatus` at the specified interval (default: 2000 ms) and exposes the current queue state. Useful for displaying background processing indicators.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `pollMs` | `number` | `2000` | Polling interval in milliseconds |
`useRelatedNotes(noteId, limit?)`
function useRelatedNotes(noteId: string, limit?: number): {
links: RelatedNote[]
loading: boolean
}
interface RelatedNote {
noteId: string
title: string | null
confidence: number | null
linkType: string
direction: 'outbound' | 'inbound'
}
Returns semantically linked notes for the given note. Merges outbound and inbound links, deduplicates, and sorts by confidence descending. Auto-refreshes when a `linking` job completes for this note.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `noteId` | `string` | required | Note to find related notes for |
| `limit` | `number` | `3` | Maximum number of related notes to return |
`useNoteConcepts(noteId)`
function useNoteConcepts(noteId: string): {
concepts: NoteConcept[]
loading: boolean
}
interface NoteConcept {
conceptId: string
prefLabel: string
schemeName: string
schemeId: string
}
Returns SKOS concepts assigned to a note via the `note_skos_tag` table. Joins through `skos_concept` and `skos_scheme` to provide full label and scheme context. Auto-refreshes when a `concept_tagging` job completes for this note.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `noteId` | `string` | required | Note to retrieve concepts for |
`useNoteProvenance(noteId)`
function useNoteProvenance(noteId: string): {
events: ProvenanceEvent[]
loading: boolean
}
interface ProvenanceEvent {
timestamp: Date
type: 'created' | 'job' | 'revision' | 'provenance'
label: string
detail?: string
agent?: string
activity?: string
attributes?: Record<string, unknown> | null
}
Aggregates a chronological provenance timeline from existing data: note creation timestamp, stored note-scoped `provenance_edge` rows, completed job queue entries, and user/AI revisions. Stored W3C PROV-style rows expose their `activity`, `agent`, and `attributes` so UIs can render source lineage, derivation, confidence, and other metadata without raw SQL. Auto-refreshes when any job completes for this note.
Job results are summarized (e.g., `"3 links found"`, `"384-dim vector"`). Stored provenance attributes are passed through unchanged after JSON parsing.
| Parameter | Type | Default | Description |
|---|---|---|---|
| `noteId` | `string` | required | Note to retrieve provenance timeline for |
`useExportShard()`
function useExportShard(): {
exportShard: (options?: ExportOptions) => Promise<void>
isExporting: boolean
progress: ExportProgress | null
error: Error | null
report: ShardCapabilityReport | null
}
Exports current data through `exportShardWithReport` and triggers a browser download only after success. The default is `1.2.0/core-v1`: attachment references are included, attachment bytes and advanced components are excluded. `report` exposes the selected capability and every reported omission or normalization, including unsupported-profile failures. Render it alongside `error` in custom export UIs. Embedding, embedding-set, and materialized-selector options are rejected for `core-v1`; blob-sidecar and clustered-file requests are rejected by its producer. Explicit `full-v1` requires `schemaVersion: '2.0.0'` and its own supported-state requirements; the provider's BlobStore is supplied unless overridden.
The core `exportShard(db, options)` API still deliberately emits historical unprofiled React-local archives when no profile is supplied. Historical import behavior is unchanged. Those legacy envelopes are not renamed or advertised as a named server profile. The hook no longer selects that legacy producer by default. Cross-repository claims remain limited to exact receipt-bound profiles and participants; the suite audit remains `NO-GO`.
`useImportShard()`
function useImportShard(): {
importShard: (file: File, strategy?: ConflictStrategy) => Promise<ImportResult>
importFromUrl: (url: string, strategy?: ConflictStrategy, prefetchOptions?: PrefetchOptions) => Promise<ImportResult>
isImporting: boolean
progress: ImportProgress | null
error: Error | null
result: ImportResult | null
}
Imports a Knowledge Shard from a user-selected file or static URL. Warmed URLs from `useShardPrefetch` reuse prefetched bytes.
`useShardPrefetch()`
function useShardPrefetch(): {
prefetch: (url: string, options?: PrefetchOptions) => Promise<PrefetchResult>
isPrefetched: (url: string) => boolean
warming: Record<string, boolean>
error: Error | null
}
Warms static shard bytes in the background and optionally verifies SHA-256 before an import click.
`useGpuCapabilities()`
function useGpuCapabilities(): {
caps: GpuCapabilities | null
vramTier: VramTier | null
isDetecting: boolean
error: Error | null
}
Detects WebGPU capabilities and derives a VRAM tier for local inference planning.
`useInferenceCapabilities()`
function useInferenceCapabilities(): {
capabilities: InferenceCapabilities | null
loading: boolean
error: Error | null
}
Detects browser and hardware inference capabilities, including WebGPU/WebNN/Chrome AI availability.
`useInferenceRouting(options?)`
function useInferenceRouting(options?: {
tasks?: InferenceTask[]
}): {
providers: InferenceProvider[]
activeProvider: InferenceProvider | null
routeValidation: ProviderRouteValidation[]
routeIssues: ProviderRouteValidationIssue[]
refresh: () => void
setActiveProvider: (id: string) => void
setRoute: (task: InferenceTask, policy: ProviderRoutePolicy) => void
clearRoute: (task: InferenceTask) => void
clearRoutes: () => void
getRoute: (task: InferenceTask) => ProviderRoutePolicy | undefined
previewRoute: (
task: InferenceTask | undefined,
capability?: keyof ProviderCapabilities,
requestModel?: string,
) => ProviderRouteSelection
probeRoute: (
task: InferenceTask | undefined,
capability?: keyof ProviderCapabilities,
requestModel?: string,
) => Promise<ProviderRouteProbeResult>
}
React wrapper around the shared `ProviderRegistry`. It subscribes to provider and route configuration events so embedded host UIs can display provider lists, switch active providers, edit task routes, validate route configuration, and probe resolved routes without managing a separate routing store.
`useLocalDiscovery(options?)`
function useLocalDiscovery(options?: UseLocalDiscoveryOptions): {
providers: DiscoveredProvider[]
discovering: boolean
error: Error | null
refresh: () => void
}
Probes local inference servers on mount and on the configured interval.
`useEmbeddingPipeline(loader)`
function useEmbeddingPipeline(loader: EmbedFunctionLoader): {
embedFunction: EmbedFunction | null
status: 'idle' | 'loading' | 'ready' | 'error'
progress: string
error: Error | null
load: () => void
unload: () => void
}
Loads a host-provided embedding function on demand and registers it with `@fortemi/core`.
`useEmbeddingWorker(transport, options?)`
function useEmbeddingWorker(
transport: EmbedTransportPort,
options?: EmbedWorkerOptions
): {
status: 'idle' | 'connected'
connect: () => void
disconnect: () => void
}
Wires a host-owned `Worker` or `MessagePort` into the core embed function so query and job embedding run off the main thread.
Graph and community hooks
`useEmbeddingSets()`
function useEmbeddingSets(): {
embeddingSets: EmbeddingSetDescriptor[]
loading: boolean
error: Error | null
refresh: () => Promise<void>
create: (input: EmbeddingSetCreateInput) => Promise<EmbeddingSetRow>
createVirtualDefinition: (input: VirtualEmbeddingSetDefinition) => Promise<EmbeddingSetRow>
}
Lists physical and virtual embedding-set descriptors and creates new physical sets or durable virtual definitions.
`useSimilarityGraph(embeddingSet, options?)`
function useSimilarityGraph(
embeddingSet: string | EmbeddingSetSelector | null | undefined,
options?: SimilarityGraphOptions & { autoRefresh?: boolean }
): {
graph: CommunityGraph | null
graphSource: SimilarityGraphResult['graphSource'] | null
cache: SimilarityGraphResult['cache'] | null
freshness: SimilarityGraphResult['freshness'] | null
loading: boolean
error: Error | null
refresh: () => Promise<SimilarityGraphResult | null>
recompute: () => Promise<SimilarityGraphResult | null>
markStale: (reason: string) => Promise<void>
}
Loads cached precomputed similarity graphs when available, falls back to live computation, and exposes cache/freshness state for UI decisions.
`useCommunities()`
function useCommunities(): {
sources: CommunitySourceDescriptor[]
activeSourceId: string | null
summaries: CommunitySummary[]
assignments: Map<string, CommunityAssignmentView>
loading: boolean
error: Error | null
preview: (filters: CommunityFilterDefinition) => Promise<CommunityAssignmentView[]>
save: (input: CommunityCreateInput) => Promise<CommunitySourceDescriptor>
rerun: (sourceId: string) => Promise<CommunityAssignmentView[]>
setActiveSource: (sourceId: string | null) => void
refresh: (sourceId?: string | null) => Promise<void>
}
Works with persisted community sources and unsaved dynamic previews for search-derived or manually authored groupings.
`useGraphController(options?)`
function useGraphController(options?: UseGraphControllerOptions): {
mode: 'citations' | 'topics' | 'precomputed' | 'dynamic-search' | 'user-authored'
graph: CommunityGraph | null
graphSource?: SimilarityGraphResult['graphSource'] | { id: string; name: string }
communitySource?: CommunitySourceDescriptor
embeddingSetSelector?: EmbeddingSetSelector
filters?: CommunityFilterDefinition
layout: GraphLayoutState
status: GraphControllerStatus
transition?: GraphTransitionState
setMode: (mode: GraphSourceMode) => void
setEmbeddingSetSelector: (selector: EmbeddingSetSelector) => void
setCommunitySource: (sourceId: string | null) => void
setFilters: (filters: CommunityFilterDefinition) => void
refresh: () => Promise<void>
recompute: () => Promise<void>
previewDynamicCommunity: (filters: CommunityFilterDefinition) => Promise<void>
saveCurrentCommunity: (input: CommunityCreateInput) => Promise<CommunitySourceDescriptor>
}
Coordinates graph-source switching for citation, topic, precomputed, dynamic-search, and user-authored graph views without exposing raw SQL or graphology internals to React UI code.
`useCapabilitySetup(options)`
function useCapabilitySetup(options: UseCapabilitySetupOptions): {
ready: boolean
error: Error | null
}
Registers host-provided capability loaders and auto-enables previously enabled capabilities from local storage unless `autoEnable` is supplied.
`useAiwgIndex(initialIndex?)`
function useAiwgIndex(initialIndex?: AiwgFortemiIndexExport): {
index: AiwgFortemiIndexExport | null
chunkedManifest: AiwgFortemiChunkManifest | null
counts: Record<string, number>
data: AiwgIndexQueryResult | AiwgChunkedIndexQueryResult | null
error: Error | null
reviewDecisions: AiwgReviewDecision[]
loadIndex: (value: unknown) => AiwgFortemiIndexExport
loadChunkedIndex: (manifest: unknown, loader: AiwgChunkedIndexLoader, options?: AiwgChunkedIndexLoadOptions) => AiwgFortemiChunkManifest
search: (query?: string, options?: AiwgIndexQueryOptions) => AiwgIndexQueryResult
searchChunked: (query?: string, options?: AiwgChunkedIndexQueryOptions) => Promise<AiwgChunkedIndexQueryResult>
getRecord: (id: string) => Promise<AiwgFortemiRecord>
clearChunkCache: () => void
setReviewDecision: (input: AiwgReviewInput) => AiwgReviewDecision
clearReviewDecision: (itemId: string) => void
exportReviewDecisions: () => AiwgReviewDecisionExport
toCommunityGraph: (options?: AiwgIndexGraphOptions) => CommunityGraph
}
Loads whole or chunked AIWG Fortemi index exports, searches them, tracks review decisions, and projects loaded indexes into community graph data.
`useShard(source, options?)`
function useShard(source: ShardReaderSource, options?: OpenShardOptions): {
reader: ShardReader | null
manifest: ShardManifest | null
loading: boolean
error: Error | null
listNotes: (options?: ShardListOptions) => Promise<{ items: ShardReaderNote[]; total: number }>
getNote: (id: string) => Promise<ShardReaderNote | null>
search: (query: string, options?: ShardSearchOptions) => Promise<ShardSearchResult>
linksOf: (id: string) => Promise<ShardLink[]>
conceptsOf: (id: string) => Promise<ShardSkosConcept[]>
relationsOf: (conceptId: string) => Promise<ShardSkosRelation[]>
provenanceOf: (id: string) => Promise<ShardProvenanceEdge[]>
getNoteFull: (id: string) => Promise<ShardNoteFull | null>
semantic: (query: string, k?: number) => Promise<Array<{ note: ShardReaderNote; score: number }>>
}
Opens a Knowledge Shard for in-place, read-only browsing and search without importing it into PGlite.
`useRemote(config)`
function useRemote(config: RemoteBackendConfig): {
backend: RemoteDataBackend
loading: boolean
error: Error | null
listNotes: (options?: BackendListOptions) => Promise<{ items: BackendNote[]; total: number }>
getNote: (id: string) => Promise<BackendNote | null>
search: (query: string, options?: RemoteSearchOptions) => Promise<RemoteSearchResult>
getNoteFull: (id: string) => Promise<BackendNoteFull | null>
linksOf: (id: string) => Promise<BackendLink[]>
conceptsOf: (id: string) => Promise<BackendConcept[]>
provenanceOf: (id: string) => Promise<BackendProvenanceEdge[]>
provenanceGraphOf: (id: string) => Promise<RemoteProvenanceGraph>
semantic: (query: string, k?: number) => Promise<BackendSearchHit[]>
semanticWithReport: (query: string, k?: number) => Promise<RemoteSearchResult>
manageNote: (input: unknown) => Promise<RemoteManageNoteResult>
}
Wraps `createRemoteBackend(config)` for React and exposes the Fortemi server-tier `DataBackend` surface with shared loading and error state.
Graph Views and Subpath Exports
Three renderer tiers over the same `@fortemi/graph` data, each on its own subpath so heavy dependencies never enter a consumer's bundle unless imported:
| Subpath | Component | Extra deps | Best for |
|---|---|---|---|
| `@fortemi/react/graph` | `GraphView` | none (PGlite-free; React + `@fortemi/graph` only) | Static SVG rendering, docs-map/static tenants |
| `@fortemi/react/graph-2d` | `SigmaGraphView` | `sigma` + `graphology` + `graphology-layout-forceatlas2` (optional peers, lazy-loaded) | Live force settling, camera focus, LOD labels |
| `@fortemi/react/graph-3d` | `ForceGraph3DView` | `react-force-graph-3d` + `three` (optional peers, lazy via `React.lazy`) | Orbitable 3D force graph, 2D/3D toggle |
`GraphView` is also re-exported from the package root; `SigmaGraphView` and `ForceGraph3DView` are subpath-only, so their renderers never enter the root module graph. Import `GraphView` from `@fortemi/react/graph` (rather than the root) when you also want to keep PGlite out of your bundle.
`GraphView`
interface GraphViewProps {
graph: CommunityGraph | null
layout?: Partial<GraphLayoutState>
filters?: GraphViewFilters
selectedNodeId?: string | null
onSelectNode?: (nodeId: string) => void
onNavigate?: (nodeId: string) => void
labelFor?: (nodeId: string) => string
draggableNodes?: boolean
width?: number
height?: number
style?: CSSProperties
}
Static SVG renderer built on `layoutCommunityGraph`/`filterCommunityGraph`. Carries no runtime dependency on `@fortemi/core` (type-only `CommunityGraph` import, erased at build).
`SigmaGraphView`
interface SigmaGraphViewProps {
graph: CommunityGraph | RenderGraph | null
snapshot?: string | RenderGraph // URL or prebaked RenderGraph; skips live layout
filters?: GraphControlFilters
labelFor?: (id: string) => string
palette?: CommunityPalette
onSelectNode?: (nodeId: string) => void
onOpenNode?: (nodeId: string) => void
settleMs?: number // ForceAtlas2 settle duration
theme?: Partial<SigmaTheme>
height?: number | string
style?: CSSProperties
}
Interactive 2D explorer backed by Sigma + graphology ForceAtlas2, dynamically imported on mount. Renders an install hint if the optional peers are missing.
`ForceGraph3DView`
interface ForceGraph3DViewProps {
graph: CommunityGraph | RenderGraph | null
snapshot?: string | RenderGraph
filters?: GraphControlFilters
labelFor?: (id: string) => string
palette?: CommunityPalette
onSelectNode?: (nodeId: string) => void
onOpenNode?: (nodeId: string) => void
theme?: Partial<ForceGraph3DTheme>
height?: number | string
style?: CSSProperties
}
3D force-directed view backed by `react-force-graph-3d` (Three.js), loaded lazily so Three only ships when the view mounts. Accepts the same data and `GraphControlFilters` as the 2D tiers — 2D/3D parity by construction.