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

PropertyTypeDescription
inProgressbooleanTrue while the action is executing
promisePromiseAwaitable promise that resolves when complete
donebooleanTrue after successful completion
errorError | undefinedError object if execution failed
pagenumberCurrent page for paginated operations
maxConcurrencynumberMaximum concurrent executions (default: 1)
autostartbooleanAuto-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 operation
  • cache - IndexedDB cache reference for results
  • reader - Async function that performs the actual operation
  • init - 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): ProgressEntrySignal

Key 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>Type
type ProgressiveHandlerResponse<T> = [T, ProgressEntrySignal]
ProgressEntrySignalType
type ProgressEntrySignal = Signal<ProgressEntry>
ProgressCallbackType
type ProgressCallback = (p: ProgressEntry, params?: unknown) => void

Common 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 
  });
}