@elqnt/workflow 2.1.3 → 2.2.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/README.md +6 -0
- package/SKILL.md +442 -0
- package/dist/api/index.d.mts +1 -1
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +3 -4
- package/dist/api/index.js.map +1 -1
- package/dist/api/index.mjs +1 -2
- package/dist/chunk-3GG6KTGJ.js +998 -0
- package/dist/chunk-3GG6KTGJ.js.map +1 -0
- package/dist/{chunk-UE4ZBFLG.mjs → chunk-4AUWFGQK.mjs} +1 -3
- package/dist/{chunk-UE4ZBFLG.mjs.map → chunk-4AUWFGQK.mjs.map} +1 -1
- package/dist/{chunk-H24IF5AA.js → chunk-6DV2LPSN.js} +2 -4
- package/dist/chunk-6DV2LPSN.js.map +1 -0
- package/dist/chunk-AXXQSYT7.mjs +998 -0
- package/dist/chunk-AXXQSYT7.mjs.map +1 -0
- package/dist/{chunk-YCEGKVXP.mjs → chunk-IY5JCMLA.mjs} +1 -3
- package/dist/{chunk-YCEGKVXP.mjs.map → chunk-IY5JCMLA.mjs.map} +1 -1
- package/dist/{chunk-JES2EBNO.js → chunk-YCO5IFZK.js} +2 -4
- package/dist/chunk-YCO5IFZK.js.map +1 -0
- package/dist/components/index.d.mts +245 -0
- package/dist/components/index.d.ts +245 -0
- package/dist/components/index.js +10 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +10 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/hooks/index.d.mts +106 -17
- package/dist/hooks/index.d.ts +106 -17
- package/dist/hooks/index.js +403 -5
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +404 -6
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +6 -247
- package/dist/index.d.ts +6 -247
- package/dist/index.js +8 -1006
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -1003
- package/dist/index.mjs.map +1 -1
- package/dist/models/index.d.mts +1 -1
- package/dist/models/index.d.ts +1 -1
- package/dist/models/index.js +3 -4
- package/dist/models/index.js.map +1 -1
- package/dist/models/index.mjs +1 -2
- package/dist/{workflow-NznrS9yA.d.ts → workflow-DcJCCEND.d.mts} +1 -1
- package/dist/{workflow-NznrS9yA.d.mts → workflow-DcJCCEND.d.ts} +1 -1
- package/package.json +12 -6
- package/dist/chunk-F5G2ALFS.js +0 -391
- package/dist/chunk-F5G2ALFS.js.map +0 -1
- package/dist/chunk-H24IF5AA.js.map +0 -1
- package/dist/chunk-JES2EBNO.js.map +0 -1
- package/dist/chunk-TZA3EPTC.mjs +0 -391
- package/dist/chunk-TZA3EPTC.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Workflow definitions and execution for Eloquent platform. Provides models, API functions, and React hooks for building and managing workflows.
|
|
4
4
|
|
|
5
|
+
> **📖 Building a custom frontend on the workflow backend?** Read
|
|
6
|
+
> [`SKILL.md`](./SKILL.md) — the authoritative, per-method contract for the
|
|
7
|
+
> `useWorkflows` / `useWorkflowInstances` / `useWorkflowTemplates` hooks, the
|
|
8
|
+
> gateway-token flow, and the recommended app structure. The package's published
|
|
9
|
+
> `.d.ts` is the source of truth; `SKILL.md` is its human-readable mirror.
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: workflow
|
|
3
|
+
description: Build a custom frontend on the Eloquent workflow backend using the @elqnt/workflow hooks. Covers the one true path (custom app → @elqnt/workflow hooks → API Gateway → workflow Go service), the gateway-token/secret flow, and the EXACT input/output spec of every hook method (useWorkflows, useWorkflowInstances, useWorkflowTemplates — definition CRUD, instance lifecycle, node execute/resume/retry, templates) plus all types.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workflow — Building a Custom Frontend
|
|
7
|
+
|
|
8
|
+
Eloquent's **workflow** service runs custom, multi-step processes: a
|
|
9
|
+
**definition** is a graph of typed nodes (trigger, human action, agent, action,
|
|
10
|
+
logic, loop, …); an **instance** is one live run of a definition; a **template**
|
|
11
|
+
is a parameterized definition you instantiate with variables. This skill is
|
|
12
|
+
**only** about one thing: building a custom app that drives it through the
|
|
13
|
+
`@elqnt/workflow` package.
|
|
14
|
+
|
|
15
|
+
> The package is `@elqnt/workflow`. It exports three hooks —
|
|
16
|
+
> `useWorkflows` (definition CRUD), `useWorkflowInstances` (instance lifecycle +
|
|
17
|
+
> node execution), and `useWorkflowTemplates` (template list/get/instantiate).
|
|
18
|
+
> It also ships the Redux store slices, the JSON schemas, the typed models, the
|
|
19
|
+
> browser API fns, and the `schema-builder` / `dynamic-schema-form` React
|
|
20
|
+
> components.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## ⛔ The contract — read this before writing any code
|
|
25
|
+
|
|
26
|
+
This skill ships **inside the package**, so the contract is not a separate file
|
|
27
|
+
to keep in sync — it **is** the package's own published type declarations
|
|
28
|
+
(`@elqnt/workflow` → `dist/**/*.d.ts`). Because your app *imports the real
|
|
29
|
+
hooks*, the TypeScript compiler enforces the contract for you: a wrong param or a
|
|
30
|
+
misused return value won't compile.
|
|
31
|
+
|
|
32
|
+
The single source of truth, in order:
|
|
33
|
+
|
|
34
|
+
1. `import { useWorkflows, useWorkflowInstances, useWorkflowTemplates } from "@elqnt/workflow/hooks"`
|
|
35
|
+
— the only three hooks.
|
|
36
|
+
2. `import type { UseWorkflowsReturn, UseWorkflowInstancesReturn, UseWorkflowTemplatesReturn } from "@elqnt/workflow/hooks"`
|
|
37
|
+
— the **named, exact** method surface of each hook (every method, its params,
|
|
38
|
+
its return type). The tables below are a human-readable mirror of these.
|
|
39
|
+
3. `import type { … } from "@elqnt/workflow/models"` — `WorkflowDefinition`,
|
|
40
|
+
`WorkflowInstance`, node/edge types, etc. (the DTOs).
|
|
41
|
+
4. `import type { WorkflowTemplate } from "@elqnt/workflow/api"` — the template
|
|
42
|
+
DTO (it lives in the api ring, not models).
|
|
43
|
+
|
|
44
|
+
**Rules (do not drift):**
|
|
45
|
+
|
|
46
|
+
1. **Use only the three exported hooks** and only the methods they expose. Do
|
|
47
|
+
**not** invent hooks, methods, params, or return shapes — if it's not on
|
|
48
|
+
`UseWorkflowsReturn` / `UseWorkflowInstancesReturn` / `UseWorkflowTemplatesReturn`,
|
|
49
|
+
it doesn't exist.
|
|
50
|
+
2. **Let the compiler check you.** Build with `tsc`; never `as any` /
|
|
51
|
+
`@ts-ignore` your way around a hook's types to force a call that isn't there.
|
|
52
|
+
3. **Never bypass the hooks.** No `fetch`/`axios` to `/api/v1/workflows/...`, no
|
|
53
|
+
NATS, no direct service calls. (Server-side only: `@elqnt/workflow/api` fns +
|
|
54
|
+
`createServerClient`, same signatures.)
|
|
55
|
+
4. **Wrap, don't scatter.** The hook is imported in exactly one app file
|
|
56
|
+
(`hooks/use-<domain>.ts`); see ["The domain layer"](#the-domain-layer).
|
|
57
|
+
5. If a requirement seems to need a call the package doesn't define, **stop and
|
|
58
|
+
flag it** — do not improvise an endpoint.
|
|
59
|
+
|
|
60
|
+
The prose/tables below explain each method; the package's shipped `.d.ts` is what
|
|
61
|
+
your code type-checks against.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Architecture — the one and only path
|
|
66
|
+
|
|
67
|
+
A component **never** calls a package hook directly. It only sees your **domain
|
|
68
|
+
type**, served by a **context**, backed by your **app hook**, which is the *only*
|
|
69
|
+
place that wraps the `@elqnt/workflow` hook. Everything below the app hook is
|
|
70
|
+
plumbing the component never imports.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
74
|
+
│ YOUR CUSTOM APP (Next.js / React) │
|
|
75
|
+
│ │
|
|
76
|
+
│ SSR layout: read API_GATEWAY_URL_PUBLIC (request-time) + ORG_ID │
|
|
77
|
+
│ └─► AppConfigProvider { apiGatewayUrl, orgId } │
|
|
78
|
+
│ │
|
|
79
|
+
│ components/workflows/* ── speak only your domain type │
|
|
80
|
+
│ │ useWorkflowsContext() │
|
|
81
|
+
│ ▼ │
|
|
82
|
+
│ contexts/workflows-context.tsx ── one shared app-hook instance │
|
|
83
|
+
│ │ │
|
|
84
|
+
│ ▼ │
|
|
85
|
+
│ hooks/use-app-workflows.ts ── app hook: CRUD + state, the only @elqnt import│
|
|
86
|
+
│ │ │
|
|
87
|
+
│ ▼ │
|
|
88
|
+
│ useWorkflows / useWorkflowInstances / useWorkflowTemplates (@elqnt/workflow/hooks)│
|
|
89
|
+
│ │ │
|
|
90
|
+
│ ▼ │
|
|
91
|
+
│ @elqnt/workflow/api → browserApiRequest │
|
|
92
|
+
│ │ getGatewayToken() ⇒ GET /api/gateway-token (mint HS256 JWT, JWT_SECRET)│
|
|
93
|
+
│ │ attaches: Authorization: Bearer <jwt>, X-Org-ID, X-User-ID, X-Product│
|
|
94
|
+
└─────────┼──────────────────────────────────────────────────────────────────────┘
|
|
95
|
+
▼
|
|
96
|
+
┌─────────────────┐ verify JWT, stamp X-Org-ID/X-Product, route match
|
|
97
|
+
│ API GATEWAY │ /api/v1/workflows/** → workflow svc
|
|
98
|
+
└───────┬─────────┘
|
|
99
|
+
▼
|
|
100
|
+
┌─────────────────┐ workflow definitions / instances; node execution engine
|
|
101
|
+
│ workflow (Go) │ per-org tenancy; emits SSE/NATS events for live nodes
|
|
102
|
+
└─────────────────┘
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Rules: the frontend **never** calls the workflow service directly and **never**
|
|
106
|
+
touches NATS. Every call is HTTP through the gateway carrying an org id + gateway
|
|
107
|
+
token. And **components never import `@elqnt/workflow`** — only the app hook does.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## The gateway token (the secret) — how the custom app gets it
|
|
112
|
+
|
|
113
|
+
Every request to the gateway needs `Authorization: Bearer <token>`. The token is
|
|
114
|
+
a short-lived **HS256 JWT** signed with the shared **gateway secret**. You never
|
|
115
|
+
hardcode it; there are two flows depending on where the code runs.
|
|
116
|
+
|
|
117
|
+
### Browser flow (what the hooks use)
|
|
118
|
+
|
|
119
|
+
The hooks → API fns → `browserApiRequest` → `getGatewayToken()` internally. You
|
|
120
|
+
do **not** pass a token to the hook. `getGatewayToken()` (from
|
|
121
|
+
`@elqnt/api-client/browser`) by default does:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
fetch("/api/gateway-token") ⇒ { token, expiresIn }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
So your custom app must expose a **`/api/gateway-token` route** that mints the JWT
|
|
128
|
+
server-side (the secret stays on the server, never reaches the browser). The
|
|
129
|
+
client caches the token and refreshes ~5 min before expiry.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// app/api/gateway-token/route.ts (Next.js route handler — server only)
|
|
133
|
+
import { NextResponse } from "next/server";
|
|
134
|
+
import * as jose from "jose";
|
|
135
|
+
|
|
136
|
+
export async function GET() {
|
|
137
|
+
const secret = new TextEncoder().encode(process.env.JWT_SECRET!); // SAME secret the gateway validates with
|
|
138
|
+
const token = await new jose.SignJWT({
|
|
139
|
+
org_id: process.env.ORG_ID!,
|
|
140
|
+
user_id: "system",
|
|
141
|
+
email: "system@my-app.com",
|
|
142
|
+
role: "system",
|
|
143
|
+
scopes: ["read:workflows", "write:workflows"], // OR-matched against the route's required scopes
|
|
144
|
+
product: "my-product", // gateway resolves product from THIS claim first
|
|
145
|
+
})
|
|
146
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
147
|
+
.setIssuedAt()
|
|
148
|
+
.setIssuer("eloquent-gateway") // must match gateway JWT_ISSUER
|
|
149
|
+
.setAudience("eloquent-api") // must match gateway JWT_AUDIENCE
|
|
150
|
+
.setExpirationTime("1h")
|
|
151
|
+
.sign(secret);
|
|
152
|
+
|
|
153
|
+
return NextResponse.json({ token, expiresIn: 3600 });
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
> The gateway re-stamps `X-Org-ID` / `X-User-ID` / `X-Product` from the **signed
|
|
158
|
+
> JWT claims** before proxying, so a forged header can't override the token — the
|
|
159
|
+
> JWT is authoritative. Routes declare required `scopes` (read routes need none;
|
|
160
|
+
> create/update/delete need `write:workflows`); the check is an **OR** match, and
|
|
161
|
+
> `admin`/`super_admin` role or a `*` scope bypasses it.
|
|
162
|
+
|
|
163
|
+
Override the token source (mobile/native, or a non-default URL) once at startup:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { configureAuth } from "@elqnt/api-client/browser";
|
|
167
|
+
configureAuth(async () => myTokenProvider()); // URL string or async () => token|null
|
|
168
|
+
// clearGatewayTokenCache() — call after switching orgs
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Server flow (server actions / SSR)
|
|
172
|
+
|
|
173
|
+
`@elqnt/api-client/server` mints the JWT itself with `jose` — no
|
|
174
|
+
`/api/gateway-token` hop. This is where the secret is injected directly:
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { createServerClient } from "@elqnt/api-client/server";
|
|
178
|
+
|
|
179
|
+
const client = createServerClient({
|
|
180
|
+
gatewayUrl: process.env.API_GATEWAY_URL_INTERNAL!, // in-cluster gateway URL (server-side)
|
|
181
|
+
jwtSecret: process.env.JWT_SECRET!, // the shared gateway secret
|
|
182
|
+
defaultProduct: "my-product", // REQUIRED: gateway reads product from the JWT claim first
|
|
183
|
+
defaultScopes: ["read", "write", "admin"],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await client.get("/api/v1/workflows", { orgId, userId });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
> The workflow **hooks are browser-only** (`"use client"`). For SSR/server
|
|
190
|
+
> actions call the API fns or `createServerClient` directly — not the hooks.
|
|
191
|
+
|
|
192
|
+
### Env vars
|
|
193
|
+
|
|
194
|
+
| Var | Used by | Purpose |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `JWT_SECRET` | `/api/gateway-token` route + `createServerClient` | sign the gateway JWT (same value the gateway validates with) |
|
|
197
|
+
| `API_GATEWAY_URL_INTERNAL` | `createServerClient` `gatewayUrl` | in-cluster gateway URL (server) |
|
|
198
|
+
| `API_GATEWAY_URL_PUBLIC` | SSR layout → `AppConfigProvider` → browser `baseUrl` | public gateway URL, read **at request time** (not `NEXT_PUBLIC_*`) |
|
|
199
|
+
| `ORG_ID` | token route / app config | the org all requests are scoped to |
|
|
200
|
+
|
|
201
|
+
### Headers the API layer sets per request
|
|
202
|
+
|
|
203
|
+
| From hook option | Header |
|
|
204
|
+
|---|---|
|
|
205
|
+
| auto token | `Authorization: Bearer <token>` |
|
|
206
|
+
| `orgId` | `X-Org-ID` |
|
|
207
|
+
| `userId` | `X-User-ID` |
|
|
208
|
+
| `userEmail` | `X-User-Email` |
|
|
209
|
+
| `product` (default `"eloquent"`) | `X-Product` |
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Hook options (all three hooks)
|
|
214
|
+
|
|
215
|
+
There is **no provider/context**. You pass options into each hook call. All three
|
|
216
|
+
take `ApiClientOptions`:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
interface ApiClientOptions {
|
|
220
|
+
baseUrl: string; // API Gateway base URL — required
|
|
221
|
+
orgId: string; // required → X-Org-ID
|
|
222
|
+
userId?: string; // → X-User-ID
|
|
223
|
+
userEmail?: string; // → X-User-Email
|
|
224
|
+
product?: string; // → X-Product, defaults to "eloquent"
|
|
225
|
+
headers?: Record<string, string>;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
type UseWorkflowsOptions = ApiClientOptions;
|
|
229
|
+
type UseWorkflowInstancesOptions = ApiClientOptions;
|
|
230
|
+
type UseWorkflowTemplatesOptions = ApiClientOptions;
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> **Imperative, not auto-fetching.** Every method is an `async` function you call
|
|
234
|
+
> on demand; there is **no** `useEffect`-on-mount inside the hook. Each hook
|
|
235
|
+
> exposes aggregate `loading` and `error` flags. On failure a method returns its
|
|
236
|
+
> default (`[]`, `null`, `false`) and sets `error` — it does **not** throw.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Hook: `useWorkflows` (definition CRUD)
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
import { useWorkflows } from "@elqnt/workflow/hooks";
|
|
244
|
+
|
|
245
|
+
const wf = useWorkflows({ baseUrl, orgId, product: "my-product" });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Returns `{ loading, error, ...methods }`:
|
|
249
|
+
|
|
250
|
+
| Method | Signature | Resolves to | Endpoint |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| `listWorkflows` | `() => Promise<WorkflowDefinition[]>` | `[]` on error | `GET /api/v1/workflows` |
|
|
253
|
+
| `getWorkflow` | `(workflowId: string) => Promise<WorkflowDefinition \| null>` | `null` | `GET /api/v1/workflows/{id}` |
|
|
254
|
+
| `createWorkflow` | `(workflow: Partial<WorkflowDefinition>) => Promise<WorkflowDefinition \| null>` | created definition | `POST /api/v1/workflows` |
|
|
255
|
+
| `updateWorkflow` | `(workflowId: string, workflow: Partial<WorkflowDefinition>) => Promise<WorkflowDefinition \| null>` | updated definition | `PUT /api/v1/workflows/{id}` |
|
|
256
|
+
| `deleteWorkflow` | `(workflowId: string) => Promise<boolean>` | `true`/`false` | `DELETE /api/v1/workflows/{id}` |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Hook: `useWorkflowInstances` (instance lifecycle + node execution)
|
|
261
|
+
|
|
262
|
+
```ts
|
|
263
|
+
import { useWorkflowInstances } from "@elqnt/workflow/hooks";
|
|
264
|
+
|
|
265
|
+
const inst = useWorkflowInstances({ baseUrl, orgId, product: "my-product" });
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Returns `{ loading, error, ...methods }`:
|
|
269
|
+
|
|
270
|
+
| Method | Signature | Resolves to | Endpoint |
|
|
271
|
+
|---|---|---|---|
|
|
272
|
+
| `listInstances` | `(definitionId: string, filters?: { userId?: string; status?: string }) => Promise<WorkflowInstance[]>` | `[]` | `GET /api/v1/workflows/{definitionId}/instances` (`?userId=&status=`) |
|
|
273
|
+
| `getInstance` | `(instanceId: string) => Promise<WorkflowInstance \| null>` | `null` | `GET /api/v1/workflows/instances/{id}` |
|
|
274
|
+
| `createInstance` | `(definitionId: string, data?: { variables?: Record<string, unknown>; autoExecute?: boolean }) => Promise<WorkflowInstance \| null>` | new instance | `POST /api/v1/workflows/{definitionId}/instances` |
|
|
275
|
+
| `updateStatus` | `(instanceId: string, status: string) => Promise<WorkflowInstance \| null>` | updated instance | `PUT /api/v1/workflows/instances/{id}/status` body `{ status }` |
|
|
276
|
+
| `executeNode` | `(instanceId: string, nodeId: string, input: Record<string, unknown>) => Promise<Record<string, unknown> \| null>` | node output map | `POST .../instances/{id}/nodes/{nodeId}/execute` body `{ input }` |
|
|
277
|
+
| `resumeNode` | `(instanceId: string, nodeId: string, result: Record<string, unknown>) => Promise<WorkflowInstance \| null>` | updated instance | `POST .../instances/{id}/nodes/{nodeId}/resume` body `{ result }` |
|
|
278
|
+
| `retryNode` | `(instanceId: string, nodeId: string) => Promise<WorkflowInstance \| null>` | updated instance | `POST .../instances/{id}/nodes/{nodeId}/retry` |
|
|
279
|
+
|
|
280
|
+
> `resumeNode` is the **human-in-the-loop** completion call: a paused
|
|
281
|
+
> `humanAction` node (review / approval / data-entry / assignment) resumes when
|
|
282
|
+
> you POST the human's `result`. `retryNode` re-runs a failed node.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## Hook: `useWorkflowTemplates` (template list / get / instantiate)
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { useWorkflowTemplates } from "@elqnt/workflow/hooks";
|
|
290
|
+
|
|
291
|
+
const tpl = useWorkflowTemplates({ baseUrl, orgId, product: "my-product" });
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Returns `{ loading, error, ...methods }`:
|
|
295
|
+
|
|
296
|
+
| Method | Signature | Resolves to | Endpoint |
|
|
297
|
+
|---|---|---|---|
|
|
298
|
+
| `listTemplates` | `(category?: string) => Promise<WorkflowTemplate[]>` | `[]` | `GET /api/v1/workflows/templates` (`?category=`) |
|
|
299
|
+
| `getTemplate` | `(templateId: string) => Promise<WorkflowTemplate \| null>` | `null` | `GET /api/v1/workflows/templates/{id}` |
|
|
300
|
+
| `instantiateTemplate` | `(templateId: string, params: { variables: Record<string, unknown>; title?: string }) => Promise<WorkflowDefinition \| null>` | new definition | `POST /api/v1/workflows/templates/{id}/instantiate` |
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
interface WorkflowTemplate {
|
|
304
|
+
id: string;
|
|
305
|
+
name: string;
|
|
306
|
+
title: string;
|
|
307
|
+
description: string;
|
|
308
|
+
category: string;
|
|
309
|
+
variables: Array<{ name: string; type: string; required: boolean; description?: string }>;
|
|
310
|
+
preview?: string;
|
|
311
|
+
createdAt: string;
|
|
312
|
+
updatedAt: string;
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Core models (`@elqnt/workflow/models`)
|
|
319
|
+
|
|
320
|
+
The definition/instance DTOs are tygo-generated from the Go service. The shapes
|
|
321
|
+
below are the load-bearing fields; the package's `.d.ts` is the full, authoritative
|
|
322
|
+
list.
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
interface WorkflowDefinition {
|
|
326
|
+
id: string;
|
|
327
|
+
title: string;
|
|
328
|
+
description: string;
|
|
329
|
+
nodes: WorkflowNode[]; // the graph
|
|
330
|
+
edges: WorkflowEdge[]; // connections between nodes
|
|
331
|
+
variables?: Record<string, unknown>;
|
|
332
|
+
status: string; // draft | active | …
|
|
333
|
+
version: number;
|
|
334
|
+
createdAt: string;
|
|
335
|
+
updatedAt: string;
|
|
336
|
+
// …plus trigger/metadata fields — see models/workflow.ts
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface WorkflowInstance {
|
|
340
|
+
id: string;
|
|
341
|
+
definitionId: string;
|
|
342
|
+
status: string; // running | paused | completed | failed | …
|
|
343
|
+
currentNodeIds: string[]; // nodes currently active / awaiting input
|
|
344
|
+
variables: Record<string, unknown>;
|
|
345
|
+
nodeStates: Record<string, unknown>; // per-node execution state
|
|
346
|
+
createdAt: string;
|
|
347
|
+
updatedAt: string;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Node types** (`NodeType` consts): `trigger`, `humanAction`, `agent`, `action`,
|
|
352
|
+
`logic`, `loop`, `parallel`, `delay`, `data`, `integration`, `timer`, `subflow`,
|
|
353
|
+
`custom`, `accounting`, `documentExtraction`. Each has a `NodeSubType` family
|
|
354
|
+
(e.g. `humanActionApproval`, `agentChat`, `actionSendEmail`,
|
|
355
|
+
`logicIf`/`logicSwitch`/`logicFor`). The full enumerations live in
|
|
356
|
+
`models/workflow.ts` — reference them by the exported consts, never string
|
|
357
|
+
literals.
|
|
358
|
+
|
|
359
|
+
---
|
|
360
|
+
|
|
361
|
+
## Store, schema & components (other rings)
|
|
362
|
+
|
|
363
|
+
- **Redux store** (`@elqnt/workflow`, root): `workflowDefinitionReducer` and
|
|
364
|
+
`workflowInstanceReducer` (+ their `*State` / `*Selector` types). Mount them in
|
|
365
|
+
your `configureStore`. These are how the editor UI holds in-progress
|
|
366
|
+
definition/instance state client-side.
|
|
367
|
+
- **JSON schemas** (`@elqnt/workflow`, root, via `./schema`): generated JSON
|
|
368
|
+
Schemas for node config validation.
|
|
369
|
+
- **Components** (`@elqnt/workflow/components`, also re-exported from root):
|
|
370
|
+
`SchemaBuilder` and `DynamicSchemaForm` — `"use client"` React building blocks
|
|
371
|
+
for editing node config schemas and rendering schema-driven forms.
|
|
372
|
+
|
|
373
|
+
---
|
|
374
|
+
|
|
375
|
+
## The domain layer
|
|
376
|
+
|
|
377
|
+
A real custom app stacks **models → app hook → context → components** on top of
|
|
378
|
+
the package hooks, so components never see `WorkflowDefinition`/`WorkflowInstance`
|
|
379
|
+
or the gateway directly.
|
|
380
|
+
|
|
381
|
+
| Layer | Where (your app) | Role |
|
|
382
|
+
|---|---|---|
|
|
383
|
+
| Components | `components/<domain>/*` | render; consume the context; see only the domain type |
|
|
384
|
+
| Context | `contexts/<domain>-context.tsx` | one shared app-hook instance for the subtree |
|
|
385
|
+
| App hook | `hooks/use-app-workflows.ts` | CRUD + state; **the only file importing `@elqnt/workflow`** |
|
|
386
|
+
| Domain type | `models/<domain>.ts` | the camelCase business type |
|
|
387
|
+
| Config | `contexts/app-config-context.tsx` | `{ apiGatewayUrl, orgId }` from SSR |
|
|
388
|
+
| — package — | `@elqnt/workflow/{hooks,api,models}` | the gateway client the app hook wraps |
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
// hooks/use-app-workflows.ts — the ONLY file that imports @elqnt/workflow
|
|
392
|
+
"use client";
|
|
393
|
+
import { useMemo } from "react";
|
|
394
|
+
import { useWorkflows, useWorkflowInstances } from "@elqnt/workflow/hooks";
|
|
395
|
+
import { useAppConfig } from "@/contexts/app-config-context";
|
|
396
|
+
|
|
397
|
+
export function useAppWorkflows() {
|
|
398
|
+
const { apiGatewayUrl, orgId } = useAppConfig();
|
|
399
|
+
const defs = useWorkflows({ baseUrl: apiGatewayUrl, orgId, product: "my-product" });
|
|
400
|
+
const inst = useWorkflowInstances({ baseUrl: apiGatewayUrl, orgId, product: "my-product" });
|
|
401
|
+
|
|
402
|
+
return useMemo(() => ({
|
|
403
|
+
loading: defs.loading || inst.loading,
|
|
404
|
+
error: defs.error ?? inst.error,
|
|
405
|
+
listWorkflows: defs.listWorkflows,
|
|
406
|
+
startRun: (definitionId: string) => inst.createInstance(definitionId, { autoExecute: true }),
|
|
407
|
+
approve: (instanceId: string, nodeId: string, result: Record<string, unknown>) =>
|
|
408
|
+
inst.resumeNode(instanceId, nodeId, result),
|
|
409
|
+
}), [defs, inst]);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export type UseAppWorkflowsReturn = ReturnType<typeof useAppWorkflows>;
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
Read the gateway URL **server-side at request time from
|
|
416
|
+
`API_GATEWAY_URL_PUBLIC`** and forward it through `AppConfigProvider` — do **not**
|
|
417
|
+
use a `NEXT_PUBLIC_*` var (those bake the build-env URL into the bundle forever).
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Gotchas
|
|
422
|
+
|
|
423
|
+
- **Three hooks, reached only via `@elqnt/workflow/hooks`.** The root barrel
|
|
424
|
+
re-exports models / api / store / schema / components — **never** the hooks
|
|
425
|
+
(rule 1, the root-barrel rule). Import hooks from the `/hooks` subpath so
|
|
426
|
+
`"use client"` is never dragged into a server-component import.
|
|
427
|
+
- **Methods don't throw.** They return defaults (`[]` / `null` / `false`) and set
|
|
428
|
+
`error`. Check `error` / null results; don't wrap in try/catch expecting throws.
|
|
429
|
+
- **The package has no provider.** Options go into every hook call — which is why
|
|
430
|
+
your app builds `AppConfigProvider` (inject `baseUrl`/`orgId` once via SSR) and
|
|
431
|
+
the domain context, so components and the app hook never re-thread config.
|
|
432
|
+
- **Components never import `@elqnt/workflow`.** It's wrapped in one app hook;
|
|
433
|
+
components speak the domain type via the context.
|
|
434
|
+
- **`product` matters.** The gateway resolves product from the **JWT claim**
|
|
435
|
+
first (server) and `X-Product` (browser). Mismatched product = wrong tenancy.
|
|
436
|
+
- **`WorkflowTemplate` lives in `@elqnt/workflow/api`, not `/models`.** The
|
|
437
|
+
template DTO + its responses are defined alongside the API fns.
|
|
438
|
+
- **Human-in-the-loop = `resumeNode`.** A paused `humanAction` node resumes only
|
|
439
|
+
when you POST the human's `result` via `resumeNode`; `retryNode` re-runs a
|
|
440
|
+
failed node. Use the node's `id` from the instance's `currentNodeIds`.
|
|
441
|
+
- **Hooks are browser-only.** For SSR / server actions call the `@elqnt/workflow/api`
|
|
442
|
+
fns (or `createServerClient`) directly — same signatures, no React.
|
package/dist/api/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClientOptions, ApiResponse } from '@elqnt/api-client';
|
|
2
2
|
import { ResponseMetadata } from '@elqnt/types';
|
|
3
|
-
import {
|
|
3
|
+
import { W as WorkflowDefinition, a as WorkflowDefinitionResponse, b as WorkflowInstanceResponse, L as ListWorkflowInstancesResponse, c as ListWorkflowDefinitionsResponse } from '../workflow-DcJCCEND.mjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Workflow API functions
|
package/dist/api/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ApiClientOptions, ApiResponse } from '@elqnt/api-client';
|
|
2
2
|
import { ResponseMetadata } from '@elqnt/types';
|
|
3
|
-
import {
|
|
3
|
+
import { W as WorkflowDefinition, a as WorkflowDefinitionResponse, b as WorkflowInstanceResponse, L as ListWorkflowInstancesResponse, c as ListWorkflowDefinitionsResponse } from '../workflow-DcJCCEND.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Workflow API functions
|
package/dist/api/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
var _chunkYCO5IFZKjs = require('../chunk-YCO5IFZK.js');
|
|
17
18
|
|
|
18
|
-
var _chunkJES2EBNOjs = require('../chunk-JES2EBNO.js');
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
|
|
@@ -31,6 +31,5 @@ var _chunkJES2EBNOjs = require('../chunk-JES2EBNO.js');
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
exports.createWorkflowApi = _chunkJES2EBNOjs.createWorkflowApi; exports.createWorkflowInstanceApi = _chunkJES2EBNOjs.createWorkflowInstanceApi; exports.deleteWorkflowApi = _chunkJES2EBNOjs.deleteWorkflowApi; exports.executeWorkflowNodeApi = _chunkJES2EBNOjs.executeWorkflowNodeApi; exports.getWorkflowApi = _chunkJES2EBNOjs.getWorkflowApi; exports.getWorkflowInstanceApi = _chunkJES2EBNOjs.getWorkflowInstanceApi; exports.getWorkflowTemplateApi = _chunkJES2EBNOjs.getWorkflowTemplateApi; exports.instantiateWorkflowTemplateApi = _chunkJES2EBNOjs.instantiateWorkflowTemplateApi; exports.listWorkflowInstancesApi = _chunkJES2EBNOjs.listWorkflowInstancesApi; exports.listWorkflowTemplatesApi = _chunkJES2EBNOjs.listWorkflowTemplatesApi; exports.listWorkflowsApi = _chunkJES2EBNOjs.listWorkflowsApi; exports.resumeWorkflowNodeApi = _chunkJES2EBNOjs.resumeWorkflowNodeApi; exports.retryWorkflowNodeApi = _chunkJES2EBNOjs.retryWorkflowNodeApi; exports.updateWorkflowApi = _chunkJES2EBNOjs.updateWorkflowApi; exports.updateWorkflowInstanceStatusApi = _chunkJES2EBNOjs.updateWorkflowInstanceStatusApi;
|
|
34
|
+
exports.createWorkflowApi = _chunkYCO5IFZKjs.createWorkflowApi; exports.createWorkflowInstanceApi = _chunkYCO5IFZKjs.createWorkflowInstanceApi; exports.deleteWorkflowApi = _chunkYCO5IFZKjs.deleteWorkflowApi; exports.executeWorkflowNodeApi = _chunkYCO5IFZKjs.executeWorkflowNodeApi; exports.getWorkflowApi = _chunkYCO5IFZKjs.getWorkflowApi; exports.getWorkflowInstanceApi = _chunkYCO5IFZKjs.getWorkflowInstanceApi; exports.getWorkflowTemplateApi = _chunkYCO5IFZKjs.getWorkflowTemplateApi; exports.instantiateWorkflowTemplateApi = _chunkYCO5IFZKjs.instantiateWorkflowTemplateApi; exports.listWorkflowInstancesApi = _chunkYCO5IFZKjs.listWorkflowInstancesApi; exports.listWorkflowTemplatesApi = _chunkYCO5IFZKjs.listWorkflowTemplatesApi; exports.listWorkflowsApi = _chunkYCO5IFZKjs.listWorkflowsApi; exports.resumeWorkflowNodeApi = _chunkYCO5IFZKjs.resumeWorkflowNodeApi; exports.retryWorkflowNodeApi = _chunkYCO5IFZKjs.retryWorkflowNodeApi; exports.updateWorkflowApi = _chunkYCO5IFZKjs.updateWorkflowApi; exports.updateWorkflowInstanceStatusApi = _chunkYCO5IFZKjs.updateWorkflowInstanceStatusApi;
|
|
36
35
|
//# sourceMappingURL=index.js.map
|
package/dist/api/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/eloquent/eloquent/packages/@elqnt/workflow/dist/api/index.js"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/eloquent/eloquent/packages/@elqnt/workflow/dist/api/index.js"],"names":[],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,uDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,qkCAAC","file":"/home/runner/work/eloquent/eloquent/packages/@elqnt/workflow/dist/api/index.js"}
|
package/dist/api/index.mjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use client";
|
|
2
1
|
import {
|
|
3
2
|
createWorkflowApi,
|
|
4
3
|
createWorkflowInstanceApi,
|
|
@@ -15,7 +14,7 @@ import {
|
|
|
15
14
|
retryWorkflowNodeApi,
|
|
16
15
|
updateWorkflowApi,
|
|
17
16
|
updateWorkflowInstanceStatusApi
|
|
18
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-4AUWFGQK.mjs";
|
|
19
18
|
export {
|
|
20
19
|
createWorkflowApi,
|
|
21
20
|
createWorkflowInstanceApi,
|