@voyant-travel/workflows-react 0.107.10

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.
Files changed (81) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +52 -0
  3. package/README.md +106 -0
  4. package/dist/client.d.ts +2 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +1 -0
  7. package/dist/components/common.d.ts +30 -0
  8. package/dist/components/common.d.ts.map +1 -0
  9. package/dist/components/common.js +108 -0
  10. package/dist/components/workflow-run-actions.d.ts +7 -0
  11. package/dist/components/workflow-run-actions.d.ts.map +1 -0
  12. package/dist/components/workflow-run-actions.js +72 -0
  13. package/dist/components/workflow-run-detail-page.d.ts +10 -0
  14. package/dist/components/workflow-run-detail-page.d.ts.map +1 -0
  15. package/dist/components/workflow-run-detail-page.js +96 -0
  16. package/dist/components/workflow-runs-filters.d.ts +32 -0
  17. package/dist/components/workflow-runs-filters.d.ts.map +1 -0
  18. package/dist/components/workflow-runs-filters.js +97 -0
  19. package/dist/components/workflow-runs-page.d.ts +12 -0
  20. package/dist/components/workflow-runs-page.d.ts.map +1 -0
  21. package/dist/components/workflow-runs-page.js +132 -0
  22. package/dist/components/workflow-schedules-page.d.ts +21 -0
  23. package/dist/components/workflow-schedules-page.d.ts.map +1 -0
  24. package/dist/components/workflow-schedules-page.js +144 -0
  25. package/dist/i18n/en.d.ts +4 -0
  26. package/dist/i18n/en.d.ts.map +1 -0
  27. package/dist/i18n/en.js +135 -0
  28. package/dist/i18n/index.d.ts +5 -0
  29. package/dist/i18n/index.d.ts.map +1 -0
  30. package/dist/i18n/index.js +3 -0
  31. package/dist/i18n/messages.d.ts +125 -0
  32. package/dist/i18n/messages.d.ts.map +1 -0
  33. package/dist/i18n/messages.js +1 -0
  34. package/dist/i18n/provider.d.ts +26 -0
  35. package/dist/i18n/provider.d.ts.map +1 -0
  36. package/dist/i18n/provider.js +44 -0
  37. package/dist/i18n/ro.d.ts +4 -0
  38. package/dist/i18n/ro.d.ts.map +1 -0
  39. package/dist/i18n/ro.js +137 -0
  40. package/dist/index.d.ts +56 -0
  41. package/dist/index.d.ts.map +1 -0
  42. package/dist/index.js +21 -0
  43. package/dist/schedules-client.d.ts +2 -0
  44. package/dist/schedules-client.d.ts.map +1 -0
  45. package/dist/schedules-client.js +1 -0
  46. package/dist/types.d.ts +2 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +1 -0
  49. package/dist/ui.d.ts +9 -0
  50. package/dist/ui.d.ts.map +1 -0
  51. package/dist/ui.js +6 -0
  52. package/dist/workflow-runs-client.d.ts +124 -0
  53. package/dist/workflow-runs-client.d.ts.map +1 -0
  54. package/dist/workflow-runs-client.js +138 -0
  55. package/dist/workflow-runs.d.ts +36 -0
  56. package/dist/workflow-runs.d.ts.map +1 -0
  57. package/dist/workflow-runs.js +70 -0
  58. package/dist/workflow-schedules-client.d.ts +70 -0
  59. package/dist/workflow-schedules-client.d.ts.map +1 -0
  60. package/dist/workflow-schedules-client.js +91 -0
  61. package/package.json +123 -0
  62. package/src/client.ts +4 -0
  63. package/src/components/common.tsx +182 -0
  64. package/src/components/workflow-run-actions.tsx +160 -0
  65. package/src/components/workflow-run-detail-page.tsx +393 -0
  66. package/src/components/workflow-runs-filters.tsx +349 -0
  67. package/src/components/workflow-runs-page.tsx +357 -0
  68. package/src/components/workflow-schedules-page.tsx +398 -0
  69. package/src/i18n/en.ts +142 -0
  70. package/src/i18n/index.ts +26 -0
  71. package/src/i18n/messages.ts +125 -0
  72. package/src/i18n/provider.tsx +96 -0
  73. package/src/i18n/ro.ts +144 -0
  74. package/src/index.ts +94 -0
  75. package/src/schedules-client.ts +8 -0
  76. package/src/styles.css +11 -0
  77. package/src/types.ts +14 -0
  78. package/src/ui.ts +63 -0
  79. package/src/workflow-runs-client.ts +304 -0
  80. package/src/workflow-runs.ts +120 -0
  81. package/src/workflow-schedules-client.ts +177 -0
@@ -0,0 +1,304 @@
1
+ export interface WorkflowRunErrorPayload {
2
+ message: string
3
+ code?: string
4
+ stepName?: string
5
+ stack?: string
6
+ }
7
+
8
+ export type WorkflowRunStatus = "running" | "succeeded" | "failed" | "cancelled"
9
+
10
+ export interface WorkflowRun {
11
+ id: string
12
+ workflowName: string
13
+ trigger: string
14
+ correlationId: string | null
15
+ tags: string[]
16
+ status: WorkflowRunStatus
17
+ input: Record<string, unknown> | null
18
+ result: Record<string, unknown> | null
19
+ error: WorkflowRunErrorPayload | null
20
+ parentRunId: string | null
21
+ triggeredByUserId: string | null
22
+ resumeFromStep: string | null
23
+ startedAt: string
24
+ completedAt: string | null
25
+ durationMs: number | null
26
+ createdAt: string
27
+ updatedAt: string
28
+ }
29
+
30
+ export type WorkflowRunStepStatus = "running" | "succeeded" | "failed" | "skipped" | "compensated"
31
+
32
+ export interface WorkflowRunStep {
33
+ id: string
34
+ runId: string
35
+ stepName: string
36
+ sequence: number
37
+ status: WorkflowRunStepStatus
38
+ output: Record<string, unknown> | null
39
+ error: WorkflowRunErrorPayload | null
40
+ startedAt: string
41
+ completedAt: string | null
42
+ durationMs: number | null
43
+ }
44
+
45
+ export interface ListWorkflowRunsQuery {
46
+ workflowName?: string
47
+ status?: WorkflowRunStatus
48
+ tag?: string
49
+ parentRunId?: string
50
+ limit?: number
51
+ offset?: number
52
+ }
53
+
54
+ export interface ListWorkflowRunsResponse {
55
+ data: WorkflowRun[]
56
+ total: number
57
+ limit: number
58
+ offset: number
59
+ }
60
+
61
+ export interface WorkflowRunDetailResponse {
62
+ data: { run: WorkflowRun; steps: WorkflowRunStep[] }
63
+ }
64
+
65
+ export interface WorkflowRunActionResponse {
66
+ data: {
67
+ runId: string
68
+ parentRunId: string
69
+ resumeFromStep?: string
70
+ }
71
+ }
72
+
73
+ export interface WorkflowRunActionError {
74
+ error: string
75
+ detail?: string
76
+ idempotency?: "safe" | "unsafe" | "resume-only"
77
+ }
78
+
79
+ export type WorkflowRunActionResult =
80
+ | { ok: true; data: WorkflowRunActionResponse["data"] }
81
+ | { ok: false; error: WorkflowRunActionError }
82
+
83
+ export interface WorkflowRunsApi {
84
+ listRuns(query: ListWorkflowRunsQuery): Promise<ListWorkflowRunsResponse>
85
+ getRun(id: string): Promise<WorkflowRunDetailResponse>
86
+ rerunRun(id: string, opts?: { confirm?: boolean }): Promise<WorkflowRunActionResult>
87
+ resumeRun(id: string): Promise<WorkflowRunActionResult>
88
+ }
89
+
90
+ export type WorkflowRunsFetcher = (input: string, init?: RequestInit) => Promise<Response>
91
+
92
+ export interface WorkflowRunsApiClientOptions {
93
+ apiBase?: string
94
+ baseUrl?: string
95
+ fetcher?: WorkflowRunsFetcher
96
+ credentials?: RequestCredentials
97
+ headers?: HeadersInit
98
+ }
99
+
100
+ export interface WorkflowRunsClientOptions {
101
+ baseUrl: string
102
+ fetcher: WorkflowRunsFetcher
103
+ credentials?: RequestCredentials
104
+ headers?: HeadersInit
105
+ }
106
+
107
+ export class WorkflowRunsApiError extends Error {
108
+ readonly status: number
109
+ readonly body: unknown
110
+
111
+ constructor(message: string, status: number, body: unknown) {
112
+ super(message)
113
+ this.name = "WorkflowRunsApiError"
114
+ this.status = status
115
+ this.body = body
116
+ }
117
+ }
118
+
119
+ export const defaultWorkflowRunsFetcher: WorkflowRunsFetcher = (input, init) => fetch(input, init)
120
+
121
+ export const workflowRunsQueryKeys = {
122
+ all: ["voyant", "workflow-runs"] as const,
123
+ lists: () => [...workflowRunsQueryKeys.all, "list"] as const,
124
+ list: (query: ListWorkflowRunsQuery) => [...workflowRunsQueryKeys.lists(), query] as const,
125
+ details: () => [...workflowRunsQueryKeys.all, "detail"] as const,
126
+ detail: (id: string) => [...workflowRunsQueryKeys.details(), id] as const,
127
+ } as const
128
+
129
+ export function createWorkflowRunsClientOptions(
130
+ client?: Partial<WorkflowRunsClientOptions>,
131
+ ): WorkflowRunsClientOptions {
132
+ return {
133
+ baseUrl: client?.baseUrl ?? "",
134
+ fetcher: client?.fetcher ?? defaultWorkflowRunsFetcher,
135
+ credentials: client?.credentials,
136
+ headers: client?.headers,
137
+ }
138
+ }
139
+
140
+ export function createWorkflowRunsApiClient(
141
+ options: WorkflowRunsApiClientOptions = {},
142
+ ): WorkflowRunsApi {
143
+ const client = createWorkflowRunsClientOptions({
144
+ baseUrl: options.baseUrl ?? options.apiBase ?? "",
145
+ fetcher: options.fetcher,
146
+ credentials: options.credentials ?? "include",
147
+ headers: options.headers,
148
+ })
149
+
150
+ return {
151
+ listRuns: (query) => listWorkflowRuns(query, client),
152
+ getRun: (id) => getWorkflowRun(id, client),
153
+ rerunRun: (id, opts) => rerunWorkflowRun({ id, confirm: opts?.confirm }, client),
154
+ resumeRun: (id) => resumeWorkflowRun(id, client),
155
+ }
156
+ }
157
+
158
+ export async function listWorkflowRuns(
159
+ query: ListWorkflowRunsQuery = {},
160
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
161
+ ): Promise<ListWorkflowRunsResponse> {
162
+ return fetchWorkflowRunsJson(
163
+ `/v1/admin/workflow-runs${buildWorkflowRunsSearch(query)}`,
164
+ { method: "GET" },
165
+ client,
166
+ )
167
+ }
168
+
169
+ export async function getWorkflowRun(
170
+ id: string,
171
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
172
+ ): Promise<WorkflowRunDetailResponse> {
173
+ return fetchWorkflowRunsJson(
174
+ `/v1/admin/workflow-runs/${encodeURIComponent(id)}`,
175
+ { method: "GET" },
176
+ client,
177
+ )
178
+ }
179
+
180
+ export async function rerunWorkflowRun(
181
+ input: { id: string; confirm?: boolean },
182
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
183
+ ): Promise<WorkflowRunActionResult> {
184
+ return runWorkflowRunAction(
185
+ await requestWorkflowRuns(
186
+ `/v1/admin/workflow-runs/${encodeURIComponent(input.id)}/rerun`,
187
+ {
188
+ method: "POST",
189
+ body: JSON.stringify({ confirm: input.confirm ?? false }),
190
+ },
191
+ client,
192
+ ),
193
+ )
194
+ }
195
+
196
+ export async function resumeWorkflowRun(
197
+ id: string,
198
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
199
+ ): Promise<WorkflowRunActionResult> {
200
+ return runWorkflowRunAction(
201
+ await requestWorkflowRuns(
202
+ `/v1/admin/workflow-runs/${encodeURIComponent(id)}/resume`,
203
+ { method: "POST" },
204
+ client,
205
+ ),
206
+ )
207
+ }
208
+
209
+ export function workflowRunIsTerminal(status: WorkflowRunStatus): boolean {
210
+ return status !== "running"
211
+ }
212
+
213
+ async function fetchWorkflowRunsJson<T>(
214
+ path: string,
215
+ init: RequestInit,
216
+ client: WorkflowRunsClientOptions,
217
+ ): Promise<T> {
218
+ const response = await requestWorkflowRuns(path, init, client)
219
+ const body = await safeJson(response)
220
+ if (!response.ok) {
221
+ throw new WorkflowRunsApiError(
222
+ extractErrorMessage(response.status, response.statusText, body),
223
+ response.status,
224
+ body,
225
+ )
226
+ }
227
+ return body as T
228
+ }
229
+
230
+ async function requestWorkflowRuns(
231
+ path: string,
232
+ init: RequestInit,
233
+ client: WorkflowRunsClientOptions,
234
+ ): Promise<Response> {
235
+ const headers = new Headers(client.headers)
236
+ for (const [key, value] of new Headers(init.headers).entries()) {
237
+ headers.set(key, value)
238
+ }
239
+ if (init.body !== undefined && !headers.has("Content-Type")) {
240
+ headers.set("Content-Type", "application/json")
241
+ }
242
+
243
+ const requestInit: RequestInit = { ...init, headers }
244
+ if (client.credentials !== undefined) {
245
+ requestInit.credentials = client.credentials
246
+ }
247
+
248
+ return client.fetcher(joinUrl(client.baseUrl, path), requestInit)
249
+ }
250
+
251
+ async function runWorkflowRunAction(res: Response): Promise<WorkflowRunActionResult> {
252
+ const body = await safeJson(res)
253
+ if (res.ok) {
254
+ return { ok: true, data: (body as WorkflowRunActionResponse).data }
255
+ }
256
+ return { ok: false, error: body as WorkflowRunActionError }
257
+ }
258
+
259
+ function buildWorkflowRunsSearch(query: ListWorkflowRunsQuery): string {
260
+ const params = new URLSearchParams()
261
+ if (query.workflowName) params.set("workflowName", query.workflowName)
262
+ if (query.status) params.set("status", query.status)
263
+ if (query.tag) params.set("tag", query.tag)
264
+ if (query.parentRunId) params.set("parentRunId", query.parentRunId)
265
+ if (query.limit !== undefined) params.set("limit", String(query.limit))
266
+ if (query.offset !== undefined) params.set("offset", String(query.offset))
267
+ const search = params.toString()
268
+ return search ? `?${search}` : ""
269
+ }
270
+
271
+ async function safeJson(response: Response): Promise<unknown> {
272
+ const text = await response.text()
273
+ if (!text) return undefined
274
+ try {
275
+ return JSON.parse(text)
276
+ } catch {
277
+ return text
278
+ }
279
+ }
280
+
281
+ function extractErrorMessage(status: number, statusText: string, body: unknown): string {
282
+ if (typeof body === "object" && body !== null) {
283
+ if ("error" in body && typeof body.error === "string") return body.error
284
+ if ("detail" in body && typeof body.detail === "string") return body.detail
285
+ }
286
+ return `Workflow runs API error: ${status} ${statusText}`
287
+ }
288
+
289
+ function joinUrl(baseUrl: string, path: string): string {
290
+ const resolvedBaseUrl = resolveBaseUrl(baseUrl)
291
+ const trimmedBase = resolvedBaseUrl.endsWith("/") ? resolvedBaseUrl.slice(0, -1) : resolvedBaseUrl
292
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`
293
+ return `${trimmedBase}${trimmedPath}`
294
+ }
295
+
296
+ function resolveBaseUrl(baseUrl: string): string {
297
+ if (baseUrl.trim()) return baseUrl
298
+
299
+ if (typeof window !== "undefined") {
300
+ return `${window.location.origin}/api`
301
+ }
302
+
303
+ return "http://localhost:3300/api"
304
+ }
@@ -0,0 +1,120 @@
1
+ "use client"
2
+
3
+ import { queryOptions, useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
4
+ import {
5
+ defaultFetcher,
6
+ useVoyantReactContext,
7
+ type VoyantFetcher,
8
+ type VoyantReactContextValue,
9
+ VoyantReactProvider,
10
+ type VoyantReactProviderProps,
11
+ } from "@voyant-travel/react"
12
+ import {
13
+ createWorkflowRunsClientOptions,
14
+ getWorkflowRun,
15
+ type ListWorkflowRunsQuery,
16
+ listWorkflowRuns,
17
+ rerunWorkflowRun,
18
+ resumeWorkflowRun,
19
+ type WorkflowRunsClientOptions,
20
+ workflowRunIsTerminal,
21
+ workflowRunsQueryKeys,
22
+ } from "./workflow-runs-client.js"
23
+
24
+ export * from "./workflow-runs-client.js"
25
+ export {
26
+ defaultFetcher,
27
+ useVoyantReactContext as useVoyantWorkflowsContext,
28
+ type VoyantFetcher,
29
+ type VoyantReactContextValue as VoyantWorkflowsContextValue,
30
+ VoyantReactProvider as VoyantWorkflowsProvider,
31
+ type VoyantReactProviderProps as VoyantWorkflowsProviderProps,
32
+ }
33
+
34
+ export function getWorkflowRunsQueryOptions(
35
+ query: ListWorkflowRunsQuery = {},
36
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
37
+ options: { pollIntervalMs?: number } = {},
38
+ ) {
39
+ const pollIntervalMs = options.pollIntervalMs ?? 5000
40
+
41
+ return queryOptions({
42
+ queryKey: workflowRunsQueryKeys.list(query),
43
+ queryFn: () => listWorkflowRuns(query, client),
44
+ refetchInterval: (queryState) => {
45
+ const data = queryState.state.data
46
+ return data?.data.some((run) => !workflowRunIsTerminal(run.status)) ? pollIntervalMs : false
47
+ },
48
+ refetchIntervalInBackground: false,
49
+ })
50
+ }
51
+
52
+ export function getWorkflowRunQueryOptions(
53
+ id: string | null | undefined,
54
+ client: WorkflowRunsClientOptions = createWorkflowRunsClientOptions(),
55
+ options: { pollIntervalMs?: number } = {},
56
+ ) {
57
+ const pollIntervalMs = options.pollIntervalMs ?? 2000
58
+ const runId = id ?? ""
59
+
60
+ return queryOptions({
61
+ queryKey: workflowRunsQueryKeys.detail(runId),
62
+ queryFn: () => getWorkflowRun(runId, client),
63
+ enabled: Boolean(runId),
64
+ refetchInterval: (queryState) => {
65
+ const run = queryState.state.data?.data.run
66
+ return run && !workflowRunIsTerminal(run.status) ? pollIntervalMs : false
67
+ },
68
+ refetchIntervalInBackground: false,
69
+ })
70
+ }
71
+
72
+ export function useWorkflowRuns(
73
+ query: ListWorkflowRunsQuery = {},
74
+ options: { pollIntervalMs?: number } = {},
75
+ ) {
76
+ const client = useVoyantReactContext()
77
+ return useQuery(getWorkflowRunsQueryOptions(query, client, options))
78
+ }
79
+
80
+ export function useWorkflowRun(
81
+ id: string | null | undefined,
82
+ options: { pollIntervalMs?: number } = {},
83
+ ) {
84
+ const client = useVoyantReactContext()
85
+ return useQuery(getWorkflowRunQueryOptions(id, client, options))
86
+ }
87
+
88
+ export function useRerunMutation() {
89
+ const queryClient = useQueryClient()
90
+ const client = useVoyantReactContext()
91
+
92
+ return useMutation({
93
+ mutationFn: (input: { id: string; confirm?: boolean }) => rerunWorkflowRun(input, client),
94
+ onSuccess: (result) => {
95
+ void queryClient.invalidateQueries({ queryKey: workflowRunsQueryKeys.all })
96
+ if (result.ok) {
97
+ void queryClient.invalidateQueries({
98
+ queryKey: workflowRunsQueryKeys.detail(result.data.parentRunId),
99
+ })
100
+ }
101
+ },
102
+ })
103
+ }
104
+
105
+ export function useResumeMutation() {
106
+ const queryClient = useQueryClient()
107
+ const client = useVoyantReactContext()
108
+
109
+ return useMutation({
110
+ mutationFn: (id: string) => resumeWorkflowRun(id, client),
111
+ onSuccess: (result) => {
112
+ void queryClient.invalidateQueries({ queryKey: workflowRunsQueryKeys.all })
113
+ if (result.ok) {
114
+ void queryClient.invalidateQueries({
115
+ queryKey: workflowRunsQueryKeys.detail(result.data.parentRunId),
116
+ })
117
+ }
118
+ },
119
+ })
120
+ }
@@ -0,0 +1,177 @@
1
+ // Client for the orchestrator's `/api/schedules/:env` aggregate view.
2
+ // Mirrors `workflow-runs-client.ts` so the workflow-schedules UI can be
3
+ // pointed at any worker that exposes `@voyant-travel/workflows-orchestrator-cloudflare`'s
4
+ // schedules handler.
5
+
6
+ export interface WorkflowScheduleDecl {
7
+ cron?: string
8
+ every?: string | number
9
+ at?: string
10
+ timezone?: string
11
+ enabled?: boolean
12
+ overlap?: "skip" | "queue" | "allow"
13
+ environments?: ("production" | "preview" | "development")[]
14
+ name?: string
15
+ input?: unknown
16
+ }
17
+
18
+ export interface WorkflowScheduleSummary {
19
+ workflowId: string
20
+ scheduleId: string
21
+ schedule: WorkflowScheduleDecl
22
+ /** Epoch millis of the next computed fire, or null when undecidable. */
23
+ nextRunAt: number | null
24
+ enabled: boolean
25
+ disabledReason?: "registration_disabled" | "env_filtered"
26
+ /** Epoch millis of the last scheduler dispatch attempt, when known. */
27
+ lastFireAt?: number | null
28
+ /** Run id produced by the last scheduler dispatch attempt, when known. */
29
+ lastRunId?: string | null
30
+ /** Last scheduler dispatch/lock error, when known. */
31
+ lastError?: string | null
32
+ /** Epoch millis until which this schedule is locked, when known. */
33
+ lockedUntil?: number | null
34
+ /** Epoch millis of the last successful scheduled run, when known. */
35
+ lastSuccessfulRunAt?: number | null
36
+ /** Epoch millis when the persisted scheduler state was last updated. */
37
+ stateUpdatedAt?: number | null
38
+ }
39
+
40
+ export interface ListWorkflowSchedulesResponse {
41
+ environment: string
42
+ versionId: string
43
+ schedulesEnabledByEnv?: boolean
44
+ data: WorkflowScheduleSummary[]
45
+ }
46
+
47
+ export interface WorkflowSchedulesApi {
48
+ listSchedules(environment: string): Promise<ListWorkflowSchedulesResponse>
49
+ }
50
+
51
+ export type WorkflowSchedulesFetcher = (input: string, init?: RequestInit) => Promise<Response>
52
+
53
+ export interface WorkflowSchedulesApiClientOptions {
54
+ /** Base URL for the orchestrator. Defaults to the current origin. */
55
+ apiBase?: string
56
+ baseUrl?: string
57
+ fetcher?: WorkflowSchedulesFetcher
58
+ credentials?: RequestCredentials
59
+ headers?: HeadersInit
60
+ }
61
+
62
+ export interface WorkflowSchedulesClientOptions {
63
+ baseUrl: string
64
+ fetcher: WorkflowSchedulesFetcher
65
+ credentials?: RequestCredentials
66
+ headers?: HeadersInit
67
+ }
68
+
69
+ export class WorkflowSchedulesApiError extends Error {
70
+ readonly status: number
71
+ readonly body: unknown
72
+
73
+ constructor(message: string, status: number, body: unknown) {
74
+ super(message)
75
+ this.name = "WorkflowSchedulesApiError"
76
+ this.status = status
77
+ this.body = body
78
+ }
79
+ }
80
+
81
+ export const defaultWorkflowSchedulesFetcher: WorkflowSchedulesFetcher = (input, init) =>
82
+ fetch(input, init)
83
+
84
+ export const workflowSchedulesQueryKeys = {
85
+ all: ["voyant", "workflow-schedules"] as const,
86
+ list: (environment: string) => [...workflowSchedulesQueryKeys.all, "list", environment] as const,
87
+ } as const
88
+
89
+ export function createWorkflowSchedulesClientOptions(
90
+ client?: Partial<WorkflowSchedulesClientOptions>,
91
+ ): WorkflowSchedulesClientOptions {
92
+ return {
93
+ baseUrl: client?.baseUrl ?? "",
94
+ fetcher: client?.fetcher ?? defaultWorkflowSchedulesFetcher,
95
+ credentials: client?.credentials,
96
+ headers: client?.headers,
97
+ }
98
+ }
99
+
100
+ export function createWorkflowSchedulesApiClient(
101
+ options: WorkflowSchedulesApiClientOptions = {},
102
+ ): WorkflowSchedulesApi {
103
+ const client = createWorkflowSchedulesClientOptions({
104
+ baseUrl: options.baseUrl ?? options.apiBase ?? "",
105
+ fetcher: options.fetcher,
106
+ credentials: options.credentials ?? "include",
107
+ headers: options.headers,
108
+ })
109
+
110
+ return {
111
+ listSchedules: (environment) => listWorkflowSchedules(environment, client),
112
+ }
113
+ }
114
+
115
+ export async function listWorkflowSchedules(
116
+ environment: string,
117
+ client: WorkflowSchedulesClientOptions = createWorkflowSchedulesClientOptions(),
118
+ ): Promise<ListWorkflowSchedulesResponse> {
119
+ const path = `/api/schedules/${encodeURIComponent(environment)}`
120
+ const response = await request(path, { method: "GET" }, client)
121
+ const body = await safeJson(response)
122
+ if (!response.ok) {
123
+ throw new WorkflowSchedulesApiError(
124
+ extractErrorMessage(response.status, response.statusText, body),
125
+ response.status,
126
+ body,
127
+ )
128
+ }
129
+ return body as ListWorkflowSchedulesResponse
130
+ }
131
+
132
+ async function request(
133
+ path: string,
134
+ init: RequestInit,
135
+ client: WorkflowSchedulesClientOptions,
136
+ ): Promise<Response> {
137
+ const headers = new Headers(client.headers)
138
+ for (const [key, value] of new Headers(init.headers).entries()) {
139
+ headers.set(key, value)
140
+ }
141
+ const requestInit: RequestInit = { ...init, headers }
142
+ if (client.credentials !== undefined) {
143
+ requestInit.credentials = client.credentials
144
+ }
145
+ return client.fetcher(joinUrl(client.baseUrl, path), requestInit)
146
+ }
147
+
148
+ async function safeJson(response: Response): Promise<unknown> {
149
+ const text = await response.text()
150
+ if (!text) return undefined
151
+ try {
152
+ return JSON.parse(text)
153
+ } catch {
154
+ return text
155
+ }
156
+ }
157
+
158
+ function extractErrorMessage(status: number, statusText: string, body: unknown): string {
159
+ if (typeof body === "object" && body !== null) {
160
+ if ("error" in body && typeof body.error === "string") return body.error
161
+ if ("message" in body && typeof body.message === "string") return body.message
162
+ }
163
+ return `Workflow schedules API error: ${status} ${statusText}`
164
+ }
165
+
166
+ function joinUrl(baseUrl: string, path: string): string {
167
+ const resolved = resolveBaseUrl(baseUrl)
168
+ const trimmedBase = resolved.endsWith("/") ? resolved.slice(0, -1) : resolved
169
+ const trimmedPath = path.startsWith("/") ? path : `/${path}`
170
+ return `${trimmedBase}${trimmedPath}`
171
+ }
172
+
173
+ function resolveBaseUrl(baseUrl: string): string {
174
+ if (baseUrl.trim()) return baseUrl
175
+ if (typeof window !== "undefined") return window.location.origin
176
+ return "http://localhost:8787"
177
+ }