flowforge-client 0.1.2
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/README.md +343 -0
- package/dist/index.d.mts +908 -0
- package/dist/index.d.ts +908 -0
- package/dist/index.js +811 -0
- package/dist/index.mjs +774 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# FlowForge TypeScript Client
|
|
2
|
+
|
|
3
|
+
A type-safe, Supabase-style TypeScript client for interacting with FlowForge server from Node.js, Next.js, or any JavaScript runtime.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install flowforge-client
|
|
9
|
+
# or
|
|
10
|
+
pnpm add flowforge-client
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { createClient } from 'flowforge-client';
|
|
17
|
+
|
|
18
|
+
const ff = createClient('http://localhost:8000');
|
|
19
|
+
|
|
20
|
+
// All methods return { data, error } - never throws
|
|
21
|
+
const { data, error } = await ff.events.send('order/created', {
|
|
22
|
+
order_id: '123',
|
|
23
|
+
customer: 'Alice',
|
|
24
|
+
total: 99.99
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (error) {
|
|
28
|
+
console.error('Failed to send event:', error.message);
|
|
29
|
+
} else {
|
|
30
|
+
console.log('Event ID:', data.id);
|
|
31
|
+
console.log('Triggered runs:', data.runs);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Wait for the run to complete
|
|
35
|
+
const { data: run } = await ff.runs.waitFor(data.runs[0].id);
|
|
36
|
+
console.log('Run output:', run?.output);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage in Next.js
|
|
40
|
+
|
|
41
|
+
### Server Actions
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// app/actions.ts
|
|
45
|
+
'use server'
|
|
46
|
+
|
|
47
|
+
import { createClient } from 'flowforge-client';
|
|
48
|
+
|
|
49
|
+
const ff = createClient(process.env.FLOWFORGE_URL!);
|
|
50
|
+
|
|
51
|
+
export async function submitOrder(formData: FormData) {
|
|
52
|
+
const { data, error } = await ff.events.send('order/created', {
|
|
53
|
+
customer: formData.get('customer'),
|
|
54
|
+
items: JSON.parse(formData.get('items') as string),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (error) throw new Error(error.message);
|
|
58
|
+
return { eventId: data.id, runId: data.runs[0]?.id };
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### API Routes
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// app/api/workflow/route.ts
|
|
66
|
+
import { createClient } from 'flowforge-client';
|
|
67
|
+
import { NextResponse } from 'next/server';
|
|
68
|
+
|
|
69
|
+
const ff = createClient(process.env.FLOWFORGE_URL!);
|
|
70
|
+
|
|
71
|
+
export async function POST(request: Request) {
|
|
72
|
+
const payload = await request.json();
|
|
73
|
+
const { data, error } = await ff.events.send('user/signup', payload);
|
|
74
|
+
|
|
75
|
+
if (error) {
|
|
76
|
+
return NextResponse.json({ error: error.message }, { status: error.status });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return NextResponse.json({
|
|
80
|
+
eventId: data.id,
|
|
81
|
+
runs: data.runs,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### Creating a Client
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { createClient } from 'flowforge-client';
|
|
92
|
+
|
|
93
|
+
// Simple URL
|
|
94
|
+
const ff = createClient('http://localhost:8000');
|
|
95
|
+
|
|
96
|
+
// With options
|
|
97
|
+
const ff = createClient('http://localhost:8000', {
|
|
98
|
+
apiKey: 'your-api-key', // Optional
|
|
99
|
+
tenantId: 'tenant-123', // Optional (multi-tenant)
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Events
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
// Send an event
|
|
107
|
+
const { data, error } = await ff.events.send('order/created', { order_id: '123' });
|
|
108
|
+
|
|
109
|
+
// With options
|
|
110
|
+
const { data } = await ff.events.send('order/created', { order_id: '123' }, {
|
|
111
|
+
id: 'custom-event-id',
|
|
112
|
+
user_id: 'user-456',
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Get an event
|
|
116
|
+
const { data: event } = await ff.events.get('event-id');
|
|
117
|
+
|
|
118
|
+
// Query events with filters
|
|
119
|
+
const { data: events } = await ff.events
|
|
120
|
+
.select()
|
|
121
|
+
.eq('name', 'order/*')
|
|
122
|
+
.limit(10)
|
|
123
|
+
.execute();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Runs
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// Get a run with its steps
|
|
130
|
+
const { data: run } = await ff.runs.get('run-id');
|
|
131
|
+
console.log(run.status); // 'pending' | 'running' | 'completed' | 'failed'
|
|
132
|
+
console.log(run.steps); // Array of step details
|
|
133
|
+
|
|
134
|
+
// Query runs with type-safe filters
|
|
135
|
+
const { data: runs } = await ff.runs
|
|
136
|
+
.select()
|
|
137
|
+
.eq('status', 'completed')
|
|
138
|
+
.eq('function_id', 'process-order')
|
|
139
|
+
.order('created_at', 'desc')
|
|
140
|
+
.limit(10)
|
|
141
|
+
.execute();
|
|
142
|
+
|
|
143
|
+
// Wait for completion (polling)
|
|
144
|
+
const { data: completedRun, error } = await ff.runs.waitFor('run-id', {
|
|
145
|
+
timeout: 60000, // 60 seconds
|
|
146
|
+
interval: 1000, // poll every second
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Cancel a run
|
|
150
|
+
await ff.runs.cancel('run-id');
|
|
151
|
+
|
|
152
|
+
// Replay a failed run
|
|
153
|
+
const { data: newRun } = await ff.runs.replay('run-id');
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Functions
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Query functions with type-safe filters
|
|
160
|
+
const { data: fns } = await ff.functions
|
|
161
|
+
.select()
|
|
162
|
+
.eq('trigger_type', 'event')
|
|
163
|
+
.eq('is_active', true)
|
|
164
|
+
.execute();
|
|
165
|
+
|
|
166
|
+
// Get function details
|
|
167
|
+
const { data: fn } = await ff.functions.get('process-order');
|
|
168
|
+
|
|
169
|
+
// Create a new function
|
|
170
|
+
const { data: created } = await ff.functions.create({
|
|
171
|
+
id: 'my-workflow',
|
|
172
|
+
name: 'My Workflow',
|
|
173
|
+
trigger_type: 'event',
|
|
174
|
+
trigger_value: 'order/*',
|
|
175
|
+
endpoint_url: 'http://localhost:3000/api/workflows/order',
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Update a function
|
|
179
|
+
await ff.functions.update('my-workflow', { is_active: false });
|
|
180
|
+
|
|
181
|
+
// Delete a function
|
|
182
|
+
await ff.functions.delete('my-workflow');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Tools
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Query tools
|
|
189
|
+
const { data: tools } = await ff.tools
|
|
190
|
+
.select()
|
|
191
|
+
.eq('requires_approval', true)
|
|
192
|
+
.eq('is_active', true)
|
|
193
|
+
.execute();
|
|
194
|
+
|
|
195
|
+
// Get a tool
|
|
196
|
+
const { data: tool } = await ff.tools.get('send-email');
|
|
197
|
+
|
|
198
|
+
// Create a tool
|
|
199
|
+
await ff.tools.create({
|
|
200
|
+
name: 'send-email',
|
|
201
|
+
description: 'Send an email to a recipient',
|
|
202
|
+
parameters: {
|
|
203
|
+
type: 'object',
|
|
204
|
+
properties: {
|
|
205
|
+
to: { type: 'string', description: 'Recipient email' },
|
|
206
|
+
subject: { type: 'string' },
|
|
207
|
+
body: { type: 'string' },
|
|
208
|
+
},
|
|
209
|
+
required: ['to', 'subject', 'body'],
|
|
210
|
+
},
|
|
211
|
+
requires_approval: true,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Update a tool
|
|
215
|
+
await ff.tools.update('send-email', { requires_approval: false });
|
|
216
|
+
|
|
217
|
+
// Delete a tool
|
|
218
|
+
await ff.tools.delete('my-tool');
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Approvals (Human-in-the-Loop)
|
|
222
|
+
|
|
223
|
+
```typescript
|
|
224
|
+
// Query pending approvals
|
|
225
|
+
const { data: pending } = await ff.approvals
|
|
226
|
+
.select()
|
|
227
|
+
.eq('status', 'pending')
|
|
228
|
+
.execute();
|
|
229
|
+
|
|
230
|
+
// Get approval details
|
|
231
|
+
const { data: approval } = await ff.approvals.get('approval-id');
|
|
232
|
+
|
|
233
|
+
// Approve a tool call
|
|
234
|
+
await ff.approvals.approve('approval-id');
|
|
235
|
+
|
|
236
|
+
// Approve with modified arguments
|
|
237
|
+
await ff.approvals.approve('approval-id', {
|
|
238
|
+
modifiedArguments: { amount: 100 },
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Reject a tool call
|
|
242
|
+
await ff.approvals.reject('approval-id', 'Amount too high');
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Health & Stats
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
// Check server health
|
|
249
|
+
const { data: healthy } = await ff.health.check();
|
|
250
|
+
if (healthy) console.log('Server is healthy');
|
|
251
|
+
|
|
252
|
+
// Get statistics
|
|
253
|
+
const { data: stats } = await ff.health.stats();
|
|
254
|
+
console.log(stats.runs.completed);
|
|
255
|
+
console.log(stats.queue.pending);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Error Handling
|
|
259
|
+
|
|
260
|
+
All methods return `{ data, error }` and never throw. This makes error handling explicit and type-safe:
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { createClient, FlowForgeError } from 'flowforge-client';
|
|
264
|
+
|
|
265
|
+
const ff = createClient('http://localhost:8000');
|
|
266
|
+
|
|
267
|
+
const { data, error } = await ff.events.send('order/created', {});
|
|
268
|
+
|
|
269
|
+
if (error) {
|
|
270
|
+
console.error('API Error:', error.message);
|
|
271
|
+
console.error('Status:', error.status);
|
|
272
|
+
console.error('Code:', error.code);
|
|
273
|
+
console.error('Details:', error.detail);
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## TypeScript Types
|
|
278
|
+
|
|
279
|
+
All types are exported for your convenience:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
import type {
|
|
283
|
+
// Core types
|
|
284
|
+
Run,
|
|
285
|
+
RunWithSteps,
|
|
286
|
+
RunStatus,
|
|
287
|
+
Step,
|
|
288
|
+
StepType,
|
|
289
|
+
StepStatus,
|
|
290
|
+
Event,
|
|
291
|
+
FlowForgeFunction,
|
|
292
|
+
Tool,
|
|
293
|
+
Approval,
|
|
294
|
+
Stats,
|
|
295
|
+
HealthStatus,
|
|
296
|
+
TriggerType,
|
|
297
|
+
ApprovalStatus,
|
|
298
|
+
|
|
299
|
+
// Filter types (for type-safe queries)
|
|
300
|
+
RunFilters,
|
|
301
|
+
FunctionFilters,
|
|
302
|
+
EventFilters,
|
|
303
|
+
ToolFilters,
|
|
304
|
+
ApprovalFilters,
|
|
305
|
+
|
|
306
|
+
// Input types
|
|
307
|
+
CreateFunctionInput,
|
|
308
|
+
UpdateFunctionInput,
|
|
309
|
+
CreateToolInput,
|
|
310
|
+
UpdateToolInput,
|
|
311
|
+
SendEventInput,
|
|
312
|
+
|
|
313
|
+
// Result type
|
|
314
|
+
Result,
|
|
315
|
+
FlowForgeError,
|
|
316
|
+
ClientOptions,
|
|
317
|
+
} from 'flowforge-client';
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Query Builder
|
|
321
|
+
|
|
322
|
+
The client uses a chainable query builder for filtering and pagination:
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Available methods
|
|
326
|
+
ff.runs
|
|
327
|
+
.select() // Start a query
|
|
328
|
+
.eq('status', 'completed') // Exact match filter
|
|
329
|
+
.order('created_at', 'desc') // Sort results
|
|
330
|
+
.limit(10) // Limit results
|
|
331
|
+
.offset(20) // Skip results (pagination)
|
|
332
|
+
.execute(); // Execute and return array
|
|
333
|
+
|
|
334
|
+
// Get a single result
|
|
335
|
+
const { data: run } = await ff.runs
|
|
336
|
+
.select()
|
|
337
|
+
.eq('function_id', 'my-fn')
|
|
338
|
+
.single();
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
MIT
|