Skip to content

feat: add generic type support for getRun/getRuns and useRuns#15

Merged
coji merged 11 commits into
mainfrom
feat/typed-use-runs
Jan 3, 2026
Merged

feat: add generic type support for getRun/getRuns and useRuns#15
coji merged 11 commits into
mainfrom
feat/typed-use-runs

Conversation

@coji
Copy link
Copy Markdown
Owner

@coji coji commented Jan 3, 2026

Summary

@coji/durably

  • Add generic type parameter to getRun<T>() and getRuns<T>() for type-safe run retrieval
    // Untyped (returns Run)
    const run = await durably.getRun(runId)
    
    // Typed (returns custom type)
    type MyRun = Run & { payload: { userId: string }; output: { count: number } | null }
    const typedRun = await durably.getRun<MyRun>(runId)

@coji/durably-react

  • Add generic type parameter support to useRuns hook for type-safe dashboard access
  • Three usage patterns now supported:
    • useRuns<TRun>(options) - Generic type for multi-job dashboards
    • useRuns(jobDefinition, options?) - JobDefinition for single job with auto-filter
    • useRuns(options?) - Untyped for simple cases
  • New type exports: TypedRun<TInput, TOutput> (browser) and TypedClientRun<TInput, TOutput> (client)
  • Add isJobDefinition type guard function
  • Centralize shared types to types.ts

Example Usage

import { useRuns, TypedRun } from '@coji/durably-react'
import type { JobInput, JobOutput } from '@coji/durably'

// Define union type for all jobs in dashboard
type DashboardRun =
  | TypedRun<JobInput<typeof dataSyncJob>, JobOutput<typeof dataSyncJob>>
  | TypedRun<JobInput<typeof importCsvJob>, JobOutput<typeof importCsvJob>>

function Dashboard() {
  const { runs } = useRuns<DashboardRun>({ pageSize: 10 })
  // runs are typed as DashboardRun[]

  const showDetails = async (runId: string) => {
    const run = await durably.getRun<DashboardRun>(runId)
    // run is typed as DashboardRun | null
  }
}

Test plan

  • pnpm validate passes
  • Type inference works correctly in examples
  • Type-level tests added for generics
  • Documentation updated (website, llms.md, CHANGELOG)

Closes #14

🤖 Generated with Claude Code

Add multiple ways to get type-safe run access in useRuns:

- `useRuns<TRun>(options)` - Pass type parameter for dashboards with multiple job types
- `useRuns(jobDefinition, options?)` - Pass JobDefinition to infer types and auto-filter by jobName
- `useRuns(options?)` - Untyped usage for simple cases

New exports:
- `TypedRun<TInput, TOutput>` type for browser hooks
- `TypedClientRun<TInput, TOutput>` type for client hooks

Examples updated to demonstrate typed dashboard pattern with union types.

Closes #14

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
durably-demo Ready Ready Preview Jan 3, 2026 4:04pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 3, 2026

📝 Walkthrough

Walkthrough

Added generic, strongly-typed run types (TypedRun / TypedClientRun), three overloads for useRuns (generic union, JobDefinition, untyped), updated hook implementations/exports and examples to use typed runs, made Durably storage/getters generic, bumped packages to 0.7.0, updated docs/changelog, and added website/public/llms.txt to .prettierignore.

Changes

Cohort / File(s) Summary
Core Hook (Browser)
packages/durably-react/src/hooks/use-runs.ts
Introduced TypedRun<TInput,TOutput>, made UseRunsResult generic, added three useRuns overloads (generic, JobDefinition, untyped), implemented JobDefinition detection, typed internal state/returns, and added paging/refresh controls.
Core Hook (Client)
packages/durably-react/src/client/use-runs.ts, packages/durably-react/src/client/index.ts, packages/durably-react/src/client.ts
Added TypedClientRun<TInput,TOutput>, generic UseRunsClientResult, three useRuns overloads (including JobDefinition-based), re-exported TypedClientRun in client index/exports.
Public Types & Utilities
packages/durably-react/src/types.ts, packages/durably-react/src/index.ts
Added TypedRun, ClientRun, TypedClientRun, and isJobDefinition type guard; exported TypedRun/TypedClientRun via package index.
Core Durably API & Storage
packages/durably/src/durably.ts, packages/durably/src/storage.ts, packages/durably/docs/llms.md, website/api/create-durably.md
Made getRun/getRuns generic (<T extends Run = Run>), propagated generics into storage implementation and docs/examples, added filter/pagination fields to RunFilter in docs.
Examples — Jobs API
examples/.../app/jobs/index.ts, examples/browser-vite-react/src/jobs/index.ts, examples/fullstack-react-router/app/jobs/index.ts, examples/browser-react-router-spa/app/jobs/index.ts
Consolidated job re-exports and added JobInput/JobOutput-based type aliases (e.g., DataSyncInput/Output), preserving backward-compatible aliases like ImportCsvOutput.
Examples — Dashboards / Routes / Components
examples/.../routes/_index/dashboard.tsx, examples/browser-vite-react/src/components/dashboard.tsx, examples/fullstack-react-router/app/routes/_index/dashboard.tsx
Added local DashboardRun union types and updated useRuns calls to typed overloads (e.g., useRuns<DashboardRun> or useRuns(jobDefinition, ...)), adjusted local state typing.
Docs & Site Content
packages/durably-react/docs/llms.md, website/api/durably-react/*.md, website/public/llms.txt
Documented TypedRun/TypedClientRun, the three useRuns overloads, updated examples and signatures, and revised paging/return values (page, hasMore, navigation methods, refresh).
Changelog & Meta
CHANGELOG.md, .prettierignore, packages/durably-react/package.json, packages/durably/package.json
Bumped packages to 0.7.0, added changelog entries, and added website/public/llms.txt to .prettierignore.
Tests
packages/durably-react/tests/types.test.ts
Added TypeScript type-inference tests for TypedRun/TypedClientRun and hook result types (browser and client variants).

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Component
    participant Hook as "useRuns Hook"
    participant ClientAPI as "durably.getRuns / getRun"
    participant Storage as "Storage/DB"

    Component->>Hook: call useRuns(jobDef | useRuns<DashboardRun>(opts) | useRuns(opts))
    Note over Hook: isJobDefinition? → derive jobName & TInput/TOutput\nor use provided generic/options
    Hook->>ClientAPI: request runs (jobName?, status?, pageSize?, offset?)
    ClientAPI->>Storage: query runs
    Storage-->>ClientAPI: raw run records
    ClientAPI-->>Hook: raw runs array
    Note over Hook: cast/parse → TypedRun<TInput,TOutput>[]\ncompute page/hasMore, expose controls
    Hook-->>Component: { runs: TypedRun[], page, hasMore, nextPage, prevPage, goToPage, refresh }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through types with nimble paws,
TypedRun and friends fixed type-ful laws.
JobDefs and unions in a cozy stew,
Dashboards now know what outputs do.
Hop, nibble, celebrate—type-safe carrots for you!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully implements both preferred options from issue #14: supports JobDefinition-based usage with automatic type inference and jobName filtering, plus generic type parameter usage for multi-job dashboards.
Out of Scope Changes check ✅ Passed All changes are within scope: adding TypedRun/TypedClientRun types, updating useRuns/useRuns client hooks with generics and overloads, updating examples to use typed patterns, and updating documentation and CHANGELOG.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main change: adding generic type support for getRun/getRuns and useRuns across the durably packages.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Update version from 0.6.1 to 0.7.0 for new feature release
- Move [Unreleased] changelog entry to [0.7.0] with today's date

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (4)
packages/durably-react/src/hooks/use-runs.ts (1)

39-42: Inconsistent default type for TOutput between TypedRun and UseRunsResult.

TypedRun (line 10-12) defaults TOutput to Record<string, unknown> | undefined, but UseRunsResult (line 41) defaults it to Record<string, unknown> without undefined. This inconsistency may cause type mismatches when the result type is inferred.

🔎 Proposed fix
 export interface UseRunsResult<
   TInput extends Record<string, unknown> = Record<string, unknown>,
-  TOutput extends Record<string, unknown> | undefined = Record<string, unknown>,
+  TOutput extends Record<string, unknown> | undefined =
+    | Record<string, unknown>
+    | undefined,
 > {
packages/durably-react/src/client/use-runs.ts (1)

96-99: Same TOutput default inconsistency as browser hook.

UseRunsClientResult defaults TOutput to Record<string, unknown> (line 98), but TypedClientRun (line 28-30) defaults to Record<string, unknown> | undefined. Align these for consistency.

🔎 Proposed fix
 export interface UseRunsClientResult<
   TInput extends Record<string, unknown> = Record<string, unknown>,
-  TOutput extends Record<string, unknown> | undefined = Record<string, unknown>,
+  TOutput extends Record<string, unknown> | undefined =
+    | Record<string, unknown>
+    | undefined,
 > {
examples/browser-react-router-spa/app/routes/_index/dashboard.tsx (1)

13-35: Consider aligning selectedRun type with DashboardRun for consistency.

The runs array is typed as DashboardRun[] via the generic parameter on line 31, but selectedRun is typed as Run on line 35. For type consistency in this example demonstrating typed patterns, consider using DashboardRun for selectedRun as well.

🔎 Suggested type alignment
-  const [selectedRun, setSelectedRun] = useState<Run | null>(null)
+  const [selectedRun, setSelectedRun] = useState<DashboardRun | null>(null)
examples/browser-vite-react/src/components/dashboard.tsx (1)

13-32: Consider aligning selectedRun type with DashboardRun for consistency.

The runs array is typed as DashboardRun[] via the generic parameter on line 28, but selectedRun is typed as Run on line 32. For type consistency in this example demonstrating typed patterns, consider using DashboardRun for selectedRun as well.

🔎 Suggested type alignment
-  const [selectedRun, setSelectedRun] = useState<Run | null>(null)
+  const [selectedRun, setSelectedRun] = useState<DashboardRun | null>(null)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10af60a and 9d8a641.

📒 Files selected for processing (17)
  • .prettierignore
  • CHANGELOG.md
  • examples/browser-react-router-spa/app/jobs/index.ts
  • examples/browser-react-router-spa/app/routes/_index/dashboard.tsx
  • examples/browser-vite-react/src/components/dashboard.tsx
  • examples/browser-vite-react/src/jobs/index.ts
  • examples/fullstack-react-router/app/jobs/index.ts
  • examples/fullstack-react-router/app/routes/_index/dashboard.tsx
  • packages/durably-react/docs/llms.md
  • packages/durably-react/src/client.ts
  • packages/durably-react/src/client/index.ts
  • packages/durably-react/src/client/use-runs.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/index.ts
  • website/api/durably-react/browser.md
  • website/api/durably-react/client.md
  • website/public/llms.txt
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,mjs,mts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,ts,mjs,mts}: This library is ESM-only. CommonJS is not supported. Always use top-level await for async initialization (e.g., await durably.migrate()). Do not wrap in async IIFE or Promise chains.
Jobs must be defined via defineJob() and registered with durably.register(), receiving a step context and payload
Steps are created via step.run(), with each step's success state and return value persisted automatically

Files:

  • packages/durably-react/src/client/index.ts
  • packages/durably-react/src/index.ts
  • examples/fullstack-react-router/app/jobs/index.ts
  • packages/durably-react/src/client/use-runs.ts
  • examples/browser-react-router-spa/app/jobs/index.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/client.ts
  • examples/browser-vite-react/src/jobs/index.ts
🧠 Learnings (8)
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : Jobs must be defined via `defineJob()` and registered with `durably.register()`, receiving a step context and payload

Applied to files:

  • CHANGELOG.md
  • packages/durably-react/src/index.ts
  • examples/fullstack-react-router/app/jobs/index.ts
  • website/api/durably-react/client.md
  • packages/durably-react/src/client/use-runs.ts
  • examples/browser-react-router-spa/app/jobs/index.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • examples/browser-vite-react/src/jobs/index.ts
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : Steps are created via `step.run()`, with each step's success state and return value persisted automatically

Applied to files:

  • packages/durably-react/src/index.ts
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to packages/durably/docs/llms.md : Keep `packages/durably/docs/llms.md` in sync with API changes as it is bundled in the npm package and symlinked to `website/public/llms.txt` for web access

Applied to files:

  • website/public/llms.txt
  • .prettierignore
  • packages/durably-react/docs/llms.md
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : This library is ESM-only. CommonJS is not supported. Always use top-level `await` for async initialization (e.g., `await durably.migrate()`). Do not wrap in async IIFE or Promise chains.

Applied to files:

  • examples/fullstack-react-router/app/jobs/index.ts
  • examples/browser-react-router-spa/app/jobs/index.ts
  • examples/browser-vite-react/src/jobs/index.ts
📚 Learning: 2026-01-02T14:03:25.739Z
Learnt from: coji
Repo: coji/durably PR: 11
File: examples/browser-react-router-spa/app/routes/_index/dashboard.tsx:24-34
Timestamp: 2026-01-02T14:03:25.739Z
Learning: In example code under the examples/ directory, error handling is intentionally omitted to keep the code simple and focused on demonstrating the Durably API. Treat these files as illustrative, not production-ready. For real applications, ensure proper error handling and user feedback; do not copy this pattern verbatim into production code.

Applied to files:

  • examples/browser-vite-react/src/components/dashboard.tsx
  • examples/fullstack-react-router/app/routes/_index/dashboard.tsx
  • examples/browser-react-router-spa/app/routes/_index/dashboard.tsx
📚 Learning: 2026-01-02T14:03:31.683Z
Learnt from: coji
Repo: coji/durably PR: 11
File: examples/browser-vite-react/src/components/dashboard.tsx:36-53
Timestamp: 2026-01-02T14:03:31.683Z
Learning: In all example files under the examples directory (e.g., examples/**/*.tsx), prioritize simplicity and clarity over comprehensive error handling. Focus on demonstrating Durably API usage patterns; omit production-grade error handling in examples to keep them readable and centered on core concepts. Where appropriate, include concise comments to explain the pattern being demonstrated.

Applied to files:

  • examples/browser-vite-react/src/components/dashboard.tsx
  • examples/fullstack-react-router/app/routes/_index/dashboard.tsx
  • examples/browser-react-router-spa/app/routes/_index/dashboard.tsx
📚 Learning: 2026-01-02T14:03:32.230Z
Learnt from: coji
Repo: coji/durably PR: 11
File: examples/fullstack-react-router/app/routes/_index.tsx:28-46
Timestamp: 2026-01-02T14:03:32.230Z
Learning: In the coji/durably repository's examples directory, code examples (e.g., any .tsx under examples) should prioritize simplicity and readability over comprehensive error handling to clearly demonstrate the Durably API usage patterns. Include minimal, demonstrative error handling only as necessary for clarity.

Applied to files:

  • examples/browser-vite-react/src/components/dashboard.tsx
  • examples/fullstack-react-router/app/routes/_index/dashboard.tsx
  • examples/browser-react-router-spa/app/routes/_index/dashboard.tsx
📚 Learning: 2026-01-02T14:03:31.517Z
Learnt from: coji
Repo: coji/durably PR: 11
File: examples/browser-vite-react/src/components/dashboard.tsx:24-34
Timestamp: 2026-01-02T14:03:31.517Z
Learning: In example code directories (e.g., examples/browser-vite-react, examples/fullstack-react-router) error handling may be omitted to keep code simple and readable and to focus on demonstrating the Durably API. This is intentional for examples, not a production best practice. When reviewing code in these example paths, verify that the lack of error handling is confined to examples and that the surrounding documentation clearly states this is for illustration. Do not apply this omission as a general guidance for production code.

Applied to files:

  • examples/browser-vite-react/src/components/dashboard.tsx
🧬 Code graph analysis (4)
examples/fullstack-react-router/app/jobs/index.ts (1)
examples/fullstack-react-router/app/jobs/import-csv.ts (1)
  • ImportCsvOutput (25-25)
examples/browser-vite-react/src/components/dashboard.tsx (1)
packages/durably-react/src/hooks/use-runs.ts (2)
  • TypedRun (8-16)
  • useRuns (136-240)
examples/browser-react-router-spa/app/routes/_index/dashboard.tsx (3)
packages/durably-react/src/hooks/use-runs.ts (1)
  • TypedRun (8-16)
examples/browser-react-router-spa/app/jobs/index.ts (6)
  • DataSyncInput (19-19)
  • DataSyncOutput (20-20)
  • ImportCsvInput (21-21)
  • ImportCsvOutput (16-16)
  • ProcessImageInput (22-22)
  • ProcessImageOutput (23-23)
examples/browser-vite-react/src/jobs/index.ts (4)
  • DataSyncInput (15-15)
  • DataSyncOutput (16-16)
  • ProcessImageInput (17-17)
  • ProcessImageOutput (18-18)
packages/durably-react/src/client/use-runs.ts (4)
packages/durably-react/src/client.ts (4)
  • TypedClientRun (34-34)
  • ClientRun (33-33)
  • UseRunsClientResult (36-36)
  • useRuns (31-31)
packages/durably-react/src/client/index.ts (4)
  • TypedClientRun (34-34)
  • ClientRun (33-33)
  • UseRunsClientResult (36-36)
  • useRuns (31-31)
packages/durably-react/src/hooks/use-runs.ts (1)
  • useRuns (136-240)
packages/durably/src/server.ts (1)
  • runs (242-261)
🔇 Additional comments (27)
packages/durably-react/src/hooks/use-runs.ts (2)

109-133: Well-designed overload structure for multiple usage patterns.

The three overloads cleanly support the different use cases:

  1. Generic type parameter for multi-job dashboards
  2. JobDefinition for single-job with auto-filtering
  3. Untyped for backward compatibility

The conditional type inference in overload 1 (lines 117-119) correctly extracts input/output types from the provided TRun.


148-161: JobDefinition detection heuristic is reasonable but could be more explicit.

The check 'name' in jobDefinitionOrOptions && 'run' in jobDefinitionOrOptions works for the current API but could match other objects with these properties. Consider adding a type guard or checking for a discriminant property if JobDefinition gains one in the future.

For now, this approach is acceptable given the controlled usage context.

packages/durably-react/src/client/use-runs.ts (2)

185-196: Client hook correctly requires api option with JobDefinition.

Unlike the browser hook where options are optional (uses context), the client hook requires options with api in overload 2 (line 192). This is correct since the client mode needs an explicit API endpoint.


172-183: Overload signatures mirror browser hook correctly.

The three overloads follow the same pattern as the browser version, maintaining API consistency across both modes. The conditional type inference for extracting input/output from TRun is identical.

.prettierignore (1)

8-8: Appropriate addition to ignore symlinked documentation file.

website/public/llms.txt is symlinked from packages/durably/docs/llms.md and shouldn't be formatted by Prettier. Based on learnings, this file is bundled in the npm package for AI agent consumption.

CHANGELOG.md (1)

8-28: Comprehensive changelog entry documenting the new feature.

The entry clearly documents:

  • All three usage patterns for useRuns
  • New TypedRun and TypedClientRun types
  • Practical code example showing union type for multi-job dashboards
packages/durably-react/src/client/index.ts (1)

31-37: Correctly exports new TypedClientRun type.

The new type is properly added to the public API exports, enabling consumers to use it for typed dashboard patterns.

examples/fullstack-react-router/app/routes/_index/dashboard.tsx (1)

26-37: Excellent demonstration of typed multi-job dashboard pattern.

The DashboardRun union type (lines 27-30) clearly shows how to combine multiple job types for a unified dashboard view, and the generic parameter usage useRuns<DashboardRun> (line 34) demonstrates the intended API pattern.

packages/durably-react/src/index.ts (1)

12-13: Correctly exports new TypedRun type for browser mode.

The new type is properly added to the browser-complete entry point, enabling typed dashboard usage in browser mode.

examples/browser-react-router-spa/app/routes/_index/dashboard.tsx (1)

22-26: LGTM! Clear demonstration of multi-job dashboard typing.

The DashboardRun union type effectively demonstrates how to use TypedRun for multi-job dashboards with type-safe access to different job inputs and outputs.

packages/durably-react/src/client.ts (1)

32-37: LGTM! TypedClientRun export correctly added.

The new TypedClientRun type export is properly placed alongside the related ClientRun export, enabling type-safe client-side run handling.

examples/browser-vite-react/src/components/dashboard.tsx (1)

20-23: LGTM! Clear demonstration of multi-job dashboard typing.

The DashboardRun union type effectively demonstrates how to compose TypedRun for multi-job dashboards with type-safe access to job-specific inputs and outputs.

examples/browser-vite-react/src/jobs/index.ts (1)

8-18: LGTM! Centralized job type exports pattern.

The centralized export pattern using JobInput and JobOutput utility types provides a clean way to expose typed interfaces for dashboard consumption. This aligns well with the broader PR pattern for typed dashboards.

website/public/llms.txt (2)

699-735: LGTM! Clear documentation of typed useRuns patterns.

The documentation effectively explains the three usage options (generic, JobDefinition, untyped) with clear examples. The progression from most flexible (generic) to most convenient (JobDefinition) to simplest (untyped) helps users choose the right approach.


1002-1018: TypedClientRun correctly uses the input field from ClientRun.

The field names are intentionally different by design: Run (server-side) uses payload, while ClientRun (client API) uses input. TypedClientRun properly extends ClientRun with generic types and correctly omits and redefines the input field. No changes needed.

Likely an incorrect or invalid review comment.

examples/browser-react-router-spa/app/jobs/index.ts (2)

8-13: LGTM! Clean consolidation of job exports.

The barrel export pattern is well-structured with appropriate type imports and a single consolidated export statement. This provides a clean public API surface for the jobs.


15-23: LGTM! Well-structured type aliases for typed dashboard support.

The type aliases provide a clean public API surface that enables type-safe useRuns usage as described in the PR objectives. Backward compatibility is maintained via the ImportCsvOutput alias, and the naming convention is consistent across all jobs.

examples/fullstack-react-router/app/jobs/index.ts (1)

8-23: LGTM! Consistent pattern across examples.

This file follows the exact same structure as examples/browser-react-router-spa/app/jobs/index.ts, providing consistency across different example implementations. The barrel export pattern with typed aliases aligns perfectly with the PR's goal of enabling type-safe dashboard access.

website/api/durably-react/browser.md (4)

249-276: LGTM! Clear documentation of generic type parameter usage.

The documentation clearly demonstrates the generic type parameter pattern for multi-job dashboards. The DashboardRun union type example and the jobName-based type narrowing pattern are well-explained and practical.


278-307: LGTM! Excellent documentation of the preferred JobDefinition pattern.

This section clearly demonstrates the preferred API pattern from issue #14, where passing a JobDefinition provides automatic type inference and jobName filtering. The inline comment highlighting the typed output is particularly helpful for users.


309-341: LGTM! Complete documentation of all usage patterns.

The untyped fallback ensures backward compatibility, and the signatures section provides a clear quick reference for all three overload patterns. The documentation comprehensively covers the full API surface introduced in this PR.


343-363: LGTM! Accurate and complete API reference tables.

The options and return type tables accurately document the updated API surface. The clarification that jobName is "only for untyped usage" is particularly helpful, as it guides users toward the preferred JobDefinition pattern for filtered, typed runs.

packages/durably-react/docs/llms.md (2)

264-300: LGTM! Concise and complete useRuns documentation for LLM context.

The documentation effectively presents all three usage patterns in a compact format suitable for LLM consumption. The structure parallels the user-facing docs while maintaining brevity, which aligns with the purpose of this file.


567-583: LGTM! Well-defined type exports for browser and client contexts.

The TypedRun and TypedClientRun type definitions are clearly documented with appropriate generic constraints. The separation between browser hooks (using payload) and client hooks (using input) correctly reflects the different contexts. The nullable output type accurately represents runtime behavior.

website/api/durably-react/client.md (3)

217-244: LGTM! Correct client-side implementation of generic type pattern.

The documentation correctly demonstrates the generic type parameter pattern for client hooks, using TypedClientRun instead of TypedRun and including the required api parameter. The pattern is consistent with the browser documentation while properly adapted for the client context.


246-296: LGTM! Complete coverage of all client-side usage patterns.

The JobDefinition and untyped usage patterns are well-documented with clear examples. The required api parameter is consistently included, and the inline comments about typed/untyped output help users understand the benefits of each approach.


298-332: LGTM! Complete and accurate API reference for client useRuns.

The signatures section and API reference tables comprehensively document the client-side useRuns API. The clarification that jobName is only for untyped usage guides users toward the type-safe patterns, and the return type documentation accurately reflects the TypedClientRun generic structure.

…finition detection

- Move TypedRun, TypedClientRun, and ClientRun to types.ts for single source of truth
- Add isJobDefinition type guard function for robust JobDefinition detection
- Re-export types from hooks for backward compatibility
- Replace inline 'name' in obj && 'run' in obj checks with type guard

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add tests for TypedRun and TypedClientRun type inference
- Add tests for UseRunsResult and UseRunsClientResult generic types
- Add tests for union types in multi-job dashboard scenarios
- Verify pagination controls and error handling types

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/durably-react/src/client/use-runs.ts (1)

186-197: Potential runtime error when optionsArg is undefined.

In overload 2, options is required (not optional), so this should be safe. However, the implementation signature allows optionsArg?: ... which means TypeScript won't catch if someone bypasses overloads. Line 194 casts without checking, and line 197 destructures api which would throw if options is undefined.

Consider adding a runtime check or assertion for safety:

🔎 Proposed defensive check
   const options = isJob
-    ? (optionsArg as Omit<UseRunsClientOptions, 'jobName'>)
+    ? (optionsArg ?? {}) as Omit<UseRunsClientOptions, 'jobName'>
     : (jobDefinitionOrOptions as UseRunsClientOptions)
+
+  if (!options.api && !('api' in (jobDefinitionOrOptions as object))) {
+    throw new Error('useRuns: api option is required')
+  }
 
   const { api, status, pageSize = 10 } = options

Alternatively, this is fine if you trust TypeScript consumers to use the overloads correctly.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 965ff73 and 33e595b.

📒 Files selected for processing (3)
  • packages/durably-react/src/client/use-runs.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/types.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,ts,mjs,mts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{js,ts,mjs,mts}: This library is ESM-only. CommonJS is not supported. Always use top-level await for async initialization (e.g., await durably.migrate()). Do not wrap in async IIFE or Promise chains.
Jobs must be defined via defineJob() and registered with durably.register(), receiving a step context and payload
Steps are created via step.run(), with each step's success state and return value persisted automatically

Files:

  • packages/durably-react/src/types.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/client/use-runs.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : Jobs must be defined via `defineJob()` and registered with `durably.register()`, receiving a step context and payload
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : Jobs must be defined via `defineJob()` and registered with `durably.register()`, receiving a step context and payload

Applied to files:

  • packages/durably-react/src/types.ts
  • packages/durably-react/src/hooks/use-runs.ts
  • packages/durably-react/src/client/use-runs.ts
🧬 Code graph analysis (2)
packages/durably-react/src/hooks/use-runs.ts (1)
packages/durably-react/src/types.ts (2)
  • TypedRun (113-121)
  • isJobDefinition (160-172)
packages/durably-react/src/client/use-runs.ts (1)
packages/durably-react/src/types.ts (2)
  • TypedClientRun (146-154)
  • isJobDefinition (160-172)
🔇 Additional comments (12)
packages/durably-react/src/hooks/use-runs.ts (4)

1-7: LGTM!

Clean imports and convenient re-export of TypedRun for consumers who import from this hook module.


100-124: LGTM!

The three overloads are well-designed:

  • Overload 1 handles union types for multi-job dashboards with proper conditional type inference.
  • Overload 2 provides automatic jobName filtering and type inference from JobDefinition.
  • Overload 3 maintains backward compatibility.

139-148: LGTM!

The implementation correctly uses the isJobDefinition type guard to distinguish between call patterns, deriving jobName and options appropriately for each case.


159-175: LGTM!

The refresh callback properly passes jobName and status to the API, and the type cast on line 171 is a reasonable approach given the runtime data comes from an external source. The dependency array is complete.

packages/durably-react/src/types.ts (4)

113-121: LGTM!

The TypedRun type correctly extends the base Run type by omitting payload and output, then adding them back with generic types. The output: TOutput | null correctly handles the case where output hasn't been produced yet.


127-140: LGTM!

The ClientRun interface provides a clean client-facing shape with string dates and appropriate nullable fields. This aligns well with typical JSON serialization from server responses.


146-154: LGTM!

TypedClientRun mirrors TypedRun but for client mode, using Omit<ClientRun, 'input' | 'output'> to replace the untyped fields with generics.


160-172: LGTM!

The isJobDefinition type guard is robust:

  • Checks for object type and non-null
  • Verifies presence of name and run properties (distinguishing from options objects which use jobName)
  • Confirms run is a function

This reliably distinguishes JobDefinition from UseRunsOptions/UseRunsClientOptions at runtime.

packages/durably-react/src/client/use-runs.ts (4)

1-11: LGTM!

Clean imports and appropriate re-exports. The client hook correctly imports TypedClientRun and isJobDefinition from the shared types module.


149-173: LGTM!

The overloads mirror the browser hook pattern correctly, using TypedClientRun instead of TypedRun. The client-specific UseRunsClientOptions requires api as mandatory, which is appropriate for HTTP-based fetching.


254-323: LGTM!

The SSE subscription logic is well-implemented:

  • Only subscribes on page 0 for efficiency
  • Properly cleans up EventSource when navigating away
  • Handles various event types appropriately (refresh on lifecycle events, in-place updates for progress/step completion)
  • Correct cleanup on unmount

339-349: LGTM!

The return object correctly includes all pagination controls and the refresh function, matching the UseRunsClientResult interface.

Comment thread packages/durably-react/src/client/use-runs.ts
Comment thread packages/durably-react/src/hooks/use-runs.ts
- Fix TOutput default type in UseRunsResult and UseRunsClientResult to
  match TypedRun/TypedClientRun (Record<string, unknown> | undefined)
- Update example dashboards to use DashboardRun type for selectedRun
  instead of Run for better type consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add generic type parameter to getRun<T>() and getRuns<T>() for type-safe
  run retrieval
- Update examples to use typed getRun<DashboardRun>() instead of type cast
- Update CHANGELOG with new feature

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add typed getRun<T>/getRuns<T> examples to llms.md
- Update website API docs with generic type parameter documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/durably/docs/llms.md (1)

1-173: Fix Prettier formatting violations before merge.

The file has formatting violations in the type definitions (lines 152 and 171). Type MyRun declarations exceed the 80-character line width and must be reformatted. Run prettier --write packages/durably/docs/llms.md from the packages/durably directory to fix.

Formatting changes needed
-type MyRun = Run & { payload: { userId: string }; output: { count: number } | null }
+type MyRun = Run & {
+  payload: { userId: string }
+  output: { count: number } | null
+}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6611020 and 78c282f.

📒 Files selected for processing (3)
  • packages/durably/docs/llms.md
  • packages/durably/package.json
  • website/api/create-durably.md
✅ Files skipped from review due to trivial changes (1)
  • packages/durably/package.json
🧰 Additional context used
📓 Path-based instructions (1)
packages/durably/docs/llms.md

📄 CodeRabbit inference engine (CLAUDE.md)

Keep packages/durably/docs/llms.md in sync with API changes as it is bundled in the npm package and symlinked to website/public/llms.txt for web access

Files:

  • packages/durably/docs/llms.md
🧠 Learnings (2)
📓 Common learnings
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to packages/durably/docs/llms.md : Keep `packages/durably/docs/llms.md` in sync with API changes as it is bundled in the npm package and symlinked to `website/public/llms.txt` for web access
📚 Learning: 2026-01-03T06:23:01.913Z
Learnt from: CR
Repo: coji/durably PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-03T06:23:01.913Z
Learning: Applies to **/*.{js,ts,mjs,mts} : Jobs must be defined via `defineJob()` and registered with `durably.register()`, receiving a step context and payload

Applied to files:

  • website/api/create-durably.md
🪛 GitHub Actions: CI
packages/durably/docs/llms.md

[warning] 1-1: Prettier formatting issues found. Run 'prettier --write' to fix formatting in this file.

🔇 Additional comments (3)
packages/durably/docs/llms.md (1)

148-173: Excellent documentation of generic type support for getRun/getRuns.

The added examples clearly demonstrate both untyped and typed usage patterns, with concrete type alias examples (MyRun) that help users understand how to define custom run types. The documentation is well-aligned with the PR's goal of enabling type-safe run access. Once formatting is fixed, this will integrate seamlessly with the bundled npm package documentation.

website/api/create-durably.md (2)

125-158: API documentation accurately reflects generic type support.

The signature updates and examples correctly document the new generic type parameter support for getRun<T extends Run = Run> and getRuns<T extends Run = Run>. The examples clearly show both untyped (returns Run) and typed (returns custom T) usage patterns, making it easy for developers to adopt the new capability.


144-149: Field order in documentation differs from source code.

The RunFilter interface definition is functionally correct, but the field order in the documentation (jobName, status, limit, offset) does not match the source code order (status, jobName, limit, offset) in packages/durably/src/storage.ts and packages/durably/src/job.ts. Additionally, the source code includes JSDoc comments for the limit and offset fields that are not reflected in the documentation. Consider reordering the fields to match the source and adding the field descriptions for consistency.

coji and others added 2 commits January 4, 2026 01:03
@coji coji changed the title feat(durably-react): add generic type support for useRuns hook feat: add generic type support for getRun/getRuns and useRuns Jan 3, 2026
@coji coji merged commit 09767b9 into main Jan 3, 2026
9 of 10 checks passed
@coji coji mentioned this pull request Jan 3, 2026
@coji coji deleted the feat/typed-use-runs branch March 5, 2026 12:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Add generic type support to useRuns hook

1 participant