@coji/durably-react 0.6.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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/client.d.ts +542 -0
- package/dist/client.js +571 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +284 -0
- package/dist/index.js +464 -0
- package/dist/index.js.map +1 -0
- package/dist/types-BDUvsa8u.d.ts +67 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 coji
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @coji/durably-react
|
|
2
|
+
|
|
3
|
+
React bindings for [Durably](https://github.com/coji/durably) - step-oriented resumable batch execution.
|
|
4
|
+
|
|
5
|
+
**[Documentation](https://coji.github.io/durably/)** | **[GitHub](https://github.com/coji/durably)**
|
|
6
|
+
|
|
7
|
+
> **Note:** This package is ESM-only. CommonJS is not supported.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Browser mode (with SQLocal)
|
|
13
|
+
npm install @coji/durably-react @coji/durably kysely zod sqlocal
|
|
14
|
+
|
|
15
|
+
# Server-connected mode (client only)
|
|
16
|
+
npm install @coji/durably-react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Suspense } from 'react'
|
|
23
|
+
import { createDurably, defineJob } from '@coji/durably'
|
|
24
|
+
import { DurablyProvider, useJob } from '@coji/durably-react'
|
|
25
|
+
import { SQLocalKysely } from 'sqlocal/kysely'
|
|
26
|
+
import { z } from 'zod'
|
|
27
|
+
|
|
28
|
+
const myJob = defineJob({
|
|
29
|
+
name: 'my-job',
|
|
30
|
+
input: z.object({ id: z.string() }),
|
|
31
|
+
run: async (step, payload) => {
|
|
32
|
+
await step.run('step-1', async () => {
|
|
33
|
+
/* ... */
|
|
34
|
+
})
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// Initialize Durably
|
|
39
|
+
async function initDurably() {
|
|
40
|
+
const sqlocal = new SQLocalKysely('app.sqlite3')
|
|
41
|
+
const durably = createDurably({ dialect: sqlocal.dialect }).register({
|
|
42
|
+
myJob,
|
|
43
|
+
})
|
|
44
|
+
await durably.init() // migrate + start
|
|
45
|
+
return durably
|
|
46
|
+
}
|
|
47
|
+
const durablyPromise = initDurably()
|
|
48
|
+
|
|
49
|
+
function App() {
|
|
50
|
+
return (
|
|
51
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
52
|
+
<DurablyProvider durably={durablyPromise}>
|
|
53
|
+
<MyComponent />
|
|
54
|
+
</DurablyProvider>
|
|
55
|
+
</Suspense>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function MyComponent() {
|
|
60
|
+
const { trigger, isRunning, isCompleted } = useJob(myJob)
|
|
61
|
+
return (
|
|
62
|
+
<button onClick={() => trigger({ id: '123' })} disabled={isRunning}>
|
|
63
|
+
Run
|
|
64
|
+
</button>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Server-Connected Mode
|
|
70
|
+
|
|
71
|
+
For full-stack apps, use hooks from `@coji/durably-react/client`:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { useJob } from '@coji/durably-react/client'
|
|
75
|
+
|
|
76
|
+
function MyComponent() {
|
|
77
|
+
const { trigger, status, output, isRunning } = useJob<
|
|
78
|
+
{ id: string },
|
|
79
|
+
{ result: number }
|
|
80
|
+
>({
|
|
81
|
+
api: '/api/durably',
|
|
82
|
+
jobName: 'my-job',
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<button onClick={() => trigger({ id: '123' })} disabled={isRunning}>
|
|
87
|
+
Run
|
|
88
|
+
</button>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
For full documentation, visit [coji.github.io/durably](https://coji.github.io/durably/).
|
|
96
|
+
|
|
97
|
+
- [React Guide](https://coji.github.io/durably/guide/react) - Browser mode with hooks
|
|
98
|
+
- [Full-Stack Guide](https://coji.github.io/durably/guide/full-stack) - Server-connected mode
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { JobDefinition } from '@coji/durably';
|
|
2
|
+
import { R as RunStatus, L as LogEntry, P as Progress } from './types-BDUvsa8u.js';
|
|
3
|
+
|
|
4
|
+
interface UseJobClientOptions {
|
|
5
|
+
/**
|
|
6
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
7
|
+
*/
|
|
8
|
+
api: string;
|
|
9
|
+
/**
|
|
10
|
+
* Job name to trigger
|
|
11
|
+
*/
|
|
12
|
+
jobName: string;
|
|
13
|
+
/**
|
|
14
|
+
* Initial Run ID to subscribe to (for reconnection scenarios)
|
|
15
|
+
* When provided, the hook will immediately start subscribing to this run
|
|
16
|
+
*/
|
|
17
|
+
initialRunId?: string;
|
|
18
|
+
}
|
|
19
|
+
interface UseJobClientResult<TInput, TOutput> {
|
|
20
|
+
/**
|
|
21
|
+
* Whether the hook is ready (always true for client mode)
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Trigger the job with the given input
|
|
25
|
+
*/
|
|
26
|
+
trigger: (input: TInput) => Promise<{
|
|
27
|
+
runId: string;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Trigger and wait for completion
|
|
31
|
+
*/
|
|
32
|
+
triggerAndWait: (input: TInput) => Promise<{
|
|
33
|
+
runId: string;
|
|
34
|
+
output: TOutput;
|
|
35
|
+
}>;
|
|
36
|
+
/**
|
|
37
|
+
* Current run status
|
|
38
|
+
*/
|
|
39
|
+
status: RunStatus | null;
|
|
40
|
+
/**
|
|
41
|
+
* Output from completed run
|
|
42
|
+
*/
|
|
43
|
+
output: TOutput | null;
|
|
44
|
+
/**
|
|
45
|
+
* Error message from failed run
|
|
46
|
+
*/
|
|
47
|
+
error: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* Logs collected during execution
|
|
50
|
+
*/
|
|
51
|
+
logs: LogEntry[];
|
|
52
|
+
/**
|
|
53
|
+
* Current progress
|
|
54
|
+
*/
|
|
55
|
+
progress: Progress | null;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a run is currently running
|
|
58
|
+
*/
|
|
59
|
+
isRunning: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Whether a run is pending
|
|
62
|
+
*/
|
|
63
|
+
isPending: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Whether the run completed successfully
|
|
66
|
+
*/
|
|
67
|
+
isCompleted: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether the run failed
|
|
70
|
+
*/
|
|
71
|
+
isFailed: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Current run ID
|
|
74
|
+
*/
|
|
75
|
+
currentRunId: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* Reset all state
|
|
78
|
+
*/
|
|
79
|
+
reset: () => void;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Hook for triggering and subscribing to jobs via server API.
|
|
83
|
+
* Uses fetch for triggering and EventSource for SSE subscription.
|
|
84
|
+
*/
|
|
85
|
+
declare function useJob<TInput extends Record<string, unknown> = Record<string, unknown>, TOutput extends Record<string, unknown> = Record<string, unknown>>(options: UseJobClientOptions): UseJobClientResult<TInput, TOutput>;
|
|
86
|
+
|
|
87
|
+
interface UseJobLogsClientOptions {
|
|
88
|
+
/**
|
|
89
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
90
|
+
*/
|
|
91
|
+
api: string;
|
|
92
|
+
/**
|
|
93
|
+
* The run ID to subscribe to logs for
|
|
94
|
+
*/
|
|
95
|
+
runId: string | null;
|
|
96
|
+
/**
|
|
97
|
+
* Maximum number of logs to keep (default: unlimited)
|
|
98
|
+
*/
|
|
99
|
+
maxLogs?: number;
|
|
100
|
+
}
|
|
101
|
+
interface UseJobLogsClientResult {
|
|
102
|
+
/**
|
|
103
|
+
* Whether the hook is ready (always true for client mode)
|
|
104
|
+
*/
|
|
105
|
+
/**
|
|
106
|
+
* Logs collected during execution
|
|
107
|
+
*/
|
|
108
|
+
logs: LogEntry[];
|
|
109
|
+
/**
|
|
110
|
+
* Clear all logs
|
|
111
|
+
*/
|
|
112
|
+
clearLogs: () => void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Hook for subscribing to logs from a run via server API.
|
|
116
|
+
* Uses EventSource for SSE subscription.
|
|
117
|
+
*/
|
|
118
|
+
declare function useJobLogs(options: UseJobLogsClientOptions): UseJobLogsClientResult;
|
|
119
|
+
|
|
120
|
+
interface UseJobRunClientOptions {
|
|
121
|
+
/**
|
|
122
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
123
|
+
*/
|
|
124
|
+
api: string;
|
|
125
|
+
/**
|
|
126
|
+
* The run ID to subscribe to
|
|
127
|
+
*/
|
|
128
|
+
runId: string | null;
|
|
129
|
+
/**
|
|
130
|
+
* Callback when run starts (transitions to pending/running)
|
|
131
|
+
*/
|
|
132
|
+
onStart?: () => void;
|
|
133
|
+
/**
|
|
134
|
+
* Callback when run completes successfully
|
|
135
|
+
*/
|
|
136
|
+
onComplete?: () => void;
|
|
137
|
+
/**
|
|
138
|
+
* Callback when run fails
|
|
139
|
+
*/
|
|
140
|
+
onFail?: () => void;
|
|
141
|
+
}
|
|
142
|
+
interface UseJobRunClientResult<TOutput = unknown> {
|
|
143
|
+
/**
|
|
144
|
+
* Whether the hook is ready (always true for client mode)
|
|
145
|
+
*/
|
|
146
|
+
/**
|
|
147
|
+
* Current run status
|
|
148
|
+
*/
|
|
149
|
+
status: RunStatus | null;
|
|
150
|
+
/**
|
|
151
|
+
* Output from completed run
|
|
152
|
+
*/
|
|
153
|
+
output: TOutput | null;
|
|
154
|
+
/**
|
|
155
|
+
* Error message from failed run
|
|
156
|
+
*/
|
|
157
|
+
error: string | null;
|
|
158
|
+
/**
|
|
159
|
+
* Logs collected during execution
|
|
160
|
+
*/
|
|
161
|
+
logs: LogEntry[];
|
|
162
|
+
/**
|
|
163
|
+
* Current progress
|
|
164
|
+
*/
|
|
165
|
+
progress: Progress | null;
|
|
166
|
+
/**
|
|
167
|
+
* Whether a run is currently running
|
|
168
|
+
*/
|
|
169
|
+
isRunning: boolean;
|
|
170
|
+
/**
|
|
171
|
+
* Whether a run is pending
|
|
172
|
+
*/
|
|
173
|
+
isPending: boolean;
|
|
174
|
+
/**
|
|
175
|
+
* Whether the run completed successfully
|
|
176
|
+
*/
|
|
177
|
+
isCompleted: boolean;
|
|
178
|
+
/**
|
|
179
|
+
* Whether the run failed
|
|
180
|
+
*/
|
|
181
|
+
isFailed: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Whether the run was cancelled
|
|
184
|
+
*/
|
|
185
|
+
isCancelled: boolean;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Hook for subscribing to an existing run via server API.
|
|
189
|
+
* Uses EventSource for SSE subscription.
|
|
190
|
+
*/
|
|
191
|
+
declare function useJobRun<TOutput = unknown>(options: UseJobRunClientOptions): UseJobRunClientResult<TOutput>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Extract input type from a JobDefinition or JobHandle
|
|
195
|
+
*/
|
|
196
|
+
type InferInput$1<T> = T extends JobDefinition<string, infer TInput, unknown> ? TInput extends Record<string, unknown> ? TInput : Record<string, unknown> : T extends {
|
|
197
|
+
trigger: (input: infer TInput) => unknown;
|
|
198
|
+
} ? TInput extends Record<string, unknown> ? TInput : Record<string, unknown> : Record<string, unknown>;
|
|
199
|
+
/**
|
|
200
|
+
* Extract output type from a JobDefinition or JobHandle
|
|
201
|
+
*/
|
|
202
|
+
type InferOutput$1<T> = T extends JobDefinition<string, unknown, infer TOutput> ? TOutput extends Record<string, unknown> ? TOutput : Record<string, unknown> : T extends {
|
|
203
|
+
trigger: (input: unknown) => Promise<{
|
|
204
|
+
output?: infer TOutput;
|
|
205
|
+
}>;
|
|
206
|
+
} ? TOutput extends Record<string, unknown> ? TOutput : Record<string, unknown> : Record<string, unknown>;
|
|
207
|
+
/**
|
|
208
|
+
* Type-safe hooks for a specific job
|
|
209
|
+
*/
|
|
210
|
+
interface JobClient<TInput, TOutput> {
|
|
211
|
+
/**
|
|
212
|
+
* Hook for triggering and monitoring the job
|
|
213
|
+
*/
|
|
214
|
+
useJob: () => UseJobClientResult<TInput, TOutput>;
|
|
215
|
+
/**
|
|
216
|
+
* Hook for subscribing to an existing run by ID
|
|
217
|
+
*/
|
|
218
|
+
useRun: (runId: string | null) => UseJobRunClientResult<TOutput>;
|
|
219
|
+
/**
|
|
220
|
+
* Hook for subscribing to logs from a run
|
|
221
|
+
*/
|
|
222
|
+
useLogs: (runId: string | null, options?: {
|
|
223
|
+
maxLogs?: number;
|
|
224
|
+
}) => UseJobLogsClientResult;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Options for createDurablyClient
|
|
228
|
+
*/
|
|
229
|
+
interface CreateDurablyClientOptions {
|
|
230
|
+
/**
|
|
231
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
232
|
+
*/
|
|
233
|
+
api: string;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* A type-safe client with hooks for each registered job
|
|
237
|
+
*/
|
|
238
|
+
type DurablyClient<TJobs extends Record<string, unknown>> = {
|
|
239
|
+
[K in keyof TJobs]: JobClient<InferInput$1<TJobs[K]>, InferOutput$1<TJobs[K]>>;
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Create a type-safe Durably client with hooks for all registered jobs.
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* ```tsx
|
|
246
|
+
* // Server: register jobs
|
|
247
|
+
* // app/lib/durably.server.ts
|
|
248
|
+
* export const jobs = durably.register({
|
|
249
|
+
* importCsv: importCsvJob,
|
|
250
|
+
* syncUsers: syncUsersJob,
|
|
251
|
+
* })
|
|
252
|
+
*
|
|
253
|
+
* // Client: create typed client
|
|
254
|
+
* // app/lib/durably.client.ts
|
|
255
|
+
* import type { jobs } from '~/lib/durably.server'
|
|
256
|
+
* import { createDurablyClient } from '@coji/durably-react/client'
|
|
257
|
+
*
|
|
258
|
+
* export const durably = createDurablyClient<typeof jobs>({
|
|
259
|
+
* api: '/api/durably',
|
|
260
|
+
* })
|
|
261
|
+
*
|
|
262
|
+
* // In your component - fully type-safe with autocomplete
|
|
263
|
+
* function CsvImporter() {
|
|
264
|
+
* const { trigger, output, isRunning } = durably.importCsv.useJob()
|
|
265
|
+
*
|
|
266
|
+
* return (
|
|
267
|
+
* <button onClick={() => trigger({ rows: [...] })}>
|
|
268
|
+
* Import
|
|
269
|
+
* </button>
|
|
270
|
+
* )
|
|
271
|
+
* }
|
|
272
|
+
* ```
|
|
273
|
+
*/
|
|
274
|
+
declare function createDurablyClient<TJobs extends Record<string, unknown>>(options: CreateDurablyClientOptions): DurablyClient<TJobs>;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Extract input type from a JobDefinition
|
|
278
|
+
*/
|
|
279
|
+
type InferInput<T> = T extends JobDefinition<string, infer TInput, unknown> ? TInput extends Record<string, unknown> ? TInput : Record<string, unknown> : Record<string, unknown>;
|
|
280
|
+
/**
|
|
281
|
+
* Extract output type from a JobDefinition
|
|
282
|
+
*/
|
|
283
|
+
type InferOutput<T> = T extends JobDefinition<string, unknown, infer TOutput> ? TOutput extends Record<string, unknown> ? TOutput : Record<string, unknown> : Record<string, unknown>;
|
|
284
|
+
/**
|
|
285
|
+
* Options for createJobHooks
|
|
286
|
+
*/
|
|
287
|
+
interface CreateJobHooksOptions {
|
|
288
|
+
/**
|
|
289
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
290
|
+
*/
|
|
291
|
+
api: string;
|
|
292
|
+
/**
|
|
293
|
+
* Job name (must match the server-side job name)
|
|
294
|
+
*/
|
|
295
|
+
jobName: string;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Type-safe hooks for a specific job
|
|
299
|
+
*/
|
|
300
|
+
interface JobHooks<TInput, TOutput> {
|
|
301
|
+
/**
|
|
302
|
+
* Hook for triggering and monitoring the job
|
|
303
|
+
*/
|
|
304
|
+
useJob: () => UseJobClientResult<TInput, TOutput>;
|
|
305
|
+
/**
|
|
306
|
+
* Hook for subscribing to an existing run by ID
|
|
307
|
+
*/
|
|
308
|
+
useRun: (runId: string | null) => UseJobRunClientResult<TOutput>;
|
|
309
|
+
/**
|
|
310
|
+
* Hook for subscribing to logs from a run
|
|
311
|
+
*/
|
|
312
|
+
useLogs: (runId: string | null, options?: {
|
|
313
|
+
maxLogs?: number;
|
|
314
|
+
}) => UseJobLogsClientResult;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Create type-safe hooks for a specific job.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```tsx
|
|
321
|
+
* // Import job type from server (type-only import is safe)
|
|
322
|
+
* import type { importCsvJob } from '~/lib/durably.server'
|
|
323
|
+
* import { createJobHooks } from '@coji/durably-react/client'
|
|
324
|
+
*
|
|
325
|
+
* const importCsv = createJobHooks<typeof importCsvJob>({
|
|
326
|
+
* api: '/api/durably',
|
|
327
|
+
* jobName: 'import-csv',
|
|
328
|
+
* })
|
|
329
|
+
*
|
|
330
|
+
* // In your component - fully type-safe
|
|
331
|
+
* function CsvImporter() {
|
|
332
|
+
* const { trigger, output, progress, isRunning } = importCsv.useJob()
|
|
333
|
+
*
|
|
334
|
+
* return (
|
|
335
|
+
* <button onClick={() => trigger({ rows: [...] })}>
|
|
336
|
+
* Import
|
|
337
|
+
* </button>
|
|
338
|
+
* )
|
|
339
|
+
* }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
declare function createJobHooks<TJob extends JobDefinition<string, any, any>>(options: CreateJobHooksOptions): JobHooks<InferInput<TJob>, InferOutput<TJob>>;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Run type for client mode (matches server response)
|
|
346
|
+
*/
|
|
347
|
+
interface ClientRun {
|
|
348
|
+
id: string;
|
|
349
|
+
jobName: string;
|
|
350
|
+
status: RunStatus;
|
|
351
|
+
input: unknown;
|
|
352
|
+
output: unknown | null;
|
|
353
|
+
error: string | null;
|
|
354
|
+
currentStepIndex: number;
|
|
355
|
+
stepCount: number;
|
|
356
|
+
progress: Progress | null;
|
|
357
|
+
createdAt: string;
|
|
358
|
+
startedAt: string | null;
|
|
359
|
+
completedAt: string | null;
|
|
360
|
+
}
|
|
361
|
+
interface UseRunsClientOptions {
|
|
362
|
+
/**
|
|
363
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
364
|
+
*/
|
|
365
|
+
api: string;
|
|
366
|
+
/**
|
|
367
|
+
* Filter by job name
|
|
368
|
+
*/
|
|
369
|
+
jobName?: string;
|
|
370
|
+
/**
|
|
371
|
+
* Filter by status
|
|
372
|
+
*/
|
|
373
|
+
status?: RunStatus;
|
|
374
|
+
/**
|
|
375
|
+
* Number of runs per page
|
|
376
|
+
* @default 10
|
|
377
|
+
*/
|
|
378
|
+
pageSize?: number;
|
|
379
|
+
}
|
|
380
|
+
interface UseRunsClientResult {
|
|
381
|
+
/**
|
|
382
|
+
* List of runs for the current page
|
|
383
|
+
*/
|
|
384
|
+
runs: ClientRun[];
|
|
385
|
+
/**
|
|
386
|
+
* Current page (0-indexed)
|
|
387
|
+
*/
|
|
388
|
+
page: number;
|
|
389
|
+
/**
|
|
390
|
+
* Whether there are more pages
|
|
391
|
+
*/
|
|
392
|
+
hasMore: boolean;
|
|
393
|
+
/**
|
|
394
|
+
* Whether data is being loaded
|
|
395
|
+
*/
|
|
396
|
+
isLoading: boolean;
|
|
397
|
+
/**
|
|
398
|
+
* Error message if fetch failed
|
|
399
|
+
*/
|
|
400
|
+
error: string | null;
|
|
401
|
+
/**
|
|
402
|
+
* Go to the next page
|
|
403
|
+
*/
|
|
404
|
+
nextPage: () => void;
|
|
405
|
+
/**
|
|
406
|
+
* Go to the previous page
|
|
407
|
+
*/
|
|
408
|
+
prevPage: () => void;
|
|
409
|
+
/**
|
|
410
|
+
* Go to a specific page
|
|
411
|
+
*/
|
|
412
|
+
goToPage: (page: number) => void;
|
|
413
|
+
/**
|
|
414
|
+
* Refresh the current page
|
|
415
|
+
*/
|
|
416
|
+
refresh: () => Promise<void>;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Hook for listing runs via server API with pagination.
|
|
420
|
+
* First page (page 0) automatically subscribes to SSE for real-time updates.
|
|
421
|
+
* Other pages are static and require manual refresh.
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* ```tsx
|
|
425
|
+
* function RunHistory() {
|
|
426
|
+
* const { runs, page, hasMore, nextPage, prevPage, refresh } = useRuns({
|
|
427
|
+
* api: '/api/durably',
|
|
428
|
+
* jobName: 'import-csv',
|
|
429
|
+
* pageSize: 10,
|
|
430
|
+
* })
|
|
431
|
+
*
|
|
432
|
+
* return (
|
|
433
|
+
* <div>
|
|
434
|
+
* {runs.map(run => (
|
|
435
|
+
* <div key={run.id}>{run.jobName}: {run.status}</div>
|
|
436
|
+
* ))}
|
|
437
|
+
* <button onClick={prevPage} disabled={page === 0}>Prev</button>
|
|
438
|
+
* <button onClick={nextPage} disabled={!hasMore}>Next</button>
|
|
439
|
+
* <button onClick={refresh}>Refresh</button>
|
|
440
|
+
* </div>
|
|
441
|
+
* )
|
|
442
|
+
* }
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
declare function useRuns(options: UseRunsClientOptions): UseRunsClientResult;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Run record returned from the server API
|
|
449
|
+
*/
|
|
450
|
+
interface RunRecord {
|
|
451
|
+
id: string;
|
|
452
|
+
jobName: string;
|
|
453
|
+
status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
454
|
+
payload: unknown;
|
|
455
|
+
output: unknown | null;
|
|
456
|
+
error: string | null;
|
|
457
|
+
progress: {
|
|
458
|
+
current: number;
|
|
459
|
+
total?: number;
|
|
460
|
+
message?: string;
|
|
461
|
+
} | null;
|
|
462
|
+
currentStepIndex: number;
|
|
463
|
+
stepCount: number;
|
|
464
|
+
createdAt: string;
|
|
465
|
+
startedAt: string | null;
|
|
466
|
+
completedAt: string | null;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Step record returned from the server API
|
|
470
|
+
*/
|
|
471
|
+
interface StepRecord {
|
|
472
|
+
name: string;
|
|
473
|
+
status: 'completed' | 'failed';
|
|
474
|
+
output: unknown;
|
|
475
|
+
}
|
|
476
|
+
interface UseRunActionsClientOptions {
|
|
477
|
+
/**
|
|
478
|
+
* API endpoint URL (e.g., '/api/durably')
|
|
479
|
+
*/
|
|
480
|
+
api: string;
|
|
481
|
+
}
|
|
482
|
+
interface UseRunActionsClientResult {
|
|
483
|
+
/**
|
|
484
|
+
* Retry a failed or cancelled run
|
|
485
|
+
*/
|
|
486
|
+
retry: (runId: string) => Promise<void>;
|
|
487
|
+
/**
|
|
488
|
+
* Cancel a pending or running run
|
|
489
|
+
*/
|
|
490
|
+
cancel: (runId: string) => Promise<void>;
|
|
491
|
+
/**
|
|
492
|
+
* Delete a run (only completed, failed, or cancelled runs)
|
|
493
|
+
*/
|
|
494
|
+
deleteRun: (runId: string) => Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Get a single run by ID
|
|
497
|
+
*/
|
|
498
|
+
getRun: (runId: string) => Promise<RunRecord | null>;
|
|
499
|
+
/**
|
|
500
|
+
* Get steps for a run
|
|
501
|
+
*/
|
|
502
|
+
getSteps: (runId: string) => Promise<StepRecord[]>;
|
|
503
|
+
/**
|
|
504
|
+
* Whether an action is in progress
|
|
505
|
+
*/
|
|
506
|
+
isLoading: boolean;
|
|
507
|
+
/**
|
|
508
|
+
* Error message from last action
|
|
509
|
+
*/
|
|
510
|
+
error: string | null;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Hook for run actions (retry, cancel) via server API.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* ```tsx
|
|
517
|
+
* function RunActions({ runId, status }: { runId: string; status: string }) {
|
|
518
|
+
* const { retry, cancel, isLoading, error } = useRunActions({
|
|
519
|
+
* api: '/api/durably',
|
|
520
|
+
* })
|
|
521
|
+
*
|
|
522
|
+
* return (
|
|
523
|
+
* <div>
|
|
524
|
+
* {status === 'failed' && (
|
|
525
|
+
* <button onClick={() => retry(runId)} disabled={isLoading}>
|
|
526
|
+
* Retry
|
|
527
|
+
* </button>
|
|
528
|
+
* )}
|
|
529
|
+
* {(status === 'pending' || status === 'running') && (
|
|
530
|
+
* <button onClick={() => cancel(runId)} disabled={isLoading}>
|
|
531
|
+
* Cancel
|
|
532
|
+
* </button>
|
|
533
|
+
* )}
|
|
534
|
+
* {error && <span className="error">{error}</span>}
|
|
535
|
+
* </div>
|
|
536
|
+
* )
|
|
537
|
+
* }
|
|
538
|
+
* ```
|
|
539
|
+
*/
|
|
540
|
+
declare function useRunActions(options: UseRunActionsClientOptions): UseRunActionsClientResult;
|
|
541
|
+
|
|
542
|
+
export { type ClientRun, type CreateDurablyClientOptions, type CreateJobHooksOptions, type DurablyClient, type JobClient, type JobHooks, LogEntry, Progress, type RunRecord, RunStatus, type StepRecord, 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 };
|