@cueapi/cuechain 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/dist/index.cjs +193 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +186 -0
- package/dist/index.d.ts +186 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vector Apps Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# @cueapi/cuechain
|
|
2
|
+
|
|
3
|
+
> Cuechain is not an LLM chaining framework. It is a contract verification primitive for TypeScript pipelines.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/cueapi/cuechain/actions)
|
|
6
|
+
[](https://www.npmjs.com/package/@cueapi/cuechain)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
## The Problem
|
|
10
|
+
|
|
11
|
+
Imagine you ask a robot to bake a cake in six steps. At step 1, the robot grabs a tennis ball instead of an egg. It doesn't notice. It "cracks" the tennis ball, adds flour, mixes, pours, bakes, and announces "cake is done." From the outside, everything looks fine. A scheduler would record the execution as successful. But what's on the table is not a cake.
|
|
12
|
+
|
|
13
|
+
Every non-trivial AI agent workflow is a chain of steps where one step's output feeds the next step's input. LLMs are non-deterministic. They return JSON that almost matches the schema, fields that are the right shape but wrong content, arrays with the wrong length. Without verification at every handoff, bad data propagates silently through the pipeline and surfaces far from its root cause.
|
|
14
|
+
|
|
15
|
+
**Cuechain catches the tennis ball at step 1.** It validates contracts between every step in a pipeline, runs quality gates on outputs, and feeds failure context back into retries so LLM-driven steps can self-correct.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @cueapi/cuechain zod
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Zod is a peer dependency. You probably already have it.
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { z } from 'zod'
|
|
29
|
+
import { defineStep, pipeline } from '@cueapi/cuechain'
|
|
30
|
+
|
|
31
|
+
// Step 1: Load data
|
|
32
|
+
const loadData = defineStep({
|
|
33
|
+
name: 'load-data',
|
|
34
|
+
input: z.object({ source: z.string() }),
|
|
35
|
+
output: z.object({ text: z.string(), wordCount: z.number() }),
|
|
36
|
+
run: async (input) => {
|
|
37
|
+
const text = `Content from ${input.source}`
|
|
38
|
+
return { text, wordCount: text.split(/\s+/).length }
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Step 2: Summarize (with a gate and retry)
|
|
43
|
+
const summarize = defineStep({
|
|
44
|
+
name: 'summarize',
|
|
45
|
+
input: z.object({ text: z.string(), wordCount: z.number() }),
|
|
46
|
+
output: z.object({ summary: z.string() }),
|
|
47
|
+
gates: [
|
|
48
|
+
(out) =>
|
|
49
|
+
out.summary.length <= 200
|
|
50
|
+
? { ok: true }
|
|
51
|
+
: { ok: false, reason: `Summary too long: ${out.summary.length} chars` },
|
|
52
|
+
],
|
|
53
|
+
retry: { maxAttempts: 3, on: ['gate'] },
|
|
54
|
+
run: async (input, failureContext) => {
|
|
55
|
+
if (failureContext) {
|
|
56
|
+
// Use failure context to self-correct
|
|
57
|
+
return { summary: input.text.slice(0, 100) }
|
|
58
|
+
}
|
|
59
|
+
return { summary: input.text }
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// Step 3: Format
|
|
64
|
+
const format = defineStep({
|
|
65
|
+
name: 'format',
|
|
66
|
+
input: z.object({ summary: z.string() }),
|
|
67
|
+
output: z.object({ formatted: z.string() }),
|
|
68
|
+
run: async (input) => ({ formatted: `RESULT: ${input.summary}` }),
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// Compose and run
|
|
72
|
+
const myPipeline = pipeline('my-pipeline')
|
|
73
|
+
.step(loadData)
|
|
74
|
+
.step(summarize)
|
|
75
|
+
.step(format)
|
|
76
|
+
|
|
77
|
+
const result = await myPipeline.run({ source: 'doc.md' })
|
|
78
|
+
|
|
79
|
+
if (result.ok) {
|
|
80
|
+
console.log(result.value.formatted)
|
|
81
|
+
} else {
|
|
82
|
+
console.error(`Failed at ${result.failure.step}: ${result.failure.reason}`)
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## The Five Primitives
|
|
87
|
+
|
|
88
|
+
### 1. Typed Steps
|
|
89
|
+
|
|
90
|
+
A step has an input schema, an output schema (both Zod), and an async `run` function. The runtime validates input before running and output after.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
const extractTitle = defineStep({
|
|
94
|
+
name: 'extract-title',
|
|
95
|
+
input: z.object({ draft: z.string() }),
|
|
96
|
+
output: z.object({ title: z.string().max(80) }),
|
|
97
|
+
run: async (input) => {
|
|
98
|
+
const title = await callLLM(`Extract a title from: ${input.draft}`)
|
|
99
|
+
return { title }
|
|
100
|
+
},
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 2. Pipelines
|
|
105
|
+
|
|
106
|
+
A pipeline chains steps where each step's output feeds the next step's input. TypeScript enforces type compatibility at compile time. Runtime validates at every handoff.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const draft = pipeline('draft-monthly')
|
|
110
|
+
.step(loadProfile) // { month: string } -> { profile: Profile }
|
|
111
|
+
.step(generateDraft) // { profile: Profile } -> { draft: string }
|
|
112
|
+
.step(extractTitle) // { draft: string } -> { title: string }
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Mismatched types fail the build, not the runtime.
|
|
116
|
+
|
|
117
|
+
### 3. Quality Gates
|
|
118
|
+
|
|
119
|
+
A gate is a pure synchronous function that checks a step's output beyond what a schema can express.
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const step = defineStep({
|
|
123
|
+
name: 'validate-draft',
|
|
124
|
+
input: z.object({ draft: z.string() }),
|
|
125
|
+
output: z.object({ draft: z.string(), charCount: z.number() }),
|
|
126
|
+
gates: [
|
|
127
|
+
(out) =>
|
|
128
|
+
out.charCount <= 280
|
|
129
|
+
? { ok: true }
|
|
130
|
+
: { ok: false, reason: `Draft is ${out.charCount} chars, max 280` },
|
|
131
|
+
(out) =>
|
|
132
|
+
!out.draft.includes('<script>')
|
|
133
|
+
? { ok: true }
|
|
134
|
+
: { ok: false, reason: 'Draft contains unsafe HTML' },
|
|
135
|
+
],
|
|
136
|
+
run: async (input) => ({
|
|
137
|
+
draft: input.draft,
|
|
138
|
+
charCount: input.draft.length,
|
|
139
|
+
}),
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Gates are pure code, not LLM calls. Deterministic verification only.
|
|
144
|
+
|
|
145
|
+
### 4. Retry With Failure Context
|
|
146
|
+
|
|
147
|
+
When a step fails, Cuechain retries it with the failure reason passed as a second argument. For LLM-driven steps, this means the next prompt can include why the previous attempt failed.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const step = defineStep({
|
|
151
|
+
name: 'generate',
|
|
152
|
+
input: z.object({ topic: z.string() }),
|
|
153
|
+
output: z.object({ text: z.string() }),
|
|
154
|
+
gates: [(out) => out.text.length < 500 ? { ok: true } : { ok: false, reason: 'too long' }],
|
|
155
|
+
retry: { maxAttempts: 3, on: ['schema', 'gate'] },
|
|
156
|
+
run: async (input, failureContext) => {
|
|
157
|
+
const prompt = failureContext
|
|
158
|
+
? `Generate text about ${input.topic}. Previous attempt failed: ${failureContext.reason}`
|
|
159
|
+
: `Generate text about ${input.topic}`
|
|
160
|
+
return { text: await callLLM(prompt) }
|
|
161
|
+
},
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`failureContext` includes `{ reason, attempt, type }` where `type` is `'schema'`, `'gate'`, or `'exception'`.
|
|
166
|
+
|
|
167
|
+
### 5. Structured Failures
|
|
168
|
+
|
|
169
|
+
Pipelines return `Result<T>` — either success with a value, or a structured failure identifying exactly what went wrong.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const result = await myPipeline.run(input)
|
|
173
|
+
|
|
174
|
+
if (!result.ok) {
|
|
175
|
+
result.failure.step // which step failed
|
|
176
|
+
result.failure.reason // human-readable reason
|
|
177
|
+
result.failure.type // 'schema_input' | 'schema_output' | 'gate' | 'exception'
|
|
178
|
+
result.failure.attempts // how many attempts were made
|
|
179
|
+
result.failure.input // what the step received
|
|
180
|
+
result.failure.output // what the step produced (if it got that far)
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Prefer thrown exceptions? Use `.runOrThrow()`:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
try {
|
|
188
|
+
const value = await myPipeline.runOrThrow(input)
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (error instanceof PipelineError) {
|
|
191
|
+
console.error(error.failure) // same structured failure
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Introspection
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
const desc = myPipeline.describe()
|
|
200
|
+
// {
|
|
201
|
+
// name: 'my-pipeline',
|
|
202
|
+
// steps: [
|
|
203
|
+
// { name: 'load-data', inputSchema: {...}, outputSchema: {...}, gates: 0, retry: {...} },
|
|
204
|
+
// { name: 'summarize', inputSchema: {...}, outputSchema: {...}, gates: 1, retry: {...} },
|
|
205
|
+
// ]
|
|
206
|
+
// }
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Schemas are serialized as JSON Schema via `zod-to-json-schema`.
|
|
210
|
+
|
|
211
|
+
## Cuechain + CueAPI
|
|
212
|
+
|
|
213
|
+
Cuechain verifies contracts between steps. [CueAPI](https://cueapi.ai) verifies outcomes against reality. Use one, use both.
|
|
214
|
+
|
|
215
|
+
**Standalone Cuechain:** run a contract-verified pipeline from any trigger — a button click, an HTTP handler, a test, a local script. No infrastructure required.
|
|
216
|
+
|
|
217
|
+
**Standalone CueAPI:** schedule a cue that fires a webhook. The handler does whatever it wants internally. No Cuechain required.
|
|
218
|
+
|
|
219
|
+
**Composed:** CueAPI fires on schedule, the handler invokes a Cuechain pipeline, the pipeline runs contract-verified steps and returns a structured result, the handler reports that result back to CueAPI as the outcome. Outcome verification at the temporal boundary, contract verification at the data boundary.
|
|
220
|
+
|
|
221
|
+
Cuechain does not import CueAPI. The relationship is compositional, not architectural.
|
|
222
|
+
|
|
223
|
+
## What Cuechain Is Not
|
|
224
|
+
|
|
225
|
+
- **Not a scheduler.** That's [CueAPI](https://cueapi.ai).
|
|
226
|
+
- **Not a durable execution engine.** That's Inngest, Trigger.dev, Temporal.
|
|
227
|
+
- **Not a functional effects system.** That's Effect-TS.
|
|
228
|
+
- **Not an LLM framework.** That's LangChain, LangGraph, Mastra. Cuechain runs any async function as a step.
|
|
229
|
+
- **Not a state machine.** That's XState.
|
|
230
|
+
- **Not a UI, dashboard, or hosted service.** It's a library. Zero infrastructure.
|
|
231
|
+
|
|
232
|
+
Narrow scope is the product.
|
|
233
|
+
|
|
234
|
+
## API
|
|
235
|
+
|
|
236
|
+
### `defineStep(config)`
|
|
237
|
+
|
|
238
|
+
Define a pipeline step.
|
|
239
|
+
|
|
240
|
+
| Field | Type | Required | Description |
|
|
241
|
+
|-------|------|----------|-------------|
|
|
242
|
+
| `name` | `string` | Yes | Step identifier |
|
|
243
|
+
| `input` | `z.ZodType` | Yes | Input schema |
|
|
244
|
+
| `output` | `z.ZodType` | Yes | Output schema |
|
|
245
|
+
| `run` | `(input, failureContext?) => Promise<output>` | Yes | Step function |
|
|
246
|
+
| `gates` | `Gate[]` | No | Quality gate functions |
|
|
247
|
+
| `retry` | `{ maxAttempts?, on? }` | No | Retry config |
|
|
248
|
+
|
|
249
|
+
### `pipeline(name)`
|
|
250
|
+
|
|
251
|
+
Create a pipeline builder. Chain `.step()` to add steps.
|
|
252
|
+
|
|
253
|
+
### `Pipeline.run(input)`
|
|
254
|
+
|
|
255
|
+
Returns `Result<T>` — `{ ok: true, value }` or `{ ok: false, failure }`.
|
|
256
|
+
|
|
257
|
+
### `Pipeline.runOrThrow(input)`
|
|
258
|
+
|
|
259
|
+
Returns the value directly. Throws `PipelineError` on failure.
|
|
260
|
+
|
|
261
|
+
### `Pipeline.describe()`
|
|
262
|
+
|
|
263
|
+
Returns `PipelineDescription` with step metadata and JSON Schemas.
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
MIT. Vector Apps Inc.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var zodToJsonSchema = require('zod-to-json-schema');
|
|
4
|
+
|
|
5
|
+
// src/step.ts
|
|
6
|
+
function defineStep(config) {
|
|
7
|
+
return {
|
|
8
|
+
name: config.name,
|
|
9
|
+
inputSchema: config.input,
|
|
10
|
+
outputSchema: config.output,
|
|
11
|
+
gates: config.gates ?? [],
|
|
12
|
+
retry: {
|
|
13
|
+
maxAttempts: config.retry?.maxAttempts ?? 1,
|
|
14
|
+
on: config.retry?.on ?? ["schema", "gate"]
|
|
15
|
+
},
|
|
16
|
+
run: config.run
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/runner.ts
|
|
21
|
+
function formatZodError(error) {
|
|
22
|
+
return error.issues.map((issue) => {
|
|
23
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
24
|
+
return `${path}${issue.message}`;
|
|
25
|
+
}).join("; ");
|
|
26
|
+
}
|
|
27
|
+
function runGates(gates, output) {
|
|
28
|
+
for (const gate of gates) {
|
|
29
|
+
const result = gate(output);
|
|
30
|
+
if (!result.ok) {
|
|
31
|
+
return { reason: result.reason, context: result.context };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
async function executeStep(step, input) {
|
|
37
|
+
const inputResult = step.inputSchema.safeParse(input);
|
|
38
|
+
if (!inputResult.success) {
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
failure: {
|
|
42
|
+
step: step.name,
|
|
43
|
+
reason: `Input validation failed: ${formatZodError(inputResult.error)}`,
|
|
44
|
+
type: "schema_input",
|
|
45
|
+
attempts: 0,
|
|
46
|
+
input
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const validatedInput = inputResult.data;
|
|
51
|
+
const maxAttempts = step.retry.maxAttempts;
|
|
52
|
+
const retryOn = new Set(step.retry.on);
|
|
53
|
+
let failureContext;
|
|
54
|
+
let lastFailure;
|
|
55
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
56
|
+
try {
|
|
57
|
+
const rawOutput = await step.run(validatedInput, failureContext);
|
|
58
|
+
const outputResult = step.outputSchema.safeParse(rawOutput);
|
|
59
|
+
if (!outputResult.success) {
|
|
60
|
+
const reason = `Output validation failed: ${formatZodError(outputResult.error)}`;
|
|
61
|
+
lastFailure = {
|
|
62
|
+
step: step.name,
|
|
63
|
+
reason,
|
|
64
|
+
type: "schema_output",
|
|
65
|
+
attempts: attempt,
|
|
66
|
+
input: validatedInput,
|
|
67
|
+
output: rawOutput
|
|
68
|
+
};
|
|
69
|
+
if (attempt < maxAttempts && retryOn.has("schema")) {
|
|
70
|
+
failureContext = { reason, attempt, type: "schema" };
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
return { ok: false, failure: lastFailure };
|
|
74
|
+
}
|
|
75
|
+
const validatedOutput = outputResult.data;
|
|
76
|
+
const gateFailure = runGates(step.gates, validatedOutput);
|
|
77
|
+
if (gateFailure) {
|
|
78
|
+
lastFailure = {
|
|
79
|
+
step: step.name,
|
|
80
|
+
reason: `Gate failed: ${gateFailure.reason}`,
|
|
81
|
+
type: "gate",
|
|
82
|
+
attempts: attempt,
|
|
83
|
+
input: validatedInput,
|
|
84
|
+
output: validatedOutput
|
|
85
|
+
};
|
|
86
|
+
if (attempt < maxAttempts && retryOn.has("gate")) {
|
|
87
|
+
failureContext = {
|
|
88
|
+
reason: gateFailure.reason,
|
|
89
|
+
attempt,
|
|
90
|
+
type: "gate"
|
|
91
|
+
};
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
return { ok: false, failure: lastFailure };
|
|
95
|
+
}
|
|
96
|
+
return { ok: true, value: validatedOutput };
|
|
97
|
+
} catch (error) {
|
|
98
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
99
|
+
lastFailure = {
|
|
100
|
+
step: step.name,
|
|
101
|
+
reason: `Exception: ${reason}`,
|
|
102
|
+
type: "exception",
|
|
103
|
+
attempts: attempt,
|
|
104
|
+
input: validatedInput
|
|
105
|
+
};
|
|
106
|
+
if (attempt < maxAttempts && retryOn.has("exception")) {
|
|
107
|
+
failureContext = { reason, attempt, type: "exception" };
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
return { ok: false, failure: lastFailure };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
failure: lastFailure ?? {
|
|
116
|
+
step: step.name,
|
|
117
|
+
reason: "Unknown failure",
|
|
118
|
+
type: "exception",
|
|
119
|
+
attempts: maxAttempts,
|
|
120
|
+
input: validatedInput
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/types.ts
|
|
126
|
+
var PipelineError = class extends Error {
|
|
127
|
+
constructor(failure) {
|
|
128
|
+
super(`Pipeline failed at step "${failure.step}": ${failure.reason}`);
|
|
129
|
+
this.failure = failure;
|
|
130
|
+
this.name = "PipelineError";
|
|
131
|
+
}
|
|
132
|
+
failure;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// src/pipeline.ts
|
|
136
|
+
var PipelineImpl = class _PipelineImpl {
|
|
137
|
+
constructor(name, steps) {
|
|
138
|
+
this.name = name;
|
|
139
|
+
this.steps = steps;
|
|
140
|
+
}
|
|
141
|
+
name;
|
|
142
|
+
steps;
|
|
143
|
+
step(newStep) {
|
|
144
|
+
return new _PipelineImpl(this.name, [
|
|
145
|
+
...this.steps,
|
|
146
|
+
newStep
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
async run(input) {
|
|
150
|
+
let current = input;
|
|
151
|
+
for (const step of this.steps) {
|
|
152
|
+
const result = await executeStep(step, current);
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
current = result.value;
|
|
157
|
+
}
|
|
158
|
+
return { ok: true, value: current };
|
|
159
|
+
}
|
|
160
|
+
async runOrThrow(input) {
|
|
161
|
+
const result = await this.run(input);
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
throw new PipelineError(result.failure);
|
|
164
|
+
}
|
|
165
|
+
return result.value;
|
|
166
|
+
}
|
|
167
|
+
describe() {
|
|
168
|
+
return {
|
|
169
|
+
name: this.name,
|
|
170
|
+
steps: this.steps.map((step) => ({
|
|
171
|
+
name: step.name,
|
|
172
|
+
inputSchema: zodToJsonSchema.zodToJsonSchema(step.inputSchema),
|
|
173
|
+
outputSchema: zodToJsonSchema.zodToJsonSchema(step.outputSchema),
|
|
174
|
+
gates: step.gates.length,
|
|
175
|
+
retry: step.retry
|
|
176
|
+
}))
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
function pipeline(name) {
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
step(firstStep) {
|
|
184
|
+
return new PipelineImpl(name, [firstStep]);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
exports.PipelineError = PipelineError;
|
|
190
|
+
exports.defineStep = defineStep;
|
|
191
|
+
exports.pipeline = pipeline;
|
|
192
|
+
//# sourceMappingURL=index.cjs.map
|
|
193
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/step.ts","../src/runner.ts","../src/types.ts","../src/pipeline.ts"],"names":["zodToJsonSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { z } from 'zod'\nimport type { Step, StepConfig } from './types.js'\n\n/**\n * Define a pipeline step with typed input/output schemas, optional quality gates,\n * and retry configuration.\n *\n * @example\n * ```ts\n * const extractTitle = defineStep({\n * name: 'extract-title',\n * input: z.object({ draft: z.string() }),\n * output: z.object({ title: z.string().max(80) }),\n * gates: [\n * (out) => out.title.includes(':')\n * ? { ok: false, reason: 'title must not contain colons' }\n * : { ok: true }\n * ],\n * retry: { maxAttempts: 3, on: ['schema', 'gate'] },\n * run: async (input, failureContext) => {\n * return { title: 'extracted title' };\n * }\n * });\n * ```\n */\nexport function defineStep<TInput, TOutput>(\n config: StepConfig<TInput, TOutput>,\n): Step<TInput, TOutput> {\n return {\n name: config.name,\n inputSchema: config.input as z.ZodType<TInput>,\n outputSchema: config.output as z.ZodType<TOutput>,\n gates: config.gates ?? [],\n retry: {\n maxAttempts: config.retry?.maxAttempts ?? 1,\n on: config.retry?.on ?? ['schema', 'gate'],\n },\n run: config.run,\n }\n}\n","import type { ZodError } from 'zod'\nimport type { FailureContext, Failure, Gate, Result, Step } from './types.js'\n\n/**\n * Format a ZodError into a human-readable reason string.\n */\nfunction formatZodError(error: ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\n/**\n * Run quality gates against a step's output.\n * Returns the first failure, or null if all gates pass.\n */\nfunction runGates(gates: Gate<unknown>[], output: unknown): { reason: string; context?: unknown } | null {\n for (const gate of gates) {\n const result = gate(output)\n if (!result.ok) {\n return { reason: result.reason, context: result.context }\n }\n }\n return null\n}\n\n/**\n * Execute a single step with retry logic.\n * Returns a Result with the validated output or a structured failure.\n */\nexport async function executeStep(\n step: Step<unknown, unknown>,\n input: unknown,\n): Promise<Result<unknown>> {\n // Validate input schema\n const inputResult = step.inputSchema.safeParse(input)\n if (!inputResult.success) {\n return {\n ok: false,\n failure: {\n step: step.name,\n reason: `Input validation failed: ${formatZodError(inputResult.error)}`,\n type: 'schema_input',\n attempts: 0,\n input,\n },\n }\n }\n\n const validatedInput = inputResult.data\n const maxAttempts = step.retry.maxAttempts\n const retryOn = new Set(step.retry.on)\n\n let failureContext: FailureContext | undefined\n let lastFailure: Failure | undefined\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n // Run the step function\n const rawOutput = await step.run(validatedInput, failureContext)\n\n // Validate output schema\n const outputResult = step.outputSchema.safeParse(rawOutput)\n if (!outputResult.success) {\n const reason = `Output validation failed: ${formatZodError(outputResult.error)}`\n lastFailure = {\n step: step.name,\n reason,\n type: 'schema_output',\n attempts: attempt,\n input: validatedInput,\n output: rawOutput,\n }\n\n if (attempt < maxAttempts && retryOn.has('schema')) {\n failureContext = { reason, attempt, type: 'schema' }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n\n const validatedOutput = outputResult.data\n\n // Run quality gates\n const gateFailure = runGates(step.gates as Gate<unknown>[], validatedOutput)\n if (gateFailure) {\n lastFailure = {\n step: step.name,\n reason: `Gate failed: ${gateFailure.reason}`,\n type: 'gate',\n attempts: attempt,\n input: validatedInput,\n output: validatedOutput,\n }\n\n if (attempt < maxAttempts && retryOn.has('gate')) {\n failureContext = {\n reason: gateFailure.reason,\n attempt,\n type: 'gate',\n }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n\n // Success\n return { ok: true, value: validatedOutput }\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n lastFailure = {\n step: step.name,\n reason: `Exception: ${reason}`,\n type: 'exception',\n attempts: attempt,\n input: validatedInput,\n }\n\n if (attempt < maxAttempts && retryOn.has('exception')) {\n failureContext = { reason, attempt, type: 'exception' }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n }\n\n // Should not reach here, but safety net\n return {\n ok: false,\n failure: lastFailure ?? {\n step: step.name,\n reason: 'Unknown failure',\n type: 'exception',\n attempts: maxAttempts,\n input: validatedInput,\n },\n }\n}\n","import type { z } from 'zod'\n\n/**\n * Result of a quality gate check.\n * Gates are pure synchronous functions — no LLM calls, no async.\n */\nexport type GateResult =\n | { ok: true }\n | { ok: false; reason: string; context?: unknown }\n\n/**\n * A quality gate function. Receives a step's validated output\n * and returns a pass/fail verdict.\n */\nexport type Gate<T> = (output: T) => GateResult\n\n/**\n * Context passed to a step's `run` function on retry attempts.\n * Contains the reason the previous attempt failed, the attempt number,\n * and the failure type so the step can self-correct.\n */\nexport interface FailureContext {\n reason: string\n attempt: number\n type: 'schema' | 'gate' | 'exception'\n}\n\n/**\n * What kinds of failures should trigger a retry.\n */\nexport type RetryOn = 'schema' | 'gate' | 'exception'\n\n/**\n * Retry configuration for a step.\n */\nexport interface RetryConfig {\n /** Maximum number of attempts (including the first). Default: 1 (no retry). */\n maxAttempts?: number\n /** Which failure types trigger a retry. Default: ['schema', 'gate'] */\n on?: RetryOn[]\n}\n\n/**\n * Configuration for defining a step.\n * Generic parameters are inferred value types, not Zod schema types.\n */\nexport interface StepConfig<TInput, TOutput> {\n name: string\n input: z.ZodType<TInput, z.ZodTypeDef, unknown>\n output: z.ZodType<TOutput, z.ZodTypeDef, unknown>\n gates?: Gate<TOutput>[]\n retry?: RetryConfig\n run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>\n}\n\n/**\n * A defined step — the unit of work in a pipeline.\n * Generic parameters are value types (what the step consumes/produces),\n * not Zod schema types. This allows compile-time type checking of\n * step-to-step handoffs without Zod subclass compatibility issues.\n */\nexport interface Step<TInput = unknown, TOutput = unknown> {\n readonly name: string\n readonly inputSchema: z.ZodType<TInput>\n readonly outputSchema: z.ZodType<TOutput>\n readonly gates: Gate<TOutput>[]\n readonly retry: Required<RetryConfig>\n readonly run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>\n}\n\n/**\n * Structured failure returned when a pipeline halts.\n */\nexport interface Failure {\n step: string\n reason: string\n type: 'schema_input' | 'schema_output' | 'gate' | 'exception'\n attempts: number\n input: unknown\n output?: unknown\n}\n\n/**\n * Pipeline result type. Success or structured failure.\n */\nexport type Result<T> =\n | { ok: true; value: T }\n | { ok: false; failure: Failure }\n\n/**\n * Metadata returned by pipeline.describe().\n */\nexport interface PipelineDescription {\n name: string\n steps: StepDescription[]\n}\n\nexport interface StepDescription {\n name: string\n inputSchema: Record<string, unknown>\n outputSchema: Record<string, unknown>\n gates: number\n retry: Required<RetryConfig>\n}\n\n/**\n * Error thrown by .runOrThrow() on pipeline failure.\n */\nexport class PipelineError extends Error {\n constructor(public readonly failure: Failure) {\n super(`Pipeline failed at step \"${failure.step}\": ${failure.reason}`)\n this.name = 'PipelineError'\n }\n}\n","import { zodToJsonSchema } from 'zod-to-json-schema'\nimport { executeStep } from './runner.js'\nimport type { Failure, PipelineDescription, Result, Step } from './types.js'\nimport { PipelineError } from './types.js'\n\n/**\n * A compiled pipeline that can be run, described, or run-or-throw.\n */\nexport interface Pipeline<TInput, TOutput> {\n /** Pipeline name */\n readonly name: string\n\n /**\n * Add a step to the pipeline. Returns a new pipeline with the step appended.\n * TypeScript enforces that the step's input type matches the previous step's output type.\n */\n step<TNext>(step: Step<TOutput, TNext>): Pipeline<TInput, TNext>\n\n /**\n * Run the pipeline. Returns a Result — either { ok: true, value } or { ok: false, failure }.\n * No exceptions escape.\n */\n run(input: TInput): Promise<Result<TOutput>>\n\n /**\n * Run the pipeline and throw a PipelineError on failure.\n * For developers who prefer thrown exceptions over Result types.\n */\n runOrThrow(input: TInput): Promise<TOutput>\n\n /**\n * Return structured metadata about the pipeline:\n * step names, JSON Schema for inputs/outputs, gate count, retry config.\n */\n describe(): PipelineDescription\n}\n\n/**\n * Initial pipeline builder returned by pipeline().\n * The first .step() call sets both input and output types.\n */\nexport interface PipelineBuilder {\n readonly name: string\n step<TInput, TOutput>(step: Step<TInput, TOutput>): Pipeline<TInput, TOutput>\n}\n\n/**\n * Internal pipeline implementation.\n */\nclass PipelineImpl<TInput, TOutput> implements Pipeline<TInput, TOutput> {\n constructor(\n readonly name: string,\n // Use Step<unknown, unknown> internally; type safety is at the API boundary\n private readonly steps: Step<unknown, unknown>[],\n ) {}\n\n step<TNext>(newStep: Step<TOutput, TNext>): Pipeline<TInput, TNext> {\n return new PipelineImpl<TInput, TNext>(this.name, [\n ...this.steps,\n newStep as Step<unknown, unknown>,\n ])\n }\n\n async run(input: TInput): Promise<Result<TOutput>> {\n let current: unknown = input\n\n for (const step of this.steps) {\n const result = await executeStep(step, current)\n if (!result.ok) {\n return result as { ok: false; failure: Failure }\n }\n current = result.value\n }\n\n return { ok: true, value: current as TOutput }\n }\n\n async runOrThrow(input: TInput): Promise<TOutput> {\n const result = await this.run(input)\n if (!result.ok) {\n throw new PipelineError(result.failure)\n }\n return result.value\n }\n\n describe(): PipelineDescription {\n return {\n name: this.name,\n steps: this.steps.map((step) => ({\n name: step.name,\n inputSchema: zodToJsonSchema(step.inputSchema) as Record<string, unknown>,\n outputSchema: zodToJsonSchema(step.outputSchema) as Record<string, unknown>,\n gates: step.gates.length,\n retry: step.retry,\n })),\n }\n }\n}\n\n/**\n * Create a new pipeline with the given name.\n * Call .step() to add the first step — this sets the pipeline's input type.\n *\n * @example\n * ```ts\n * const myPipeline = pipeline('my-pipeline')\n * .step(loadData)\n * .step(transformData)\n * .step(validateResult);\n *\n * const result = await myPipeline.run({ source: 'input.json' });\n * ```\n */\nexport function pipeline(name: string): PipelineBuilder {\n return {\n name,\n step<TInput, TOutput>(firstStep: Step<TInput, TOutput>): Pipeline<TInput, TOutput> {\n return new PipelineImpl<TInput, TOutput>(name, [firstStep as Step<unknown, unknown>])\n },\n }\n}\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of a quality gate check.
|
|
5
|
+
* Gates are pure synchronous functions — no LLM calls, no async.
|
|
6
|
+
*/
|
|
7
|
+
type GateResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: string;
|
|
12
|
+
context?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A quality gate function. Receives a step's validated output
|
|
16
|
+
* and returns a pass/fail verdict.
|
|
17
|
+
*/
|
|
18
|
+
type Gate<T> = (output: T) => GateResult;
|
|
19
|
+
/**
|
|
20
|
+
* Context passed to a step's `run` function on retry attempts.
|
|
21
|
+
* Contains the reason the previous attempt failed, the attempt number,
|
|
22
|
+
* and the failure type so the step can self-correct.
|
|
23
|
+
*/
|
|
24
|
+
interface FailureContext {
|
|
25
|
+
reason: string;
|
|
26
|
+
attempt: number;
|
|
27
|
+
type: 'schema' | 'gate' | 'exception';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* What kinds of failures should trigger a retry.
|
|
31
|
+
*/
|
|
32
|
+
type RetryOn = 'schema' | 'gate' | 'exception';
|
|
33
|
+
/**
|
|
34
|
+
* Retry configuration for a step.
|
|
35
|
+
*/
|
|
36
|
+
interface RetryConfig {
|
|
37
|
+
/** Maximum number of attempts (including the first). Default: 1 (no retry). */
|
|
38
|
+
maxAttempts?: number;
|
|
39
|
+
/** Which failure types trigger a retry. Default: ['schema', 'gate'] */
|
|
40
|
+
on?: RetryOn[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configuration for defining a step.
|
|
44
|
+
* Generic parameters are inferred value types, not Zod schema types.
|
|
45
|
+
*/
|
|
46
|
+
interface StepConfig<TInput, TOutput> {
|
|
47
|
+
name: string;
|
|
48
|
+
input: z.ZodType<TInput, z.ZodTypeDef, unknown>;
|
|
49
|
+
output: z.ZodType<TOutput, z.ZodTypeDef, unknown>;
|
|
50
|
+
gates?: Gate<TOutput>[];
|
|
51
|
+
retry?: RetryConfig;
|
|
52
|
+
run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A defined step — the unit of work in a pipeline.
|
|
56
|
+
* Generic parameters are value types (what the step consumes/produces),
|
|
57
|
+
* not Zod schema types. This allows compile-time type checking of
|
|
58
|
+
* step-to-step handoffs without Zod subclass compatibility issues.
|
|
59
|
+
*/
|
|
60
|
+
interface Step<TInput = unknown, TOutput = unknown> {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly inputSchema: z.ZodType<TInput>;
|
|
63
|
+
readonly outputSchema: z.ZodType<TOutput>;
|
|
64
|
+
readonly gates: Gate<TOutput>[];
|
|
65
|
+
readonly retry: Required<RetryConfig>;
|
|
66
|
+
readonly run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Structured failure returned when a pipeline halts.
|
|
70
|
+
*/
|
|
71
|
+
interface Failure {
|
|
72
|
+
step: string;
|
|
73
|
+
reason: string;
|
|
74
|
+
type: 'schema_input' | 'schema_output' | 'gate' | 'exception';
|
|
75
|
+
attempts: number;
|
|
76
|
+
input: unknown;
|
|
77
|
+
output?: unknown;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pipeline result type. Success or structured failure.
|
|
81
|
+
*/
|
|
82
|
+
type Result<T> = {
|
|
83
|
+
ok: true;
|
|
84
|
+
value: T;
|
|
85
|
+
} | {
|
|
86
|
+
ok: false;
|
|
87
|
+
failure: Failure;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Metadata returned by pipeline.describe().
|
|
91
|
+
*/
|
|
92
|
+
interface PipelineDescription {
|
|
93
|
+
name: string;
|
|
94
|
+
steps: StepDescription[];
|
|
95
|
+
}
|
|
96
|
+
interface StepDescription {
|
|
97
|
+
name: string;
|
|
98
|
+
inputSchema: Record<string, unknown>;
|
|
99
|
+
outputSchema: Record<string, unknown>;
|
|
100
|
+
gates: number;
|
|
101
|
+
retry: Required<RetryConfig>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Error thrown by .runOrThrow() on pipeline failure.
|
|
105
|
+
*/
|
|
106
|
+
declare class PipelineError extends Error {
|
|
107
|
+
readonly failure: Failure;
|
|
108
|
+
constructor(failure: Failure);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Define a pipeline step with typed input/output schemas, optional quality gates,
|
|
113
|
+
* and retry configuration.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const extractTitle = defineStep({
|
|
118
|
+
* name: 'extract-title',
|
|
119
|
+
* input: z.object({ draft: z.string() }),
|
|
120
|
+
* output: z.object({ title: z.string().max(80) }),
|
|
121
|
+
* gates: [
|
|
122
|
+
* (out) => out.title.includes(':')
|
|
123
|
+
* ? { ok: false, reason: 'title must not contain colons' }
|
|
124
|
+
* : { ok: true }
|
|
125
|
+
* ],
|
|
126
|
+
* retry: { maxAttempts: 3, on: ['schema', 'gate'] },
|
|
127
|
+
* run: async (input, failureContext) => {
|
|
128
|
+
* return { title: 'extracted title' };
|
|
129
|
+
* }
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function defineStep<TInput, TOutput>(config: StepConfig<TInput, TOutput>): Step<TInput, TOutput>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A compiled pipeline that can be run, described, or run-or-throw.
|
|
137
|
+
*/
|
|
138
|
+
interface Pipeline<TInput, TOutput> {
|
|
139
|
+
/** Pipeline name */
|
|
140
|
+
readonly name: string;
|
|
141
|
+
/**
|
|
142
|
+
* Add a step to the pipeline. Returns a new pipeline with the step appended.
|
|
143
|
+
* TypeScript enforces that the step's input type matches the previous step's output type.
|
|
144
|
+
*/
|
|
145
|
+
step<TNext>(step: Step<TOutput, TNext>): Pipeline<TInput, TNext>;
|
|
146
|
+
/**
|
|
147
|
+
* Run the pipeline. Returns a Result — either { ok: true, value } or { ok: false, failure }.
|
|
148
|
+
* No exceptions escape.
|
|
149
|
+
*/
|
|
150
|
+
run(input: TInput): Promise<Result<TOutput>>;
|
|
151
|
+
/**
|
|
152
|
+
* Run the pipeline and throw a PipelineError on failure.
|
|
153
|
+
* For developers who prefer thrown exceptions over Result types.
|
|
154
|
+
*/
|
|
155
|
+
runOrThrow(input: TInput): Promise<TOutput>;
|
|
156
|
+
/**
|
|
157
|
+
* Return structured metadata about the pipeline:
|
|
158
|
+
* step names, JSON Schema for inputs/outputs, gate count, retry config.
|
|
159
|
+
*/
|
|
160
|
+
describe(): PipelineDescription;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Initial pipeline builder returned by pipeline().
|
|
164
|
+
* The first .step() call sets both input and output types.
|
|
165
|
+
*/
|
|
166
|
+
interface PipelineBuilder {
|
|
167
|
+
readonly name: string;
|
|
168
|
+
step<TInput, TOutput>(step: Step<TInput, TOutput>): Pipeline<TInput, TOutput>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create a new pipeline with the given name.
|
|
172
|
+
* Call .step() to add the first step — this sets the pipeline's input type.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* const myPipeline = pipeline('my-pipeline')
|
|
177
|
+
* .step(loadData)
|
|
178
|
+
* .step(transformData)
|
|
179
|
+
* .step(validateResult);
|
|
180
|
+
*
|
|
181
|
+
* const result = await myPipeline.run({ source: 'input.json' });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function pipeline(name: string): PipelineBuilder;
|
|
185
|
+
|
|
186
|
+
export { type Failure, type FailureContext, type Gate, type GateResult, type Pipeline, type PipelineBuilder, type PipelineDescription, PipelineError, type Result, type RetryConfig, type RetryOn, type Step, type StepConfig, type StepDescription, defineStep, pipeline };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of a quality gate check.
|
|
5
|
+
* Gates are pure synchronous functions — no LLM calls, no async.
|
|
6
|
+
*/
|
|
7
|
+
type GateResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
} | {
|
|
10
|
+
ok: false;
|
|
11
|
+
reason: string;
|
|
12
|
+
context?: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* A quality gate function. Receives a step's validated output
|
|
16
|
+
* and returns a pass/fail verdict.
|
|
17
|
+
*/
|
|
18
|
+
type Gate<T> = (output: T) => GateResult;
|
|
19
|
+
/**
|
|
20
|
+
* Context passed to a step's `run` function on retry attempts.
|
|
21
|
+
* Contains the reason the previous attempt failed, the attempt number,
|
|
22
|
+
* and the failure type so the step can self-correct.
|
|
23
|
+
*/
|
|
24
|
+
interface FailureContext {
|
|
25
|
+
reason: string;
|
|
26
|
+
attempt: number;
|
|
27
|
+
type: 'schema' | 'gate' | 'exception';
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* What kinds of failures should trigger a retry.
|
|
31
|
+
*/
|
|
32
|
+
type RetryOn = 'schema' | 'gate' | 'exception';
|
|
33
|
+
/**
|
|
34
|
+
* Retry configuration for a step.
|
|
35
|
+
*/
|
|
36
|
+
interface RetryConfig {
|
|
37
|
+
/** Maximum number of attempts (including the first). Default: 1 (no retry). */
|
|
38
|
+
maxAttempts?: number;
|
|
39
|
+
/** Which failure types trigger a retry. Default: ['schema', 'gate'] */
|
|
40
|
+
on?: RetryOn[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Configuration for defining a step.
|
|
44
|
+
* Generic parameters are inferred value types, not Zod schema types.
|
|
45
|
+
*/
|
|
46
|
+
interface StepConfig<TInput, TOutput> {
|
|
47
|
+
name: string;
|
|
48
|
+
input: z.ZodType<TInput, z.ZodTypeDef, unknown>;
|
|
49
|
+
output: z.ZodType<TOutput, z.ZodTypeDef, unknown>;
|
|
50
|
+
gates?: Gate<TOutput>[];
|
|
51
|
+
retry?: RetryConfig;
|
|
52
|
+
run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* A defined step — the unit of work in a pipeline.
|
|
56
|
+
* Generic parameters are value types (what the step consumes/produces),
|
|
57
|
+
* not Zod schema types. This allows compile-time type checking of
|
|
58
|
+
* step-to-step handoffs without Zod subclass compatibility issues.
|
|
59
|
+
*/
|
|
60
|
+
interface Step<TInput = unknown, TOutput = unknown> {
|
|
61
|
+
readonly name: string;
|
|
62
|
+
readonly inputSchema: z.ZodType<TInput>;
|
|
63
|
+
readonly outputSchema: z.ZodType<TOutput>;
|
|
64
|
+
readonly gates: Gate<TOutput>[];
|
|
65
|
+
readonly retry: Required<RetryConfig>;
|
|
66
|
+
readonly run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Structured failure returned when a pipeline halts.
|
|
70
|
+
*/
|
|
71
|
+
interface Failure {
|
|
72
|
+
step: string;
|
|
73
|
+
reason: string;
|
|
74
|
+
type: 'schema_input' | 'schema_output' | 'gate' | 'exception';
|
|
75
|
+
attempts: number;
|
|
76
|
+
input: unknown;
|
|
77
|
+
output?: unknown;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Pipeline result type. Success or structured failure.
|
|
81
|
+
*/
|
|
82
|
+
type Result<T> = {
|
|
83
|
+
ok: true;
|
|
84
|
+
value: T;
|
|
85
|
+
} | {
|
|
86
|
+
ok: false;
|
|
87
|
+
failure: Failure;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Metadata returned by pipeline.describe().
|
|
91
|
+
*/
|
|
92
|
+
interface PipelineDescription {
|
|
93
|
+
name: string;
|
|
94
|
+
steps: StepDescription[];
|
|
95
|
+
}
|
|
96
|
+
interface StepDescription {
|
|
97
|
+
name: string;
|
|
98
|
+
inputSchema: Record<string, unknown>;
|
|
99
|
+
outputSchema: Record<string, unknown>;
|
|
100
|
+
gates: number;
|
|
101
|
+
retry: Required<RetryConfig>;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Error thrown by .runOrThrow() on pipeline failure.
|
|
105
|
+
*/
|
|
106
|
+
declare class PipelineError extends Error {
|
|
107
|
+
readonly failure: Failure;
|
|
108
|
+
constructor(failure: Failure);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Define a pipeline step with typed input/output schemas, optional quality gates,
|
|
113
|
+
* and retry configuration.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```ts
|
|
117
|
+
* const extractTitle = defineStep({
|
|
118
|
+
* name: 'extract-title',
|
|
119
|
+
* input: z.object({ draft: z.string() }),
|
|
120
|
+
* output: z.object({ title: z.string().max(80) }),
|
|
121
|
+
* gates: [
|
|
122
|
+
* (out) => out.title.includes(':')
|
|
123
|
+
* ? { ok: false, reason: 'title must not contain colons' }
|
|
124
|
+
* : { ok: true }
|
|
125
|
+
* ],
|
|
126
|
+
* retry: { maxAttempts: 3, on: ['schema', 'gate'] },
|
|
127
|
+
* run: async (input, failureContext) => {
|
|
128
|
+
* return { title: 'extracted title' };
|
|
129
|
+
* }
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
declare function defineStep<TInput, TOutput>(config: StepConfig<TInput, TOutput>): Step<TInput, TOutput>;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* A compiled pipeline that can be run, described, or run-or-throw.
|
|
137
|
+
*/
|
|
138
|
+
interface Pipeline<TInput, TOutput> {
|
|
139
|
+
/** Pipeline name */
|
|
140
|
+
readonly name: string;
|
|
141
|
+
/**
|
|
142
|
+
* Add a step to the pipeline. Returns a new pipeline with the step appended.
|
|
143
|
+
* TypeScript enforces that the step's input type matches the previous step's output type.
|
|
144
|
+
*/
|
|
145
|
+
step<TNext>(step: Step<TOutput, TNext>): Pipeline<TInput, TNext>;
|
|
146
|
+
/**
|
|
147
|
+
* Run the pipeline. Returns a Result — either { ok: true, value } or { ok: false, failure }.
|
|
148
|
+
* No exceptions escape.
|
|
149
|
+
*/
|
|
150
|
+
run(input: TInput): Promise<Result<TOutput>>;
|
|
151
|
+
/**
|
|
152
|
+
* Run the pipeline and throw a PipelineError on failure.
|
|
153
|
+
* For developers who prefer thrown exceptions over Result types.
|
|
154
|
+
*/
|
|
155
|
+
runOrThrow(input: TInput): Promise<TOutput>;
|
|
156
|
+
/**
|
|
157
|
+
* Return structured metadata about the pipeline:
|
|
158
|
+
* step names, JSON Schema for inputs/outputs, gate count, retry config.
|
|
159
|
+
*/
|
|
160
|
+
describe(): PipelineDescription;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Initial pipeline builder returned by pipeline().
|
|
164
|
+
* The first .step() call sets both input and output types.
|
|
165
|
+
*/
|
|
166
|
+
interface PipelineBuilder {
|
|
167
|
+
readonly name: string;
|
|
168
|
+
step<TInput, TOutput>(step: Step<TInput, TOutput>): Pipeline<TInput, TOutput>;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create a new pipeline with the given name.
|
|
172
|
+
* Call .step() to add the first step — this sets the pipeline's input type.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* const myPipeline = pipeline('my-pipeline')
|
|
177
|
+
* .step(loadData)
|
|
178
|
+
* .step(transformData)
|
|
179
|
+
* .step(validateResult);
|
|
180
|
+
*
|
|
181
|
+
* const result = await myPipeline.run({ source: 'input.json' });
|
|
182
|
+
* ```
|
|
183
|
+
*/
|
|
184
|
+
declare function pipeline(name: string): PipelineBuilder;
|
|
185
|
+
|
|
186
|
+
export { type Failure, type FailureContext, type Gate, type GateResult, type Pipeline, type PipelineBuilder, type PipelineDescription, PipelineError, type Result, type RetryConfig, type RetryOn, type Step, type StepConfig, type StepDescription, defineStep, pipeline };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
2
|
+
|
|
3
|
+
// src/step.ts
|
|
4
|
+
function defineStep(config) {
|
|
5
|
+
return {
|
|
6
|
+
name: config.name,
|
|
7
|
+
inputSchema: config.input,
|
|
8
|
+
outputSchema: config.output,
|
|
9
|
+
gates: config.gates ?? [],
|
|
10
|
+
retry: {
|
|
11
|
+
maxAttempts: config.retry?.maxAttempts ?? 1,
|
|
12
|
+
on: config.retry?.on ?? ["schema", "gate"]
|
|
13
|
+
},
|
|
14
|
+
run: config.run
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/runner.ts
|
|
19
|
+
function formatZodError(error) {
|
|
20
|
+
return error.issues.map((issue) => {
|
|
21
|
+
const path = issue.path.length > 0 ? `${issue.path.join(".")}: ` : "";
|
|
22
|
+
return `${path}${issue.message}`;
|
|
23
|
+
}).join("; ");
|
|
24
|
+
}
|
|
25
|
+
function runGates(gates, output) {
|
|
26
|
+
for (const gate of gates) {
|
|
27
|
+
const result = gate(output);
|
|
28
|
+
if (!result.ok) {
|
|
29
|
+
return { reason: result.reason, context: result.context };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
async function executeStep(step, input) {
|
|
35
|
+
const inputResult = step.inputSchema.safeParse(input);
|
|
36
|
+
if (!inputResult.success) {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
failure: {
|
|
40
|
+
step: step.name,
|
|
41
|
+
reason: `Input validation failed: ${formatZodError(inputResult.error)}`,
|
|
42
|
+
type: "schema_input",
|
|
43
|
+
attempts: 0,
|
|
44
|
+
input
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const validatedInput = inputResult.data;
|
|
49
|
+
const maxAttempts = step.retry.maxAttempts;
|
|
50
|
+
const retryOn = new Set(step.retry.on);
|
|
51
|
+
let failureContext;
|
|
52
|
+
let lastFailure;
|
|
53
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
const rawOutput = await step.run(validatedInput, failureContext);
|
|
56
|
+
const outputResult = step.outputSchema.safeParse(rawOutput);
|
|
57
|
+
if (!outputResult.success) {
|
|
58
|
+
const reason = `Output validation failed: ${formatZodError(outputResult.error)}`;
|
|
59
|
+
lastFailure = {
|
|
60
|
+
step: step.name,
|
|
61
|
+
reason,
|
|
62
|
+
type: "schema_output",
|
|
63
|
+
attempts: attempt,
|
|
64
|
+
input: validatedInput,
|
|
65
|
+
output: rawOutput
|
|
66
|
+
};
|
|
67
|
+
if (attempt < maxAttempts && retryOn.has("schema")) {
|
|
68
|
+
failureContext = { reason, attempt, type: "schema" };
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
return { ok: false, failure: lastFailure };
|
|
72
|
+
}
|
|
73
|
+
const validatedOutput = outputResult.data;
|
|
74
|
+
const gateFailure = runGates(step.gates, validatedOutput);
|
|
75
|
+
if (gateFailure) {
|
|
76
|
+
lastFailure = {
|
|
77
|
+
step: step.name,
|
|
78
|
+
reason: `Gate failed: ${gateFailure.reason}`,
|
|
79
|
+
type: "gate",
|
|
80
|
+
attempts: attempt,
|
|
81
|
+
input: validatedInput,
|
|
82
|
+
output: validatedOutput
|
|
83
|
+
};
|
|
84
|
+
if (attempt < maxAttempts && retryOn.has("gate")) {
|
|
85
|
+
failureContext = {
|
|
86
|
+
reason: gateFailure.reason,
|
|
87
|
+
attempt,
|
|
88
|
+
type: "gate"
|
|
89
|
+
};
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
return { ok: false, failure: lastFailure };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, value: validatedOutput };
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
97
|
+
lastFailure = {
|
|
98
|
+
step: step.name,
|
|
99
|
+
reason: `Exception: ${reason}`,
|
|
100
|
+
type: "exception",
|
|
101
|
+
attempts: attempt,
|
|
102
|
+
input: validatedInput
|
|
103
|
+
};
|
|
104
|
+
if (attempt < maxAttempts && retryOn.has("exception")) {
|
|
105
|
+
failureContext = { reason, attempt, type: "exception" };
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
return { ok: false, failure: lastFailure };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
failure: lastFailure ?? {
|
|
114
|
+
step: step.name,
|
|
115
|
+
reason: "Unknown failure",
|
|
116
|
+
type: "exception",
|
|
117
|
+
attempts: maxAttempts,
|
|
118
|
+
input: validatedInput
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/types.ts
|
|
124
|
+
var PipelineError = class extends Error {
|
|
125
|
+
constructor(failure) {
|
|
126
|
+
super(`Pipeline failed at step "${failure.step}": ${failure.reason}`);
|
|
127
|
+
this.failure = failure;
|
|
128
|
+
this.name = "PipelineError";
|
|
129
|
+
}
|
|
130
|
+
failure;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/pipeline.ts
|
|
134
|
+
var PipelineImpl = class _PipelineImpl {
|
|
135
|
+
constructor(name, steps) {
|
|
136
|
+
this.name = name;
|
|
137
|
+
this.steps = steps;
|
|
138
|
+
}
|
|
139
|
+
name;
|
|
140
|
+
steps;
|
|
141
|
+
step(newStep) {
|
|
142
|
+
return new _PipelineImpl(this.name, [
|
|
143
|
+
...this.steps,
|
|
144
|
+
newStep
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
147
|
+
async run(input) {
|
|
148
|
+
let current = input;
|
|
149
|
+
for (const step of this.steps) {
|
|
150
|
+
const result = await executeStep(step, current);
|
|
151
|
+
if (!result.ok) {
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
current = result.value;
|
|
155
|
+
}
|
|
156
|
+
return { ok: true, value: current };
|
|
157
|
+
}
|
|
158
|
+
async runOrThrow(input) {
|
|
159
|
+
const result = await this.run(input);
|
|
160
|
+
if (!result.ok) {
|
|
161
|
+
throw new PipelineError(result.failure);
|
|
162
|
+
}
|
|
163
|
+
return result.value;
|
|
164
|
+
}
|
|
165
|
+
describe() {
|
|
166
|
+
return {
|
|
167
|
+
name: this.name,
|
|
168
|
+
steps: this.steps.map((step) => ({
|
|
169
|
+
name: step.name,
|
|
170
|
+
inputSchema: zodToJsonSchema(step.inputSchema),
|
|
171
|
+
outputSchema: zodToJsonSchema(step.outputSchema),
|
|
172
|
+
gates: step.gates.length,
|
|
173
|
+
retry: step.retry
|
|
174
|
+
}))
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
function pipeline(name) {
|
|
179
|
+
return {
|
|
180
|
+
name,
|
|
181
|
+
step(firstStep) {
|
|
182
|
+
return new PipelineImpl(name, [firstStep]);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export { PipelineError, defineStep, pipeline };
|
|
188
|
+
//# sourceMappingURL=index.js.map
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/step.ts","../src/runner.ts","../src/types.ts","../src/pipeline.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { z } from 'zod'\nimport type { Step, StepConfig } from './types.js'\n\n/**\n * Define a pipeline step with typed input/output schemas, optional quality gates,\n * and retry configuration.\n *\n * @example\n * ```ts\n * const extractTitle = defineStep({\n * name: 'extract-title',\n * input: z.object({ draft: z.string() }),\n * output: z.object({ title: z.string().max(80) }),\n * gates: [\n * (out) => out.title.includes(':')\n * ? { ok: false, reason: 'title must not contain colons' }\n * : { ok: true }\n * ],\n * retry: { maxAttempts: 3, on: ['schema', 'gate'] },\n * run: async (input, failureContext) => {\n * return { title: 'extracted title' };\n * }\n * });\n * ```\n */\nexport function defineStep<TInput, TOutput>(\n config: StepConfig<TInput, TOutput>,\n): Step<TInput, TOutput> {\n return {\n name: config.name,\n inputSchema: config.input as z.ZodType<TInput>,\n outputSchema: config.output as z.ZodType<TOutput>,\n gates: config.gates ?? [],\n retry: {\n maxAttempts: config.retry?.maxAttempts ?? 1,\n on: config.retry?.on ?? ['schema', 'gate'],\n },\n run: config.run,\n }\n}\n","import type { ZodError } from 'zod'\nimport type { FailureContext, Failure, Gate, Result, Step } from './types.js'\n\n/**\n * Format a ZodError into a human-readable reason string.\n */\nfunction formatZodError(error: ZodError): string {\n return error.issues\n .map((issue) => {\n const path = issue.path.length > 0 ? `${issue.path.join('.')}: ` : ''\n return `${path}${issue.message}`\n })\n .join('; ')\n}\n\n/**\n * Run quality gates against a step's output.\n * Returns the first failure, or null if all gates pass.\n */\nfunction runGates(gates: Gate<unknown>[], output: unknown): { reason: string; context?: unknown } | null {\n for (const gate of gates) {\n const result = gate(output)\n if (!result.ok) {\n return { reason: result.reason, context: result.context }\n }\n }\n return null\n}\n\n/**\n * Execute a single step with retry logic.\n * Returns a Result with the validated output or a structured failure.\n */\nexport async function executeStep(\n step: Step<unknown, unknown>,\n input: unknown,\n): Promise<Result<unknown>> {\n // Validate input schema\n const inputResult = step.inputSchema.safeParse(input)\n if (!inputResult.success) {\n return {\n ok: false,\n failure: {\n step: step.name,\n reason: `Input validation failed: ${formatZodError(inputResult.error)}`,\n type: 'schema_input',\n attempts: 0,\n input,\n },\n }\n }\n\n const validatedInput = inputResult.data\n const maxAttempts = step.retry.maxAttempts\n const retryOn = new Set(step.retry.on)\n\n let failureContext: FailureContext | undefined\n let lastFailure: Failure | undefined\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n // Run the step function\n const rawOutput = await step.run(validatedInput, failureContext)\n\n // Validate output schema\n const outputResult = step.outputSchema.safeParse(rawOutput)\n if (!outputResult.success) {\n const reason = `Output validation failed: ${formatZodError(outputResult.error)}`\n lastFailure = {\n step: step.name,\n reason,\n type: 'schema_output',\n attempts: attempt,\n input: validatedInput,\n output: rawOutput,\n }\n\n if (attempt < maxAttempts && retryOn.has('schema')) {\n failureContext = { reason, attempt, type: 'schema' }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n\n const validatedOutput = outputResult.data\n\n // Run quality gates\n const gateFailure = runGates(step.gates as Gate<unknown>[], validatedOutput)\n if (gateFailure) {\n lastFailure = {\n step: step.name,\n reason: `Gate failed: ${gateFailure.reason}`,\n type: 'gate',\n attempts: attempt,\n input: validatedInput,\n output: validatedOutput,\n }\n\n if (attempt < maxAttempts && retryOn.has('gate')) {\n failureContext = {\n reason: gateFailure.reason,\n attempt,\n type: 'gate',\n }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n\n // Success\n return { ok: true, value: validatedOutput }\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error)\n lastFailure = {\n step: step.name,\n reason: `Exception: ${reason}`,\n type: 'exception',\n attempts: attempt,\n input: validatedInput,\n }\n\n if (attempt < maxAttempts && retryOn.has('exception')) {\n failureContext = { reason, attempt, type: 'exception' }\n continue\n }\n return { ok: false, failure: lastFailure }\n }\n }\n\n // Should not reach here, but safety net\n return {\n ok: false,\n failure: lastFailure ?? {\n step: step.name,\n reason: 'Unknown failure',\n type: 'exception',\n attempts: maxAttempts,\n input: validatedInput,\n },\n }\n}\n","import type { z } from 'zod'\n\n/**\n * Result of a quality gate check.\n * Gates are pure synchronous functions — no LLM calls, no async.\n */\nexport type GateResult =\n | { ok: true }\n | { ok: false; reason: string; context?: unknown }\n\n/**\n * A quality gate function. Receives a step's validated output\n * and returns a pass/fail verdict.\n */\nexport type Gate<T> = (output: T) => GateResult\n\n/**\n * Context passed to a step's `run` function on retry attempts.\n * Contains the reason the previous attempt failed, the attempt number,\n * and the failure type so the step can self-correct.\n */\nexport interface FailureContext {\n reason: string\n attempt: number\n type: 'schema' | 'gate' | 'exception'\n}\n\n/**\n * What kinds of failures should trigger a retry.\n */\nexport type RetryOn = 'schema' | 'gate' | 'exception'\n\n/**\n * Retry configuration for a step.\n */\nexport interface RetryConfig {\n /** Maximum number of attempts (including the first). Default: 1 (no retry). */\n maxAttempts?: number\n /** Which failure types trigger a retry. Default: ['schema', 'gate'] */\n on?: RetryOn[]\n}\n\n/**\n * Configuration for defining a step.\n * Generic parameters are inferred value types, not Zod schema types.\n */\nexport interface StepConfig<TInput, TOutput> {\n name: string\n input: z.ZodType<TInput, z.ZodTypeDef, unknown>\n output: z.ZodType<TOutput, z.ZodTypeDef, unknown>\n gates?: Gate<TOutput>[]\n retry?: RetryConfig\n run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>\n}\n\n/**\n * A defined step — the unit of work in a pipeline.\n * Generic parameters are value types (what the step consumes/produces),\n * not Zod schema types. This allows compile-time type checking of\n * step-to-step handoffs without Zod subclass compatibility issues.\n */\nexport interface Step<TInput = unknown, TOutput = unknown> {\n readonly name: string\n readonly inputSchema: z.ZodType<TInput>\n readonly outputSchema: z.ZodType<TOutput>\n readonly gates: Gate<TOutput>[]\n readonly retry: Required<RetryConfig>\n readonly run: (input: TInput, failureContext?: FailureContext) => Promise<TOutput>\n}\n\n/**\n * Structured failure returned when a pipeline halts.\n */\nexport interface Failure {\n step: string\n reason: string\n type: 'schema_input' | 'schema_output' | 'gate' | 'exception'\n attempts: number\n input: unknown\n output?: unknown\n}\n\n/**\n * Pipeline result type. Success or structured failure.\n */\nexport type Result<T> =\n | { ok: true; value: T }\n | { ok: false; failure: Failure }\n\n/**\n * Metadata returned by pipeline.describe().\n */\nexport interface PipelineDescription {\n name: string\n steps: StepDescription[]\n}\n\nexport interface StepDescription {\n name: string\n inputSchema: Record<string, unknown>\n outputSchema: Record<string, unknown>\n gates: number\n retry: Required<RetryConfig>\n}\n\n/**\n * Error thrown by .runOrThrow() on pipeline failure.\n */\nexport class PipelineError extends Error {\n constructor(public readonly failure: Failure) {\n super(`Pipeline failed at step \"${failure.step}\": ${failure.reason}`)\n this.name = 'PipelineError'\n }\n}\n","import { zodToJsonSchema } from 'zod-to-json-schema'\nimport { executeStep } from './runner.js'\nimport type { Failure, PipelineDescription, Result, Step } from './types.js'\nimport { PipelineError } from './types.js'\n\n/**\n * A compiled pipeline that can be run, described, or run-or-throw.\n */\nexport interface Pipeline<TInput, TOutput> {\n /** Pipeline name */\n readonly name: string\n\n /**\n * Add a step to the pipeline. Returns a new pipeline with the step appended.\n * TypeScript enforces that the step's input type matches the previous step's output type.\n */\n step<TNext>(step: Step<TOutput, TNext>): Pipeline<TInput, TNext>\n\n /**\n * Run the pipeline. Returns a Result — either { ok: true, value } or { ok: false, failure }.\n * No exceptions escape.\n */\n run(input: TInput): Promise<Result<TOutput>>\n\n /**\n * Run the pipeline and throw a PipelineError on failure.\n * For developers who prefer thrown exceptions over Result types.\n */\n runOrThrow(input: TInput): Promise<TOutput>\n\n /**\n * Return structured metadata about the pipeline:\n * step names, JSON Schema for inputs/outputs, gate count, retry config.\n */\n describe(): PipelineDescription\n}\n\n/**\n * Initial pipeline builder returned by pipeline().\n * The first .step() call sets both input and output types.\n */\nexport interface PipelineBuilder {\n readonly name: string\n step<TInput, TOutput>(step: Step<TInput, TOutput>): Pipeline<TInput, TOutput>\n}\n\n/**\n * Internal pipeline implementation.\n */\nclass PipelineImpl<TInput, TOutput> implements Pipeline<TInput, TOutput> {\n constructor(\n readonly name: string,\n // Use Step<unknown, unknown> internally; type safety is at the API boundary\n private readonly steps: Step<unknown, unknown>[],\n ) {}\n\n step<TNext>(newStep: Step<TOutput, TNext>): Pipeline<TInput, TNext> {\n return new PipelineImpl<TInput, TNext>(this.name, [\n ...this.steps,\n newStep as Step<unknown, unknown>,\n ])\n }\n\n async run(input: TInput): Promise<Result<TOutput>> {\n let current: unknown = input\n\n for (const step of this.steps) {\n const result = await executeStep(step, current)\n if (!result.ok) {\n return result as { ok: false; failure: Failure }\n }\n current = result.value\n }\n\n return { ok: true, value: current as TOutput }\n }\n\n async runOrThrow(input: TInput): Promise<TOutput> {\n const result = await this.run(input)\n if (!result.ok) {\n throw new PipelineError(result.failure)\n }\n return result.value\n }\n\n describe(): PipelineDescription {\n return {\n name: this.name,\n steps: this.steps.map((step) => ({\n name: step.name,\n inputSchema: zodToJsonSchema(step.inputSchema) as Record<string, unknown>,\n outputSchema: zodToJsonSchema(step.outputSchema) as Record<string, unknown>,\n gates: step.gates.length,\n retry: step.retry,\n })),\n }\n }\n}\n\n/**\n * Create a new pipeline with the given name.\n * Call .step() to add the first step — this sets the pipeline's input type.\n *\n * @example\n * ```ts\n * const myPipeline = pipeline('my-pipeline')\n * .step(loadData)\n * .step(transformData)\n * .step(validateResult);\n *\n * const result = await myPipeline.run({ source: 'input.json' });\n * ```\n */\nexport function pipeline(name: string): PipelineBuilder {\n return {\n name,\n step<TInput, TOutput>(firstStep: Step<TInput, TOutput>): Pipeline<TInput, TOutput> {\n return new PipelineImpl<TInput, TOutput>(name, [firstStep as Step<unknown, unknown>])\n },\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cueapi/cuechain",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Contract-verification primitive for TypeScript AI agent pipelines",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsup",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"lint": "biome check src/ tests/",
|
|
30
|
+
"lint:fix": "biome check --write src/ tests/",
|
|
31
|
+
"format": "biome format --write src/ tests/",
|
|
32
|
+
"ci": "pnpm typecheck && pnpm lint && pnpm test && pnpm build"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"ai",
|
|
36
|
+
"agent",
|
|
37
|
+
"pipeline",
|
|
38
|
+
"contract",
|
|
39
|
+
"verification",
|
|
40
|
+
"typescript",
|
|
41
|
+
"zod",
|
|
42
|
+
"workflow",
|
|
43
|
+
"cueapi"
|
|
44
|
+
],
|
|
45
|
+
"author": "Vector Apps Inc",
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "https://github.com/cueapi/cuechain.git"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/cueapi/cuechain",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/cueapi/cuechain/issues"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"zod-to-json-schema": "^3.24.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"zod": "^3.22.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@biomejs/biome": "^1.9.0",
|
|
63
|
+
"@types/node": "^25.6.0",
|
|
64
|
+
"tsup": "^8.0.0",
|
|
65
|
+
"tsx": "^4.21.0",
|
|
66
|
+
"typescript": "^5.5.0",
|
|
67
|
+
"vitest": "^3.0.0",
|
|
68
|
+
"zod": "^3.24.0",
|
|
69
|
+
"zod-to-json-schema": "^3.24.0"
|
|
70
|
+
},
|
|
71
|
+
"pnpm": {
|
|
72
|
+
"onlyBuiltDependencies": [
|
|
73
|
+
"@biomejs/biome",
|
|
74
|
+
"esbuild"
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
}
|