@coji/durably-react 0.8.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-42AVE35N.js → chunk-XRJEZWAV.js} +1 -1
- package/dist/chunk-XRJEZWAV.js.map +1 -0
- package/dist/client.d.ts +15 -27
- package/dist/client.js +22 -9
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -5
- package/dist/index.js.map +1 -1
- package/dist/{types-DY4Y6ggJ.d.ts → types-xrRs7jov.d.ts} +25 -24
- package/package.json +16 -16
- package/dist/chunk-42AVE35N.js.map +0 -1
package/README.md
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/shared/create-log-entry.ts","../src/shared/subscription-reducer.ts","../src/shared/use-subscription.ts"],"sourcesContent":["// Shared type definitions for @coji/durably-react\n\nimport type { ClientRun, JobDefinition, Run } from '@coji/durably'\n\n// Type inference utilities for extracting Input/Output types from JobDefinition\nexport type InferInput<T> =\n T extends JobDefinition<string, infer TInput, unknown>\n ? TInput extends Record<string, unknown>\n ? TInput\n : Record<string, unknown>\n : T extends { trigger: (input: infer TInput) => unknown }\n ? TInput extends Record<string, unknown>\n ? TInput\n : Record<string, unknown>\n : Record<string, unknown>\n\nexport type InferOutput<T> =\n T extends JobDefinition<string, unknown, infer TOutput>\n ? TOutput extends Record<string, unknown>\n ? TOutput\n : Record<string, unknown>\n : T extends {\n trigger: (input: unknown) => Promise<{ output?: infer TOutput }>\n }\n ? TOutput extends Record<string, unknown>\n ? TOutput\n : Record<string, unknown>\n : Record<string, unknown>\n\nexport type RunStatus =\n | 'pending'\n | 'running'\n | 'completed'\n | 'failed'\n | 'cancelled'\n\nexport interface Progress {\n current: number\n total?: number\n message?: string\n}\n\nexport interface LogEntry {\n id: string\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n timestamp: string\n}\n\n// Shared subscription state (used by both direct and SSE subscriptions)\nexport interface SubscriptionState<TOutput = unknown> {\n status: RunStatus | null\n output: TOutput | null\n error: string | null\n logs: LogEntry[]\n progress: Progress | null\n}\n\n// SSE event types (sent from server).\n// Note: Unlike core DurablyEvent, these omit timestamp/sequence because\n// the SSE handler in server.ts sends only the fields needed by the UI.\nexport type DurablyEvent =\n | { type: 'run:start'; runId: string; jobName: string; input: unknown }\n | {\n type: 'run:complete'\n runId: string\n jobName: string\n output: unknown\n duration: number\n }\n | { type: 'run:fail'; runId: string; jobName: string; error: string }\n | { type: 'run:cancel'; runId: string; jobName: string }\n | { type: 'run:delete'; runId: string; jobName: string }\n | { type: 'run:trigger'; runId: string; jobName: string; input: unknown }\n | { type: 'run:retry'; runId: string; jobName: string }\n | {\n type: 'run:progress'\n runId: string\n jobName: string\n progress: Progress\n }\n | {\n type: 'step:start'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n }\n | {\n type: 'step:complete'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n output: unknown\n }\n | {\n type: 'step:cancel'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n labels: Record<string, string>\n }\n | {\n type: 'log:write'\n runId: string\n jobName: string\n stepName: string | null\n labels: Record<string, string>\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n }\n\n// =============================================================================\n// Typed Run types for useRuns hooks\n// =============================================================================\n\n/**\n * A typed version of Run with generic input/output types.\n * Used by browser hooks (direct durably access).\n */\nexport type TypedRun<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> = Omit<Run, 'input' | 'output'> & {\n input: TInput\n output: TOutput | null\n}\n\n// ClientRun is imported from '@coji/durably' and re-exported for consumers.\nexport type { ClientRun } from '@coji/durably'\n\n/**\n * A typed version of ClientRun with generic input/output types.\n * Used by client hooks (HTTP/SSE connection).\n */\nexport type TypedClientRun<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> = Omit<ClientRun, 'input' | 'output'> & {\n input: TInput\n output: TOutput | null\n}\n\n/**\n * Type guard to check if an object is a JobDefinition.\n * Used to distinguish between JobDefinition and options objects in overloaded functions.\n */\nexport function isJobDefinition<\n TName extends string = string,\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined = undefined,\n>(obj: unknown): obj is JobDefinition<TName, TInput, TOutput> {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'name' in obj &&\n 'run' in obj &&\n typeof (obj as { run: unknown }).run === 'function'\n )\n}\n","import type { LogEntry } from '../types'\n\nexport interface CreateLogEntryParams {\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n}\n\n/**\n * Creates a LogEntry with auto-generated id and timestamp.\n * Extracted to eliminate duplication between subscription hooks.\n */\nexport function createLogEntry(params: CreateLogEntryParams): LogEntry {\n return {\n id: crypto.randomUUID(),\n runId: params.runId,\n stepName: params.stepName,\n level: params.level,\n message: params.message,\n data: params.data,\n timestamp: new Date().toISOString(),\n }\n}\n\n/**\n * Appends a log entry to the array, respecting maxLogs limit.\n */\nexport function appendLog(\n logs: LogEntry[],\n newLog: LogEntry,\n maxLogs: number,\n): LogEntry[] {\n const newLogs = [...logs, newLog]\n if (maxLogs > 0 && newLogs.length > maxLogs) {\n return newLogs.slice(-maxLogs)\n }\n return newLogs\n}\n","import type { Progress, SubscriptionState } from '../types'\nimport { appendLog, createLogEntry } from './create-log-entry'\n\n// Action types for subscription state transitions\nexport type SubscriptionAction<TOutput = unknown> =\n | { type: 'run:start' }\n | { type: 'run:complete'; output: TOutput }\n | { type: 'run:fail'; error: string }\n | { type: 'run:cancel' }\n | { type: 'run:retry' }\n | { type: 'run:progress'; progress: Progress }\n | {\n type: 'log:write'\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n maxLogs: number\n }\n | { type: 'reset' }\n | { type: 'clear_logs' }\n | { type: 'connection_error'; error: string }\n\nexport const initialSubscriptionState: SubscriptionState<unknown> = {\n status: null,\n output: null,\n error: null,\n logs: [],\n progress: null,\n}\n\n/**\n * Pure reducer for subscription state transitions.\n * Extracted to eliminate duplication between useRunSubscription and useSSESubscription.\n */\nexport function subscriptionReducer<TOutput = unknown>(\n state: SubscriptionState<TOutput>,\n action: SubscriptionAction<TOutput>,\n): SubscriptionState<TOutput> {\n switch (action.type) {\n case 'run:start':\n return { ...state, status: 'running' }\n\n case 'run:complete':\n return { ...state, status: 'completed', output: action.output }\n\n case 'run:fail':\n return { ...state, status: 'failed', error: action.error }\n\n case 'run:cancel':\n return { ...state, status: 'cancelled' }\n\n case 'run:retry':\n return { ...state, status: 'pending', error: null }\n\n case 'run:progress':\n return { ...state, progress: action.progress }\n\n case 'log:write': {\n const newLog = createLogEntry({\n runId: action.runId,\n stepName: action.stepName,\n level: action.level,\n message: action.message,\n data: action.data,\n })\n return { ...state, logs: appendLog(state.logs, newLog, action.maxLogs) }\n }\n\n case 'reset':\n return initialSubscriptionState as SubscriptionState<TOutput>\n\n case 'clear_logs':\n return { ...state, logs: [] }\n\n case 'connection_error':\n return { ...state, error: action.error }\n\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react'\nimport type { SubscriptionState } from '../types'\nimport type { EventSubscriber } from './event-subscriber'\nimport {\n initialSubscriptionState,\n subscriptionReducer,\n} from './subscription-reducer'\n\nexport interface UseSubscriptionOptions {\n /**\n * Maximum number of logs to keep (0 = unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseSubscriptionResult<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n /**\n * Clear all logs\n */\n clearLogs: () => void\n /**\n * Reset all state\n */\n reset: () => void\n}\n\n/**\n * Core subscription hook that works with any EventSubscriber implementation.\n * This unifies the subscription logic between Durably.on and SSE.\n */\nexport function useSubscription<TOutput = unknown>(\n subscriber: EventSubscriber | null,\n runId: string | null,\n options?: UseSubscriptionOptions,\n): UseSubscriptionResult<TOutput> {\n const [state, dispatch] = useReducer(\n subscriptionReducer<TOutput>,\n initialSubscriptionState as SubscriptionState<TOutput>,\n )\n\n const runIdRef = useRef<string | null>(runId)\n const prevRunIdRef = useRef<string | null>(null)\n\n const maxLogs = options?.maxLogs ?? 0\n\n // Reset state when runId changes\n if (prevRunIdRef.current !== runId) {\n prevRunIdRef.current = runId\n if (runIdRef.current !== runId) {\n dispatch({ type: 'reset' })\n }\n }\n runIdRef.current = runId\n\n useEffect(() => {\n if (!subscriber || !runId) return\n\n const unsubscribe = subscriber.subscribe<TOutput>(runId, (event) => {\n // Verify runId hasn't changed during async operation\n if (runIdRef.current !== runId) return\n\n switch (event.type) {\n case 'run:start':\n case 'run:cancel':\n case 'run:retry':\n dispatch({ type: event.type })\n break\n case 'run:complete':\n dispatch({ type: 'run:complete', output: event.output })\n break\n case 'run:fail':\n dispatch({ type: 'run:fail', error: event.error })\n break\n case 'run:progress':\n dispatch({ type: 'run:progress', progress: event.progress })\n break\n case 'log:write':\n dispatch({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n maxLogs,\n })\n break\n case 'connection_error':\n dispatch({ type: 'connection_error', error: event.error })\n break\n }\n })\n\n return unsubscribe\n }, [subscriber, runId, maxLogs])\n\n const clearLogs = useCallback(() => {\n dispatch({ type: 'clear_logs' })\n }, [])\n\n const reset = useCallback(() => {\n dispatch({ type: 'reset' })\n }, [])\n\n return {\n ...state,\n clearLogs,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { R as RunStatus, L as LogEntry, P as Progress, I as InferInput, a as InferOutput, b as TypedClientRun } from './types-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { R as RunStatus, L as LogEntry, P as Progress, I as InferInput, a as InferOutput, b as TypedClientRun } from './types-xrRs7jov.js';
|
|
2
|
+
import { JobDefinition, ClientRun } from '@coji/durably';
|
|
3
|
+
export { ClientRun } from '@coji/durably';
|
|
4
4
|
|
|
5
5
|
interface UseJobClientOptions {
|
|
6
6
|
/**
|
|
@@ -347,11 +347,20 @@ interface UseRunsClientOptions {
|
|
|
347
347
|
* Filter by status
|
|
348
348
|
*/
|
|
349
349
|
status?: RunStatus;
|
|
350
|
+
/**
|
|
351
|
+
* Filter by labels (all specified labels must match)
|
|
352
|
+
*/
|
|
353
|
+
labels?: Record<string, string>;
|
|
350
354
|
/**
|
|
351
355
|
* Number of runs per page
|
|
352
356
|
* @default 10
|
|
353
357
|
*/
|
|
354
358
|
pageSize?: number;
|
|
359
|
+
/**
|
|
360
|
+
* Subscribe to real-time updates via SSE (first page only)
|
|
361
|
+
* @default true
|
|
362
|
+
*/
|
|
363
|
+
realtime?: boolean;
|
|
355
364
|
}
|
|
356
365
|
interface UseRunsClientResult<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> | undefined = Record<string, unknown> | undefined> {
|
|
357
366
|
/**
|
|
@@ -429,33 +438,12 @@ declare function useRuns<TRun extends TypedClientRun<Record<string, unknown>, Re
|
|
|
429
438
|
declare function useRuns<TName extends string, TInput extends Record<string, unknown>, TOutput extends Record<string, unknown> | undefined>(jobDefinition: JobDefinition<TName, TInput, TOutput>, options: Omit<UseRunsClientOptions, 'jobName'>): UseRunsClientResult<TInput, TOutput>;
|
|
430
439
|
declare function useRuns(options: UseRunsClientOptions): UseRunsClientResult;
|
|
431
440
|
|
|
432
|
-
/**
|
|
433
|
-
* Run record returned from the server API
|
|
434
|
-
*/
|
|
435
|
-
interface RunRecord {
|
|
436
|
-
id: string;
|
|
437
|
-
jobName: string;
|
|
438
|
-
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
439
|
-
payload: unknown;
|
|
440
|
-
output: unknown | null;
|
|
441
|
-
error: string | null;
|
|
442
|
-
progress: {
|
|
443
|
-
current: number;
|
|
444
|
-
total?: number;
|
|
445
|
-
message?: string;
|
|
446
|
-
} | null;
|
|
447
|
-
currentStepIndex: number;
|
|
448
|
-
stepCount: number;
|
|
449
|
-
createdAt: string;
|
|
450
|
-
startedAt: string | null;
|
|
451
|
-
completedAt: string | null;
|
|
452
|
-
}
|
|
453
441
|
/**
|
|
454
442
|
* Step record returned from the server API
|
|
455
443
|
*/
|
|
456
444
|
interface StepRecord {
|
|
457
445
|
name: string;
|
|
458
|
-
status: 'completed' | 'failed';
|
|
446
|
+
status: 'completed' | 'failed' | 'cancelled';
|
|
459
447
|
output: unknown;
|
|
460
448
|
}
|
|
461
449
|
interface UseRunActionsClientOptions {
|
|
@@ -480,7 +468,7 @@ interface UseRunActionsClientResult {
|
|
|
480
468
|
/**
|
|
481
469
|
* Get a single run by ID
|
|
482
470
|
*/
|
|
483
|
-
getRun: (runId: string) => Promise<
|
|
471
|
+
getRun: (runId: string) => Promise<ClientRun | null>;
|
|
484
472
|
/**
|
|
485
473
|
* Get steps for a run
|
|
486
474
|
*/
|
|
@@ -524,4 +512,4 @@ interface UseRunActionsClientResult {
|
|
|
524
512
|
*/
|
|
525
513
|
declare function useRunActions(options: UseRunActionsClientOptions): UseRunActionsClientResult;
|
|
526
514
|
|
|
527
|
-
export { type CreateDurablyClientOptions, type CreateJobHooksOptions, type DurablyClient, type JobClient, type JobHooks, LogEntry, Progress,
|
|
515
|
+
export { type CreateDurablyClientOptions, type CreateJobHooksOptions, type DurablyClient, type JobClient, type JobHooks, LogEntry, Progress, RunStatus, type StepRecord, TypedClientRun, type UseJobClientOptions, type UseJobClientResult, type UseJobLogsClientOptions, type UseJobLogsClientResult, type UseJobRunClientOptions, type UseJobRunClientResult, type UseRunActionsClientOptions, type UseRunActionsClientResult, type UseRunsClientOptions, type UseRunsClientResult, createDurablyClient, createJobHooks, useJob, useJobLogs, useJobRun, useRunActions, useRuns };
|
package/dist/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isJobDefinition,
|
|
3
3
|
useSubscription
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-XRJEZWAV.js";
|
|
5
5
|
|
|
6
6
|
// src/client/use-job.ts
|
|
7
7
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -324,16 +324,21 @@ function createJobHooks(options) {
|
|
|
324
324
|
}
|
|
325
325
|
|
|
326
326
|
// src/client/use-runs.ts
|
|
327
|
-
import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
327
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
328
328
|
function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
329
329
|
const isJob = isJobDefinition(jobDefinitionOrOptions);
|
|
330
330
|
const jobName = isJob ? jobDefinitionOrOptions.name : jobDefinitionOrOptions.jobName;
|
|
331
331
|
const options = isJob ? optionsArg : jobDefinitionOrOptions;
|
|
332
|
-
const { api, status, pageSize = 10 } = options;
|
|
332
|
+
const { api, status, labels, pageSize = 10, realtime = true } = options;
|
|
333
|
+
const labelsKey = labels ? JSON.stringify(labels) : void 0;
|
|
334
|
+
const stableLabels = useMemo2(
|
|
335
|
+
() => labelsKey ? JSON.parse(labelsKey) : void 0,
|
|
336
|
+
[labelsKey]
|
|
337
|
+
);
|
|
333
338
|
const [runs, setRuns] = useState2([]);
|
|
334
339
|
const [page, setPage] = useState2(0);
|
|
335
340
|
const [hasMore, setHasMore] = useState2(false);
|
|
336
|
-
const [isLoading, setIsLoading] = useState2(
|
|
341
|
+
const [isLoading, setIsLoading] = useState2(true);
|
|
337
342
|
const [error, setError] = useState2(null);
|
|
338
343
|
const isMountedRef = useRef3(true);
|
|
339
344
|
const eventSourceRef = useRef3(null);
|
|
@@ -344,6 +349,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
344
349
|
const params = new URLSearchParams();
|
|
345
350
|
if (jobName) params.set("jobName", jobName);
|
|
346
351
|
if (status) params.set("status", status);
|
|
352
|
+
appendLabelsToParams(params, stableLabels);
|
|
347
353
|
params.set("limit", String(pageSize + 1));
|
|
348
354
|
params.set("offset", String(page * pageSize));
|
|
349
355
|
const url = `${api}/runs?${params.toString()}`;
|
|
@@ -365,7 +371,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
365
371
|
setIsLoading(false);
|
|
366
372
|
}
|
|
367
373
|
}
|
|
368
|
-
}, [api, jobName, status, pageSize, page]);
|
|
374
|
+
}, [api, jobName, status, stableLabels, pageSize, page]);
|
|
369
375
|
useEffect3(() => {
|
|
370
376
|
isMountedRef.current = true;
|
|
371
377
|
refresh();
|
|
@@ -374,7 +380,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
374
380
|
};
|
|
375
381
|
}, [refresh]);
|
|
376
382
|
useEffect3(() => {
|
|
377
|
-
if (page !== 0) {
|
|
383
|
+
if (!realtime || page !== 0) {
|
|
378
384
|
if (eventSourceRef.current) {
|
|
379
385
|
eventSourceRef.current.close();
|
|
380
386
|
eventSourceRef.current = null;
|
|
@@ -383,13 +389,14 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
383
389
|
}
|
|
384
390
|
const params = new URLSearchParams();
|
|
385
391
|
if (jobName) params.set("jobName", jobName);
|
|
392
|
+
appendLabelsToParams(params, stableLabels);
|
|
386
393
|
const sseUrl = `${api}/runs/subscribe${params.toString() ? `?${params.toString()}` : ""}`;
|
|
387
394
|
const eventSource = new EventSource(sseUrl);
|
|
388
395
|
eventSourceRef.current = eventSource;
|
|
389
396
|
eventSource.onmessage = (event) => {
|
|
390
397
|
try {
|
|
391
398
|
const data = JSON.parse(event.data);
|
|
392
|
-
if (data.type === "run:trigger" || data.type === "run:start" || data.type === "run:complete" || data.type === "run:fail" || data.type === "run:cancel" || data.type === "run:retry") {
|
|
399
|
+
if (data.type === "run:trigger" || data.type === "run:start" || data.type === "run:complete" || data.type === "run:fail" || data.type === "run:cancel" || data.type === "run:delete" || data.type === "run:retry") {
|
|
393
400
|
refresh();
|
|
394
401
|
}
|
|
395
402
|
if (data.type === "run:progress") {
|
|
@@ -406,7 +413,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
406
413
|
)
|
|
407
414
|
);
|
|
408
415
|
}
|
|
409
|
-
if (data.type === "step:start" || data.type === "step:fail") {
|
|
416
|
+
if (data.type === "step:start" || data.type === "step:fail" || data.type === "step:cancel") {
|
|
410
417
|
refresh();
|
|
411
418
|
}
|
|
412
419
|
} catch {
|
|
@@ -418,7 +425,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
418
425
|
eventSource.close();
|
|
419
426
|
eventSourceRef.current = null;
|
|
420
427
|
};
|
|
421
|
-
}, [api, jobName, page, refresh]);
|
|
428
|
+
}, [api, jobName, stableLabels, page, realtime, refresh]);
|
|
422
429
|
const nextPage = useCallback2(() => {
|
|
423
430
|
if (hasMore) {
|
|
424
431
|
setPage((p) => p + 1);
|
|
@@ -442,6 +449,12 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
442
449
|
refresh
|
|
443
450
|
};
|
|
444
451
|
}
|
|
452
|
+
function appendLabelsToParams(params, labels) {
|
|
453
|
+
if (!labels) return;
|
|
454
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
455
|
+
params.set(`label.${key}`, value);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
445
458
|
|
|
446
459
|
// src/client/use-run-actions.ts
|
|
447
460
|
import { useCallback as useCallback3, useState as useState3 } from "react";
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/use-job.ts","../src/client/use-sse-subscription.ts","../src/shared/sse-event-subscriber.ts","../src/client/use-job-logs.ts","../src/client/use-job-run.ts","../src/client/create-durably-client.ts","../src/client/create-job-hooks.ts","../src/client/use-runs.ts","../src/client/use-run-actions.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Job name to trigger\n */\n jobName: string\n /**\n * Initial Run ID to subscribe to (for reconnection scenarios)\n * When provided, the hook will immediately start subscribing to this run\n */\n initialRunId?: string\n /**\n * Automatically resume tracking a running/pending job on mount\n * @default true\n */\n autoResume?: boolean\n /**\n * Automatically switch to tracking the latest triggered job\n * @default true\n */\n followLatest?: boolean\n}\n\nexport interface UseJobClientResult<TInput, TOutput> {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Trigger the job with the given input\n */\n trigger: (input: TInput) => Promise<{ runId: string }>\n /**\n * Trigger and wait for completion\n */\n triggerAndWait: (input: TInput) => Promise<{ runId: string; output: TOutput }>\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n /**\n * Current run ID\n */\n currentRunId: string | null\n /**\n * Reset all state\n */\n reset: () => void\n}\n\n/**\n * Hook for triggering and subscribing to jobs via server API.\n * Uses fetch for triggering and EventSource for SSE subscription.\n */\nexport function useJob<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> = Record<string, unknown>,\n>(options: UseJobClientOptions): UseJobClientResult<TInput, TOutput> {\n const {\n api,\n jobName,\n initialRunId,\n autoResume = true,\n followLatest = true,\n } = options\n\n const [currentRunId, setCurrentRunId] = useState<string | null>(\n initialRunId ?? null,\n )\n const [isPending, setIsPending] = useState(false)\n\n // Track if user has triggered a run (to prevent autoResume from overwriting)\n const hasUserTriggered = useRef(false)\n\n const subscription = useSSESubscription<TOutput>(api, currentRunId)\n\n // Auto-resume: fetch running/pending job on mount\n useEffect(() => {\n if (!autoResume) return\n if (initialRunId) return // Skip if initialRunId is provided\n\n const abortController = new AbortController()\n\n const findActiveRun = async () => {\n // Try running first\n const runningParams = new URLSearchParams({\n jobName,\n status: 'running',\n limit: '1',\n })\n const runningRes = await fetch(`${api}/runs?${runningParams}`, {\n signal: abortController.signal,\n })\n if (runningRes.ok) {\n const runs = (await runningRes.json()) as Array<{ id: string }>\n if (runs.length > 0) {\n // Don't overwrite if user already triggered a run\n if (hasUserTriggered.current) return\n setCurrentRunId(runs[0].id)\n return\n }\n }\n\n // Try pending\n const pendingParams = new URLSearchParams({\n jobName,\n status: 'pending',\n limit: '1',\n })\n const pendingRes = await fetch(`${api}/runs?${pendingParams}`, {\n signal: abortController.signal,\n })\n if (pendingRes.ok) {\n const runs = (await pendingRes.json()) as Array<{ id: string }>\n if (runs.length > 0) {\n // Don't overwrite if user already triggered a run\n if (hasUserTriggered.current) return\n setCurrentRunId(runs[0].id)\n }\n }\n }\n\n findActiveRun().catch((err) => {\n // Ignore abort errors\n if (err.name !== 'AbortError') {\n console.error('autoResume error:', err)\n }\n })\n\n return () => {\n abortController.abort()\n }\n }, [api, jobName, autoResume, initialRunId])\n\n // Follow latest: subscribe to job-level SSE for run:trigger/run:start events\n useEffect(() => {\n if (!followLatest) return\n\n const params = new URLSearchParams({ jobName })\n const eventSource = new EventSource(`${api}/runs/subscribe?${params}`)\n\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data) as {\n type: string\n runId?: string\n }\n if (\n (data.type === 'run:trigger' || data.type === 'run:start') &&\n data.runId\n ) {\n setCurrentRunId(data.runId)\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n // SSE connection error - could reconnect or log for debugging\n // No need to surface error to user as this is a background subscription\n }\n\n return () => {\n eventSource.close()\n }\n }, [api, jobName, followLatest])\n\n const trigger = useCallback(\n async (input: TInput): Promise<{ runId: string }> => {\n // Mark that user has triggered (prevents autoResume from overwriting)\n hasUserTriggered.current = true\n\n // Reset state\n subscription.reset()\n setIsPending(true)\n\n const response = await fetch(`${api}/trigger`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ jobName, input }),\n })\n\n if (!response.ok) {\n setIsPending(false)\n const errorText = await response.text()\n throw new Error(errorText || `HTTP ${response.status}`)\n }\n\n const { runId } = (await response.json()) as { runId: string }\n setCurrentRunId(runId)\n\n return { runId }\n },\n [api, jobName, subscription.reset],\n )\n\n const triggerAndWait = useCallback(\n async (input: TInput): Promise<{ runId: string; output: TOutput }> => {\n const { runId } = await trigger(input)\n\n return new Promise((resolve, reject) => {\n const checkInterval = setInterval(() => {\n if (subscription.status === 'completed' && subscription.output) {\n clearInterval(checkInterval)\n resolve({ runId, output: subscription.output })\n } else if (subscription.status === 'failed') {\n clearInterval(checkInterval)\n reject(new Error(subscription.error ?? 'Job failed'))\n } else if (subscription.status === 'cancelled') {\n clearInterval(checkInterval)\n reject(new Error('Job cancelled'))\n }\n }, 50)\n })\n },\n [trigger, subscription.status, subscription.output, subscription.error],\n )\n\n const reset = useCallback(() => {\n subscription.reset()\n setCurrentRunId(null)\n setIsPending(false)\n }, [subscription.reset])\n\n // Compute effective status (pending overrides null when we've triggered but SSE hasn't started)\n const effectiveStatus = subscription.status ?? (isPending ? 'pending' : null)\n\n // Clear pending when we get a real status\n useEffect(() => {\n if (subscription.status && isPending) {\n setIsPending(false)\n }\n }, [subscription.status, isPending])\n\n return {\n trigger,\n triggerAndWait,\n status: effectiveStatus,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: effectiveStatus === 'running',\n isPending: effectiveStatus === 'pending',\n isCompleted: effectiveStatus === 'completed',\n isFailed: effectiveStatus === 'failed',\n isCancelled: effectiveStatus === 'cancelled',\n currentRunId,\n reset,\n }\n}\n","import { useMemo } from 'react'\nimport { createSSEEventSubscriber } from '../shared/sse-event-subscriber'\nimport {\n useSubscription,\n type UseSubscriptionOptions,\n type UseSubscriptionResult,\n} from '../shared/use-subscription'\nimport type { SubscriptionState } from '../types'\n\n/** @deprecated Use SubscriptionState from '../types' instead */\nexport type SSESubscriptionState<TOutput = unknown> = SubscriptionState<TOutput>\n\n/** @deprecated Use UseSubscriptionOptions from '../shared/use-subscription' instead */\nexport type UseSSESubscriptionOptions = UseSubscriptionOptions\n\n/** @deprecated Use UseSubscriptionResult from '../shared/use-subscription' instead */\nexport type UseSSESubscriptionResult<TOutput = unknown> =\n UseSubscriptionResult<TOutput>\n\n/**\n * Internal hook for subscribing to run events via SSE.\n * Used by client-mode hooks (useJob, useJobRun, useJobLogs).\n *\n * @deprecated Consider using useSubscription with createSSEEventSubscriber directly.\n */\nexport function useSSESubscription<TOutput = unknown>(\n api: string | null,\n runId: string | null,\n options?: UseSSESubscriptionOptions,\n): UseSSESubscriptionResult<TOutput> {\n const subscriber = useMemo(\n () => (api ? createSSEEventSubscriber(api) : null),\n [api],\n )\n\n return useSubscription<TOutput>(subscriber, runId, options)\n}\n","import type { DurablyEvent } from '../types'\nimport type { EventSubscriber, SubscriptionEvent } from './event-subscriber'\n\n/**\n * EventSubscriber implementation using Server-Sent Events (SSE).\n * Used in client environments that communicate with a Durably server via HTTP.\n */\nexport function createSSEEventSubscriber(apiBaseUrl: string): EventSubscriber {\n return {\n subscribe<TOutput = unknown>(\n runId: string,\n onEvent: (event: SubscriptionEvent<TOutput>) => void,\n ): () => void {\n const url = `${apiBaseUrl}/subscribe?runId=${encodeURIComponent(runId)}`\n const eventSource = new EventSource(url)\n\n eventSource.onmessage = (messageEvent) => {\n try {\n const data = JSON.parse(messageEvent.data) as DurablyEvent\n if (data.runId !== runId) return\n\n switch (data.type) {\n case 'run:start':\n onEvent({ type: 'run:start' })\n break\n case 'run:complete':\n onEvent({\n type: 'run:complete',\n output: data.output as TOutput,\n })\n break\n case 'run:fail':\n onEvent({ type: 'run:fail', error: data.error })\n break\n case 'run:cancel':\n onEvent({ type: 'run:cancel' })\n break\n case 'run:retry':\n onEvent({ type: 'run:retry' })\n break\n case 'run:progress':\n onEvent({ type: 'run:progress', progress: data.progress })\n break\n case 'log:write':\n onEvent({\n type: 'log:write',\n runId: data.runId,\n stepName: null,\n level: data.level,\n message: data.message,\n data: data.data,\n })\n break\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n onEvent({ type: 'connection_error', error: 'Connection failed' })\n eventSource.close()\n }\n\n return () => {\n eventSource.close()\n }\n },\n }\n}\n","import type { LogEntry } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobLogsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * The run ID to subscribe to logs for\n */\n runId: string | null\n /**\n * Maximum number of logs to keep (default: unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobLogsClientResult {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Clear all logs\n */\n clearLogs: () => void\n}\n\n/**\n * Hook for subscribing to logs from a run via server API.\n * Uses EventSource for SSE subscription.\n */\nexport function useJobLogs(\n options: UseJobLogsClientOptions,\n): UseJobLogsClientResult {\n const { api, runId, maxLogs } = options\n\n const subscription = useSSESubscription(api, runId, { maxLogs })\n\n return {\n logs: subscription.logs,\n clearLogs: subscription.clearLogs,\n }\n}\n","import { useEffect, useRef } from 'react'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobRunClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * The run ID to subscribe to\n */\n runId: string | null\n /**\n * Callback when run starts (transitions to pending/running)\n */\n onStart?: () => void\n /**\n * Callback when run completes successfully\n */\n onComplete?: () => void\n /**\n * Callback when run fails\n */\n onFail?: () => void\n}\n\nexport interface UseJobRunClientResult<TOutput = unknown> {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n}\n\n/**\n * Hook for subscribing to an existing run via server API.\n * Uses EventSource for SSE subscription.\n */\nexport function useJobRun<TOutput = unknown>(\n options: UseJobRunClientOptions,\n): UseJobRunClientResult<TOutput> {\n const { api, runId, onStart, onComplete, onFail } = options\n\n const subscription = useSSESubscription<TOutput>(api, runId)\n\n // If we have a runId but no status yet, treat as pending\n const effectiveStatus = subscription.status ?? (runId ? 'pending' : null)\n\n const isCompleted = effectiveStatus === 'completed'\n const isFailed = effectiveStatus === 'failed'\n const isPending = effectiveStatus === 'pending'\n const isRunning = effectiveStatus === 'running'\n const isCancelled = effectiveStatus === 'cancelled'\n\n // Track previous status to detect transitions (use effectiveStatus, not subscription.status)\n const prevStatusRef = useRef<RunStatus | null>(null)\n\n useEffect(() => {\n const prevStatus = prevStatusRef.current\n prevStatusRef.current = effectiveStatus\n\n // Only fire callbacks on status transitions\n if (prevStatus !== effectiveStatus) {\n // Fire onStart when transitioning from null to pending/running\n if (prevStatus === null && (isPending || isRunning) && onStart) {\n onStart()\n }\n if (isCompleted && onComplete) {\n onComplete()\n }\n if (isFailed && onFail) {\n onFail()\n }\n }\n }, [\n effectiveStatus,\n isPending,\n isRunning,\n isCompleted,\n isFailed,\n onStart,\n onComplete,\n onFail,\n ])\n\n return {\n status: effectiveStatus,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning,\n isPending,\n isCompleted,\n isFailed,\n isCancelled,\n }\n}\n","import type { InferInput, InferOutput } from '../types'\nimport { useJob, type UseJobClientResult } from './use-job'\nimport { useJobLogs, type UseJobLogsClientResult } from './use-job-logs'\nimport { useJobRun, type UseJobRunClientResult } from './use-job-run'\n\n/**\n * Type-safe hooks for a specific job\n */\nexport interface JobClient<TInput, TOutput> {\n /**\n * Hook for triggering and monitoring the job\n */\n useJob: () => UseJobClientResult<TInput, TOutput>\n\n /**\n * Hook for subscribing to an existing run by ID\n */\n useRun: (runId: string | null) => UseJobRunClientResult<TOutput>\n\n /**\n * Hook for subscribing to logs from a run\n */\n useLogs: (\n runId: string | null,\n options?: { maxLogs?: number },\n ) => UseJobLogsClientResult\n}\n\n/**\n * Options for createDurablyClient\n */\nexport interface CreateDurablyClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n}\n\n/**\n * A type-safe client with hooks for each registered job\n */\nexport type DurablyClient<TJobs extends Record<string, unknown>> = {\n [K in keyof TJobs]: JobClient<InferInput<TJobs[K]>, InferOutput<TJobs[K]>>\n}\n\n/**\n * Create a type-safe Durably client with hooks for all registered jobs.\n *\n * @example\n * ```tsx\n * // Server: register jobs\n * // app/lib/durably.server.ts\n * export const jobs = durably.register({\n * importCsv: importCsvJob,\n * syncUsers: syncUsersJob,\n * })\n *\n * // Client: create typed client\n * // app/lib/durably.client.ts\n * import type { jobs } from '~/lib/durably.server'\n * import { createDurablyClient } from '@coji/durably-react/client'\n *\n * export const durably = createDurablyClient<typeof jobs>({\n * api: '/api/durably',\n * })\n *\n * // In your component - fully type-safe with autocomplete\n * function CsvImporter() {\n * const { trigger, output, isRunning } = durably.importCsv.useJob()\n *\n * return (\n * <button onClick={() => trigger({ rows: [...] })}>\n * Import\n * </button>\n * )\n * }\n * ```\n */\nexport function createDurablyClient<TJobs extends Record<string, unknown>>(\n options: CreateDurablyClientOptions,\n): DurablyClient<TJobs> {\n const { api } = options\n\n // Create a proxy that generates job clients on demand\n return new Proxy({} as DurablyClient<TJobs>, {\n get(_target, jobKey: string) {\n return {\n useJob: () => {\n return useJob({ api, jobName: jobKey })\n },\n\n useRun: (runId: string | null) => {\n return useJobRun({ api, runId })\n },\n\n useLogs: (runId: string | null, logsOptions?: { maxLogs?: number }) => {\n return useJobLogs({ api, runId, maxLogs: logsOptions?.maxLogs })\n },\n }\n },\n })\n}\n","import type { JobDefinition } from '@coji/durably'\nimport type { InferInput, InferOutput } from '../types'\nimport { useJob, type UseJobClientResult } from './use-job'\nimport { useJobLogs, type UseJobLogsClientResult } from './use-job-logs'\nimport { useJobRun, type UseJobRunClientResult } from './use-job-run'\n\n/**\n * Options for createJobHooks\n */\nexport interface CreateJobHooksOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Job name (must match the server-side job name)\n */\n jobName: string\n}\n\n/**\n * Type-safe hooks for a specific job\n */\nexport interface JobHooks<TInput, TOutput> {\n /**\n * Hook for triggering and monitoring the job\n */\n useJob: () => UseJobClientResult<TInput, TOutput>\n\n /**\n * Hook for subscribing to an existing run by ID\n */\n useRun: (runId: string | null) => UseJobRunClientResult<TOutput>\n\n /**\n * Hook for subscribing to logs from a run\n */\n useLogs: (\n runId: string | null,\n options?: { maxLogs?: number },\n ) => UseJobLogsClientResult\n}\n\n/**\n * Create type-safe hooks for a specific job.\n *\n * @example\n * ```tsx\n * // Import job type from server (type-only import is safe)\n * import type { importCsvJob } from '~/lib/durably.server'\n * import { createJobHooks } from '@coji/durably-react/client'\n *\n * const importCsv = createJobHooks<typeof importCsvJob>({\n * api: '/api/durably',\n * jobName: 'import-csv',\n * })\n *\n * // In your component - fully type-safe\n * function CsvImporter() {\n * const { trigger, output, progress, isRunning } = importCsv.useJob()\n *\n * return (\n * <button onClick={() => trigger({ rows: [...] })}>\n * Import\n * </button>\n * )\n * }\n * ```\n */\nexport function createJobHooks<\n // biome-ignore lint/suspicious/noExplicitAny: TJob needs to accept any JobDefinition\n TJob extends JobDefinition<string, any, any>,\n>(\n options: CreateJobHooksOptions,\n): JobHooks<InferInput<TJob>, InferOutput<TJob>> {\n const { api, jobName } = options\n\n return {\n useJob: () => {\n return useJob<InferInput<TJob>, InferOutput<TJob>>({ api, jobName })\n },\n\n useRun: (runId: string | null) => {\n return useJobRun<InferOutput<TJob>>({ api, runId })\n },\n\n useLogs: (runId: string | null, logsOptions?: { maxLogs?: number }) => {\n return useJobLogs({ api, runId, maxLogs: logsOptions?.maxLogs })\n },\n }\n}\n","import type { JobDefinition } from '@coji/durably'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n type Progress,\n type RunStatus,\n type TypedClientRun,\n isJobDefinition,\n} from '../types'\n\n// Re-export types for convenience\nexport type { ClientRun, TypedClientRun } from '../types'\n\n/**\n * SSE notification event from /runs/subscribe\n */\ntype RunUpdateEvent =\n | {\n type:\n | 'run:trigger'\n | 'run:start'\n | 'run:complete'\n | 'run:fail'\n | 'run:cancel'\n | 'run:retry'\n runId: string\n jobName: string\n }\n | { type: 'run:progress'; runId: string; jobName: string; progress: Progress }\n | {\n type: 'step:start' | 'step:complete'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n }\n | {\n type: 'step:fail'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n error: string\n }\n | {\n type: 'log:write'\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n }\n\nexport interface UseRunsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Filter by job name\n */\n jobName?: string\n /**\n * Filter by status\n */\n status?: RunStatus\n /**\n * Number of runs per page\n * @default 10\n */\n pageSize?: number\n}\n\nexport interface UseRunsClientResult<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> {\n /**\n * List of runs for the current page\n */\n runs: TypedClientRun<TInput, TOutput>[]\n /**\n * Current page (0-indexed)\n */\n page: number\n /**\n * Whether there are more pages\n */\n hasMore: boolean\n /**\n * Whether data is being loaded\n */\n isLoading: boolean\n /**\n * Error message if fetch failed\n */\n error: string | null\n /**\n * Go to the next page\n */\n nextPage: () => void\n /**\n * Go to the previous page\n */\n prevPage: () => void\n /**\n * Go to a specific page\n */\n goToPage: (page: number) => void\n /**\n * Refresh the current page\n */\n refresh: () => Promise<void>\n}\n\n/**\n * Hook for listing runs via server API with pagination.\n * First page (page 0) automatically subscribes to SSE for real-time updates.\n * Other pages are static and require manual refresh.\n *\n * @example With generic type parameter (dashboard with multiple job types)\n * ```tsx\n * type DashboardRun = TypedClientRun<ImportInput, ImportOutput> | TypedClientRun<SyncInput, SyncOutput>\n *\n * function Dashboard() {\n * const { runs } = useRuns<DashboardRun>({ api: '/api/durably', pageSize: 10 })\n * // runs are typed as DashboardRun[]\n * }\n * ```\n *\n * @example With JobDefinition (single job, auto-filters by jobName)\n * ```tsx\n * const myJob = defineJob({ name: 'my-job', ... })\n *\n * function RunHistory() {\n * const { runs } = useRuns(myJob, { api: '/api/durably' })\n * // runs[0].output is typed!\n * return <div>{runs[0]?.output?.someField}</div>\n * }\n * ```\n *\n * @example With options only (untyped)\n * ```tsx\n * function RunHistory() {\n * const { runs } = useRuns({ api: '/api/durably', pageSize: 10 })\n * // runs[0].output is unknown\n * }\n * ```\n */\n// Overload 1: With generic type parameter\nexport function useRuns<\n TRun extends TypedClientRun<\n Record<string, unknown>,\n Record<string, unknown> | undefined\n >,\n>(\n options: UseRunsClientOptions,\n): UseRunsClientResult<\n TRun extends TypedClientRun<infer I, infer _O> ? I : Record<string, unknown>,\n TRun extends TypedClientRun<infer _I, infer O> ? O : Record<string, unknown>\n>\n\n// Overload 2: With JobDefinition for type inference (auto-filters by jobName)\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options: Omit<UseRunsClientOptions, 'jobName'>,\n): UseRunsClientResult<TInput, TOutput>\n\n// Overload 3: Without type parameter (untyped, backward compatible)\nexport function useRuns(options: UseRunsClientOptions): UseRunsClientResult\n\n// Implementation\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinitionOrOptions:\n | JobDefinition<TName, TInput, TOutput>\n | UseRunsClientOptions,\n optionsArg?: Omit<UseRunsClientOptions, 'jobName'>,\n): UseRunsClientResult<TInput, TOutput> {\n // Determine if first argument is a JobDefinition using type guard\n const isJob = isJobDefinition(jobDefinitionOrOptions)\n\n const jobName = isJob\n ? jobDefinitionOrOptions.name\n : (jobDefinitionOrOptions as UseRunsClientOptions).jobName\n\n const options = isJob\n ? (optionsArg as Omit<UseRunsClientOptions, 'jobName'>)\n : (jobDefinitionOrOptions as UseRunsClientOptions)\n\n const { api, status, pageSize = 10 } = options\n\n const [runs, setRuns] = useState<TypedClientRun<TInput, TOutput>[]>([])\n const [page, setPage] = useState(0)\n const [hasMore, setHasMore] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const isMountedRef = useRef(true)\n const eventSourceRef = useRef<EventSource | null>(null)\n\n const refresh = useCallback(async () => {\n setIsLoading(true)\n setError(null)\n\n try {\n const params = new URLSearchParams()\n if (jobName) params.set('jobName', jobName)\n if (status) params.set('status', status)\n params.set('limit', String(pageSize + 1))\n params.set('offset', String(page * pageSize))\n\n const url = `${api}/runs?${params.toString()}`\n const response = await fetch(url)\n\n if (!response.ok) {\n throw new Error(`Failed to fetch runs: ${response.statusText}`)\n }\n\n const data = (await response.json()) as TypedClientRun<TInput, TOutput>[]\n\n if (isMountedRef.current) {\n setHasMore(data.length > pageSize)\n setRuns(data.slice(0, pageSize))\n }\n } catch (err) {\n if (isMountedRef.current) {\n setError(err instanceof Error ? err.message : 'Unknown error')\n }\n } finally {\n if (isMountedRef.current) {\n setIsLoading(false)\n }\n }\n }, [api, jobName, status, pageSize, page])\n\n // Initial fetch\n useEffect(() => {\n isMountedRef.current = true\n refresh()\n\n return () => {\n isMountedRef.current = false\n }\n }, [refresh])\n\n // SSE subscription for first page only\n useEffect(() => {\n // Only subscribe to SSE on first page\n if (page !== 0) {\n // Clean up any existing connection when navigating away from first page\n if (eventSourceRef.current) {\n eventSourceRef.current.close()\n eventSourceRef.current = null\n }\n return\n }\n\n // Build SSE URL\n const params = new URLSearchParams()\n if (jobName) params.set('jobName', jobName)\n const sseUrl = `${api}/runs/subscribe${params.toString() ? `?${params.toString()}` : ''}`\n\n const eventSource = new EventSource(sseUrl)\n eventSourceRef.current = eventSource\n\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data) as RunUpdateEvent\n // On run lifecycle events, refresh the list\n if (\n data.type === 'run:trigger' ||\n data.type === 'run:start' ||\n data.type === 'run:complete' ||\n data.type === 'run:fail' ||\n data.type === 'run:cancel' ||\n data.type === 'run:retry'\n ) {\n refresh()\n }\n // On progress update, update the run in place\n if (data.type === 'run:progress') {\n setRuns((prev) =>\n prev.map((run) =>\n run.id === data.runId ? { ...run, progress: data.progress } : run,\n ),\n )\n }\n // On step complete, update currentStepIndex\n if (data.type === 'step:complete') {\n setRuns((prev) =>\n prev.map((run) =>\n run.id === data.runId\n ? { ...run, currentStepIndex: data.stepIndex + 1 }\n : run,\n ),\n )\n }\n // On step start or fail, refresh to get latest state\n if (data.type === 'step:start' || data.type === 'step:fail') {\n refresh()\n }\n // log:write is handled by useJobLogs, not useRuns\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n // EventSource will automatically reconnect\n }\n\n return () => {\n eventSource.close()\n eventSourceRef.current = null\n }\n }, [api, jobName, page, refresh])\n\n const nextPage = useCallback(() => {\n if (hasMore) {\n setPage((p) => p + 1)\n }\n }, [hasMore])\n\n const prevPage = useCallback(() => {\n setPage((p) => Math.max(0, p - 1))\n }, [])\n\n const goToPage = useCallback((newPage: number) => {\n setPage(Math.max(0, newPage))\n }, [])\n\n return {\n runs,\n page,\n hasMore,\n isLoading,\n error,\n nextPage,\n prevPage,\n goToPage,\n refresh,\n }\n}\n","import { useCallback, useState } from 'react'\n\n/**\n * Run record returned from the server API\n */\nexport interface RunRecord {\n id: string\n jobName: string\n status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n payload: unknown\n output: unknown | null\n error: string | null\n progress: { current: number; total?: number; message?: string } | null\n currentStepIndex: number\n stepCount: number\n createdAt: string\n startedAt: string | null\n completedAt: string | null\n}\n\n/**\n * Step record returned from the server API\n */\nexport interface StepRecord {\n name: string\n status: 'completed' | 'failed'\n output: unknown\n}\n\nexport interface UseRunActionsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n}\n\nexport interface UseRunActionsClientResult {\n /**\n * Retry a failed or cancelled run\n */\n retry: (runId: string) => Promise<void>\n /**\n * Cancel a pending or running run\n */\n cancel: (runId: string) => Promise<void>\n /**\n * Delete a run (only completed, failed, or cancelled runs)\n */\n deleteRun: (runId: string) => Promise<void>\n /**\n * Get a single run by ID\n */\n getRun: (runId: string) => Promise<RunRecord | null>\n /**\n * Get steps for a run\n */\n getSteps: (runId: string) => Promise<StepRecord[]>\n /**\n * Whether an action is in progress\n */\n isLoading: boolean\n /**\n * Error message from last action\n */\n error: string | null\n}\n\n/**\n * Hook for run actions (retry, cancel) via server API.\n *\n * @example\n * ```tsx\n * function RunActions({ runId, status }: { runId: string; status: string }) {\n * const { retry, cancel, isLoading, error } = useRunActions({\n * api: '/api/durably',\n * })\n *\n * return (\n * <div>\n * {status === 'failed' && (\n * <button onClick={() => retry(runId)} disabled={isLoading}>\n * Retry\n * </button>\n * )}\n * {(status === 'pending' || status === 'running') && (\n * <button onClick={() => cancel(runId)} disabled={isLoading}>\n * Cancel\n * </button>\n * )}\n * {error && <span className=\"error\">{error}</span>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useRunActions(\n options: UseRunActionsClientOptions,\n): UseRunActionsClientResult {\n const { api } = options\n\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const retry = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/retry?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'POST' })\n\n if (!response.ok) {\n let errorMessage = `Failed to retry: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const cancel = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/cancel?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'POST' })\n\n if (!response.ok) {\n let errorMessage = `Failed to cancel: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const deleteRun = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/run?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n let errorMessage = `Failed to delete: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const getRun = useCallback(\n async (runId: string): Promise<RunRecord | null> => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/run?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url)\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n let errorMessage = `Failed to get run: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n\n return (await response.json()) as RunRecord\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const getSteps = useCallback(\n async (runId: string): Promise<StepRecord[]> => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/steps?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url)\n\n if (!response.ok) {\n let errorMessage = `Failed to get steps: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n\n return (await response.json()) as StepRecord[]\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n return {\n retry,\n cancel,\n deleteRun,\n getRun,\n getSteps,\n isLoading,\n error,\n }\n}\n"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect","useCallback","useEffect","useRef","useState","useState","useRef","useCallback","useEffect","useCallback","useState"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/use-job.ts","../src/client/use-sse-subscription.ts","../src/shared/sse-event-subscriber.ts","../src/client/use-job-logs.ts","../src/client/use-job-run.ts","../src/client/create-durably-client.ts","../src/client/create-job-hooks.ts","../src/client/use-runs.ts","../src/client/use-run-actions.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Job name to trigger\n */\n jobName: string\n /**\n * Initial Run ID to subscribe to (for reconnection scenarios)\n * When provided, the hook will immediately start subscribing to this run\n */\n initialRunId?: string\n /**\n * Automatically resume tracking a running/pending job on mount\n * @default true\n */\n autoResume?: boolean\n /**\n * Automatically switch to tracking the latest triggered job\n * @default true\n */\n followLatest?: boolean\n}\n\nexport interface UseJobClientResult<TInput, TOutput> {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Trigger the job with the given input\n */\n trigger: (input: TInput) => Promise<{ runId: string }>\n /**\n * Trigger and wait for completion\n */\n triggerAndWait: (input: TInput) => Promise<{ runId: string; output: TOutput }>\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n /**\n * Current run ID\n */\n currentRunId: string | null\n /**\n * Reset all state\n */\n reset: () => void\n}\n\n/**\n * Hook for triggering and subscribing to jobs via server API.\n * Uses fetch for triggering and EventSource for SSE subscription.\n */\nexport function useJob<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> = Record<string, unknown>,\n>(options: UseJobClientOptions): UseJobClientResult<TInput, TOutput> {\n const {\n api,\n jobName,\n initialRunId,\n autoResume = true,\n followLatest = true,\n } = options\n\n const [currentRunId, setCurrentRunId] = useState<string | null>(\n initialRunId ?? null,\n )\n const [isPending, setIsPending] = useState(false)\n\n // Track if user has triggered a run (to prevent autoResume from overwriting)\n const hasUserTriggered = useRef(false)\n\n const subscription = useSSESubscription<TOutput>(api, currentRunId)\n\n // Auto-resume: fetch running/pending job on mount\n useEffect(() => {\n if (!autoResume) return\n if (initialRunId) return // Skip if initialRunId is provided\n\n const abortController = new AbortController()\n\n const findActiveRun = async () => {\n // Try running first\n const runningParams = new URLSearchParams({\n jobName,\n status: 'running',\n limit: '1',\n })\n const runningRes = await fetch(`${api}/runs?${runningParams}`, {\n signal: abortController.signal,\n })\n if (runningRes.ok) {\n const runs = (await runningRes.json()) as Array<{ id: string }>\n if (runs.length > 0) {\n // Don't overwrite if user already triggered a run\n if (hasUserTriggered.current) return\n setCurrentRunId(runs[0].id)\n return\n }\n }\n\n // Try pending\n const pendingParams = new URLSearchParams({\n jobName,\n status: 'pending',\n limit: '1',\n })\n const pendingRes = await fetch(`${api}/runs?${pendingParams}`, {\n signal: abortController.signal,\n })\n if (pendingRes.ok) {\n const runs = (await pendingRes.json()) as Array<{ id: string }>\n if (runs.length > 0) {\n // Don't overwrite if user already triggered a run\n if (hasUserTriggered.current) return\n setCurrentRunId(runs[0].id)\n }\n }\n }\n\n findActiveRun().catch((err) => {\n // Ignore abort errors\n if (err.name !== 'AbortError') {\n console.error('autoResume error:', err)\n }\n })\n\n return () => {\n abortController.abort()\n }\n }, [api, jobName, autoResume, initialRunId])\n\n // Follow latest: subscribe to job-level SSE for run:trigger/run:start events\n useEffect(() => {\n if (!followLatest) return\n\n const params = new URLSearchParams({ jobName })\n const eventSource = new EventSource(`${api}/runs/subscribe?${params}`)\n\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data) as {\n type: string\n runId?: string\n }\n if (\n (data.type === 'run:trigger' || data.type === 'run:start') &&\n data.runId\n ) {\n setCurrentRunId(data.runId)\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n // SSE connection error - could reconnect or log for debugging\n // No need to surface error to user as this is a background subscription\n }\n\n return () => {\n eventSource.close()\n }\n }, [api, jobName, followLatest])\n\n const trigger = useCallback(\n async (input: TInput): Promise<{ runId: string }> => {\n // Mark that user has triggered (prevents autoResume from overwriting)\n hasUserTriggered.current = true\n\n // Reset state\n subscription.reset()\n setIsPending(true)\n\n const response = await fetch(`${api}/trigger`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ jobName, input }),\n })\n\n if (!response.ok) {\n setIsPending(false)\n const errorText = await response.text()\n throw new Error(errorText || `HTTP ${response.status}`)\n }\n\n const { runId } = (await response.json()) as { runId: string }\n setCurrentRunId(runId)\n\n return { runId }\n },\n [api, jobName, subscription.reset],\n )\n\n const triggerAndWait = useCallback(\n async (input: TInput): Promise<{ runId: string; output: TOutput }> => {\n const { runId } = await trigger(input)\n\n return new Promise((resolve, reject) => {\n const checkInterval = setInterval(() => {\n if (subscription.status === 'completed' && subscription.output) {\n clearInterval(checkInterval)\n resolve({ runId, output: subscription.output })\n } else if (subscription.status === 'failed') {\n clearInterval(checkInterval)\n reject(new Error(subscription.error ?? 'Job failed'))\n } else if (subscription.status === 'cancelled') {\n clearInterval(checkInterval)\n reject(new Error('Job cancelled'))\n }\n }, 50)\n })\n },\n [trigger, subscription.status, subscription.output, subscription.error],\n )\n\n const reset = useCallback(() => {\n subscription.reset()\n setCurrentRunId(null)\n setIsPending(false)\n }, [subscription.reset])\n\n // Compute effective status (pending overrides null when we've triggered but SSE hasn't started)\n const effectiveStatus = subscription.status ?? (isPending ? 'pending' : null)\n\n // Clear pending when we get a real status\n useEffect(() => {\n if (subscription.status && isPending) {\n setIsPending(false)\n }\n }, [subscription.status, isPending])\n\n return {\n trigger,\n triggerAndWait,\n status: effectiveStatus,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: effectiveStatus === 'running',\n isPending: effectiveStatus === 'pending',\n isCompleted: effectiveStatus === 'completed',\n isFailed: effectiveStatus === 'failed',\n isCancelled: effectiveStatus === 'cancelled',\n currentRunId,\n reset,\n }\n}\n","import { useMemo } from 'react'\nimport { createSSEEventSubscriber } from '../shared/sse-event-subscriber'\nimport {\n useSubscription,\n type UseSubscriptionOptions,\n type UseSubscriptionResult,\n} from '../shared/use-subscription'\nimport type { SubscriptionState } from '../types'\n\n/** @deprecated Use SubscriptionState from '../types' instead */\nexport type SSESubscriptionState<TOutput = unknown> = SubscriptionState<TOutput>\n\n/** @deprecated Use UseSubscriptionOptions from '../shared/use-subscription' instead */\nexport type UseSSESubscriptionOptions = UseSubscriptionOptions\n\n/** @deprecated Use UseSubscriptionResult from '../shared/use-subscription' instead */\nexport type UseSSESubscriptionResult<TOutput = unknown> =\n UseSubscriptionResult<TOutput>\n\n/**\n * Internal hook for subscribing to run events via SSE.\n * Used by client-mode hooks (useJob, useJobRun, useJobLogs).\n *\n * @deprecated Consider using useSubscription with createSSEEventSubscriber directly.\n */\nexport function useSSESubscription<TOutput = unknown>(\n api: string | null,\n runId: string | null,\n options?: UseSSESubscriptionOptions,\n): UseSSESubscriptionResult<TOutput> {\n const subscriber = useMemo(\n () => (api ? createSSEEventSubscriber(api) : null),\n [api],\n )\n\n return useSubscription<TOutput>(subscriber, runId, options)\n}\n","import type { DurablyEvent } from '../types'\nimport type { EventSubscriber, SubscriptionEvent } from './event-subscriber'\n\n/**\n * EventSubscriber implementation using Server-Sent Events (SSE).\n * Used in client environments that communicate with a Durably server via HTTP.\n */\nexport function createSSEEventSubscriber(apiBaseUrl: string): EventSubscriber {\n return {\n subscribe<TOutput = unknown>(\n runId: string,\n onEvent: (event: SubscriptionEvent<TOutput>) => void,\n ): () => void {\n const url = `${apiBaseUrl}/subscribe?runId=${encodeURIComponent(runId)}`\n const eventSource = new EventSource(url)\n\n eventSource.onmessage = (messageEvent) => {\n try {\n const data = JSON.parse(messageEvent.data) as DurablyEvent\n if (data.runId !== runId) return\n\n switch (data.type) {\n case 'run:start':\n onEvent({ type: 'run:start' })\n break\n case 'run:complete':\n onEvent({\n type: 'run:complete',\n output: data.output as TOutput,\n })\n break\n case 'run:fail':\n onEvent({ type: 'run:fail', error: data.error })\n break\n case 'run:cancel':\n onEvent({ type: 'run:cancel' })\n break\n case 'run:retry':\n onEvent({ type: 'run:retry' })\n break\n case 'run:progress':\n onEvent({ type: 'run:progress', progress: data.progress })\n break\n case 'log:write':\n onEvent({\n type: 'log:write',\n runId: data.runId,\n stepName: null,\n level: data.level,\n message: data.message,\n data: data.data,\n })\n break\n }\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n onEvent({ type: 'connection_error', error: 'Connection failed' })\n eventSource.close()\n }\n\n return () => {\n eventSource.close()\n }\n },\n }\n}\n","import type { LogEntry } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobLogsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * The run ID to subscribe to logs for\n */\n runId: string | null\n /**\n * Maximum number of logs to keep (default: unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobLogsClientResult {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Clear all logs\n */\n clearLogs: () => void\n}\n\n/**\n * Hook for subscribing to logs from a run via server API.\n * Uses EventSource for SSE subscription.\n */\nexport function useJobLogs(\n options: UseJobLogsClientOptions,\n): UseJobLogsClientResult {\n const { api, runId, maxLogs } = options\n\n const subscription = useSSESubscription(api, runId, { maxLogs })\n\n return {\n logs: subscription.logs,\n clearLogs: subscription.clearLogs,\n }\n}\n","import { useEffect, useRef } from 'react'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useSSESubscription } from './use-sse-subscription'\n\nexport interface UseJobRunClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * The run ID to subscribe to\n */\n runId: string | null\n /**\n * Callback when run starts (transitions to pending/running)\n */\n onStart?: () => void\n /**\n * Callback when run completes successfully\n */\n onComplete?: () => void\n /**\n * Callback when run fails\n */\n onFail?: () => void\n}\n\nexport interface UseJobRunClientResult<TOutput = unknown> {\n /**\n * Whether the hook is ready (always true for client mode)\n */\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n}\n\n/**\n * Hook for subscribing to an existing run via server API.\n * Uses EventSource for SSE subscription.\n */\nexport function useJobRun<TOutput = unknown>(\n options: UseJobRunClientOptions,\n): UseJobRunClientResult<TOutput> {\n const { api, runId, onStart, onComplete, onFail } = options\n\n const subscription = useSSESubscription<TOutput>(api, runId)\n\n // If we have a runId but no status yet, treat as pending\n const effectiveStatus = subscription.status ?? (runId ? 'pending' : null)\n\n const isCompleted = effectiveStatus === 'completed'\n const isFailed = effectiveStatus === 'failed'\n const isPending = effectiveStatus === 'pending'\n const isRunning = effectiveStatus === 'running'\n const isCancelled = effectiveStatus === 'cancelled'\n\n // Track previous status to detect transitions (use effectiveStatus, not subscription.status)\n const prevStatusRef = useRef<RunStatus | null>(null)\n\n useEffect(() => {\n const prevStatus = prevStatusRef.current\n prevStatusRef.current = effectiveStatus\n\n // Only fire callbacks on status transitions\n if (prevStatus !== effectiveStatus) {\n // Fire onStart when transitioning from null to pending/running\n if (prevStatus === null && (isPending || isRunning) && onStart) {\n onStart()\n }\n if (isCompleted && onComplete) {\n onComplete()\n }\n if (isFailed && onFail) {\n onFail()\n }\n }\n }, [\n effectiveStatus,\n isPending,\n isRunning,\n isCompleted,\n isFailed,\n onStart,\n onComplete,\n onFail,\n ])\n\n return {\n status: effectiveStatus,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning,\n isPending,\n isCompleted,\n isFailed,\n isCancelled,\n }\n}\n","import type { InferInput, InferOutput } from '../types'\nimport { useJob, type UseJobClientResult } from './use-job'\nimport { useJobLogs, type UseJobLogsClientResult } from './use-job-logs'\nimport { useJobRun, type UseJobRunClientResult } from './use-job-run'\n\n/**\n * Type-safe hooks for a specific job\n */\nexport interface JobClient<TInput, TOutput> {\n /**\n * Hook for triggering and monitoring the job\n */\n useJob: () => UseJobClientResult<TInput, TOutput>\n\n /**\n * Hook for subscribing to an existing run by ID\n */\n useRun: (runId: string | null) => UseJobRunClientResult<TOutput>\n\n /**\n * Hook for subscribing to logs from a run\n */\n useLogs: (\n runId: string | null,\n options?: { maxLogs?: number },\n ) => UseJobLogsClientResult\n}\n\n/**\n * Options for createDurablyClient\n */\nexport interface CreateDurablyClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n}\n\n/**\n * A type-safe client with hooks for each registered job\n */\nexport type DurablyClient<TJobs extends Record<string, unknown>> = {\n [K in keyof TJobs]: JobClient<InferInput<TJobs[K]>, InferOutput<TJobs[K]>>\n}\n\n/**\n * Create a type-safe Durably client with hooks for all registered jobs.\n *\n * @example\n * ```tsx\n * // Server: register jobs\n * // app/lib/durably.server.ts\n * export const jobs = durably.register({\n * importCsv: importCsvJob,\n * syncUsers: syncUsersJob,\n * })\n *\n * // Client: create typed client\n * // app/lib/durably.client.ts\n * import type { jobs } from '~/lib/durably.server'\n * import { createDurablyClient } from '@coji/durably-react/client'\n *\n * export const durably = createDurablyClient<typeof jobs>({\n * api: '/api/durably',\n * })\n *\n * // In your component - fully type-safe with autocomplete\n * function CsvImporter() {\n * const { trigger, output, isRunning } = durably.importCsv.useJob()\n *\n * return (\n * <button onClick={() => trigger({ rows: [...] })}>\n * Import\n * </button>\n * )\n * }\n * ```\n */\nexport function createDurablyClient<TJobs extends Record<string, unknown>>(\n options: CreateDurablyClientOptions,\n): DurablyClient<TJobs> {\n const { api } = options\n\n // Create a proxy that generates job clients on demand\n return new Proxy({} as DurablyClient<TJobs>, {\n get(_target, jobKey: string) {\n return {\n useJob: () => {\n return useJob({ api, jobName: jobKey })\n },\n\n useRun: (runId: string | null) => {\n return useJobRun({ api, runId })\n },\n\n useLogs: (runId: string | null, logsOptions?: { maxLogs?: number }) => {\n return useJobLogs({ api, runId, maxLogs: logsOptions?.maxLogs })\n },\n }\n },\n })\n}\n","import type { JobDefinition } from '@coji/durably'\nimport type { InferInput, InferOutput } from '../types'\nimport { useJob, type UseJobClientResult } from './use-job'\nimport { useJobLogs, type UseJobLogsClientResult } from './use-job-logs'\nimport { useJobRun, type UseJobRunClientResult } from './use-job-run'\n\n/**\n * Options for createJobHooks\n */\nexport interface CreateJobHooksOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Job name (must match the server-side job name)\n */\n jobName: string\n}\n\n/**\n * Type-safe hooks for a specific job\n */\nexport interface JobHooks<TInput, TOutput> {\n /**\n * Hook for triggering and monitoring the job\n */\n useJob: () => UseJobClientResult<TInput, TOutput>\n\n /**\n * Hook for subscribing to an existing run by ID\n */\n useRun: (runId: string | null) => UseJobRunClientResult<TOutput>\n\n /**\n * Hook for subscribing to logs from a run\n */\n useLogs: (\n runId: string | null,\n options?: { maxLogs?: number },\n ) => UseJobLogsClientResult\n}\n\n/**\n * Create type-safe hooks for a specific job.\n *\n * @example\n * ```tsx\n * // Import job type from server (type-only import is safe)\n * import type { importCsvJob } from '~/lib/durably.server'\n * import { createJobHooks } from '@coji/durably-react/client'\n *\n * const importCsv = createJobHooks<typeof importCsvJob>({\n * api: '/api/durably',\n * jobName: 'import-csv',\n * })\n *\n * // In your component - fully type-safe\n * function CsvImporter() {\n * const { trigger, output, progress, isRunning } = importCsv.useJob()\n *\n * return (\n * <button onClick={() => trigger({ rows: [...] })}>\n * Import\n * </button>\n * )\n * }\n * ```\n */\nexport function createJobHooks<\n // biome-ignore lint/suspicious/noExplicitAny: TJob needs to accept any JobDefinition\n TJob extends JobDefinition<string, any, any>,\n>(\n options: CreateJobHooksOptions,\n): JobHooks<InferInput<TJob>, InferOutput<TJob>> {\n const { api, jobName } = options\n\n return {\n useJob: () => {\n return useJob<InferInput<TJob>, InferOutput<TJob>>({ api, jobName })\n },\n\n useRun: (runId: string | null) => {\n return useJobRun<InferOutput<TJob>>({ api, runId })\n },\n\n useLogs: (runId: string | null, logsOptions?: { maxLogs?: number }) => {\n return useJobLogs({ api, runId, maxLogs: logsOptions?.maxLogs })\n },\n }\n}\n","import type { JobDefinition } from '@coji/durably'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n type Progress,\n type RunStatus,\n type TypedClientRun,\n isJobDefinition,\n} from '../types'\n\n// Re-export types for convenience\nexport type { ClientRun, TypedClientRun } from '../types'\n\n/**\n * SSE notification event from /runs/subscribe\n */\ntype RunUpdateEvent =\n | {\n type:\n | 'run:trigger'\n | 'run:start'\n | 'run:complete'\n | 'run:fail'\n | 'run:cancel'\n | 'run:delete'\n | 'run:retry'\n runId: string\n jobName: string\n }\n | { type: 'run:progress'; runId: string; jobName: string; progress: Progress }\n | {\n type: 'step:start' | 'step:complete'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n }\n | {\n type: 'step:fail'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n error: string\n labels: Record<string, string>\n }\n | {\n type: 'step:cancel'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n labels: Record<string, string>\n }\n | {\n type: 'log:write'\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n }\n\nexport interface UseRunsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n /**\n * Filter by job name\n */\n jobName?: string\n /**\n * Filter by status\n */\n status?: RunStatus\n /**\n * Filter by labels (all specified labels must match)\n */\n labels?: Record<string, string>\n /**\n * Number of runs per page\n * @default 10\n */\n pageSize?: number\n /**\n * Subscribe to real-time updates via SSE (first page only)\n * @default true\n */\n realtime?: boolean\n}\n\nexport interface UseRunsClientResult<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> {\n /**\n * List of runs for the current page\n */\n runs: TypedClientRun<TInput, TOutput>[]\n /**\n * Current page (0-indexed)\n */\n page: number\n /**\n * Whether there are more pages\n */\n hasMore: boolean\n /**\n * Whether data is being loaded\n */\n isLoading: boolean\n /**\n * Error message if fetch failed\n */\n error: string | null\n /**\n * Go to the next page\n */\n nextPage: () => void\n /**\n * Go to the previous page\n */\n prevPage: () => void\n /**\n * Go to a specific page\n */\n goToPage: (page: number) => void\n /**\n * Refresh the current page\n */\n refresh: () => Promise<void>\n}\n\n/**\n * Hook for listing runs via server API with pagination.\n * First page (page 0) automatically subscribes to SSE for real-time updates.\n * Other pages are static and require manual refresh.\n *\n * @example With generic type parameter (dashboard with multiple job types)\n * ```tsx\n * type DashboardRun = TypedClientRun<ImportInput, ImportOutput> | TypedClientRun<SyncInput, SyncOutput>\n *\n * function Dashboard() {\n * const { runs } = useRuns<DashboardRun>({ api: '/api/durably', pageSize: 10 })\n * // runs are typed as DashboardRun[]\n * }\n * ```\n *\n * @example With JobDefinition (single job, auto-filters by jobName)\n * ```tsx\n * const myJob = defineJob({ name: 'my-job', ... })\n *\n * function RunHistory() {\n * const { runs } = useRuns(myJob, { api: '/api/durably' })\n * // runs[0].output is typed!\n * return <div>{runs[0]?.output?.someField}</div>\n * }\n * ```\n *\n * @example With options only (untyped)\n * ```tsx\n * function RunHistory() {\n * const { runs } = useRuns({ api: '/api/durably', pageSize: 10 })\n * // runs[0].output is unknown\n * }\n * ```\n */\n// Overload 1: With generic type parameter\nexport function useRuns<\n TRun extends TypedClientRun<\n Record<string, unknown>,\n Record<string, unknown> | undefined\n >,\n>(\n options: UseRunsClientOptions,\n): UseRunsClientResult<\n TRun extends TypedClientRun<infer I, infer _O> ? I : Record<string, unknown>,\n TRun extends TypedClientRun<infer _I, infer O> ? O : Record<string, unknown>\n>\n\n// Overload 2: With JobDefinition for type inference (auto-filters by jobName)\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options: Omit<UseRunsClientOptions, 'jobName'>,\n): UseRunsClientResult<TInput, TOutput>\n\n// Overload 3: Without type parameter (untyped, backward compatible)\nexport function useRuns(options: UseRunsClientOptions): UseRunsClientResult\n\n// Implementation\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinitionOrOptions:\n | JobDefinition<TName, TInput, TOutput>\n | UseRunsClientOptions,\n optionsArg?: Omit<UseRunsClientOptions, 'jobName'>,\n): UseRunsClientResult<TInput, TOutput> {\n // Determine if first argument is a JobDefinition using type guard\n const isJob = isJobDefinition(jobDefinitionOrOptions)\n\n const jobName = isJob\n ? jobDefinitionOrOptions.name\n : (jobDefinitionOrOptions as UseRunsClientOptions).jobName\n\n const options = isJob\n ? (optionsArg as Omit<UseRunsClientOptions, 'jobName'>)\n : (jobDefinitionOrOptions as UseRunsClientOptions)\n\n const { api, status, labels, pageSize = 10, realtime = true } = options\n\n // Stabilize labels reference to prevent infinite re-renders\n const labelsKey = labels ? JSON.stringify(labels) : undefined\n const stableLabels = useMemo(\n () =>\n labelsKey ? (JSON.parse(labelsKey) as Record<string, string>) : undefined,\n [labelsKey],\n )\n\n const [runs, setRuns] = useState<TypedClientRun<TInput, TOutput>[]>([])\n const [page, setPage] = useState(0)\n const [hasMore, setHasMore] = useState(false)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n\n const isMountedRef = useRef(true)\n const eventSourceRef = useRef<EventSource | null>(null)\n\n const refresh = useCallback(async () => {\n setIsLoading(true)\n setError(null)\n\n try {\n const params = new URLSearchParams()\n if (jobName) params.set('jobName', jobName)\n if (status) params.set('status', status)\n appendLabelsToParams(params, stableLabels)\n params.set('limit', String(pageSize + 1))\n params.set('offset', String(page * pageSize))\n\n const url = `${api}/runs?${params.toString()}`\n const response = await fetch(url)\n\n if (!response.ok) {\n throw new Error(`Failed to fetch runs: ${response.statusText}`)\n }\n\n const data = (await response.json()) as TypedClientRun<TInput, TOutput>[]\n\n if (isMountedRef.current) {\n setHasMore(data.length > pageSize)\n setRuns(data.slice(0, pageSize))\n }\n } catch (err) {\n if (isMountedRef.current) {\n setError(err instanceof Error ? err.message : 'Unknown error')\n }\n } finally {\n if (isMountedRef.current) {\n setIsLoading(false)\n }\n }\n }, [api, jobName, status, stableLabels, pageSize, page])\n\n // Initial fetch\n useEffect(() => {\n isMountedRef.current = true\n refresh()\n\n return () => {\n isMountedRef.current = false\n }\n }, [refresh])\n\n // SSE subscription for first page only (when realtime is enabled)\n useEffect(() => {\n // Only subscribe to SSE on first page with realtime enabled\n if (!realtime || page !== 0) {\n // Clean up any existing connection when navigating away from first page\n if (eventSourceRef.current) {\n eventSourceRef.current.close()\n eventSourceRef.current = null\n }\n return\n }\n\n // Build SSE URL\n const params = new URLSearchParams()\n if (jobName) params.set('jobName', jobName)\n appendLabelsToParams(params, stableLabels)\n const sseUrl = `${api}/runs/subscribe${params.toString() ? `?${params.toString()}` : ''}`\n\n const eventSource = new EventSource(sseUrl)\n eventSourceRef.current = eventSource\n\n eventSource.onmessage = (event) => {\n try {\n const data = JSON.parse(event.data) as RunUpdateEvent\n // On run lifecycle events, refresh the list\n if (\n data.type === 'run:trigger' ||\n data.type === 'run:start' ||\n data.type === 'run:complete' ||\n data.type === 'run:fail' ||\n data.type === 'run:cancel' ||\n data.type === 'run:delete' ||\n data.type === 'run:retry'\n ) {\n refresh()\n }\n // On progress update, update the run in place\n if (data.type === 'run:progress') {\n setRuns((prev) =>\n prev.map((run) =>\n run.id === data.runId ? { ...run, progress: data.progress } : run,\n ),\n )\n }\n // On step complete, update currentStepIndex\n if (data.type === 'step:complete') {\n setRuns((prev) =>\n prev.map((run) =>\n run.id === data.runId\n ? { ...run, currentStepIndex: data.stepIndex + 1 }\n : run,\n ),\n )\n }\n // On step start or fail, refresh to get latest state\n if (\n data.type === 'step:start' ||\n data.type === 'step:fail' ||\n data.type === 'step:cancel'\n ) {\n refresh()\n }\n // log:write is handled by useJobLogs, not useRuns\n } catch {\n // Ignore parse errors\n }\n }\n\n eventSource.onerror = () => {\n // EventSource will automatically reconnect\n }\n\n return () => {\n eventSource.close()\n eventSourceRef.current = null\n }\n }, [api, jobName, stableLabels, page, realtime, refresh])\n\n const nextPage = useCallback(() => {\n if (hasMore) {\n setPage((p) => p + 1)\n }\n }, [hasMore])\n\n const prevPage = useCallback(() => {\n setPage((p) => Math.max(0, p - 1))\n }, [])\n\n const goToPage = useCallback((newPage: number) => {\n setPage(Math.max(0, newPage))\n }, [])\n\n return {\n runs,\n page,\n hasMore,\n isLoading,\n error,\n nextPage,\n prevPage,\n goToPage,\n refresh,\n }\n}\n\nfunction appendLabelsToParams(\n params: URLSearchParams,\n labels: Record<string, string> | undefined,\n) {\n if (!labels) return\n for (const [key, value] of Object.entries(labels)) {\n params.set(`label.${key}`, value)\n }\n}\n","import { useCallback, useState } from 'react'\nimport type { ClientRun } from '../types'\n\n/**\n * Step record returned from the server API\n */\nexport interface StepRecord {\n name: string\n status: 'completed' | 'failed' | 'cancelled'\n output: unknown\n}\n\nexport interface UseRunActionsClientOptions {\n /**\n * API endpoint URL (e.g., '/api/durably')\n */\n api: string\n}\n\nexport interface UseRunActionsClientResult {\n /**\n * Retry a failed or cancelled run\n */\n retry: (runId: string) => Promise<void>\n /**\n * Cancel a pending or running run\n */\n cancel: (runId: string) => Promise<void>\n /**\n * Delete a run (only completed, failed, or cancelled runs)\n */\n deleteRun: (runId: string) => Promise<void>\n /**\n * Get a single run by ID\n */\n getRun: (runId: string) => Promise<ClientRun | null>\n /**\n * Get steps for a run\n */\n getSteps: (runId: string) => Promise<StepRecord[]>\n /**\n * Whether an action is in progress\n */\n isLoading: boolean\n /**\n * Error message from last action\n */\n error: string | null\n}\n\n/**\n * Hook for run actions (retry, cancel) via server API.\n *\n * @example\n * ```tsx\n * function RunActions({ runId, status }: { runId: string; status: string }) {\n * const { retry, cancel, isLoading, error } = useRunActions({\n * api: '/api/durably',\n * })\n *\n * return (\n * <div>\n * {status === 'failed' && (\n * <button onClick={() => retry(runId)} disabled={isLoading}>\n * Retry\n * </button>\n * )}\n * {(status === 'pending' || status === 'running') && (\n * <button onClick={() => cancel(runId)} disabled={isLoading}>\n * Cancel\n * </button>\n * )}\n * {error && <span className=\"error\">{error}</span>}\n * </div>\n * )\n * }\n * ```\n */\nexport function useRunActions(\n options: UseRunActionsClientOptions,\n): UseRunActionsClientResult {\n const { api } = options\n\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const retry = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/retry?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'POST' })\n\n if (!response.ok) {\n let errorMessage = `Failed to retry: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const cancel = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/cancel?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'POST' })\n\n if (!response.ok) {\n let errorMessage = `Failed to cancel: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const deleteRun = useCallback(\n async (runId: string) => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/run?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url, { method: 'DELETE' })\n\n if (!response.ok) {\n let errorMessage = `Failed to delete: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const getRun = useCallback(\n async (runId: string): Promise<ClientRun | null> => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/run?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url)\n\n if (response.status === 404) {\n return null\n }\n\n if (!response.ok) {\n let errorMessage = `Failed to get run: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n\n return (await response.json()) as ClientRun\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n const getSteps = useCallback(\n async (runId: string): Promise<StepRecord[]> => {\n setIsLoading(true)\n setError(null)\n\n try {\n const url = `${api}/steps?runId=${encodeURIComponent(runId)}`\n const response = await fetch(url)\n\n if (!response.ok) {\n let errorMessage = `Failed to get steps: ${response.statusText}`\n try {\n const data = await response.json()\n if (data.error) {\n errorMessage = data.error\n }\n } catch {\n // Response is not JSON, use statusText\n }\n throw new Error(errorMessage)\n }\n\n return (await response.json()) as StepRecord[]\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error'\n setError(message)\n throw err\n } finally {\n setIsLoading(false)\n }\n },\n [api],\n )\n\n return {\n retry,\n cancel,\n deleteRun,\n getRun,\n getSteps,\n isLoading,\n error,\n }\n}\n"],"mappings":"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","names":["useEffect","useRef","useRef","useEffect","useCallback","useEffect","useMemo","useRef","useState","useMemo","useState","useRef","useCallback","useEffect","useCallback","useState"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { Durably, JobDefinition } from '@coji/durably';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
|
-
import { R as RunStatus, L as LogEntry, P as Progress, T as TypedRun } from './types-
|
|
5
|
-
export { D as DurablyEvent } from './types-
|
|
4
|
+
import { R as RunStatus, L as LogEntry, P as Progress, T as TypedRun } from './types-xrRs7jov.js';
|
|
5
|
+
export { D as DurablyEvent } from './types-xrRs7jov.js';
|
|
6
6
|
|
|
7
7
|
interface DurablyContextValue {
|
|
8
8
|
durably: Durably;
|
|
@@ -212,6 +212,10 @@ interface UseRunsOptions {
|
|
|
212
212
|
* Filter by status
|
|
213
213
|
*/
|
|
214
214
|
status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
215
|
+
/**
|
|
216
|
+
* Filter by labels (all specified labels must match)
|
|
217
|
+
*/
|
|
218
|
+
labels?: Record<string, string>;
|
|
215
219
|
/**
|
|
216
220
|
* Number of runs per page
|
|
217
221
|
* @default 10
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
isJobDefinition,
|
|
4
4
|
subscriptionReducer,
|
|
5
5
|
useSubscription
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XRJEZWAV.js";
|
|
7
7
|
|
|
8
8
|
// src/context.tsx
|
|
9
9
|
import { Suspense, createContext, use, useContext } from "react";
|
|
@@ -404,7 +404,7 @@ function useJobRun(options) {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
// src/hooks/use-runs.ts
|
|
407
|
-
import { useCallback as useCallback3, useEffect as useEffect5, useState } from "react";
|
|
407
|
+
import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo3, useState } from "react";
|
|
408
408
|
function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
409
409
|
const { durably } = useDurably();
|
|
410
410
|
const isJob = isJobDefinition(jobDefinitionOrOptions);
|
|
@@ -413,10 +413,15 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
413
413
|
const pageSize = options?.pageSize ?? 10;
|
|
414
414
|
const realtime = options?.realtime ?? true;
|
|
415
415
|
const status = options?.status;
|
|
416
|
+
const labelsKey = options?.labels ? JSON.stringify(options.labels) : void 0;
|
|
417
|
+
const labels = useMemo3(
|
|
418
|
+
() => labelsKey ? JSON.parse(labelsKey) : void 0,
|
|
419
|
+
[labelsKey]
|
|
420
|
+
);
|
|
416
421
|
const [runs, setRuns] = useState([]);
|
|
417
422
|
const [page, setPage] = useState(0);
|
|
418
423
|
const [hasMore, setHasMore] = useState(false);
|
|
419
|
-
const [isLoading, setIsLoading] = useState(
|
|
424
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
420
425
|
const refresh = useCallback3(async () => {
|
|
421
426
|
if (!durably) return;
|
|
422
427
|
setIsLoading(true);
|
|
@@ -424,6 +429,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
424
429
|
const data = await durably.getRuns({
|
|
425
430
|
jobName,
|
|
426
431
|
status,
|
|
432
|
+
labels,
|
|
427
433
|
limit: pageSize + 1,
|
|
428
434
|
offset: page * pageSize
|
|
429
435
|
});
|
|
@@ -432,7 +438,7 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
432
438
|
} finally {
|
|
433
439
|
setIsLoading(false);
|
|
434
440
|
}
|
|
435
|
-
}, [durably, jobName, status, pageSize, page]);
|
|
441
|
+
}, [durably, jobName, status, labels, pageSize, page]);
|
|
436
442
|
useEffect5(() => {
|
|
437
443
|
if (!durably) return;
|
|
438
444
|
refresh();
|
|
@@ -443,10 +449,13 @@ function useRuns(jobDefinitionOrOptions, optionsArg) {
|
|
|
443
449
|
durably.on("run:complete", refresh),
|
|
444
450
|
durably.on("run:fail", refresh),
|
|
445
451
|
durably.on("run:cancel", refresh),
|
|
452
|
+
durably.on("run:delete", refresh),
|
|
446
453
|
durably.on("run:retry", refresh),
|
|
447
454
|
durably.on("run:progress", refresh),
|
|
448
455
|
durably.on("step:start", refresh),
|
|
449
|
-
durably.on("step:complete", refresh)
|
|
456
|
+
durably.on("step:complete", refresh),
|
|
457
|
+
durably.on("step:fail", refresh),
|
|
458
|
+
durably.on("step:cancel", refresh)
|
|
450
459
|
];
|
|
451
460
|
return () => {
|
|
452
461
|
for (const unsubscribe of unsubscribes) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.tsx","../src/hooks/use-job.ts","../src/hooks/use-auto-resume.ts","../src/hooks/use-job-subscription.ts","../src/hooks/use-run-subscription.ts","../src/shared/durably-event-subscriber.ts","../src/hooks/use-job-logs.ts","../src/hooks/use-job-run.ts","../src/hooks/use-runs.ts"],"sourcesContent":["import type { Durably } from '@coji/durably'\nimport { Suspense, createContext, use, useContext, type ReactNode } from 'react'\n\ninterface DurablyContextValue {\n durably: Durably\n}\n\nconst DurablyContext = createContext<DurablyContextValue | null>(null)\n\nexport interface DurablyProviderProps {\n /**\n * Durably instance or Promise that resolves to one.\n * The instance should already be initialized via `await durably.init()`.\n *\n * When passing a Promise, wrap the provider with Suspense or use the fallback prop.\n *\n * @example\n * // With Suspense (recommended)\n * <Suspense fallback={<Loading />}>\n * <DurablyProvider durably={durablyPromise}>\n * <App />\n * </DurablyProvider>\n * </Suspense>\n *\n * @example\n * // With fallback prop\n * <DurablyProvider durably={durablyPromise} fallback={<Loading />}>\n * <App />\n * </DurablyProvider>\n */\n durably: Durably | Promise<Durably>\n /**\n * Fallback to show while waiting for the Durably Promise to resolve.\n * This wraps the provider content in a Suspense boundary automatically.\n */\n fallback?: ReactNode\n children: ReactNode\n}\n\n/**\n * Internal component that uses the `use()` hook to resolve the Promise\n */\nfunction DurablyProviderInner({\n durably: durablyOrPromise,\n children,\n}: Omit<DurablyProviderProps, 'fallback'>) {\n const durably =\n durablyOrPromise instanceof Promise\n ? use(durablyOrPromise)\n : durablyOrPromise\n\n return (\n <DurablyContext.Provider value={{ durably }}>\n {children}\n </DurablyContext.Provider>\n )\n}\n\nexport function DurablyProvider({\n durably,\n fallback,\n children,\n}: DurablyProviderProps) {\n const inner = (\n <DurablyProviderInner durably={durably}>{children}</DurablyProviderInner>\n )\n\n if (fallback !== undefined) {\n return <Suspense fallback={fallback}>{inner}</Suspense>\n }\n\n return inner\n}\n\nexport function useDurably(): DurablyContextValue {\n const context = useContext(DurablyContext)\n if (!context) {\n throw new Error('useDurably must be used within a DurablyProvider')\n }\n return context\n}\n","import type { JobDefinition, JobHandle } from '@coji/durably'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useDurably } from '../context'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useAutoResume } from './use-auto-resume'\nimport { useJobSubscription } from './use-job-subscription'\n\nexport interface UseJobOptions {\n /**\n * Initial Run ID to subscribe to (for reconnection scenarios)\n */\n initialRunId?: string\n /**\n * Automatically resume tracking any pending or running job on initialization.\n * If a pending or running run exists for this job, the hook will subscribe to it.\n * @default true\n */\n autoResume?: boolean\n /**\n * Automatically switch to tracking the latest running job when a new run starts.\n * When true, the hook will update to track any new run for this job as soon as it starts running.\n * When false, the hook will only track the run that was triggered or explicitly set.\n * @default true\n */\n followLatest?: boolean\n}\n\nexport interface UseJobResult<TInput, TOutput> {\n /**\n * Trigger the job with the given input\n */\n trigger: (input: TInput) => Promise<{ runId: string }>\n /**\n * Trigger and wait for completion\n */\n triggerAndWait: (input: TInput) => Promise<{ runId: string; output: TOutput }>\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n /**\n * Current run ID\n */\n currentRunId: string | null\n /**\n * Reset all state\n */\n reset: () => void\n}\n\nexport function useJob<\n TName extends string,\n TInput extends Record<string, unknown>,\n // biome-ignore lint/suspicious/noConfusingVoidType: TOutput can be void for jobs without return value\n TOutput extends Record<string, unknown> | void,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options?: UseJobOptions,\n): UseJobResult<TInput, TOutput> {\n const { durably } = useDurably()\n\n const jobHandleRef = useRef<JobHandle<TName, TInput, TOutput> | null>(null)\n\n // Register job\n useEffect(() => {\n if (!durably) return\n\n const d = durably.register({\n _job: jobDefinition,\n })\n jobHandleRef.current = d.jobs._job\n }, [durably, jobDefinition])\n\n // Use the extracted job subscription hook\n const subscription = useJobSubscription<TOutput>(\n durably,\n jobDefinition.name,\n {\n followLatest: options?.followLatest,\n },\n )\n\n // Auto-resume callbacks - stable reference\n const autoResumeCallbacks = useMemo(\n () => ({\n onRunFound: (runId: string, _status: RunStatus) => {\n subscription.setCurrentRunId(runId)\n },\n }),\n [subscription.setCurrentRunId],\n )\n\n // Use the extracted auto-resume hook\n useAutoResume(\n jobHandleRef.current,\n {\n enabled: options?.autoResume,\n initialRunId: options?.initialRunId,\n },\n autoResumeCallbacks,\n )\n\n // Handle initialRunId - set it to start tracking\n useEffect(() => {\n if (!durably || !options?.initialRunId) return\n\n subscription.setCurrentRunId(options.initialRunId)\n }, [durably, options?.initialRunId, subscription.setCurrentRunId])\n\n const trigger = useCallback(\n async (input: TInput): Promise<{ runId: string }> => {\n const jobHandle = jobHandleRef.current\n if (!jobHandle) {\n throw new Error('Job not ready')\n }\n\n // Reset state before triggering\n subscription.reset()\n\n const run = await jobHandle.trigger(input)\n subscription.setCurrentRunId(run.id)\n\n return { runId: run.id }\n },\n [subscription],\n )\n\n const triggerAndWait = useCallback(\n async (input: TInput): Promise<{ runId: string; output: TOutput }> => {\n const jobHandle = jobHandleRef.current\n if (!jobHandle || !durably) {\n throw new Error('Job not ready')\n }\n\n // Reset state before triggering\n subscription.reset()\n\n const run = await jobHandle.trigger(input)\n subscription.setCurrentRunId(run.id)\n\n // Wait for completion by polling\n return new Promise((resolve, reject) => {\n const checkCompletion = async () => {\n const updatedRun = await jobHandle.getRun(run.id)\n if (!updatedRun) {\n reject(new Error('Run not found'))\n return\n }\n\n if (updatedRun.status === 'completed') {\n resolve({ runId: run.id, output: updatedRun.output as TOutput })\n } else if (updatedRun.status === 'failed') {\n reject(new Error(updatedRun.error ?? 'Job failed'))\n } else if (updatedRun.status === 'cancelled') {\n reject(new Error('Job cancelled'))\n } else {\n // Still running, check again\n setTimeout(checkCompletion, 50)\n }\n }\n checkCompletion()\n })\n },\n [durably, subscription],\n )\n\n return {\n trigger,\n triggerAndWait,\n status: subscription.status,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: subscription.status === 'running',\n isPending: subscription.status === 'pending',\n isCompleted: subscription.status === 'completed',\n isFailed: subscription.status === 'failed',\n isCancelled: subscription.status === 'cancelled',\n currentRunId: subscription.currentRunId,\n reset: subscription.reset,\n }\n}\n","import type { JobHandle } from '@coji/durably'\nimport { useEffect } from 'react'\nimport type { RunStatus } from '../types'\n\nexport interface UseAutoResumeOptions {\n /**\n * Whether to automatically resume tracking pending/running runs\n * @default true\n */\n enabled?: boolean\n /**\n * Skip auto-resume if an initial run ID is provided\n */\n skipIfInitialRunId?: boolean\n /**\n * Initial run ID (if provided, auto-resume is skipped)\n */\n initialRunId?: string\n}\n\nexport interface UseAutoResumeCallbacks {\n /**\n * Called when a run is found to resume\n */\n onRunFound: (runId: string, status: RunStatus) => void\n}\n\n/**\n * Hook that automatically finds and resumes tracking of pending/running runs.\n * Extracted from useJob to separate the auto-resume concern.\n */\nexport function useAutoResume<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput,\n>(\n jobHandle: JobHandle<TName, TInput, TOutput> | null,\n options: UseAutoResumeOptions,\n callbacks: UseAutoResumeCallbacks,\n): void {\n const enabled = options.enabled !== false\n const skipIfInitialRunId = options.skipIfInitialRunId !== false\n const initialRunId = options.initialRunId\n\n useEffect(() => {\n if (!jobHandle) return\n if (!enabled) return\n if (skipIfInitialRunId && initialRunId) return\n\n let cancelled = false\n\n const findActiveRun = async () => {\n // First check for running runs\n const runningRuns = await jobHandle.getRuns({ status: 'running' })\n if (cancelled) return\n\n if (runningRuns.length > 0) {\n const run = runningRuns[0]\n callbacks.onRunFound(run.id, run.status as RunStatus)\n return\n }\n\n // Then check for pending runs\n const pendingRuns = await jobHandle.getRuns({ status: 'pending' })\n if (cancelled) return\n\n if (pendingRuns.length > 0) {\n const run = pendingRuns[0]\n callbacks.onRunFound(run.id, run.status as RunStatus)\n }\n }\n\n findActiveRun()\n\n return () => {\n cancelled = true\n }\n }, [jobHandle, enabled, skipIfInitialRunId, initialRunId, callbacks])\n}\n","import type { Durably } from '@coji/durably'\nimport { useCallback, useEffect, useReducer, useRef } from 'react'\nimport {\n initialSubscriptionState,\n subscriptionReducer,\n type SubscriptionAction,\n} from '../shared/subscription-reducer'\nimport type { SubscriptionState } from '../types'\n\nexport interface UseJobSubscriptionOptions {\n /**\n * Automatically switch to tracking the latest running job when a new run starts.\n * @default true\n */\n followLatest?: boolean\n /**\n * Maximum number of logs to keep (0 = unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobSubscriptionResult<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n /**\n * Current run ID being tracked\n */\n currentRunId: string | null\n /**\n * Set the current run ID to track\n */\n setCurrentRunId: (runId: string | null) => void\n /**\n * Clear all logs\n */\n clearLogs: () => void\n /**\n * Reset all state including currentRunId\n */\n reset: () => void\n}\n\n// Extended state for job subscription (includes currentRunId)\ninterface JobSubscriptionState<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n currentRunId: string | null\n}\n\n// Extended actions for job subscription\ntype JobSubscriptionAction<TOutput = unknown> =\n | SubscriptionAction<TOutput>\n | { type: 'set_run_id'; runId: string | null }\n | {\n type: 'switch_to_run'\n runId: string\n }\n\nfunction jobSubscriptionReducer<TOutput = unknown>(\n state: JobSubscriptionState<TOutput>,\n action: JobSubscriptionAction<TOutput>,\n): JobSubscriptionState<TOutput> {\n switch (action.type) {\n case 'set_run_id':\n return { ...state, currentRunId: action.runId }\n\n case 'switch_to_run':\n // Switch to a new run, resetting state\n return {\n ...initialSubscriptionState,\n currentRunId: action.runId,\n status: 'running',\n } as JobSubscriptionState<TOutput>\n\n case 'reset':\n return {\n ...(initialSubscriptionState as SubscriptionState<TOutput>),\n currentRunId: null,\n }\n\n default:\n // Delegate to base subscription reducer\n return {\n ...subscriptionReducer(state, action as SubscriptionAction<TOutput>),\n currentRunId: state.currentRunId,\n }\n }\n}\n\n/**\n * Hook for subscribing to job events with followLatest support.\n * This is a specialized version of useSubscription for job-level tracking.\n */\nexport function useJobSubscription<TOutput = unknown>(\n durably: Durably | null,\n jobName: string,\n options?: UseJobSubscriptionOptions,\n): UseJobSubscriptionResult<TOutput> {\n const initialState: JobSubscriptionState<TOutput> = {\n ...(initialSubscriptionState as SubscriptionState<TOutput>),\n currentRunId: null,\n }\n\n const [state, dispatch] = useReducer(\n jobSubscriptionReducer<TOutput>,\n initialState,\n )\n\n const currentRunIdRef = useRef<string | null>(null)\n currentRunIdRef.current = state.currentRunId\n\n const followLatest = options?.followLatest !== false\n const maxLogs = options?.maxLogs ?? 0\n\n useEffect(() => {\n if (!durably) return\n\n const unsubscribes: (() => void)[] = []\n\n unsubscribes.push(\n durably.on('run:start', (event) => {\n if (event.jobName !== jobName) return\n\n if (followLatest) {\n // Switch to tracking the new run\n dispatch({ type: 'switch_to_run', runId: event.runId })\n currentRunIdRef.current = event.runId\n } else {\n // Only update if this is our current run\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:start' })\n }\n }),\n )\n\n unsubscribes.push(\n durably.on('run:complete', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:complete', output: event.output as TOutput })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:fail', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:fail', error: event.error })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:cancel', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:cancel' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:retry', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:retry' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:progress', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:progress', progress: event.progress })\n }),\n )\n\n unsubscribes.push(\n durably.on('log:write', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n maxLogs,\n })\n }),\n )\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n }, [durably, jobName, followLatest, maxLogs])\n\n const setCurrentRunId = useCallback((runId: string | null) => {\n dispatch({ type: 'set_run_id', runId })\n currentRunIdRef.current = runId\n }, [])\n\n const clearLogs = useCallback(() => {\n dispatch({ type: 'clear_logs' })\n }, [])\n\n const reset = useCallback(() => {\n dispatch({ type: 'reset' })\n currentRunIdRef.current = null\n }, [])\n\n return {\n ...state,\n setCurrentRunId,\n clearLogs,\n reset,\n }\n}\n","import type { Durably } from '@coji/durably'\nimport { useMemo } from 'react'\nimport { createDurablyEventSubscriber } from '../shared/durably-event-subscriber'\nimport {\n useSubscription,\n type UseSubscriptionOptions,\n type UseSubscriptionResult,\n} from '../shared/use-subscription'\nimport type { SubscriptionState } from '../types'\n\n/** @deprecated Use SubscriptionState from '../types' instead */\nexport type RunSubscriptionState<TOutput = unknown> = SubscriptionState<TOutput>\n\n/** @deprecated Use UseSubscriptionOptions from '../shared/use-subscription' instead */\nexport type UseRunSubscriptionOptions = UseSubscriptionOptions\n\n/** @deprecated Use UseSubscriptionResult from '../shared/use-subscription' instead */\nexport type UseRunSubscriptionResult<TOutput = unknown> =\n UseSubscriptionResult<TOutput>\n\n/**\n * Internal hook for subscribing to run events via Durably.on().\n * Shared by useJob, useJobRun, and useJobLogs.\n *\n * @deprecated Consider using useSubscription with createDurablyEventSubscriber directly.\n */\nexport function useRunSubscription<TOutput = unknown>(\n durably: Durably | null,\n runId: string | null,\n options?: UseRunSubscriptionOptions,\n): UseRunSubscriptionResult<TOutput> {\n const subscriber = useMemo(\n () => (durably ? createDurablyEventSubscriber(durably) : null),\n [durably],\n )\n\n return useSubscription<TOutput>(subscriber, runId, options)\n}\n","import type { Durably } from '@coji/durably'\nimport type { EventSubscriber, SubscriptionEvent } from './event-subscriber'\n\n/**\n * EventSubscriber implementation using Durably.on() for direct subscriptions.\n * Used in browser environments where Durably instance is available.\n */\nexport function createDurablyEventSubscriber(\n durably: Durably,\n): EventSubscriber {\n return {\n subscribe<TOutput = unknown>(\n runId: string,\n onEvent: (event: SubscriptionEvent<TOutput>) => void,\n ): () => void {\n const unsubscribes: (() => void)[] = []\n\n unsubscribes.push(\n durably.on('run:start', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:start' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:complete', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:complete', output: event.output as TOutput })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:fail', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:fail', error: event.error })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:cancel', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:cancel' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:retry', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:retry' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:progress', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:progress', progress: event.progress })\n }),\n )\n\n unsubscribes.push(\n durably.on('log:write', (event) => {\n if (event.runId !== runId) return\n onEvent({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n })\n }),\n )\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n },\n }\n}\n","import { useDurably } from '../context'\nimport type { LogEntry } from '../types'\nimport { useRunSubscription } from './use-run-subscription'\n\nexport interface UseJobLogsOptions {\n /**\n * The run ID to subscribe to logs for\n */\n runId: string | null\n /**\n * Maximum number of logs to keep (default: unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobLogsResult {\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Clear all logs\n */\n clearLogs: () => void\n}\n\n/**\n * Hook for subscribing to logs from a run.\n * Use this when you only need logs, not full run status.\n */\nexport function useJobLogs(options: UseJobLogsOptions): UseJobLogsResult {\n const { durably } = useDurably()\n const { runId, maxLogs } = options\n\n const subscription = useRunSubscription(durably, runId, { maxLogs })\n\n return {\n logs: subscription.logs,\n clearLogs: subscription.clearLogs,\n }\n}\n","import { useEffect, useRef } from 'react'\nimport { useDurably } from '../context'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useRunSubscription } from './use-run-subscription'\n\nexport interface UseJobRunOptions {\n /**\n * The run ID to subscribe to\n */\n runId: string | null\n}\n\nexport interface UseJobRunResult<TOutput = unknown> {\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n}\n\n/**\n * Hook for subscribing to an existing run by ID.\n * Use this when you have a runId and want to track its status.\n */\nexport function useJobRun<TOutput = unknown>(\n options: UseJobRunOptions,\n): UseJobRunResult<TOutput> {\n const { durably } = useDurably()\n const { runId } = options\n\n const subscription = useRunSubscription<TOutput>(durably, runId)\n\n // Fetch initial state when runId changes\n const fetchedRef = useRef<Set<string>>(new Set())\n\n useEffect(() => {\n if (!durably || !runId || fetchedRef.current.has(runId)) return\n\n // Mark as fetched to avoid duplicate fetches\n fetchedRef.current.add(runId)\n\n // Try to fetch current run state\n // Note: We need to use internal APIs or polling here\n // For now, we rely on event-based updates\n }, [durably, runId])\n\n return {\n status: subscription.status,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: subscription.status === 'running',\n isPending: subscription.status === 'pending',\n isCompleted: subscription.status === 'completed',\n isFailed: subscription.status === 'failed',\n isCancelled: subscription.status === 'cancelled',\n }\n}\n","import type { JobDefinition } from '@coji/durably'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useDurably } from '../context'\nimport { type TypedRun, isJobDefinition } from '../types'\n\n// Re-export TypedRun for convenience\nexport type { TypedRun } from '../types'\n\nexport interface UseRunsOptions {\n /**\n * Filter by job name\n */\n jobName?: string\n /**\n * Filter by status\n */\n status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n /**\n * Number of runs per page\n * @default 10\n */\n pageSize?: number\n /**\n * Subscribe to real-time updates\n * @default true\n */\n realtime?: boolean\n}\n\nexport interface UseRunsResult<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> {\n /**\n * List of runs for the current page\n */\n runs: TypedRun<TInput, TOutput>[]\n /**\n * Current page (0-indexed)\n */\n page: number\n /**\n * Whether there are more pages\n */\n hasMore: boolean\n /**\n * Whether data is being loaded\n */\n isLoading: boolean\n /**\n * Go to the next page\n */\n nextPage: () => void\n /**\n * Go to the previous page\n */\n prevPage: () => void\n /**\n * Go to a specific page\n */\n goToPage: (page: number) => void\n /**\n * Refresh the current page\n */\n refresh: () => Promise<void>\n}\n\n/**\n * Hook for listing runs with pagination and real-time updates.\n *\n * @example With generic type parameter (dashboard with multiple job types)\n * ```tsx\n * type DashboardRun = TypedRun<ImportInput, ImportOutput> | TypedRun<SyncInput, SyncOutput>\n *\n * function Dashboard() {\n * const { runs } = useRuns<DashboardRun>({ pageSize: 10 })\n * // runs are typed as DashboardRun[]\n * }\n * ```\n *\n * @example With JobDefinition (single job, auto-filters by jobName)\n * ```tsx\n * const myJob = defineJob({ name: 'my-job', ... })\n *\n * function Dashboard() {\n * const { runs } = useRuns(myJob)\n * // runs[0].output is typed!\n * return <div>{runs[0]?.output?.someField}</div>\n * }\n * ```\n *\n * @example With options only (untyped)\n * ```tsx\n * function Dashboard() {\n * const { runs } = useRuns({ pageSize: 20 })\n * // runs[0].output is unknown\n * }\n * ```\n */\n// Overload 1: With generic type parameter\nexport function useRuns<\n TRun extends TypedRun<\n Record<string, unknown>,\n Record<string, unknown> | undefined\n >,\n>(\n options?: UseRunsOptions,\n): UseRunsResult<\n TRun extends TypedRun<infer I, infer _O> ? I : Record<string, unknown>,\n TRun extends TypedRun<infer _I, infer O> ? O : Record<string, unknown>\n>\n\n// Overload 2: With JobDefinition for type inference (auto-filters by jobName)\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options?: Omit<UseRunsOptions, 'jobName'>,\n): UseRunsResult<TInput, TOutput>\n\n// Overload 3: Without type parameter (untyped, backward compatible)\nexport function useRuns(options?: UseRunsOptions): UseRunsResult\n\n// Implementation\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinitionOrOptions?:\n | JobDefinition<TName, TInput, TOutput>\n | UseRunsOptions,\n optionsArg?: Omit<UseRunsOptions, 'jobName'>,\n): UseRunsResult<TInput, TOutput> {\n const { durably } = useDurably()\n\n // Determine if first argument is a JobDefinition using type guard\n const isJob = isJobDefinition(jobDefinitionOrOptions)\n\n const jobName = isJob\n ? jobDefinitionOrOptions.name\n : (jobDefinitionOrOptions as UseRunsOptions | undefined)?.jobName\n\n const options = isJob\n ? optionsArg\n : (jobDefinitionOrOptions as UseRunsOptions | undefined)\n\n const pageSize = options?.pageSize ?? 10\n const realtime = options?.realtime ?? true\n const status = options?.status\n\n const [runs, setRuns] = useState<TypedRun<TInput, TOutput>[]>([])\n const [page, setPage] = useState(0)\n const [hasMore, setHasMore] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n\n const refresh = useCallback(async () => {\n if (!durably) return\n\n setIsLoading(true)\n try {\n const data = await durably.getRuns({\n jobName,\n status,\n limit: pageSize + 1,\n offset: page * pageSize,\n })\n setHasMore(data.length > pageSize)\n setRuns(data.slice(0, pageSize) as TypedRun<TInput, TOutput>[])\n } finally {\n setIsLoading(false)\n }\n }, [durably, jobName, status, pageSize, page])\n\n // Initial fetch and subscribe to events\n useEffect(() => {\n if (!durably) return\n\n refresh()\n\n if (!realtime) return\n\n const unsubscribes = [\n durably.on('run:trigger', refresh),\n durably.on('run:start', refresh),\n durably.on('run:complete', refresh),\n durably.on('run:fail', refresh),\n durably.on('run:cancel', refresh),\n durably.on('run:retry', refresh),\n durably.on('run:progress', refresh),\n durably.on('step:start', refresh),\n durably.on('step:complete', refresh),\n ]\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n }, [durably, refresh, realtime])\n\n const nextPage = useCallback(() => {\n if (hasMore) {\n setPage((p) => p + 1)\n }\n }, [hasMore])\n\n const prevPage = useCallback(() => {\n setPage((p) => Math.max(0, p - 1))\n }, [])\n\n const goToPage = useCallback((newPage: number) => {\n setPage(Math.max(0, newPage))\n }, [])\n\n return {\n runs,\n page,\n hasMore,\n isLoading,\n nextPage,\n prevPage,\n goToPage,\n refresh,\n }\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useEffect","useEffect","useRef","useEffect","useCallback","useMemo","useMemo","useEffect","useRef","useRef","useEffect","useCallback","useEffect","useCallback","useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/hooks/use-job.ts","../src/hooks/use-auto-resume.ts","../src/hooks/use-job-subscription.ts","../src/hooks/use-run-subscription.ts","../src/shared/durably-event-subscriber.ts","../src/hooks/use-job-logs.ts","../src/hooks/use-job-run.ts","../src/hooks/use-runs.ts"],"sourcesContent":["import type { Durably } from '@coji/durably'\nimport { Suspense, createContext, use, useContext, type ReactNode } from 'react'\n\ninterface DurablyContextValue {\n durably: Durably\n}\n\nconst DurablyContext = createContext<DurablyContextValue | null>(null)\n\nexport interface DurablyProviderProps {\n /**\n * Durably instance or Promise that resolves to one.\n * The instance should already be initialized via `await durably.init()`.\n *\n * When passing a Promise, wrap the provider with Suspense or use the fallback prop.\n *\n * @example\n * // With Suspense (recommended)\n * <Suspense fallback={<Loading />}>\n * <DurablyProvider durably={durablyPromise}>\n * <App />\n * </DurablyProvider>\n * </Suspense>\n *\n * @example\n * // With fallback prop\n * <DurablyProvider durably={durablyPromise} fallback={<Loading />}>\n * <App />\n * </DurablyProvider>\n */\n durably: Durably | Promise<Durably>\n /**\n * Fallback to show while waiting for the Durably Promise to resolve.\n * This wraps the provider content in a Suspense boundary automatically.\n */\n fallback?: ReactNode\n children: ReactNode\n}\n\n/**\n * Internal component that uses the `use()` hook to resolve the Promise\n */\nfunction DurablyProviderInner({\n durably: durablyOrPromise,\n children,\n}: Omit<DurablyProviderProps, 'fallback'>) {\n const durably =\n durablyOrPromise instanceof Promise\n ? use(durablyOrPromise)\n : durablyOrPromise\n\n return (\n <DurablyContext.Provider value={{ durably }}>\n {children}\n </DurablyContext.Provider>\n )\n}\n\nexport function DurablyProvider({\n durably,\n fallback,\n children,\n}: DurablyProviderProps) {\n const inner = (\n <DurablyProviderInner durably={durably}>{children}</DurablyProviderInner>\n )\n\n if (fallback !== undefined) {\n return <Suspense fallback={fallback}>{inner}</Suspense>\n }\n\n return inner\n}\n\nexport function useDurably(): DurablyContextValue {\n const context = useContext(DurablyContext)\n if (!context) {\n throw new Error('useDurably must be used within a DurablyProvider')\n }\n return context\n}\n","import type { JobDefinition, JobHandle } from '@coji/durably'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useDurably } from '../context'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useAutoResume } from './use-auto-resume'\nimport { useJobSubscription } from './use-job-subscription'\n\nexport interface UseJobOptions {\n /**\n * Initial Run ID to subscribe to (for reconnection scenarios)\n */\n initialRunId?: string\n /**\n * Automatically resume tracking any pending or running job on initialization.\n * If a pending or running run exists for this job, the hook will subscribe to it.\n * @default true\n */\n autoResume?: boolean\n /**\n * Automatically switch to tracking the latest running job when a new run starts.\n * When true, the hook will update to track any new run for this job as soon as it starts running.\n * When false, the hook will only track the run that was triggered or explicitly set.\n * @default true\n */\n followLatest?: boolean\n}\n\nexport interface UseJobResult<TInput, TOutput> {\n /**\n * Trigger the job with the given input\n */\n trigger: (input: TInput) => Promise<{ runId: string }>\n /**\n * Trigger and wait for completion\n */\n triggerAndWait: (input: TInput) => Promise<{ runId: string; output: TOutput }>\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n /**\n * Current run ID\n */\n currentRunId: string | null\n /**\n * Reset all state\n */\n reset: () => void\n}\n\nexport function useJob<\n TName extends string,\n TInput extends Record<string, unknown>,\n // biome-ignore lint/suspicious/noConfusingVoidType: TOutput can be void for jobs without return value\n TOutput extends Record<string, unknown> | void,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options?: UseJobOptions,\n): UseJobResult<TInput, TOutput> {\n const { durably } = useDurably()\n\n const jobHandleRef = useRef<JobHandle<TName, TInput, TOutput> | null>(null)\n\n // Register job\n useEffect(() => {\n if (!durably) return\n\n const d = durably.register({\n _job: jobDefinition,\n })\n jobHandleRef.current = d.jobs._job\n }, [durably, jobDefinition])\n\n // Use the extracted job subscription hook\n const subscription = useJobSubscription<TOutput>(\n durably,\n jobDefinition.name,\n {\n followLatest: options?.followLatest,\n },\n )\n\n // Auto-resume callbacks - stable reference\n const autoResumeCallbacks = useMemo(\n () => ({\n onRunFound: (runId: string, _status: RunStatus) => {\n subscription.setCurrentRunId(runId)\n },\n }),\n [subscription.setCurrentRunId],\n )\n\n // Use the extracted auto-resume hook\n useAutoResume(\n jobHandleRef.current,\n {\n enabled: options?.autoResume,\n initialRunId: options?.initialRunId,\n },\n autoResumeCallbacks,\n )\n\n // Handle initialRunId - set it to start tracking\n useEffect(() => {\n if (!durably || !options?.initialRunId) return\n\n subscription.setCurrentRunId(options.initialRunId)\n }, [durably, options?.initialRunId, subscription.setCurrentRunId])\n\n const trigger = useCallback(\n async (input: TInput): Promise<{ runId: string }> => {\n const jobHandle = jobHandleRef.current\n if (!jobHandle) {\n throw new Error('Job not ready')\n }\n\n // Reset state before triggering\n subscription.reset()\n\n const run = await jobHandle.trigger(input)\n subscription.setCurrentRunId(run.id)\n\n return { runId: run.id }\n },\n [subscription],\n )\n\n const triggerAndWait = useCallback(\n async (input: TInput): Promise<{ runId: string; output: TOutput }> => {\n const jobHandle = jobHandleRef.current\n if (!jobHandle || !durably) {\n throw new Error('Job not ready')\n }\n\n // Reset state before triggering\n subscription.reset()\n\n const run = await jobHandle.trigger(input)\n subscription.setCurrentRunId(run.id)\n\n // Wait for completion by polling\n return new Promise((resolve, reject) => {\n const checkCompletion = async () => {\n const updatedRun = await jobHandle.getRun(run.id)\n if (!updatedRun) {\n reject(new Error('Run not found'))\n return\n }\n\n if (updatedRun.status === 'completed') {\n resolve({ runId: run.id, output: updatedRun.output as TOutput })\n } else if (updatedRun.status === 'failed') {\n reject(new Error(updatedRun.error ?? 'Job failed'))\n } else if (updatedRun.status === 'cancelled') {\n reject(new Error('Job cancelled'))\n } else {\n // Still running, check again\n setTimeout(checkCompletion, 50)\n }\n }\n checkCompletion()\n })\n },\n [durably, subscription],\n )\n\n return {\n trigger,\n triggerAndWait,\n status: subscription.status,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: subscription.status === 'running',\n isPending: subscription.status === 'pending',\n isCompleted: subscription.status === 'completed',\n isFailed: subscription.status === 'failed',\n isCancelled: subscription.status === 'cancelled',\n currentRunId: subscription.currentRunId,\n reset: subscription.reset,\n }\n}\n","import type { JobHandle } from '@coji/durably'\nimport { useEffect } from 'react'\nimport type { RunStatus } from '../types'\n\nexport interface UseAutoResumeOptions {\n /**\n * Whether to automatically resume tracking pending/running runs\n * @default true\n */\n enabled?: boolean\n /**\n * Skip auto-resume if an initial run ID is provided\n */\n skipIfInitialRunId?: boolean\n /**\n * Initial run ID (if provided, auto-resume is skipped)\n */\n initialRunId?: string\n}\n\nexport interface UseAutoResumeCallbacks {\n /**\n * Called when a run is found to resume\n */\n onRunFound: (runId: string, status: RunStatus) => void\n}\n\n/**\n * Hook that automatically finds and resumes tracking of pending/running runs.\n * Extracted from useJob to separate the auto-resume concern.\n */\nexport function useAutoResume<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput,\n>(\n jobHandle: JobHandle<TName, TInput, TOutput> | null,\n options: UseAutoResumeOptions,\n callbacks: UseAutoResumeCallbacks,\n): void {\n const enabled = options.enabled !== false\n const skipIfInitialRunId = options.skipIfInitialRunId !== false\n const initialRunId = options.initialRunId\n\n useEffect(() => {\n if (!jobHandle) return\n if (!enabled) return\n if (skipIfInitialRunId && initialRunId) return\n\n let cancelled = false\n\n const findActiveRun = async () => {\n // First check for running runs\n const runningRuns = await jobHandle.getRuns({ status: 'running' })\n if (cancelled) return\n\n if (runningRuns.length > 0) {\n const run = runningRuns[0]\n callbacks.onRunFound(run.id, run.status as RunStatus)\n return\n }\n\n // Then check for pending runs\n const pendingRuns = await jobHandle.getRuns({ status: 'pending' })\n if (cancelled) return\n\n if (pendingRuns.length > 0) {\n const run = pendingRuns[0]\n callbacks.onRunFound(run.id, run.status as RunStatus)\n }\n }\n\n findActiveRun()\n\n return () => {\n cancelled = true\n }\n }, [jobHandle, enabled, skipIfInitialRunId, initialRunId, callbacks])\n}\n","import type { Durably } from '@coji/durably'\nimport { useCallback, useEffect, useReducer, useRef } from 'react'\nimport {\n initialSubscriptionState,\n subscriptionReducer,\n type SubscriptionAction,\n} from '../shared/subscription-reducer'\nimport type { SubscriptionState } from '../types'\n\nexport interface UseJobSubscriptionOptions {\n /**\n * Automatically switch to tracking the latest running job when a new run starts.\n * @default true\n */\n followLatest?: boolean\n /**\n * Maximum number of logs to keep (0 = unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobSubscriptionResult<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n /**\n * Current run ID being tracked\n */\n currentRunId: string | null\n /**\n * Set the current run ID to track\n */\n setCurrentRunId: (runId: string | null) => void\n /**\n * Clear all logs\n */\n clearLogs: () => void\n /**\n * Reset all state including currentRunId\n */\n reset: () => void\n}\n\n// Extended state for job subscription (includes currentRunId)\ninterface JobSubscriptionState<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n currentRunId: string | null\n}\n\n// Extended actions for job subscription\ntype JobSubscriptionAction<TOutput = unknown> =\n | SubscriptionAction<TOutput>\n | { type: 'set_run_id'; runId: string | null }\n | {\n type: 'switch_to_run'\n runId: string\n }\n\nfunction jobSubscriptionReducer<TOutput = unknown>(\n state: JobSubscriptionState<TOutput>,\n action: JobSubscriptionAction<TOutput>,\n): JobSubscriptionState<TOutput> {\n switch (action.type) {\n case 'set_run_id':\n return { ...state, currentRunId: action.runId }\n\n case 'switch_to_run':\n // Switch to a new run, resetting state\n return {\n ...initialSubscriptionState,\n currentRunId: action.runId,\n status: 'running',\n } as JobSubscriptionState<TOutput>\n\n case 'reset':\n return {\n ...(initialSubscriptionState as SubscriptionState<TOutput>),\n currentRunId: null,\n }\n\n default:\n // Delegate to base subscription reducer\n return {\n ...subscriptionReducer(state, action as SubscriptionAction<TOutput>),\n currentRunId: state.currentRunId,\n }\n }\n}\n\n/**\n * Hook for subscribing to job events with followLatest support.\n * This is a specialized version of useSubscription for job-level tracking.\n */\nexport function useJobSubscription<TOutput = unknown>(\n durably: Durably | null,\n jobName: string,\n options?: UseJobSubscriptionOptions,\n): UseJobSubscriptionResult<TOutput> {\n const initialState: JobSubscriptionState<TOutput> = {\n ...(initialSubscriptionState as SubscriptionState<TOutput>),\n currentRunId: null,\n }\n\n const [state, dispatch] = useReducer(\n jobSubscriptionReducer<TOutput>,\n initialState,\n )\n\n const currentRunIdRef = useRef<string | null>(null)\n currentRunIdRef.current = state.currentRunId\n\n const followLatest = options?.followLatest !== false\n const maxLogs = options?.maxLogs ?? 0\n\n useEffect(() => {\n if (!durably) return\n\n const unsubscribes: (() => void)[] = []\n\n unsubscribes.push(\n durably.on('run:start', (event) => {\n if (event.jobName !== jobName) return\n\n if (followLatest) {\n // Switch to tracking the new run\n dispatch({ type: 'switch_to_run', runId: event.runId })\n currentRunIdRef.current = event.runId\n } else {\n // Only update if this is our current run\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:start' })\n }\n }),\n )\n\n unsubscribes.push(\n durably.on('run:complete', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:complete', output: event.output as TOutput })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:fail', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:fail', error: event.error })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:cancel', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:cancel' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:retry', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:retry' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:progress', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({ type: 'run:progress', progress: event.progress })\n }),\n )\n\n unsubscribes.push(\n durably.on('log:write', (event) => {\n if (event.runId !== currentRunIdRef.current) return\n dispatch({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n maxLogs,\n })\n }),\n )\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n }, [durably, jobName, followLatest, maxLogs])\n\n const setCurrentRunId = useCallback((runId: string | null) => {\n dispatch({ type: 'set_run_id', runId })\n currentRunIdRef.current = runId\n }, [])\n\n const clearLogs = useCallback(() => {\n dispatch({ type: 'clear_logs' })\n }, [])\n\n const reset = useCallback(() => {\n dispatch({ type: 'reset' })\n currentRunIdRef.current = null\n }, [])\n\n return {\n ...state,\n setCurrentRunId,\n clearLogs,\n reset,\n }\n}\n","import type { Durably } from '@coji/durably'\nimport { useMemo } from 'react'\nimport { createDurablyEventSubscriber } from '../shared/durably-event-subscriber'\nimport {\n useSubscription,\n type UseSubscriptionOptions,\n type UseSubscriptionResult,\n} from '../shared/use-subscription'\nimport type { SubscriptionState } from '../types'\n\n/** @deprecated Use SubscriptionState from '../types' instead */\nexport type RunSubscriptionState<TOutput = unknown> = SubscriptionState<TOutput>\n\n/** @deprecated Use UseSubscriptionOptions from '../shared/use-subscription' instead */\nexport type UseRunSubscriptionOptions = UseSubscriptionOptions\n\n/** @deprecated Use UseSubscriptionResult from '../shared/use-subscription' instead */\nexport type UseRunSubscriptionResult<TOutput = unknown> =\n UseSubscriptionResult<TOutput>\n\n/**\n * Internal hook for subscribing to run events via Durably.on().\n * Shared by useJob, useJobRun, and useJobLogs.\n *\n * @deprecated Consider using useSubscription with createDurablyEventSubscriber directly.\n */\nexport function useRunSubscription<TOutput = unknown>(\n durably: Durably | null,\n runId: string | null,\n options?: UseRunSubscriptionOptions,\n): UseRunSubscriptionResult<TOutput> {\n const subscriber = useMemo(\n () => (durably ? createDurablyEventSubscriber(durably) : null),\n [durably],\n )\n\n return useSubscription<TOutput>(subscriber, runId, options)\n}\n","import type { Durably } from '@coji/durably'\nimport type { EventSubscriber, SubscriptionEvent } from './event-subscriber'\n\n/**\n * EventSubscriber implementation using Durably.on() for direct subscriptions.\n * Used in browser environments where Durably instance is available.\n */\nexport function createDurablyEventSubscriber(\n durably: Durably,\n): EventSubscriber {\n return {\n subscribe<TOutput = unknown>(\n runId: string,\n onEvent: (event: SubscriptionEvent<TOutput>) => void,\n ): () => void {\n const unsubscribes: (() => void)[] = []\n\n unsubscribes.push(\n durably.on('run:start', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:start' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:complete', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:complete', output: event.output as TOutput })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:fail', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:fail', error: event.error })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:cancel', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:cancel' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:retry', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:retry' })\n }),\n )\n\n unsubscribes.push(\n durably.on('run:progress', (event) => {\n if (event.runId !== runId) return\n onEvent({ type: 'run:progress', progress: event.progress })\n }),\n )\n\n unsubscribes.push(\n durably.on('log:write', (event) => {\n if (event.runId !== runId) return\n onEvent({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n })\n }),\n )\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n },\n }\n}\n","import { useDurably } from '../context'\nimport type { LogEntry } from '../types'\nimport { useRunSubscription } from './use-run-subscription'\n\nexport interface UseJobLogsOptions {\n /**\n * The run ID to subscribe to logs for\n */\n runId: string | null\n /**\n * Maximum number of logs to keep (default: unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseJobLogsResult {\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Clear all logs\n */\n clearLogs: () => void\n}\n\n/**\n * Hook for subscribing to logs from a run.\n * Use this when you only need logs, not full run status.\n */\nexport function useJobLogs(options: UseJobLogsOptions): UseJobLogsResult {\n const { durably } = useDurably()\n const { runId, maxLogs } = options\n\n const subscription = useRunSubscription(durably, runId, { maxLogs })\n\n return {\n logs: subscription.logs,\n clearLogs: subscription.clearLogs,\n }\n}\n","import { useEffect, useRef } from 'react'\nimport { useDurably } from '../context'\nimport type { LogEntry, Progress, RunStatus } from '../types'\nimport { useRunSubscription } from './use-run-subscription'\n\n// Note: Unlike UseJobRunClientOptions (client mode), this interface intentionally\n// omits onStart/onComplete/onFail callbacks. In browser mode, use durably.on()\n// directly for event callbacks.\nexport interface UseJobRunOptions {\n /**\n * The run ID to subscribe to\n */\n runId: string | null\n}\n\nexport interface UseJobRunResult<TOutput = unknown> {\n /**\n * Current run status\n */\n status: RunStatus | null\n /**\n * Output from completed run\n */\n output: TOutput | null\n /**\n * Error message from failed run\n */\n error: string | null\n /**\n * Logs collected during execution\n */\n logs: LogEntry[]\n /**\n * Current progress\n */\n progress: Progress | null\n /**\n * Whether a run is currently running\n */\n isRunning: boolean\n /**\n * Whether a run is pending\n */\n isPending: boolean\n /**\n * Whether the run completed successfully\n */\n isCompleted: boolean\n /**\n * Whether the run failed\n */\n isFailed: boolean\n /**\n * Whether the run was cancelled\n */\n isCancelled: boolean\n}\n\n/**\n * Hook for subscribing to an existing run by ID.\n * Use this when you have a runId and want to track its status.\n */\nexport function useJobRun<TOutput = unknown>(\n options: UseJobRunOptions,\n): UseJobRunResult<TOutput> {\n const { durably } = useDurably()\n const { runId } = options\n\n const subscription = useRunSubscription<TOutput>(durably, runId)\n\n // Fetch initial state when runId changes\n const fetchedRef = useRef<Set<string>>(new Set())\n\n useEffect(() => {\n if (!durably || !runId || fetchedRef.current.has(runId)) return\n\n // Mark as fetched to avoid duplicate fetches\n fetchedRef.current.add(runId)\n\n // Try to fetch current run state\n // Note: We need to use internal APIs or polling here\n // For now, we rely on event-based updates\n }, [durably, runId])\n\n return {\n status: subscription.status,\n output: subscription.output,\n error: subscription.error,\n logs: subscription.logs,\n progress: subscription.progress,\n isRunning: subscription.status === 'running',\n isPending: subscription.status === 'pending',\n isCompleted: subscription.status === 'completed',\n isFailed: subscription.status === 'failed',\n isCancelled: subscription.status === 'cancelled',\n }\n}\n","import type { JobDefinition } from '@coji/durably'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useDurably } from '../context'\nimport { type TypedRun, isJobDefinition } from '../types'\n\n// Re-export TypedRun for convenience\nexport type { TypedRun } from '../types'\n\nexport interface UseRunsOptions {\n /**\n * Filter by job name\n */\n jobName?: string\n /**\n * Filter by status\n */\n status?: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'\n /**\n * Filter by labels (all specified labels must match)\n */\n labels?: Record<string, string>\n /**\n * Number of runs per page\n * @default 10\n */\n pageSize?: number\n /**\n * Subscribe to real-time updates\n * @default true\n */\n realtime?: boolean\n}\n\n// Note: Unlike UseRunsClientResult (client mode), this interface intentionally\n// omits `error` because browser mode operates on a local SQLite database\n// where network errors don't occur.\nexport interface UseRunsResult<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> {\n /**\n * List of runs for the current page\n */\n runs: TypedRun<TInput, TOutput>[]\n /**\n * Current page (0-indexed)\n */\n page: number\n /**\n * Whether there are more pages\n */\n hasMore: boolean\n /**\n * Whether data is being loaded\n */\n isLoading: boolean\n /**\n * Go to the next page\n */\n nextPage: () => void\n /**\n * Go to the previous page\n */\n prevPage: () => void\n /**\n * Go to a specific page\n */\n goToPage: (page: number) => void\n /**\n * Refresh the current page\n */\n refresh: () => Promise<void>\n}\n\n/**\n * Hook for listing runs with pagination and real-time updates.\n *\n * @example With generic type parameter (dashboard with multiple job types)\n * ```tsx\n * type DashboardRun = TypedRun<ImportInput, ImportOutput> | TypedRun<SyncInput, SyncOutput>\n *\n * function Dashboard() {\n * const { runs } = useRuns<DashboardRun>({ pageSize: 10 })\n * // runs are typed as DashboardRun[]\n * }\n * ```\n *\n * @example With JobDefinition (single job, auto-filters by jobName)\n * ```tsx\n * const myJob = defineJob({ name: 'my-job', ... })\n *\n * function Dashboard() {\n * const { runs } = useRuns(myJob)\n * // runs[0].output is typed!\n * return <div>{runs[0]?.output?.someField}</div>\n * }\n * ```\n *\n * @example With options only (untyped)\n * ```tsx\n * function Dashboard() {\n * const { runs } = useRuns({ pageSize: 20 })\n * // runs[0].output is unknown\n * }\n * ```\n */\n// Overload 1: With generic type parameter\nexport function useRuns<\n TRun extends TypedRun<\n Record<string, unknown>,\n Record<string, unknown> | undefined\n >,\n>(\n options?: UseRunsOptions,\n): UseRunsResult<\n TRun extends TypedRun<infer I, infer _O> ? I : Record<string, unknown>,\n TRun extends TypedRun<infer _I, infer O> ? O : Record<string, unknown>\n>\n\n// Overload 2: With JobDefinition for type inference (auto-filters by jobName)\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinition: JobDefinition<TName, TInput, TOutput>,\n options?: Omit<UseRunsOptions, 'jobName'>,\n): UseRunsResult<TInput, TOutput>\n\n// Overload 3: Without type parameter (untyped, backward compatible)\nexport function useRuns(options?: UseRunsOptions): UseRunsResult\n\n// Implementation\nexport function useRuns<\n TName extends string,\n TInput extends Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined,\n>(\n jobDefinitionOrOptions?:\n | JobDefinition<TName, TInput, TOutput>\n | UseRunsOptions,\n optionsArg?: Omit<UseRunsOptions, 'jobName'>,\n): UseRunsResult<TInput, TOutput> {\n const { durably } = useDurably()\n\n // Determine if first argument is a JobDefinition using type guard\n const isJob = isJobDefinition(jobDefinitionOrOptions)\n\n const jobName = isJob\n ? jobDefinitionOrOptions.name\n : (jobDefinitionOrOptions as UseRunsOptions | undefined)?.jobName\n\n const options = isJob\n ? optionsArg\n : (jobDefinitionOrOptions as UseRunsOptions | undefined)\n\n const pageSize = options?.pageSize ?? 10\n const realtime = options?.realtime ?? true\n const status = options?.status\n\n // Stabilize labels reference to prevent infinite re-renders\n const labelsKey = options?.labels ? JSON.stringify(options.labels) : undefined\n const labels = useMemo(\n () =>\n labelsKey ? (JSON.parse(labelsKey) as Record<string, string>) : undefined,\n [labelsKey],\n )\n\n const [runs, setRuns] = useState<TypedRun<TInput, TOutput>[]>([])\n const [page, setPage] = useState(0)\n const [hasMore, setHasMore] = useState(false)\n const [isLoading, setIsLoading] = useState(true)\n\n const refresh = useCallback(async () => {\n if (!durably) return\n\n setIsLoading(true)\n try {\n const data = await durably.getRuns({\n jobName,\n status,\n labels,\n limit: pageSize + 1,\n offset: page * pageSize,\n })\n setHasMore(data.length > pageSize)\n setRuns(data.slice(0, pageSize) as TypedRun<TInput, TOutput>[])\n } finally {\n setIsLoading(false)\n }\n }, [durably, jobName, status, labels, pageSize, page])\n\n // Initial fetch and subscribe to events\n useEffect(() => {\n if (!durably) return\n\n refresh()\n\n if (!realtime) return\n\n const unsubscribes = [\n durably.on('run:trigger', refresh),\n durably.on('run:start', refresh),\n durably.on('run:complete', refresh),\n durably.on('run:fail', refresh),\n durably.on('run:cancel', refresh),\n durably.on('run:delete', refresh),\n durably.on('run:retry', refresh),\n durably.on('run:progress', refresh),\n durably.on('step:start', refresh),\n durably.on('step:complete', refresh),\n durably.on('step:fail', refresh),\n durably.on('step:cancel', refresh),\n ]\n\n return () => {\n for (const unsubscribe of unsubscribes) {\n unsubscribe()\n }\n }\n }, [durably, refresh, realtime])\n\n const nextPage = useCallback(() => {\n if (hasMore) {\n setPage((p) => p + 1)\n }\n }, [hasMore])\n\n const prevPage = useCallback(() => {\n setPage((p) => Math.max(0, p - 1))\n }, [])\n\n const goToPage = useCallback((newPage: number) => {\n setPage(Math.max(0, newPage))\n }, [])\n\n return {\n runs,\n page,\n hasMore,\n isLoading,\n nextPage,\n prevPage,\n goToPage,\n refresh,\n }\n}\n"],"mappings":"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","names":["useCallback","useEffect","useRef","useEffect","useEffect","useRef","useEffect","useCallback","useMemo","useMemo","useEffect","useRef","useRef","useEffect","useCallback","useEffect","useMemo","useMemo","useCallback","useEffect"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Run, JobDefinition } from '@coji/durably';
|
|
1
|
+
import { Run, JobDefinition, ClientRun } from '@coji/durably';
|
|
2
2
|
|
|
3
3
|
type InferInput<T> = T extends JobDefinition<string, infer TInput, unknown> ? TInput extends Record<string, unknown> ? TInput : Record<string, unknown> : T extends {
|
|
4
4
|
trigger: (input: infer TInput) => unknown;
|
|
@@ -27,7 +27,7 @@ type DurablyEvent = {
|
|
|
27
27
|
type: 'run:start';
|
|
28
28
|
runId: string;
|
|
29
29
|
jobName: string;
|
|
30
|
-
|
|
30
|
+
input: unknown;
|
|
31
31
|
} | {
|
|
32
32
|
type: 'run:complete';
|
|
33
33
|
runId: string;
|
|
@@ -43,6 +43,15 @@ type DurablyEvent = {
|
|
|
43
43
|
type: 'run:cancel';
|
|
44
44
|
runId: string;
|
|
45
45
|
jobName: string;
|
|
46
|
+
} | {
|
|
47
|
+
type: 'run:delete';
|
|
48
|
+
runId: string;
|
|
49
|
+
jobName: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'run:trigger';
|
|
52
|
+
runId: string;
|
|
53
|
+
jobName: string;
|
|
54
|
+
input: unknown;
|
|
46
55
|
} | {
|
|
47
56
|
type: 'run:retry';
|
|
48
57
|
runId: string;
|
|
@@ -65,40 +74,32 @@ type DurablyEvent = {
|
|
|
65
74
|
stepName: string;
|
|
66
75
|
stepIndex: number;
|
|
67
76
|
output: unknown;
|
|
77
|
+
} | {
|
|
78
|
+
type: 'step:cancel';
|
|
79
|
+
runId: string;
|
|
80
|
+
jobName: string;
|
|
81
|
+
stepName: string;
|
|
82
|
+
stepIndex: number;
|
|
83
|
+
labels: Record<string, string>;
|
|
68
84
|
} | {
|
|
69
85
|
type: 'log:write';
|
|
70
86
|
runId: string;
|
|
71
87
|
jobName: string;
|
|
88
|
+
stepName: string | null;
|
|
89
|
+
labels: Record<string, string>;
|
|
72
90
|
level: 'info' | 'warn' | 'error';
|
|
73
91
|
message: string;
|
|
74
92
|
data: unknown;
|
|
75
93
|
};
|
|
76
94
|
/**
|
|
77
|
-
* A typed version of Run with generic
|
|
95
|
+
* A typed version of Run with generic input/output types.
|
|
78
96
|
* Used by browser hooks (direct durably access).
|
|
79
97
|
*/
|
|
80
|
-
type TypedRun<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> | undefined = Record<string, unknown> | undefined> = Omit<Run, '
|
|
81
|
-
|
|
98
|
+
type TypedRun<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> | undefined = Record<string, unknown> | undefined> = Omit<Run, 'input' | 'output'> & {
|
|
99
|
+
input: TInput;
|
|
82
100
|
output: TOutput | null;
|
|
83
101
|
};
|
|
84
|
-
|
|
85
|
-
* Run type for client mode (matches server response).
|
|
86
|
-
* Used by client hooks (HTTP/SSE connection).
|
|
87
|
-
*/
|
|
88
|
-
interface ClientRun {
|
|
89
|
-
id: string;
|
|
90
|
-
jobName: string;
|
|
91
|
-
status: RunStatus;
|
|
92
|
-
input: unknown;
|
|
93
|
-
output: unknown | null;
|
|
94
|
-
error: string | null;
|
|
95
|
-
currentStepIndex: number;
|
|
96
|
-
stepCount: number;
|
|
97
|
-
progress: Progress | null;
|
|
98
|
-
createdAt: string;
|
|
99
|
-
startedAt: string | null;
|
|
100
|
-
completedAt: string | null;
|
|
101
|
-
}
|
|
102
|
+
|
|
102
103
|
/**
|
|
103
104
|
* A typed version of ClientRun with generic input/output types.
|
|
104
105
|
* Used by client hooks (HTTP/SSE connection).
|
|
@@ -108,4 +109,4 @@ type TypedClientRun<TInput extends Record<string, unknown> = Record<string, unkn
|
|
|
108
109
|
output: TOutput | null;
|
|
109
110
|
};
|
|
110
111
|
|
|
111
|
-
export type {
|
|
112
|
+
export type { DurablyEvent as D, InferInput as I, LogEntry as L, Progress as P, RunStatus as R, TypedRun as T, InferOutput as a, TypedClientRun as b };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coji/durably-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "React bindings for Durably - step-oriented resumable batch execution",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -49,25 +49,25 @@
|
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@biomejs/biome": "^2.
|
|
53
|
-
"@testing-library/react": "^16.3.
|
|
54
|
-
"@types/react": "^19.2.
|
|
52
|
+
"@biomejs/biome": "^2.4.5",
|
|
53
|
+
"@testing-library/react": "^16.3.2",
|
|
54
|
+
"@types/react": "^19.2.14",
|
|
55
55
|
"@types/react-dom": "^19.2.3",
|
|
56
|
-
"@vitejs/plugin-react": "^5.1.
|
|
57
|
-
"@vitest/browser": "^4.0.
|
|
58
|
-
"@vitest/browser-playwright": "4.0.
|
|
59
|
-
"kysely": "^0.28.
|
|
60
|
-
"playwright": "^1.
|
|
61
|
-
"prettier": "^3.
|
|
56
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
57
|
+
"@vitest/browser": "^4.0.18",
|
|
58
|
+
"@vitest/browser-playwright": "4.0.18",
|
|
59
|
+
"kysely": "^0.28.11",
|
|
60
|
+
"playwright": "^1.58.2",
|
|
61
|
+
"prettier": "^3.8.1",
|
|
62
62
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
63
|
-
"react": "^19.2.
|
|
64
|
-
"react-dom": "^19.2.
|
|
65
|
-
"sqlocal": "^0.
|
|
63
|
+
"react": "^19.2.4",
|
|
64
|
+
"react-dom": "^19.2.4",
|
|
65
|
+
"sqlocal": "^0.17.0",
|
|
66
66
|
"tsup": "^8.5.1",
|
|
67
67
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.0.
|
|
69
|
-
"zod": "^4.3.
|
|
70
|
-
"@coji/durably": "0.
|
|
68
|
+
"vitest": "^4.0.18",
|
|
69
|
+
"zod": "^4.3.6",
|
|
70
|
+
"@coji/durably": "0.10.0"
|
|
71
71
|
},
|
|
72
72
|
"scripts": {
|
|
73
73
|
"build": "tsup",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/shared/create-log-entry.ts","../src/shared/subscription-reducer.ts","../src/shared/use-subscription.ts"],"sourcesContent":["// Shared type definitions for @coji/durably-react\n\nimport type { JobDefinition, Run } from '@coji/durably'\n\n// Type inference utilities for extracting Input/Output types from JobDefinition\nexport type InferInput<T> =\n T extends JobDefinition<string, infer TInput, unknown>\n ? TInput extends Record<string, unknown>\n ? TInput\n : Record<string, unknown>\n : T extends { trigger: (input: infer TInput) => unknown }\n ? TInput extends Record<string, unknown>\n ? TInput\n : Record<string, unknown>\n : Record<string, unknown>\n\nexport type InferOutput<T> =\n T extends JobDefinition<string, unknown, infer TOutput>\n ? TOutput extends Record<string, unknown>\n ? TOutput\n : Record<string, unknown>\n : T extends {\n trigger: (input: unknown) => Promise<{ output?: infer TOutput }>\n }\n ? TOutput extends Record<string, unknown>\n ? TOutput\n : Record<string, unknown>\n : Record<string, unknown>\n\nexport type RunStatus =\n | 'pending'\n | 'running'\n | 'completed'\n | 'failed'\n | 'cancelled'\n\nexport interface Progress {\n current: number\n total?: number\n message?: string\n}\n\nexport interface LogEntry {\n id: string\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n timestamp: string\n}\n\n// Shared subscription state (used by both direct and SSE subscriptions)\nexport interface SubscriptionState<TOutput = unknown> {\n status: RunStatus | null\n output: TOutput | null\n error: string | null\n logs: LogEntry[]\n progress: Progress | null\n}\n\n// SSE event types (sent from server)\nexport type DurablyEvent =\n | { type: 'run:start'; runId: string; jobName: string; payload: unknown }\n | {\n type: 'run:complete'\n runId: string\n jobName: string\n output: unknown\n duration: number\n }\n | { type: 'run:fail'; runId: string; jobName: string; error: string }\n | { type: 'run:cancel'; runId: string; jobName: string }\n | { type: 'run:retry'; runId: string; jobName: string }\n | {\n type: 'run:progress'\n runId: string\n jobName: string\n progress: Progress\n }\n | {\n type: 'step:start'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n }\n | {\n type: 'step:complete'\n runId: string\n jobName: string\n stepName: string\n stepIndex: number\n output: unknown\n }\n | {\n type: 'log:write'\n runId: string\n jobName: string\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n }\n\n// =============================================================================\n// Typed Run types for useRuns hooks\n// =============================================================================\n\n/**\n * A typed version of Run with generic payload/output types.\n * Used by browser hooks (direct durably access).\n */\nexport type TypedRun<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> = Omit<Run, 'payload' | 'output'> & {\n payload: TInput\n output: TOutput | null\n}\n\n/**\n * Run type for client mode (matches server response).\n * Used by client hooks (HTTP/SSE connection).\n */\nexport interface ClientRun {\n id: string\n jobName: string\n status: RunStatus\n input: unknown\n output: unknown | null\n error: string | null\n currentStepIndex: number\n stepCount: number\n progress: Progress | null\n createdAt: string\n startedAt: string | null\n completedAt: string | null\n}\n\n/**\n * A typed version of ClientRun with generic input/output types.\n * Used by client hooks (HTTP/SSE connection).\n */\nexport type TypedClientRun<\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined =\n | Record<string, unknown>\n | undefined,\n> = Omit<ClientRun, 'input' | 'output'> & {\n input: TInput\n output: TOutput | null\n}\n\n/**\n * Type guard to check if an object is a JobDefinition.\n * Used to distinguish between JobDefinition and options objects in overloaded functions.\n */\nexport function isJobDefinition<\n TName extends string = string,\n TInput extends Record<string, unknown> = Record<string, unknown>,\n TOutput extends Record<string, unknown> | undefined = undefined,\n>(obj: unknown): obj is JobDefinition<TName, TInput, TOutput> {\n return (\n typeof obj === 'object' &&\n obj !== null &&\n 'name' in obj &&\n 'run' in obj &&\n typeof (obj as { run: unknown }).run === 'function'\n )\n}\n","import type { LogEntry } from '../types'\n\nexport interface CreateLogEntryParams {\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n}\n\n/**\n * Creates a LogEntry with auto-generated id and timestamp.\n * Extracted to eliminate duplication between subscription hooks.\n */\nexport function createLogEntry(params: CreateLogEntryParams): LogEntry {\n return {\n id: crypto.randomUUID(),\n runId: params.runId,\n stepName: params.stepName,\n level: params.level,\n message: params.message,\n data: params.data,\n timestamp: new Date().toISOString(),\n }\n}\n\n/**\n * Appends a log entry to the array, respecting maxLogs limit.\n */\nexport function appendLog(\n logs: LogEntry[],\n newLog: LogEntry,\n maxLogs: number,\n): LogEntry[] {\n const newLogs = [...logs, newLog]\n if (maxLogs > 0 && newLogs.length > maxLogs) {\n return newLogs.slice(-maxLogs)\n }\n return newLogs\n}\n","import type { Progress, SubscriptionState } from '../types'\nimport { appendLog, createLogEntry } from './create-log-entry'\n\n// Action types for subscription state transitions\nexport type SubscriptionAction<TOutput = unknown> =\n | { type: 'run:start' }\n | { type: 'run:complete'; output: TOutput }\n | { type: 'run:fail'; error: string }\n | { type: 'run:cancel' }\n | { type: 'run:retry' }\n | { type: 'run:progress'; progress: Progress }\n | {\n type: 'log:write'\n runId: string\n stepName: string | null\n level: 'info' | 'warn' | 'error'\n message: string\n data: unknown\n maxLogs: number\n }\n | { type: 'reset' }\n | { type: 'clear_logs' }\n | { type: 'connection_error'; error: string }\n\nexport const initialSubscriptionState: SubscriptionState<unknown> = {\n status: null,\n output: null,\n error: null,\n logs: [],\n progress: null,\n}\n\n/**\n * Pure reducer for subscription state transitions.\n * Extracted to eliminate duplication between useRunSubscription and useSSESubscription.\n */\nexport function subscriptionReducer<TOutput = unknown>(\n state: SubscriptionState<TOutput>,\n action: SubscriptionAction<TOutput>,\n): SubscriptionState<TOutput> {\n switch (action.type) {\n case 'run:start':\n return { ...state, status: 'running' }\n\n case 'run:complete':\n return { ...state, status: 'completed', output: action.output }\n\n case 'run:fail':\n return { ...state, status: 'failed', error: action.error }\n\n case 'run:cancel':\n return { ...state, status: 'cancelled' }\n\n case 'run:retry':\n return { ...state, status: 'pending', error: null }\n\n case 'run:progress':\n return { ...state, progress: action.progress }\n\n case 'log:write': {\n const newLog = createLogEntry({\n runId: action.runId,\n stepName: action.stepName,\n level: action.level,\n message: action.message,\n data: action.data,\n })\n return { ...state, logs: appendLog(state.logs, newLog, action.maxLogs) }\n }\n\n case 'reset':\n return initialSubscriptionState as SubscriptionState<TOutput>\n\n case 'clear_logs':\n return { ...state, logs: [] }\n\n case 'connection_error':\n return { ...state, error: action.error }\n\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useReducer, useRef } from 'react'\nimport type { SubscriptionState } from '../types'\nimport type { EventSubscriber } from './event-subscriber'\nimport {\n initialSubscriptionState,\n subscriptionReducer,\n} from './subscription-reducer'\n\nexport interface UseSubscriptionOptions {\n /**\n * Maximum number of logs to keep (0 = unlimited)\n */\n maxLogs?: number\n}\n\nexport interface UseSubscriptionResult<\n TOutput = unknown,\n> extends SubscriptionState<TOutput> {\n /**\n * Clear all logs\n */\n clearLogs: () => void\n /**\n * Reset all state\n */\n reset: () => void\n}\n\n/**\n * Core subscription hook that works with any EventSubscriber implementation.\n * This unifies the subscription logic between Durably.on and SSE.\n */\nexport function useSubscription<TOutput = unknown>(\n subscriber: EventSubscriber | null,\n runId: string | null,\n options?: UseSubscriptionOptions,\n): UseSubscriptionResult<TOutput> {\n const [state, dispatch] = useReducer(\n subscriptionReducer<TOutput>,\n initialSubscriptionState as SubscriptionState<TOutput>,\n )\n\n const runIdRef = useRef<string | null>(runId)\n const prevRunIdRef = useRef<string | null>(null)\n\n const maxLogs = options?.maxLogs ?? 0\n\n // Reset state when runId changes\n if (prevRunIdRef.current !== runId) {\n prevRunIdRef.current = runId\n if (runIdRef.current !== runId) {\n dispatch({ type: 'reset' })\n }\n }\n runIdRef.current = runId\n\n useEffect(() => {\n if (!subscriber || !runId) return\n\n const unsubscribe = subscriber.subscribe<TOutput>(runId, (event) => {\n // Verify runId hasn't changed during async operation\n if (runIdRef.current !== runId) return\n\n switch (event.type) {\n case 'run:start':\n case 'run:cancel':\n case 'run:retry':\n dispatch({ type: event.type })\n break\n case 'run:complete':\n dispatch({ type: 'run:complete', output: event.output })\n break\n case 'run:fail':\n dispatch({ type: 'run:fail', error: event.error })\n break\n case 'run:progress':\n dispatch({ type: 'run:progress', progress: event.progress })\n break\n case 'log:write':\n dispatch({\n type: 'log:write',\n runId: event.runId,\n stepName: event.stepName,\n level: event.level,\n message: event.message,\n data: event.data,\n maxLogs,\n })\n break\n case 'connection_error':\n dispatch({ type: 'connection_error', error: event.error })\n break\n }\n })\n\n return unsubscribe\n }, [subscriber, runId, maxLogs])\n\n const clearLogs = useCallback(() => {\n dispatch({ type: 'clear_logs' })\n }, [])\n\n const reset = useCallback(() => {\n dispatch({ type: 'reset' })\n }, [])\n\n return {\n ...state,\n clearLogs,\n reset,\n }\n}\n"],"mappings":"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","names":[]}
|