Skip to main content

IReactorClient

The IReactorClient interface is the primary way to interact with a Powerhouse reactor programmatically. It wraps lower-level APIs to provide a simpler, Promise-based interface for document operations.

import type { IReactorClient } from "@powerhousedao/reactor-browser";

:::info Import paths @powerhousedao/reactor-browser re-exports all reactor types for convenience in browser environments (editors, drive-apps, subgraphs). If you are working outside the browser — for example in a standalone Node.js script, CLI tool, or server-side processor — import directly from @powerhousedao/reactor. :::

For an architectural overview of the reactor, see Working with the Reactor. For the low-level IReactor interface and access to internal components, see Advanced Reactor Usage.

Common parameter types​

Several types appear across multiple methods. They are described here once.

ViewFilter​

Targets a specific branch, scopes, or revision when reading documents.

type ViewFilter = {
branch?: string;
scopes?: string[];
revision?: number;
};
FieldDescription
branchThe branch to read from (e.g. "main")
scopesScopes to include (e.g. ["global"])
revisionRead the document at a specific revision number

SearchFilter​

Narrows which documents a query returns.

type SearchFilter = {
type?: string;
parentId?: string;
ids?: string[];
slugs?: string[];
};

PagingOptions​

Controls pagination for list methods.

type PagingOptions = {
cursor: string;
limit: number;
};

PagedResults<T>​

Returned by all list methods. Includes a next() helper for fetching the next page.

type PagedResults<T> = {
results: T[];
options: PagingOptions;
next?: () => Promise<PagedResults<T>>;
nextCursor?: string;
totalCount?: number;
};

PropagationMode​

Controls how deletions handle child documents.

enum PropagationMode {
None = "none", // Only delete the specified document
Cascade = "cascade", // Also delete all child documents
}

CreateDocumentOptions​

Options for createEmpty().

type CreateDocumentOptions = {
parentIdentifier?: string; // id or slug of parent document
documentModelVersion?: number; // defaults to latest
};

JobInfo​

Tracks the status and result of a mutation job.

type JobInfo = {
id: string;
status: JobStatus;
createdAtUtcIso: string;
completedAtUtcIso?: string;
error?: ErrorInfo;
consistencyToken: ConsistencyToken;
meta: JobMeta;
};

See Job lifecycle for details on JobStatus values.


Pagination best practices​

All list methods (find, getOutgoingRelationships, getIncomingRelationships, getOperations, getDocumentModelModules) accept PagingOptions and return PagedResults<T>. Here are some guidelines for working with paginated results effectively.

Use next() for sequential iteration. The next() helper on PagedResults handles cursor management for you:

let page = await reactorClient.find({ type: "powerhouse/todo-list" });

while (page) {
for (const doc of page.results) {
console.log(doc.header.id);
}
page = page.next ? await page.next() : undefined;
}

Set a reasonable limit. The default page size varies by method. If you know you only need a few results, set a small limit to reduce response size:

const topFive = await reactorClient.getOutgoingRelationships(
driveId,
"child",
undefined,
{
cursor: "",
limit: 5,
},
);

Check nextCursor to know if more pages exist. When nextCursor is undefined, you have reached the end:

const page = await reactorClient.getOperations(docId);
if (page.nextCursor) {
// There are more operations to fetch
}

Avoid fetching all pages in tight loops for large datasets. If you are processing thousands of documents or operations, consider processing each page before fetching the next to keep memory usage predictable.


Cancellation with AbortSignal​

Most IReactorClient methods accept an optional AbortSignal parameter. This lets you cancel in-flight requests — useful for cleaning up when a component unmounts, a user navigates away, or a timeout is reached.

Cancel on component unmount (React):

useEffect(() => {
const controller = new AbortController();

reactorClient
.find(
{ type: "powerhouse/todo-list" },
undefined,
undefined,
controller.signal,
)
.then(setResults)
.catch((err) => {
if (err.name !== "AbortError") throw err;
});

return () => controller.abort();
}, []);

Cancel with a timeout:

const result = await reactorClient.get(
docId,
undefined,
AbortSignal.timeout(5000),
);

Cancel long-running writes. Write methods like execute() wait for the job to reach READ_READY. If this takes too long, an abort signal lets you bail out:

const controller = new AbortController();

// Set a 10-second deadline
setTimeout(() => controller.abort(), 10_000);

const updated = await reactorClient.execute(
docId,
"main",
actions,
controller.signal,
);

When a request is aborted, the method throws an AbortError. The underlying reactor job may still complete — aborting only cancels the client-side wait, not the server-side processing.


Read methods​

get​

Retrieve a single document by id or slug.

get<TDocument extends PHDocument>(
identifier: string,
view?: ViewFilter,
signal?: AbortSignal,
): Promise<TDocument>

Parameters:

NameTypeRequiredDescription
identifierstringYesDocument id or slug
viewViewFilterNoBranch, scopes, or revision filter
signalAbortSignalNoCancel the request

Example:

const doc = await reactorClient.get("my-todo-list");
const atRevision = await reactorClient.get("my-todo-list", { revision: 5 });

getOutgoingRelationships​

List documents that the given source has outgoing relationships to, filtered by relationship type. For example, pass "child" to list a parent's children.

getOutgoingRelationships(
sourceIdentifier: string,
relationshipType: string,
view?: ViewFilter,
paging?: PagingOptions,
signal?: AbortSignal,
): Promise<PagedResults<PHDocument>>

Parameters:

NameTypeRequiredDescription
sourceIdentifierstringYesSource document id or slug
relationshipTypestringYesRelationship type to filter by (e.g. "child")
viewViewFilterNoBranch/scopes filter
pagingPagingOptionsNoPagination cursor and limit
signalAbortSignalNoCancel the request

getIncomingRelationships​

List documents that have incoming relationships pointing at the given target, filtered by relationship type. For example, pass "child" to list a document's parents.

getIncomingRelationships(
targetIdentifier: string,
relationshipType: string,
view?: ViewFilter,
paging?: PagingOptions,
signal?: AbortSignal,
): Promise<PagedResults<PHDocument>>

Parameters:

NameTypeRequiredDescription
targetIdentifierstringYesTarget document id or slug
relationshipTypestringYesRelationship type to filter by (e.g. "child")
viewViewFilterNoBranch/scopes filter
pagingPagingOptionsNoPagination cursor and limit
signalAbortSignalNoCancel the request

find​

Search for documents matching criteria.

find(
search: SearchFilter,
view?: ViewFilter,
paging?: PagingOptions,
signal?: AbortSignal,
): Promise<PagedResults<PHDocument>>

Parameters:

NameTypeRequiredDescription
searchSearchFilterYesFilter by type, parentId, ids, or slugs
viewViewFilterNoBranch/scopes filter
pagingPagingOptionsNoPagination cursor and limit
signalAbortSignalNoCancel the request

Example:

const todoLists = await reactorClient.find({
type: "powerhouse/todo-list",
parentId: driveId,
});

for (const doc of todoLists.results) {
console.log(doc.header.id, doc.header.name);
}

getOperations​

Retrieve the operation history of a document.

getOperations(
documentIdentifier: string,
view?: ViewFilter,
filter?: OperationFilter,
paging?: PagingOptions,
signal?: AbortSignal,
): Promise<PagedResults<Operation>>

Parameters:

NameTypeRequiredDescription
documentIdentifierstringYesDocument id or slug
viewViewFilterNoBranch/scopes filter
filterOperationFilterNoFilter by action types, timestamps, or revision
pagingPagingOptionsNoPagination cursor and limit
signalAbortSignalNoCancel the request

OperationFilter:

interface OperationFilter {
actionTypes?: string[]; // e.g. ["ADD_TODO_ITEM"]
timestampFrom?: string; // ISO string
timestampTo?: string; // ISO string
sinceRevision?: number; // operations with index >= this value
}

getDocumentModelModules​

List registered document model modules.

getDocumentModelModules(
namespace?: string,
paging?: PagingOptions,
signal?: AbortSignal,
): Promise<PagedResults<DocumentModelModule>>

Parameters:

NameTypeRequiredDescription
namespacestringNoFilter by namespace (e.g. "powerhouse", "sky")
pagingPagingOptionsNoPagination cursor and limit
signalAbortSignalNoCancel the request

getDocumentModelModule​

Get a specific document model module by document type.

getDocumentModelModule(
documentType: string,
): Promise<DocumentModelModule>

Parameters:

NameTypeRequiredDescription
documentTypestringYese.g. "powerhouse/todo-list"

Write methods​

All write methods internally create jobs and wait for them to reach READ_READY before resolving (except executeAsync which returns immediately).

create​

Create a document from a full PHDocument object.

create<TDocument extends PHDocument>(
document: PHDocument,
parentIdentifier?: string,
signal?: AbortSignal,
): Promise<TDocument>

Parameters:

NameTypeRequiredDescription
documentPHDocumentYesDocument with optional id, slug, type, and initial state
parentIdentifierstringNoId or slug of parent document
signalAbortSignalNoCancel the request

createEmpty​

Create an empty document of a given type.

createEmpty<TDocument extends PHDocument>(
documentModelType: string,
options?: CreateDocumentOptions,
signal?: AbortSignal,
): Promise<TDocument>

Parameters:

NameTypeRequiredDescription
documentModelTypestringYese.g. "powerhouse/todo-list"
optionsCreateDocumentOptionsNoParent identifier and/or model version
signalAbortSignalNoCancel the request

createDocumentInDrive​

Create a document inside a drive as a single batched operation. More efficient than createEmpty followed by addRelationship because all actions are batched into dependent jobs.

createDocumentInDrive<TDocument extends PHDocument>(
driveId: string,
document: PHDocument,
parentFolder?: string,
signal?: AbortSignal,
): Promise<TDocument>

Parameters:

NameTypeRequiredDescription
driveIdstringYesDrive document id or slug
documentPHDocumentYesThe document to create
parentFolderstringNoFolder id within the drive
signalAbortSignalNoCancel the request

execute​

Apply actions to a document and wait for completion.

execute<TDocument extends PHDocument>(
documentIdentifier: string,
branch: string,
actions: Action[],
signal?: AbortSignal,
): Promise<TDocument>

Parameters:

NameTypeRequiredDescription
documentIdentifierstringYesDocument id or slug
branchstringYesBranch to apply actions to (e.g. "main")
actionsAction[]YesList of actions to apply
signalAbortSignalNoCancel the request

Returns the updated document after all actions are applied and read models are updated.

Example:

import { actions } from "my-package/document-models/todo-list";

const updated = await reactorClient.execute(docId, "main", [
actions.addTodoItem({ text: "Buy groceries" }),
actions.addTodoItem({ text: "Walk the dog" }),
]);

executeAsync​

Submit actions without waiting for completion. Returns a JobInfo at PENDING status.

executeAsync(
documentIdentifier: string,
branch: string,
actions: Action[],
signal?: AbortSignal,
): Promise<JobInfo>

Use waitForJob() or getJobStatus() to track progress.


rename​

Rename a document.

rename(
documentIdentifier: string,
name: string,
branch?: string,
signal?: AbortSignal,
): Promise<PHDocument>

Parameters:

NameTypeRequiredDescription
documentIdentifierstringYesDocument id or slug
namestringYesNew name
branchstringNoDefaults to "main"
signalAbortSignalNoCancel the request

addRelationship​

Add a typed relationship from a source document to a target document. To add a child, pass "child" as the relationshipType.

addRelationship(
sourceIdentifier: string,
targetIdentifier: string,
relationshipType: string,
branch?: string,
signal?: AbortSignal,
): Promise<PHDocument>

Parameters:

NameTypeRequiredDescription
sourceIdentifierstringYesSource document id or slug
targetIdentifierstringYesTarget document id or slug
relationshipTypestringYesRelationship type (e.g. "child")
branchstringNoDefaults to "main"
signalAbortSignalNoCancel the request

removeRelationship​

Remove a typed relationship from a source document to a target document.

removeRelationship(
sourceIdentifier: string,
targetIdentifier: string,
relationshipType: string,
branch?: string,
signal?: AbortSignal,
): Promise<PHDocument>

Parameters:

NameTypeRequiredDescription
sourceIdentifierstringYesSource document id or slug
targetIdentifierstringYesTarget document id or slug
relationshipTypestringYesRelationship type (e.g. "child")
branchstringNoDefaults to "main"
signalAbortSignalNoCancel the request

moveRelationship​

Move a typed relationship from one source document to another, keeping the same target.

moveRelationship(
sourceParentIdentifier: string,
targetParentIdentifier: string,
targetIdentifier: string,
relationshipType: string,
branch?: string,
signal?: AbortSignal,
): Promise<{ source: PHDocument; target: PHDocument }>

Parameters:

NameTypeRequiredDescription
sourceParentIdentifierstringYesCurrent source document id or slug
targetParentIdentifierstringYesNew source document id or slug
targetIdentifierstringYesThe target document id or slug
relationshipTypestringYesRelationship type (e.g. "child")
branchstringNoDefaults to "main"
signalAbortSignalNoCancel the request

deleteDocument​

Delete a single document.

deleteDocument(
identifier: string,
propagate?: PropagationMode,
signal?: AbortSignal,
): Promise<void>

Parameters:

NameTypeRequiredDescription
identifierstringYesDocument id or slug
propagatePropagationModeNoCascade to also delete children
signalAbortSignalNoCancel the request

deleteDocuments​

Bulk delete multiple documents.

deleteDocuments(
identifiers: string[],
propagate?: PropagationMode,
signal?: AbortSignal,
): Promise<void>

Subscriptions​

subscribe​

Subscribe to document change events matching a filter.

subscribe(
search: SearchFilter,
callback: (event: DocumentChangeEvent) => void,
view?: ViewFilter,
): () => void

Returns an unsubscribe function.

DocumentChangeEvent:

type DocumentChangeEvent = {
type: DocumentChangeType;
documents: PHDocument[];
context?: {
parentId?: string;
childId?: string;
};
};

DocumentChangeType values:

ValueDescription
CreatedA new document was created
DeletedA document was deleted
UpdatedA document's state changed
ParentAddedA parent relationship was added
ParentRemovedA parent relationship was removed
ChildAddedA child relationship was added
ChildRemovedA child relationship was removed

Example:

// Watch for all todo-list changes
const unsubscribe = reactorClient.subscribe(
{ type: "powerhouse/todo-list" },
(event) => {
if (event.type === DocumentChangeType.Updated) {
console.log(
"Updated:",
event.documents.map((d) => d.header.id),
);
}
},
);

// Later, stop listening
unsubscribe();

Job tracking​

getJobStatus​

Check the current status of a job.

getJobStatus(
jobId: string,
signal?: AbortSignal,
): Promise<JobInfo>

waitForJob​

Wait for a job to reach a terminal status (READ_READY or FAILED).

waitForJob(
jobId: string | JobInfo,
signal?: AbortSignal,
): Promise<JobInfo>

Example:

const job = await reactorClient.executeAsync(docId, "main", actions);
console.log(job.status); // "PENDING"

const completed = await reactorClient.waitForJob(job);
console.log(completed.status); // "READ_READY" or "FAILED"