Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ node_modules/
__screenshots__/
website/.vitepress/cache/
website/.vitepress/dist/
website/public/llms.txt
*.log
CHANGELOG.md
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [0.7.0] - 2026-01-03

### Added

#### @coji/durably

- **Generic type parameter for `getRun<T>()` and `getRuns<T>()`**: Type-safe run retrieval
```ts
// 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

- **Generic type support for `useRuns` hook**: Multiple ways to get type-safe run access
- `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 `TypedRun<TInput, TOutput>` type for browser hooks
- New `TypedClientRun<TInput, TOutput>` type for client hooks
```tsx
// Dashboard with multiple job types
type DashboardRun = TypedRun<ImportInput, ImportOutput> | TypedRun<SyncInput, SyncOutput>
const { runs } = useRuns<DashboardRun>({ pageSize: 10 })

// Single job with auto-filter
const { runs } = useRuns(myJob)
// runs[0].output is typed as the job's output type
```

## [0.6.1] - 2026-01-03

### Fixed
Expand Down
19 changes: 16 additions & 3 deletions examples/browser-react-router-spa/app/jobs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
* When adding a new job, import and add it here.
*/

export { dataSyncJob } from './data-sync'
export { importCsvJob, type ImportCsvOutput } from './import-csv'
export { processImageJob } from './process-image'
import type { JobInput, JobOutput } from '@coji/durably'
import { dataSyncJob } from './data-sync'
import { importCsvJob } from './import-csv'
import { processImageJob } from './process-image'

export { dataSyncJob, importCsvJob, processImageJob }

/** Type for ImportCsvOutput (for backward compatibility) */
export type ImportCsvOutput = JobOutput<typeof importCsvJob>

/** Input/Output types for all jobs - used for typed useRuns dashboard */
export type DataSyncInput = JobInput<typeof dataSyncJob>
export type DataSyncOutput = JobOutput<typeof dataSyncJob>
export type ImportCsvInput = JobInput<typeof importCsvJob>
export type ProcessImageInput = JobInput<typeof processImageJob>
export type ProcessImageOutput = JobOutput<typeof processImageJob>
25 changes: 20 additions & 5 deletions examples/browser-react-router-spa/app/routes/_index/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,42 @@
*
* Displays run history with real-time updates and pagination.
* Uses browser-only mode hooks for direct durably access.
*
* Demonstrates typed useRuns with generic type parameter for multi-job dashboards.
*/

import type { Run } from '@coji/durably'
import { useDurably, useRuns } from '@coji/durably-react'
import { type TypedRun, useDurably, useRuns } from '@coji/durably-react'
import { useState } from 'react'
import type {
DataSyncInput,
DataSyncOutput,
ImportCsvInput,
ImportCsvOutput,
ProcessImageInput,
ProcessImageOutput,
} from '~/jobs'

/** Union type for all job runs in this dashboard */
type DashboardRun =
| TypedRun<DataSyncInput, DataSyncOutput>
| TypedRun<ImportCsvInput, ImportCsvOutput>
| TypedRun<ProcessImageInput, ProcessImageOutput>

export function Dashboard() {
const { durably } = useDurably()
const { runs, page, hasMore, isLoading, refresh, nextPage, prevPage } =
useRuns({
useRuns<DashboardRun>({
pageSize: 6,
})

const [selectedRun, setSelectedRun] = useState<Run | null>(null)
const [selectedRun, setSelectedRun] = useState<DashboardRun | null>(null)
const [steps, setSteps] = useState<
{ index: number; name: string; status: string }[]
>([])

const showDetails = async (runId: string) => {
if (!durably) return
const run = await durably.getRun(runId)
const run = await durably.getRun<DashboardRun>(runId)
if (run) {
setSelectedRun(run)
const stepsData = await durably.storage.getSteps(runId)
Expand Down
22 changes: 17 additions & 5 deletions examples/browser-vite-react/src/components/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,39 @@
*
* Displays run history with real-time updates and pagination.
* Uses browser-only mode hooks for direct durably access.
*
* Demonstrates typed useRuns with generic type parameter for multi-job dashboards.
*/

import type { Run } from '@coji/durably'
import { useDurably, useRuns } from '@coji/durably-react'
import { type TypedRun, useDurably, useRuns } from '@coji/durably-react'
import { useState } from 'react'
import type {
DataSyncInput,
DataSyncOutput,
ProcessImageInput,
ProcessImageOutput,
} from '../jobs'

/** Union type for all job runs in this dashboard */
type DashboardRun =
| TypedRun<DataSyncInput, DataSyncOutput>
| TypedRun<ProcessImageInput, ProcessImageOutput>

export function Dashboard() {
const { durably } = useDurably()
const { runs, page, hasMore, isLoading, refresh, nextPage, prevPage } =
useRuns({
useRuns<DashboardRun>({
pageSize: 6,
})

const [selectedRun, setSelectedRun] = useState<Run | null>(null)
const [selectedRun, setSelectedRun] = useState<DashboardRun | null>(null)
const [steps, setSteps] = useState<
{ index: number; name: string; status: string }[]
>([])

const showDetails = async (runId: string) => {
if (!durably) return
const run = await durably.getRun(runId)
const run = await durably.getRun<DashboardRun>(runId)
if (run) {
setSelectedRun(run)
const stepsData = await durably.storage.getSteps(runId)
Expand Down
13 changes: 11 additions & 2 deletions examples/browser-vite-react/src/jobs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,14 @@
* When adding a new job, import and add it here.
*/

export { dataSyncJob } from './data-sync'
export { processImageJob } from './process-image'
import type { JobInput, JobOutput } from '@coji/durably'
import { dataSyncJob } from './data-sync'
import { processImageJob } from './process-image'

export { dataSyncJob, processImageJob }

/** Input/Output types for all jobs - used for typed useRuns dashboard */
export type DataSyncInput = JobInput<typeof dataSyncJob>
export type DataSyncOutput = JobOutput<typeof dataSyncJob>
export type ProcessImageInput = JobInput<typeof processImageJob>
export type ProcessImageOutput = JobOutput<typeof processImageJob>
19 changes: 16 additions & 3 deletions examples/fullstack-react-router/app/jobs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@
* When adding a new job, import and add it here.
*/

export { dataSyncJob } from './data-sync'
export { importCsvJob, type ImportCsvOutput } from './import-csv'
export { processImageJob } from './process-image'
import type { JobInput, JobOutput } from '@coji/durably'
import { dataSyncJob } from './data-sync'
import { importCsvJob } from './import-csv'
import { processImageJob } from './process-image'

export { dataSyncJob, importCsvJob, processImageJob }

/** Type for ImportCsvOutput (for backward compatibility) */
export type ImportCsvOutput = JobOutput<typeof importCsvJob>

/** Input/Output types for all jobs - used for typed useRuns dashboard */
export type DataSyncInput = JobInput<typeof dataSyncJob>
export type DataSyncOutput = JobOutput<typeof dataSyncJob>
export type ImportCsvInput = JobInput<typeof importCsvJob>
export type ProcessImageInput = JobInput<typeof processImageJob>
export type ProcessImageOutput = JobOutput<typeof processImageJob>
24 changes: 22 additions & 2 deletions examples/fullstack-react-router/app/routes/_index/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,35 @@
*
* Displays run history with real-time updates via SSE and pagination.
* First page auto-subscribes to SSE for instant updates.
*
* Demonstrates typed useRuns with generic type parameter for multi-job dashboards.
*/

import type { RunRecord, StepRecord } from '@coji/durably-react/client'
import { useRunActions, useRuns } from '@coji/durably-react/client'
import {
type TypedClientRun,
useRunActions,
useRuns,
} from '@coji/durably-react/client'
import { useState } from 'react'
import type {
DataSyncInput,
DataSyncOutput,
ImportCsvInput,
ImportCsvOutput,
ProcessImageInput,
ProcessImageOutput,
} from '~/jobs'

/** Union type for all job runs in this dashboard */
type DashboardRun =
| TypedClientRun<DataSyncInput, DataSyncOutput>
| TypedClientRun<ImportCsvInput, ImportCsvOutput>
| TypedClientRun<ProcessImageInput, ProcessImageOutput>

export function Dashboard() {
const { runs, isLoading, error, page, hasMore, nextPage, prevPage, refresh } =
useRuns({
useRuns<DashboardRun>({
api: '/api/durably',
pageSize: 6,
})
Expand Down
Loading