@duraton/react 0.1.0-beta.7

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/dist/index.js ADDED
@@ -0,0 +1,477 @@
1
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { keepPreviousData, skipToken, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
+ import { ATTEMPT_OUTCOMES, CONTROL_ACTION_KINDS, DELIVERY_EVENT_KINDS, DELIVERY_STATUSES, RUN_STATUSES, SIGNING_SCHEMES, STEP_STATUSES, SUBSCRIBABLE_EVENT_KINDS, createClient } from "@duraton/sdk/client";
5
+ //#region src/provider.tsx
6
+ const ClientContext = createContext(null);
7
+ function DuratonProvider({ client, children }) {
8
+ return /* @__PURE__ */ jsx(ClientContext.Provider, {
9
+ value: client,
10
+ children
11
+ });
12
+ }
13
+ function useDuratonClient() {
14
+ const client = useContext(ClientContext);
15
+ if (!client) throw new Error("useDuratonClient must be used within a DuratonProvider");
16
+ return client;
17
+ }
18
+ //#endregion
19
+ //#region src/run-status.ts
20
+ const ACTIVE_STATUSES = /* @__PURE__ */ new Set([
21
+ "queued",
22
+ "running",
23
+ "waiting"
24
+ ]);
25
+ const TERMINAL_STATUSES = /* @__PURE__ */ new Set([
26
+ "succeeded",
27
+ "failed",
28
+ "cancelled"
29
+ ]);
30
+ //#endregion
31
+ //#region src/hooks/use-runs.ts
32
+ const FALLBACK_POLL_MS$3 = 15e3;
33
+ function useRuns(options = {}) {
34
+ const client = useDuratonClient();
35
+ return useQuery({
36
+ queryKey: ["runs", options],
37
+ queryFn: () => client.runs.list(options),
38
+ refetchInterval: FALLBACK_POLL_MS$3,
39
+ placeholderData: keepPreviousData
40
+ });
41
+ }
42
+ //#endregion
43
+ //#region src/hooks/use-run-stats.ts
44
+ const FALLBACK_POLL_MS$2 = 15e3;
45
+ function useRunStats(options = {}, opts) {
46
+ const client = useDuratonClient();
47
+ return useQuery({
48
+ queryKey: ["run-stats", options],
49
+ queryFn: () => client.runs.stats(options),
50
+ refetchInterval: opts?.poll === false ? false : FALLBACK_POLL_MS$2
51
+ });
52
+ }
53
+ //#endregion
54
+ //#region src/lib/stream.ts
55
+ const RECONNECT_MS = 1e3;
56
+ function abortableDelay(ms, signal) {
57
+ return new Promise((resolve) => {
58
+ const timer = setTimeout(resolve, ms);
59
+ signal.addEventListener("abort", () => {
60
+ clearTimeout(timer);
61
+ resolve();
62
+ }, { once: true });
63
+ });
64
+ }
65
+ //#endregion
66
+ //#region src/hooks/use-runs-stream.ts
67
+ const REFETCH_MS$1 = 300;
68
+ function useRunsStream() {
69
+ const client = useDuratonClient();
70
+ const qc = useQueryClient();
71
+ useEffect(() => {
72
+ const ctrl = new AbortController();
73
+ tailNamespace(client, qc, ctrl.signal);
74
+ return () => ctrl.abort();
75
+ }, [client, qc]);
76
+ }
77
+ async function tailNamespace(client, qc, signal) {
78
+ let timer;
79
+ const scheduleRefetch = () => {
80
+ if (timer) return;
81
+ timer = setTimeout(() => {
82
+ timer = void 0;
83
+ qc.invalidateQueries({ queryKey: ["runs"] });
84
+ qc.invalidateQueries({ queryKey: ["run-stats"] });
85
+ }, REFETCH_MS$1);
86
+ };
87
+ signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
88
+ while (!signal.aborted) {
89
+ try {
90
+ for await (const frame of client.runs.watchAll({ signal })) if (frame.kind === "run_status") scheduleRefetch();
91
+ } catch {
92
+ if (signal.aborted) return;
93
+ }
94
+ if (signal.aborted) return;
95
+ await abortableDelay(RECONNECT_MS, signal);
96
+ }
97
+ }
98
+ //#endregion
99
+ //#region src/hooks/use-run.ts
100
+ const FALLBACK_POLL_MS$1 = 1e4;
101
+ function useRun(runId) {
102
+ const client = useDuratonClient();
103
+ return useQuery({
104
+ queryKey: ["run", runId],
105
+ queryFn: () => client.runs.get(runId),
106
+ refetchInterval: (query) => {
107
+ const status = query.state.data?.status;
108
+ return status && ACTIVE_STATUSES.has(status) ? FALLBACK_POLL_MS$1 : false;
109
+ }
110
+ });
111
+ }
112
+ function useRunSteps(runId, active) {
113
+ const client = useDuratonClient();
114
+ return useQuery({
115
+ queryKey: ["run-steps", runId],
116
+ queryFn: () => client.runs.steps(runId),
117
+ refetchInterval: active ? FALLBACK_POLL_MS$1 : false
118
+ });
119
+ }
120
+ //#endregion
121
+ //#region src/hooks/use-run-stream.ts
122
+ const REFETCH_MS = 250;
123
+ function useRunStream(runId) {
124
+ const client = useDuratonClient();
125
+ const qc = useQueryClient();
126
+ const [frames, setFrames] = useState([]);
127
+ useEffect(() => {
128
+ const ctrl = new AbortController();
129
+ tailRun(client, qc, runId, ctrl.signal, setFrames);
130
+ return () => ctrl.abort();
131
+ }, [
132
+ client,
133
+ qc,
134
+ runId
135
+ ]);
136
+ return frames;
137
+ }
138
+ async function tailRun(client, qc, runId, signal, setFrames) {
139
+ let from = 0;
140
+ let refetchTimer;
141
+ signal.addEventListener("abort", () => clearTimeout(refetchTimer), { once: true });
142
+ const refetch = () => {
143
+ qc.invalidateQueries({ queryKey: ["run", runId] });
144
+ qc.invalidateQueries({ queryKey: ["run-steps", runId] });
145
+ };
146
+ const scheduleRefetch = () => {
147
+ if (refetchTimer) return;
148
+ refetchTimer = setTimeout(() => {
149
+ refetchTimer = void 0;
150
+ refetch();
151
+ }, REFETCH_MS);
152
+ };
153
+ while (!signal.aborted) {
154
+ let terminal = false;
155
+ try {
156
+ for await (const frame of client.runs.watch(runId, {
157
+ from,
158
+ signal
159
+ })) {
160
+ from = frame.seq;
161
+ setFrames((prev) => prev.some((f) => f.seq === frame.seq) ? prev : [...prev, frame]);
162
+ if (frame.kind === "log") continue;
163
+ scheduleRefetch();
164
+ if (frame.kind === "run_status" && TERMINAL_STATUSES.has(frame.status)) terminal = true;
165
+ }
166
+ } catch {
167
+ if (signal.aborted) return;
168
+ }
169
+ if (terminal) {
170
+ clearTimeout(refetchTimer);
171
+ refetch();
172
+ return;
173
+ }
174
+ if (signal.aborted) return;
175
+ await abortableDelay(RECONNECT_MS, signal);
176
+ }
177
+ }
178
+ //#endregion
179
+ //#region src/hooks/use-workflows.ts
180
+ const POLL_MS$3 = 5e3;
181
+ function useWorkflows() {
182
+ const client = useDuratonClient();
183
+ return useQuery({
184
+ queryKey: ["workflows"],
185
+ queryFn: () => client.workflows.list(),
186
+ refetchInterval: POLL_MS$3
187
+ });
188
+ }
189
+ //#endregion
190
+ //#region src/hooks/use-run-controls.ts
191
+ function invalidateRuns(qc) {
192
+ qc.invalidateQueries({ queryKey: ["runs"] });
193
+ qc.invalidateQueries({ queryKey: ["control-actions"] });
194
+ }
195
+ function useRunStatusMutation(mutationFn) {
196
+ const qc = useQueryClient();
197
+ return useMutation({
198
+ mutationFn,
199
+ onSuccess: (run) => {
200
+ qc.setQueryData(["run", run.id], run);
201
+ qc.invalidateQueries({ queryKey: ["run-steps", run.id] });
202
+ invalidateRuns(qc);
203
+ }
204
+ });
205
+ }
206
+ function useCancelRun() {
207
+ const client = useDuratonClient();
208
+ return useRunStatusMutation((id) => client.runs.cancel(id));
209
+ }
210
+ function usePauseRun() {
211
+ const client = useDuratonClient();
212
+ return useRunStatusMutation((id) => client.runs.pause(id));
213
+ }
214
+ function useResumeRun() {
215
+ const client = useDuratonClient();
216
+ return useRunStatusMutation((id) => client.runs.resume(id));
217
+ }
218
+ function useForkRun(mutationFn) {
219
+ const qc = useQueryClient();
220
+ return useMutation({
221
+ mutationFn,
222
+ onSuccess: () => invalidateRuns(qc)
223
+ });
224
+ }
225
+ function useReplayRun() {
226
+ const client = useDuratonClient();
227
+ return useForkRun(({ runId, input }) => client.runs.replay(runId, input));
228
+ }
229
+ function useRetryFromStep() {
230
+ const client = useDuratonClient();
231
+ return useForkRun(({ runId, step }) => client.runs.retryFromStep(runId, step));
232
+ }
233
+ function useBulkReplay() {
234
+ const client = useDuratonClient();
235
+ const qc = useQueryClient();
236
+ return useMutation({
237
+ mutationFn: (filter) => client.runs.bulkReplay(filter),
238
+ onSuccess: () => invalidateRuns(qc)
239
+ });
240
+ }
241
+ //#endregion
242
+ //#region src/hooks/use-control-actions.ts
243
+ function useControlActions(opts = {}) {
244
+ const client = useDuratonClient();
245
+ return useQuery({
246
+ queryKey: ["control-actions", opts],
247
+ queryFn: () => client.runs.controlActions(opts)
248
+ });
249
+ }
250
+ //#endregion
251
+ //#region src/hooks/use-run-timeseries.ts
252
+ const FALLBACK_POLL_MS = 15e3;
253
+ function useRunTimeSeries(opts = {}) {
254
+ const client = useDuratonClient();
255
+ return useQuery({
256
+ queryKey: ["run-timeseries", opts],
257
+ queryFn: () => client.runs.timeseries(opts),
258
+ refetchInterval: FALLBACK_POLL_MS
259
+ });
260
+ }
261
+ //#endregion
262
+ //#region src/hooks/use-flow-state.ts
263
+ const POLL_MS$2 = 5e3;
264
+ function useFlowState(opts = {}) {
265
+ const client = useDuratonClient();
266
+ return useQuery({
267
+ queryKey: ["flow-state", opts],
268
+ queryFn: () => client.flowState(opts),
269
+ refetchInterval: POLL_MS$2,
270
+ placeholderData: keepPreviousData
271
+ });
272
+ }
273
+ //#endregion
274
+ //#region src/hooks/use-runners.ts
275
+ const POLL_MS$1 = 15e3;
276
+ function useRunners() {
277
+ const client = useDuratonClient();
278
+ return useQuery({
279
+ queryKey: ["runners"],
280
+ queryFn: () => client.runners.list(),
281
+ refetchInterval: POLL_MS$1,
282
+ placeholderData: keepPreviousData
283
+ });
284
+ }
285
+ //#endregion
286
+ //#region src/hooks/use-apps.ts
287
+ function useApps() {
288
+ const workflows = useWorkflows();
289
+ const runs = useRuns();
290
+ const runners = useRunners();
291
+ return {
292
+ data: useMemo(() => {
293
+ const wfs = workflows.data ?? [];
294
+ const rs = runs.data?.runs ?? [];
295
+ const rns = runners.data ?? [];
296
+ const names = /* @__PURE__ */ new Set();
297
+ wfs.forEach((w) => names.add(w.app));
298
+ rs.forEach((r) => names.add(r.app));
299
+ rns.forEach((r) => names.add(r.app));
300
+ return [...names].sort().map((name) => {
301
+ const appWfs = wfs.filter((w) => w.app === name);
302
+ const appRuns = rs.filter((r) => r.app === name);
303
+ const appRunners = rns.filter((r) => r.app === name);
304
+ const latestStatus = (wfName) => appRuns.filter((r) => r.workflowName === wfName).sort((a, b) => b.startedAt.localeCompare(a.startedAt))[0]?.status;
305
+ const lastSyncAt = appRuns.map((r) => r.startedAt).sort((a, b) => b.localeCompare(a))[0];
306
+ return {
307
+ name,
308
+ status: appWfs.length > 0 || appRunners.some((r) => r.live) ? "connected" : "disconnected",
309
+ workflowCount: appWfs.length,
310
+ runCount: appRuns.length,
311
+ lastSyncAt,
312
+ workflows: appWfs.map((w) => ({
313
+ name: w.name,
314
+ lastStatus: latestStatus(w.name)
315
+ })),
316
+ runners: appRunners
317
+ };
318
+ });
319
+ }, [
320
+ workflows.data,
321
+ runs.data,
322
+ runners.data
323
+ ]),
324
+ isLoading: workflows.isLoading || runs.isLoading
325
+ };
326
+ }
327
+ //#endregion
328
+ //#region src/hooks/use-events.ts
329
+ const MAX_EVENTS = 200;
330
+ function useEvents() {
331
+ const client = useDuratonClient();
332
+ const qc = useQueryClient();
333
+ const query = useQuery({
334
+ queryKey: ["events"],
335
+ queryFn: () => client.events.list()
336
+ });
337
+ useEffect(() => {
338
+ const ctrl = new AbortController();
339
+ tailEvents(client, qc, ctrl.signal);
340
+ return () => ctrl.abort();
341
+ }, [client, qc]);
342
+ return query;
343
+ }
344
+ async function tailEvents(client, qc, signal) {
345
+ while (!signal.aborted) {
346
+ try {
347
+ for await (const ev of client.events.stream({ signal })) qc.setQueryData(["events"], (prev = []) => prev.some((e) => e.id === ev.id) ? prev : [ev, ...prev].slice(0, MAX_EVENTS));
348
+ } catch {
349
+ if (signal.aborted) return;
350
+ }
351
+ if (signal.aborted) return;
352
+ await abortableDelay(RECONNECT_MS, signal);
353
+ }
354
+ }
355
+ function useTriggerEvent() {
356
+ const client = useDuratonClient();
357
+ const qc = useQueryClient();
358
+ return useMutation({
359
+ mutationFn: (input) => client.events.send(input),
360
+ onSuccess: () => {
361
+ qc.invalidateQueries({ queryKey: ["events"] });
362
+ qc.invalidateQueries({ queryKey: ["runs"] });
363
+ }
364
+ });
365
+ }
366
+ //#endregion
367
+ //#region src/hooks/use-deliveries.ts
368
+ const POLL_MS = 5e3;
369
+ function useDeliveries(opts = {}) {
370
+ const client = useDuratonClient();
371
+ return useQuery({
372
+ queryKey: ["deliveries", opts],
373
+ queryFn: () => client.webhooks.deliveries.list(opts),
374
+ refetchInterval: POLL_MS,
375
+ placeholderData: keepPreviousData
376
+ });
377
+ }
378
+ function useDelivery(id) {
379
+ const client = useDuratonClient();
380
+ return useQuery({
381
+ queryKey: ["delivery", id],
382
+ queryFn: id != null ? () => client.webhooks.deliveries.get(id) : skipToken,
383
+ refetchInterval: POLL_MS
384
+ });
385
+ }
386
+ function useEndpoints() {
387
+ const client = useDuratonClient();
388
+ return useQuery({
389
+ queryKey: ["endpoints"],
390
+ queryFn: () => client.webhooks.endpoints.list()
391
+ });
392
+ }
393
+ function useReceivers() {
394
+ const client = useDuratonClient();
395
+ return useQuery({
396
+ queryKey: ["receivers"],
397
+ queryFn: () => client.webhooks.receivers.list()
398
+ });
399
+ }
400
+ function useEndpointStats(since) {
401
+ const client = useDuratonClient();
402
+ return useQuery({
403
+ queryKey: ["endpoint-stats", since ?? null],
404
+ queryFn: () => client.webhooks.endpoints.stats(since),
405
+ refetchInterval: POLL_MS
406
+ });
407
+ }
408
+ //#endregion
409
+ //#region src/hooks/use-webhook-config.ts
410
+ function useEndpointInvalidation() {
411
+ const qc = useQueryClient();
412
+ return () => {
413
+ qc.invalidateQueries({ queryKey: ["endpoints"] });
414
+ qc.invalidateQueries({ queryKey: ["endpoint-stats"] });
415
+ };
416
+ }
417
+ function useCreateEndpoint() {
418
+ const client = useDuratonClient();
419
+ return useMutation({
420
+ mutationFn: (input) => client.webhooks.endpoints.create(input),
421
+ onSuccess: useEndpointInvalidation()
422
+ });
423
+ }
424
+ function useUpdateEndpoint() {
425
+ const client = useDuratonClient();
426
+ return useMutation({
427
+ mutationFn: ({ id, patch }) => client.webhooks.endpoints.update(id, patch),
428
+ onSuccess: useEndpointInvalidation()
429
+ });
430
+ }
431
+ function useDeleteEndpoint() {
432
+ const client = useDuratonClient();
433
+ return useMutation({
434
+ mutationFn: (id) => client.webhooks.endpoints.delete(id),
435
+ onSuccess: useEndpointInvalidation()
436
+ });
437
+ }
438
+ function useReceiverInvalidation() {
439
+ const qc = useQueryClient();
440
+ return () => void qc.invalidateQueries({ queryKey: ["receivers"] });
441
+ }
442
+ function useCreateReceiver() {
443
+ const client = useDuratonClient();
444
+ return useMutation({
445
+ mutationFn: (input) => client.webhooks.receivers.create(input),
446
+ onSuccess: useReceiverInvalidation()
447
+ });
448
+ }
449
+ function useUpdateReceiver() {
450
+ const client = useDuratonClient();
451
+ return useMutation({
452
+ mutationFn: ({ id, patch }) => client.webhooks.receivers.update(id, patch),
453
+ onSuccess: useReceiverInvalidation()
454
+ });
455
+ }
456
+ function useDeleteReceiver() {
457
+ const client = useDuratonClient();
458
+ return useMutation({
459
+ mutationFn: (id) => client.webhooks.receivers.delete(id),
460
+ onSuccess: useReceiverInvalidation()
461
+ });
462
+ }
463
+ function useRedeliverDelivery() {
464
+ const client = useDuratonClient();
465
+ const qc = useQueryClient();
466
+ return useMutation({
467
+ mutationFn: (id) => client.webhooks.deliveries.redeliver(id),
468
+ onSuccess: (_void, id) => {
469
+ qc.invalidateQueries({ queryKey: ["deliveries"] });
470
+ qc.invalidateQueries({ queryKey: ["delivery", id] });
471
+ }
472
+ });
473
+ }
474
+ //#endregion
475
+ export { ACTIVE_STATUSES, ATTEMPT_OUTCOMES, CONTROL_ACTION_KINDS, DELIVERY_EVENT_KINDS, DELIVERY_STATUSES, DuratonProvider, RUN_STATUSES, SIGNING_SCHEMES, STEP_STATUSES, SUBSCRIBABLE_EVENT_KINDS, TERMINAL_STATUSES, createClient, useApps, useBulkReplay, useCancelRun, useControlActions, useCreateEndpoint, useCreateReceiver, useDeleteEndpoint, useDeleteReceiver, useDeliveries, useDelivery, useDuratonClient, useEndpointStats, useEndpoints, useEvents, useFlowState, usePauseRun, useReceivers, useRedeliverDelivery, useReplayRun, useResumeRun, useRetryFromStep, useRun, useRunStats, useRunSteps, useRunStream, useRunTimeSeries, useRunners, useRuns, useRunsStream, useTriggerEvent, useUpdateEndpoint, useUpdateReceiver, useWorkflows };
476
+
477
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["FALLBACK_POLL_MS","FALLBACK_POLL_MS","REFETCH_MS","FALLBACK_POLL_MS","POLL_MS","POLL_MS","POLL_MS"],"sources":["../src/provider.tsx","../src/run-status.ts","../src/hooks/use-runs.ts","../src/hooks/use-run-stats.ts","../src/lib/stream.ts","../src/hooks/use-runs-stream.ts","../src/hooks/use-run.ts","../src/hooks/use-run-stream.ts","../src/hooks/use-workflows.ts","../src/hooks/use-run-controls.ts","../src/hooks/use-control-actions.ts","../src/hooks/use-run-timeseries.ts","../src/hooks/use-flow-state.ts","../src/hooks/use-runners.ts","../src/hooks/use-apps.ts","../src/hooks/use-events.ts","../src/hooks/use-deliveries.ts","../src/hooks/use-webhook-config.ts"],"sourcesContent":["import { createContext, useContext, type ReactNode } from \"react\";\nimport type { DuratonClient } from \"@duraton/sdk/client\";\n\nconst ClientContext = createContext<DuratonClient | null>(null);\n\nexport function DuratonProvider({\n client,\n children,\n}: {\n client: DuratonClient;\n children: ReactNode;\n}) {\n return <ClientContext.Provider value={client}>{children}</ClientContext.Provider>;\n}\n\nexport function useDuratonClient(): DuratonClient {\n const client = useContext(ClientContext);\n if (!client) {\n throw new Error(\"useDuratonClient must be used within a DuratonProvider\");\n }\n return client;\n}\n","import type { RunStatus } from \"@duraton/sdk/client\";\n\nexport const ACTIVE_STATUSES: ReadonlySet<RunStatus> = new Set([\"queued\", \"running\", \"waiting\"]);\nexport const TERMINAL_STATUSES: ReadonlySet<RunStatus> = new Set([\n \"succeeded\",\n \"failed\",\n \"cancelled\",\n]);\n","import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport type { ListRunsOptions, RunsPage } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\n// The namespace stream (useRunsStream) drives refetches; this slow interval is only a\n// safety net so the list still self-heals if the stream drops.\nconst FALLBACK_POLL_MS = 15000;\n\nexport function useRuns(options: ListRunsOptions = {}) {\n const client = useDuratonClient();\n return useQuery<RunsPage>({\n queryKey: [\"runs\", options],\n queryFn: () => client.runs.list(options),\n refetchInterval: FALLBACK_POLL_MS,\n placeholderData: keepPreviousData,\n });\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { RunStats, RunStatsOptions } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nconst FALLBACK_POLL_MS = 15000;\n\nexport function useRunStats(options: RunStatsOptions = {}, opts?: { poll?: boolean }) {\n const client = useDuratonClient();\n return useQuery<RunStats>({\n queryKey: [\"run-stats\", options],\n queryFn: () => client.runs.stats(options),\n refetchInterval: opts?.poll === false ? false : FALLBACK_POLL_MS,\n });\n}\n","// Backoff before reconnecting a dropped (non-terminal) SSE stream.\nexport const RECONNECT_MS = 1000;\n\n// Resolves after ms, or immediately when the signal aborts (clearing its timer\n// either way). Used by the SSE reconnect loops.\nexport function abortableDelay(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n signal.addEventListener(\n \"abort\",\n () => {\n clearTimeout(timer);\n resolve();\n },\n { once: true },\n );\n });\n}\n","import { type QueryClient, useQueryClient } from \"@tanstack/react-query\";\nimport { useEffect } from \"react\";\nimport type { DuratonClient } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\nimport { abortableDelay, RECONNECT_MS } from \"../lib/stream\";\n\n// Coalesce a burst of namespace activity into one list/stats refetch.\nconst REFETCH_MS = 300;\n\n// Tails the namespace-wide run-status stream and refetches the runs list + stats\n// on any activity. Best-effort: a dropped frame self-heals on the next change.\nexport function useRunsStream() {\n const client = useDuratonClient();\n const qc = useQueryClient();\n useEffect(() => {\n const ctrl = new AbortController();\n void tailNamespace(client, qc, ctrl.signal);\n return () => ctrl.abort();\n }, [client, qc]);\n}\n\nasync function tailNamespace(client: DuratonClient, qc: QueryClient, signal: AbortSignal) {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const scheduleRefetch = () => {\n if (timer) return;\n timer = setTimeout(() => {\n timer = undefined;\n void qc.invalidateQueries({ queryKey: [\"runs\"] });\n void qc.invalidateQueries({ queryKey: [\"run-stats\"] });\n }, REFETCH_MS);\n };\n signal.addEventListener(\"abort\", () => clearTimeout(timer), { once: true });\n\n while (!signal.aborted) {\n try {\n for await (const frame of client.runs.watchAll({ signal })) {\n if (frame.kind === \"run_status\") scheduleRefetch();\n }\n } catch {\n if (signal.aborted) return;\n }\n if (signal.aborted) return;\n await abortableDelay(RECONNECT_MS, signal);\n }\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { Run, Step } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\nimport { ACTIVE_STATUSES } from \"../run-status\";\n\nconst FALLBACK_POLL_MS = 10000;\n\nexport function useRun(runId: string) {\n const client = useDuratonClient();\n return useQuery<Run>({\n queryKey: [\"run\", runId],\n queryFn: () => client.runs.get(runId),\n refetchInterval: (query) => {\n const status = query.state.data?.status;\n return status && ACTIVE_STATUSES.has(status) ? FALLBACK_POLL_MS : false;\n },\n });\n}\n\nexport function useRunSteps(runId: string, active: boolean) {\n const client = useDuratonClient();\n return useQuery<Step[]>({\n queryKey: [\"run-steps\", runId],\n queryFn: () => client.runs.steps(runId),\n refetchInterval: active ? FALLBACK_POLL_MS : false,\n });\n}\n","import { type QueryClient, useQueryClient } from \"@tanstack/react-query\";\nimport { useEffect, useState } from \"react\";\nimport type { DuratonClient, TimelineFrame } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\nimport { abortableDelay, RECONNECT_MS } from \"../lib/stream\";\nimport { TERMINAL_STATUSES } from \"../run-status\";\n\n// Coalesce a burst of status frames into one run/steps refetch.\nconst REFETCH_MS = 250;\n\n// Tails a run's SSE timeline; status frames trigger a coalesced refetch while the\n// returned frame buffer drives the live timeline panel. Keyed by runId so a run\n// switch remounts with fresh state.\nexport function useRunStream(runId: string): TimelineFrame[] {\n const client = useDuratonClient();\n const qc = useQueryClient();\n const [frames, setFrames] = useState<TimelineFrame[]>([]);\n\n useEffect(() => {\n const ctrl = new AbortController();\n void tailRun(client, qc, runId, ctrl.signal, setFrames);\n return () => ctrl.abort();\n }, [client, qc, runId]);\n\n return frames;\n}\n\ntype SetFrames = (update: (prev: TimelineFrame[]) => TimelineFrame[]) => void;\n\nasync function tailRun(\n client: DuratonClient,\n qc: QueryClient,\n runId: string,\n signal: AbortSignal,\n setFrames: SetFrames,\n) {\n let from = 0;\n let refetchTimer: ReturnType<typeof setTimeout> | undefined;\n signal.addEventListener(\"abort\", () => clearTimeout(refetchTimer), { once: true });\n\n const refetch = () => {\n void qc.invalidateQueries({ queryKey: [\"run\", runId] });\n void qc.invalidateQueries({ queryKey: [\"run-steps\", runId] });\n };\n const scheduleRefetch = () => {\n if (refetchTimer) return;\n refetchTimer = setTimeout(() => {\n refetchTimer = undefined;\n refetch();\n }, REFETCH_MS);\n };\n\n while (!signal.aborted) {\n let terminal = false;\n try {\n for await (const frame of client.runs.watch(runId, { from, signal })) {\n from = frame.seq;\n setFrames((prev) => (prev.some((f) => f.seq === frame.seq) ? prev : [...prev, frame]));\n if (frame.kind === \"log\") continue;\n scheduleRefetch();\n if (frame.kind === \"run_status\" && TERMINAL_STATUSES.has(frame.status)) terminal = true;\n }\n } catch {\n if (signal.aborted) return;\n }\n if (terminal) {\n clearTimeout(refetchTimer);\n refetch();\n return;\n }\n if (signal.aborted) return;\n await abortableDelay(RECONNECT_MS, signal);\n }\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { WorkflowDef } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nconst POLL_MS = 5000;\n\nexport function useWorkflows() {\n const client = useDuratonClient();\n return useQuery<WorkflowDef[]>({\n queryKey: [\"workflows\"],\n queryFn: () => client.workflows.list(),\n refetchInterval: POLL_MS,\n });\n}\n","import { useMutation, useQueryClient, type QueryClient } from \"@tanstack/react-query\";\nimport type { BulkReplayFilter, BulkReplayResult, Run } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nfunction invalidateRuns(qc: QueryClient) {\n void qc.invalidateQueries({ queryKey: [\"runs\"] });\n void qc.invalidateQueries({ queryKey: [\"control-actions\"] });\n}\n\n// In-place control (cancel/pause/resume) returns the updated run. Writing it into the\n// cache lets useRun's status-driven polling self-correct without a refetch round-trip.\nfunction useRunStatusMutation(mutationFn: (id: string) => Promise<Run>) {\n const qc = useQueryClient();\n return useMutation({\n mutationFn,\n onSuccess: (run) => {\n qc.setQueryData([\"run\", run.id], run);\n void qc.invalidateQueries({ queryKey: [\"run-steps\", run.id] });\n invalidateRuns(qc);\n },\n });\n}\n\nexport function useCancelRun() {\n const client = useDuratonClient();\n return useRunStatusMutation((id) => client.runs.cancel(id));\n}\n\nexport function usePauseRun() {\n const client = useDuratonClient();\n return useRunStatusMutation((id) => client.runs.pause(id));\n}\n\nexport function useResumeRun() {\n const client = useDuratonClient();\n return useRunStatusMutation((id) => client.runs.resume(id));\n}\n\nfunction useForkRun<TArgs>(mutationFn: (args: TArgs) => Promise<Run>) {\n const qc = useQueryClient();\n return useMutation<Run, Error, TArgs>({\n mutationFn,\n onSuccess: () => invalidateRuns(qc),\n });\n}\n\nexport function useReplayRun() {\n const client = useDuratonClient();\n return useForkRun(({ runId, input }: { runId: string; input?: unknown }) =>\n client.runs.replay(runId, input),\n );\n}\n\nexport function useRetryFromStep() {\n const client = useDuratonClient();\n return useForkRun(({ runId, step }: { runId: string; step: string }) =>\n client.runs.retryFromStep(runId, step),\n );\n}\n\nexport function useBulkReplay() {\n const client = useDuratonClient();\n const qc = useQueryClient();\n return useMutation<BulkReplayResult, Error, BulkReplayFilter>({\n mutationFn: (filter) => client.runs.bulkReplay(filter),\n onSuccess: () => invalidateRuns(qc),\n });\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { ControlAction, ListControlActionsOptions } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nexport function useControlActions(opts: ListControlActionsOptions = {}) {\n const client = useDuratonClient();\n return useQuery<ControlAction[]>({\n queryKey: [\"control-actions\", opts],\n queryFn: () => client.runs.controlActions(opts),\n });\n}\n","import { useQuery } from \"@tanstack/react-query\";\nimport type { RunSeries, RunTimeSeriesOptions } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\n// The namespace stream (useRunsStream) drives refetches; this slow interval is only a safety net.\nconst FALLBACK_POLL_MS = 15000;\n\nexport function useRunTimeSeries(opts: RunTimeSeriesOptions = {}) {\n const client = useDuratonClient();\n return useQuery<RunSeries>({\n queryKey: [\"run-timeseries\", opts],\n queryFn: () => client.runs.timeseries(opts),\n refetchInterval: FALLBACK_POLL_MS,\n });\n}\n","import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport type { FlowState, FlowStateOptions } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\n// Buffer counts move on their own (a debounce fires, a batch flushes) with no run\n// event to ride, so this view polls on a short cadence of its own.\nconst POLL_MS = 5000;\n\nexport function useFlowState(opts: FlowStateOptions = {}) {\n const client = useDuratonClient();\n return useQuery<FlowState>({\n queryKey: [\"flow-state\", opts],\n queryFn: () => client.flowState(opts),\n refetchInterval: POLL_MS,\n placeholderData: keepPreviousData,\n });\n}\n","import { keepPreviousData, useQuery } from \"@tanstack/react-query\";\nimport type { Runner } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nconst POLL_MS = 15000;\n\nexport function useRunners() {\n const client = useDuratonClient();\n return useQuery<Runner[]>({\n queryKey: [\"runners\"],\n queryFn: () => client.runners.list(),\n refetchInterval: POLL_MS,\n placeholderData: keepPreviousData,\n });\n}\n","import { useMemo } from \"react\";\nimport type { Runner, RunStatus } from \"@duraton/sdk/client\";\nimport { useRunners } from \"./use-runners\";\nimport { useRuns } from \"./use-runs\";\nimport { useWorkflows } from \"./use-workflows\";\n\nexport const APP_STATUSES = [\"connected\", \"syncing\", \"error\", \"disconnected\"] as const;\nexport type AppStatus = (typeof APP_STATUSES)[number];\n\nexport interface AppWorkflow {\n name: string;\n lastStatus?: RunStatus;\n}\n\nexport interface AppInfo {\n name: string;\n status: AppStatus;\n workflowCount: number;\n runCount: number;\n lastSyncAt?: string;\n workflows: AppWorkflow[];\n runners: Runner[];\n}\n\nexport function useApps(): { data: AppInfo[]; isLoading: boolean } {\n const workflows = useWorkflows();\n const runs = useRuns();\n const runners = useRunners();\n\n const data = useMemo<AppInfo[]>(() => {\n const wfs = workflows.data ?? [];\n const rs = runs.data?.runs ?? [];\n const rns = runners.data ?? [];\n const names = new Set<string>();\n wfs.forEach((w) => names.add(w.app));\n rs.forEach((r) => names.add(r.app));\n rns.forEach((r) => names.add(r.app));\n\n return [...names].sort().map((name) => {\n const appWfs = wfs.filter((w) => w.app === name);\n const appRuns = rs.filter((r) => r.app === name);\n const appRunners = rns.filter((r) => r.app === name);\n const latestStatus = (wfName: string) =>\n appRuns\n .filter((r) => r.workflowName === wfName)\n .sort((a, b) => b.startedAt.localeCompare(a.startedAt))[0]?.status;\n const lastSyncAt = appRuns.map((r) => r.startedAt).sort((a, b) => b.localeCompare(a))[0];\n\n return {\n name,\n status: appWfs.length > 0 || appRunners.some((r) => r.live) ? \"connected\" : \"disconnected\",\n workflowCount: appWfs.length,\n runCount: appRuns.length,\n lastSyncAt,\n workflows: appWfs.map((w) => ({ name: w.name, lastStatus: latestStatus(w.name) })),\n runners: appRunners,\n };\n });\n }, [workflows.data, runs.data, runners.data]);\n\n return { data, isLoading: workflows.isLoading || runs.isLoading };\n}\n","import { type QueryClient, useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { useEffect } from \"react\";\nimport type { DuratonClient, EventLogRecord, SendEventInput } from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\nimport { abortableDelay, RECONNECT_MS } from \"../lib/stream\";\n\n// MAX_EVENTS caps the in-memory list so a long-open tab on the live stream does\n// not grow without bound; older events stay queryable via events.list().\nconst MAX_EVENTS = 200;\n\nexport function useEvents() {\n const client = useDuratonClient();\n const qc = useQueryClient();\n const query = useQuery<EventLogRecord[]>({\n queryKey: [\"events\"],\n queryFn: () => client.events.list(),\n });\n\n useEffect(() => {\n const ctrl = new AbortController();\n void tailEvents(client, qc, ctrl.signal);\n return () => ctrl.abort();\n }, [client, qc]);\n\n return query;\n}\n\nasync function tailEvents(client: DuratonClient, qc: QueryClient, signal: AbortSignal) {\n while (!signal.aborted) {\n try {\n for await (const ev of client.events.stream({ signal })) {\n qc.setQueryData<EventLogRecord[]>([\"events\"], (prev = []) =>\n prev.some((e) => e.id === ev.id) ? prev : [ev, ...prev].slice(0, MAX_EVENTS),\n );\n }\n } catch {\n if (signal.aborted) return;\n }\n if (signal.aborted) return;\n await abortableDelay(RECONNECT_MS, signal);\n }\n}\n\nexport function useTriggerEvent() {\n const client = useDuratonClient();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: (input: SendEventInput) => client.events.send(input),\n onSuccess: () => {\n void qc.invalidateQueries({ queryKey: [\"events\"] });\n void qc.invalidateQueries({ queryKey: [\"runs\"] });\n },\n });\n}\n","import { keepPreviousData, skipToken, useQuery } from \"@tanstack/react-query\";\nimport type {\n ListWebhookDeliveriesOptions,\n WebhookDeliveriesPage,\n WebhookDeliveryDetail,\n WebhookEndpoint,\n WebhookEndpointStats,\n WebhookReceiver,\n} from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\nconst POLL_MS = 5000;\n\nexport function useDeliveries(opts: ListWebhookDeliveriesOptions = {}) {\n const client = useDuratonClient();\n return useQuery<WebhookDeliveriesPage>({\n queryKey: [\"deliveries\", opts],\n queryFn: () => client.webhooks.deliveries.list(opts),\n refetchInterval: POLL_MS,\n placeholderData: keepPreviousData,\n });\n}\n\nexport function useDelivery(id: string | null) {\n const client = useDuratonClient();\n return useQuery<WebhookDeliveryDetail>({\n queryKey: [\"delivery\", id],\n queryFn: id != null ? () => client.webhooks.deliveries.get(id) : skipToken,\n refetchInterval: POLL_MS,\n });\n}\n\nexport function useEndpoints() {\n const client = useDuratonClient();\n return useQuery<WebhookEndpoint[]>({\n queryKey: [\"endpoints\"],\n queryFn: () => client.webhooks.endpoints.list(),\n });\n}\n\nexport function useReceivers() {\n const client = useDuratonClient();\n return useQuery<WebhookReceiver[]>({\n queryKey: [\"receivers\"],\n queryFn: () => client.webhooks.receivers.list(),\n });\n}\n\nexport function useEndpointStats(since?: string) {\n const client = useDuratonClient();\n return useQuery<WebhookEndpointStats[]>({\n queryKey: [\"endpoint-stats\", since ?? null],\n queryFn: () => client.webhooks.endpoints.stats(since),\n refetchInterval: POLL_MS,\n });\n}\n","import { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport type {\n EndpointInput,\n EndpointPatch,\n ReceiverInput,\n ReceiverPatch,\n} from \"@duraton/sdk/client\";\nimport { useDuratonClient } from \"../provider\";\n\n// Pure mutation hooks: cache invalidation only. Consumers add toast/navigation via\n// mutate(vars, { onSuccess, onError }).\n\nfunction useEndpointInvalidation() {\n const qc = useQueryClient();\n return () => {\n void qc.invalidateQueries({ queryKey: [\"endpoints\"] });\n void qc.invalidateQueries({ queryKey: [\"endpoint-stats\"] });\n };\n}\n\nexport function useCreateEndpoint() {\n const client = useDuratonClient();\n const invalidate = useEndpointInvalidation();\n return useMutation({\n mutationFn: (input: EndpointInput) => client.webhooks.endpoints.create(input),\n onSuccess: invalidate,\n });\n}\n\nexport function useUpdateEndpoint() {\n const client = useDuratonClient();\n const invalidate = useEndpointInvalidation();\n return useMutation({\n mutationFn: ({ id, patch }: { id: string; patch: EndpointPatch }) =>\n client.webhooks.endpoints.update(id, patch),\n onSuccess: invalidate,\n });\n}\n\nexport function useDeleteEndpoint() {\n const client = useDuratonClient();\n const invalidate = useEndpointInvalidation();\n return useMutation({\n mutationFn: (id: string) => client.webhooks.endpoints.delete(id),\n onSuccess: invalidate,\n });\n}\n\nfunction useReceiverInvalidation() {\n const qc = useQueryClient();\n return () => void qc.invalidateQueries({ queryKey: [\"receivers\"] });\n}\n\nexport function useCreateReceiver() {\n const client = useDuratonClient();\n const invalidate = useReceiverInvalidation();\n return useMutation({\n mutationFn: (input: ReceiverInput) => client.webhooks.receivers.create(input),\n onSuccess: invalidate,\n });\n}\n\nexport function useUpdateReceiver() {\n const client = useDuratonClient();\n const invalidate = useReceiverInvalidation();\n return useMutation({\n mutationFn: ({ id, patch }: { id: string; patch: ReceiverPatch }) =>\n client.webhooks.receivers.update(id, patch),\n onSuccess: invalidate,\n });\n}\n\nexport function useDeleteReceiver() {\n const client = useDuratonClient();\n const invalidate = useReceiverInvalidation();\n return useMutation({\n mutationFn: (id: string) => client.webhooks.receivers.delete(id),\n onSuccess: invalidate,\n });\n}\n\nexport function useRedeliverDelivery() {\n const client = useDuratonClient();\n const qc = useQueryClient();\n return useMutation({\n mutationFn: (id: string) => client.webhooks.deliveries.redeliver(id),\n onSuccess: (_void, id) => {\n void qc.invalidateQueries({ queryKey: [\"deliveries\"] });\n void qc.invalidateQueries({ queryKey: [\"delivery\", id] });\n },\n });\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@duraton/react",
3
+ "version": "0.1.0-beta.7",
4
+ "description": "Headless React hooks and provider for duraton - runs, workflows, and live run streams over the control API.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "homepage": "https://github.com/duraton/duraton#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/duraton/duraton.git",
11
+ "directory": "packages/react"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/duraton/duraton/issues"
15
+ },
16
+ "keywords": [
17
+ "durable",
18
+ "workflow",
19
+ "react",
20
+ "hooks",
21
+ "duraton"
22
+ ],
23
+ "exports": {
24
+ ".": {
25
+ "bun": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js",
28
+ "default": "./dist/index.js"
29
+ }
30
+ },
31
+ "main": "./dist/index.js",
32
+ "module": "./dist/index.js",
33
+ "types": "./dist/index.d.ts",
34
+ "sideEffects": false,
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "LICENSE",
39
+ "NOTICE"
40
+ ],
41
+ "engines": {
42
+ "node": ">=18"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public",
46
+ "provenance": true
47
+ },
48
+ "peerDependencies": {
49
+ "@tanstack/react-query": ">=5",
50
+ "react": ">=18"
51
+ },
52
+ "dependencies": {
53
+ "@duraton/sdk": "0.0.0"
54
+ },
55
+ "scripts": {
56
+ "build": "tsdown",
57
+ "prepublishOnly": "tsdown",
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run"
60
+ },
61
+ "devDependencies": {
62
+ "@tanstack/react-query": "^5.101.0",
63
+ "@testing-library/dom": "^10.4.1",
64
+ "@testing-library/react": "^16.3.2",
65
+ "@types/react": "^19.2.14",
66
+ "@types/react-dom": "^19.2.3",
67
+ "jsdom": "^29.1.1",
68
+ "react": "^19.2.6",
69
+ "react-dom": "^19.2.7",
70
+ "tsdown": "^0.22.3",
71
+ "typescript": "^5",
72
+ "vitest": "^4.1.9"
73
+ }
74
+ }
@@ -0,0 +1,62 @@
1
+ import { useMemo } from "react";
2
+ import type { Runner, RunStatus } from "@duraton/sdk/client";
3
+ import { useRunners } from "./use-runners";
4
+ import { useRuns } from "./use-runs";
5
+ import { useWorkflows } from "./use-workflows";
6
+
7
+ export const APP_STATUSES = ["connected", "syncing", "error", "disconnected"] as const;
8
+ export type AppStatus = (typeof APP_STATUSES)[number];
9
+
10
+ export interface AppWorkflow {
11
+ name: string;
12
+ lastStatus?: RunStatus;
13
+ }
14
+
15
+ export interface AppInfo {
16
+ name: string;
17
+ status: AppStatus;
18
+ workflowCount: number;
19
+ runCount: number;
20
+ lastSyncAt?: string;
21
+ workflows: AppWorkflow[];
22
+ runners: Runner[];
23
+ }
24
+
25
+ export function useApps(): { data: AppInfo[]; isLoading: boolean } {
26
+ const workflows = useWorkflows();
27
+ const runs = useRuns();
28
+ const runners = useRunners();
29
+
30
+ const data = useMemo<AppInfo[]>(() => {
31
+ const wfs = workflows.data ?? [];
32
+ const rs = runs.data?.runs ?? [];
33
+ const rns = runners.data ?? [];
34
+ const names = new Set<string>();
35
+ wfs.forEach((w) => names.add(w.app));
36
+ rs.forEach((r) => names.add(r.app));
37
+ rns.forEach((r) => names.add(r.app));
38
+
39
+ return [...names].sort().map((name) => {
40
+ const appWfs = wfs.filter((w) => w.app === name);
41
+ const appRuns = rs.filter((r) => r.app === name);
42
+ const appRunners = rns.filter((r) => r.app === name);
43
+ const latestStatus = (wfName: string) =>
44
+ appRuns
45
+ .filter((r) => r.workflowName === wfName)
46
+ .sort((a, b) => b.startedAt.localeCompare(a.startedAt))[0]?.status;
47
+ const lastSyncAt = appRuns.map((r) => r.startedAt).sort((a, b) => b.localeCompare(a))[0];
48
+
49
+ return {
50
+ name,
51
+ status: appWfs.length > 0 || appRunners.some((r) => r.live) ? "connected" : "disconnected",
52
+ workflowCount: appWfs.length,
53
+ runCount: appRuns.length,
54
+ lastSyncAt,
55
+ workflows: appWfs.map((w) => ({ name: w.name, lastStatus: latestStatus(w.name) })),
56
+ runners: appRunners,
57
+ };
58
+ });
59
+ }, [workflows.data, runs.data, runners.data]);
60
+
61
+ return { data, isLoading: workflows.isLoading || runs.isLoading };
62
+ }