newOS for Developers
ProgressiveHandler
Signal-based reactive state management for async operations across newOS.
packages/newgraph-signals/src/ProgressiveHandler/
Core Pattern
All newgraph-signals actions use the ProgressiveHandler pattern to provide reactive state management with built-in progress tracking, error handling, and promise resolution.
// Every action returns a tuple: [signal, progress]
const [data, progress] = someAction();
// Execute the action
progress.value.exec(params);
// Wait for completion
await progress.value.promise;
// Access the result
console.log(data.value);
// Check loading state
if (progress.value.inProgress) { /* loading */ }
// Handle errors
if (progress.value.error) { /* error */ }ProgressEntry Class
The core state container for tracking async operation progress.
class ProgressEntry {
inProgress: boolean; // Currently executing
promise?: Promise<...>; // Awaitable result
page: number; // Pagination tracking
done: boolean; // Completed successfully
paused: boolean; // Execution paused
updated: string; // ISO timestamp of last update
key: string; // Unique identifier
error?: Error; // Error if failed
autostart?: boolean; // Auto-execute on creation
maxConcurrency: number; // Max parallel executions (default: 1)
_concurrency: number; // Current concurrent count
executions: number; // Total execution count
// Throttle configuration
throttle?: {
_lastCalled?: string;
wait?: number;
maxWait?: number;
leading?: number; // Delay before execution (ms)
trailing?: number; // Delay after execution (ms)
};
// Methods
exec(params: unknown): void; // Start execution
continue(params?: unknown): void;
reset(): void; // Reset to initial state
clone(pe: Partial<ProgressEntry>): ProgressEntry;
}Properties
| Property | Type | Description |
|---|---|---|
| inProgress | boolean | True while the action is executing |
| promise | Promise | Awaitable promise that resolves when complete |
| done | boolean | True after successful completion |
| error | Error | undefined | Error object if execution failed |
| page | number | Current page for paginated operations |
| maxConcurrency | number | Maximum concurrent executions (default: 1) |
| autostart | boolean | Auto-execute when handler is created |
progressiveHandler
Factory function that creates a reactive handler for async operations.
progressiveHandler<T>(
params: Record<string, any> | undefined,
cache: T,
reader: (progress: ProgressEntry, cache: T, params?: any) => Promise<Partial<ProgressEntry>>,
init?: Partial<ProgressEntry>
): ProgressiveHandlerResponse<T>Parameters
params- Query parameters for the operationcache- IndexedDB cache reference for resultsreader- Async function that performs the actual operationinit- Initial ProgressEntry configuration
Returns
ProgressiveHandlerResponse<T> - A tuple of [cache, Signal<ProgressEntry>]
execProgressiveHandler
Imperative wrapper for executing progressive handlers outside of React components.
execProgressiveHandler<T, S>(
ph: (params: T) => ProgressiveHandlerResponse<S>,
params?: T,
opts?: {
force?: boolean; // Force re-execution
reset?: boolean | "ifnotinprogress"; // Reset state before exec
exec?: boolean;
withProgress?: boolean; // Return progress along with result
}
): Promise<S | [S, ProgressEntrySignal]>Usage Example
// Execute and await result
const folder = await execProgressiveHandler(readFolder, { id: "abc123" });
// Execute with progress tracking
const [data, progress] = await execProgressiveHandler(
readFolder,
{ id: "abc123" },
{ withProgress: true }
);
// Force re-execution even if cached
await execProgressiveHandler(readFolder, { id: "abc123" }, { force: true });
// Reset and re-execute
await execProgressiveHandler(readFolder, { id: "abc123" }, { reset: true });semaphore
Creates and manages signals for progress entries with concurrency control and throttling.
semaphore(params: {
key: string; // Unique identifier for deduplication
callback: (progress: ProgressEntry, params?: unknown) => Promise<Partial<ProgressEntry>>;
init?: Partial<ProgressEntry>;
}, params?: unknown): ProgressEntrySignalKey Features
- Deduplication — same key returns existing signal
- Concurrency control — respects maxConcurrency setting
- Throttling — leading/trailing delays
- Auto-execution — when autostart is true
Types
ProgressiveHandlerResponse<T>Typetype ProgressiveHandlerResponse<T> = [T, ProgressEntrySignal]ProgressEntrySignalTypetype ProgressEntrySignal = Signal<ProgressEntry>ProgressCallbackTypetype ProgressCallback = (p: ProgressEntry, params?: unknown) => voidCommon Patterns
React Component Usage
function FolderView({ id }: { id: string }) {
const [folder, progress] = readFolder({ id });
// Auto-execute on mount
useEffect(() => {
progress.value.exec({ id });
}, [id]);
if (progress.value.inProgress) return <Loading />;
if (progress.value.error) return <Error error={progress.value.error} />;
return <div>{folder.value?.title}</div>;
}Sequential Operations
async function createAndRead() {
// Create folder
const [, createProgress] = createFolder();
createProgress.value.exec({ title: "New Folder" });
await createProgress.value.promise;
// Read the created folder
const folder = await execProgressiveHandler(readFolder, {
id: createProgress.value.result?.id
});
}