@griffin-app/griffin-plan-executor 0.1.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 +152 -0
- package/dist/adapters/axios.d.ts +5 -0
- package/dist/adapters/axios.d.ts.map +1 -0
- package/dist/adapters/axios.js +36 -0
- package/dist/adapters/axios.js.map +1 -0
- package/dist/adapters/index.d.ts +3 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +3 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/stub.d.ts +22 -0
- package/dist/adapters/stub.d.ts.map +1 -0
- package/dist/adapters/stub.js +36 -0
- package/dist/adapters/stub.js.map +1 -0
- package/dist/events/emitter.d.ts +68 -0
- package/dist/events/emitter.d.ts.map +1 -0
- package/dist/events/emitter.js +83 -0
- package/dist/events/emitter.js.map +1 -0
- package/dist/events/emitter.test.d.ts +2 -0
- package/dist/events/emitter.test.d.ts.map +1 -0
- package/dist/events/emitter.test.js +251 -0
- package/dist/events/emitter.test.js.map +1 -0
- package/dist/events/index.d.ts +3 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +3 -0
- package/dist/events/index.js.map +1 -0
- package/dist/events/types.d.ts +109 -0
- package/dist/events/types.d.ts.map +1 -0
- package/dist/events/types.js +9 -0
- package/dist/events/types.js.map +1 -0
- package/dist/executor.d.ts +4 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +732 -0
- package/dist/executor.js.map +1 -0
- package/dist/executor.test.d.ts +2 -0
- package/dist/executor.test.d.ts.map +1 -0
- package/dist/executor.test.js +1524 -0
- package/dist/executor.test.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/secrets/index.d.ts +14 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +18 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/secrets/providers/aws.d.ts +63 -0
- package/dist/secrets/providers/aws.d.ts.map +1 -0
- package/dist/secrets/providers/aws.js +111 -0
- package/dist/secrets/providers/aws.js.map +1 -0
- package/dist/secrets/providers/env.d.ts +36 -0
- package/dist/secrets/providers/env.d.ts.map +1 -0
- package/dist/secrets/providers/env.js +37 -0
- package/dist/secrets/providers/env.js.map +1 -0
- package/dist/secrets/providers/index.d.ts +7 -0
- package/dist/secrets/providers/index.d.ts.map +1 -0
- package/dist/secrets/providers/index.js +7 -0
- package/dist/secrets/providers/index.js.map +1 -0
- package/dist/secrets/providers/vault.d.ts +75 -0
- package/dist/secrets/providers/vault.d.ts.map +1 -0
- package/dist/secrets/providers/vault.js +143 -0
- package/dist/secrets/providers/vault.js.map +1 -0
- package/dist/secrets/registry.d.ts +61 -0
- package/dist/secrets/registry.d.ts.map +1 -0
- package/dist/secrets/registry.js +182 -0
- package/dist/secrets/registry.js.map +1 -0
- package/dist/secrets/resolver.d.ts +40 -0
- package/dist/secrets/resolver.d.ts.map +1 -0
- package/dist/secrets/resolver.js +178 -0
- package/dist/secrets/resolver.js.map +1 -0
- package/dist/secrets/secrets.test.d.ts +2 -0
- package/dist/secrets/secrets.test.d.ts.map +1 -0
- package/dist/secrets/secrets.test.js +243 -0
- package/dist/secrets/secrets.test.js.map +1 -0
- package/dist/secrets/types.d.ts +71 -0
- package/dist/secrets/types.d.ts.map +1 -0
- package/dist/secrets/types.js +38 -0
- package/dist/secrets/types.js.map +1 -0
- package/dist/shared.d.ts +8 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/dist/test-plan-types.d.ts +43 -0
- package/dist/test-plan-types.d.ts.map +1 -0
- package/dist/test-plan-types.js +2 -0
- package/dist/test-plan-types.js.map +1 -0
- package/dist/types.d.ts +77 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +35 -0
- package/src/adapters/axios.ts +41 -0
- package/src/adapters/index.ts +2 -0
- package/src/adapters/stub.ts +47 -0
- package/src/events/emitter.test.ts +316 -0
- package/src/events/emitter.ts +133 -0
- package/src/events/index.ts +2 -0
- package/src/events/types.ts +132 -0
- package/src/executor.test.ts +1674 -0
- package/src/executor.ts +986 -0
- package/src/index.ts +69 -0
- package/src/secrets/index.ts +41 -0
- package/src/secrets/providers/aws.ts +179 -0
- package/src/secrets/providers/env.ts +66 -0
- package/src/secrets/providers/index.ts +15 -0
- package/src/secrets/providers/vault.ts +257 -0
- package/src/secrets/registry.ts +234 -0
- package/src/secrets/resolver.ts +249 -0
- package/src/secrets/secrets.test.ts +318 -0
- package/src/secrets/types.ts +105 -0
- package/src/shared.ts +46 -0
- package/src/test-plan-types.ts +49 -0
- package/src/types.ts +95 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# griffin Plan Executor
|
|
2
|
+
|
|
3
|
+
The griffin Plan Executor takes JSON test plans (output from the test system DSL) and executes them. It can run tests locally or remotely (e.g., in a Lambda function).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
✅ **Currently Working**:
|
|
8
|
+
|
|
9
|
+
- Execute JSON test plans
|
|
10
|
+
- Support for HTTP endpoints (GET, POST, PUT, DELETE, PATCH)
|
|
11
|
+
- Support for wait nodes
|
|
12
|
+
- Graph-based execution following edges (topological sort)
|
|
13
|
+
- Secrets resolution at runtime (env, AWS Secrets Manager, HashiCorp Vault)
|
|
14
|
+
- Local execution mode
|
|
15
|
+
- Detailed error reporting with node-by-node results
|
|
16
|
+
|
|
17
|
+
🚧 **In Development**:
|
|
18
|
+
|
|
19
|
+
- Assertion evaluation (structure in place)
|
|
20
|
+
- Remote execution mode
|
|
21
|
+
- Advanced error handling and retries
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install
|
|
27
|
+
npm run build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Programmatic Usage
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { executePlan } from "./dist/executor";
|
|
36
|
+
import type { TestPlan } from "./dist/test-plan-types";
|
|
37
|
+
|
|
38
|
+
const testPlan: TestPlan = {
|
|
39
|
+
name: "my-test",
|
|
40
|
+
endpoint_host: "http://localhost",
|
|
41
|
+
nodes: [
|
|
42
|
+
{
|
|
43
|
+
id: "health",
|
|
44
|
+
type: "endpoint",
|
|
45
|
+
method: "GET",
|
|
46
|
+
path: "/health",
|
|
47
|
+
response_format: "JSON",
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
edges: [
|
|
51
|
+
{ from: "__START__", to: "health" },
|
|
52
|
+
{ from: "health", to: "__END__" },
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const results = await executePlan(testPlan, {
|
|
57
|
+
mode: "local",
|
|
58
|
+
baseUrl: "http://localhost:3000",
|
|
59
|
+
timeout: 30000,
|
|
60
|
+
secretRegistry: secretRegistry, // Optional: for resolving secrets in test plans
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log(results);
|
|
64
|
+
// {
|
|
65
|
+
// success: true,
|
|
66
|
+
// results: [
|
|
67
|
+
// {
|
|
68
|
+
// nodeId: "health",
|
|
69
|
+
// success: true,
|
|
70
|
+
// response: { status: "ok" },
|
|
71
|
+
// duration_ms: 25
|
|
72
|
+
// }
|
|
73
|
+
// ],
|
|
74
|
+
// errors: [],
|
|
75
|
+
// totalDuration_ms: 25
|
|
76
|
+
// }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Execution Options
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
interface ExecutionOptions {
|
|
83
|
+
mode: "local" | "remote"; // Currently only 'local' is fully supported
|
|
84
|
+
baseUrl?: string; // Base URL for endpoints (overrides plan's endpoint_host)
|
|
85
|
+
timeout?: number; // Request timeout in ms (default: 30000)
|
|
86
|
+
secretRegistry?: SecretProviderRegistry; // Optional: for resolving secrets in test plans
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Execution Flow
|
|
91
|
+
|
|
92
|
+
1. **Parse the test plan JSON** - Validates structure and types
|
|
93
|
+
2. **Build execution graph** - Performs topological sort starting from `__START__`
|
|
94
|
+
3. **Execute nodes in order**:
|
|
95
|
+
- **Endpoints**: Makes HTTP requests, stores responses
|
|
96
|
+
- **Waits**: Sleeps for specified duration
|
|
97
|
+
- **Assertions**: Evaluates assertions (currently structure only)
|
|
98
|
+
4. **Collect results** - Aggregates success/failure status for each node
|
|
99
|
+
5. **Return execution results** - Includes detailed node results, errors, and timing
|
|
100
|
+
|
|
101
|
+
## Node Types
|
|
102
|
+
|
|
103
|
+
### Endpoint Nodes
|
|
104
|
+
|
|
105
|
+
- Makes HTTP requests using axios
|
|
106
|
+
- Supports all HTTP methods (GET, POST, PUT, DELETE, PATCH)
|
|
107
|
+
- Parses JSON, XML, or TEXT responses
|
|
108
|
+
- Stores response for use in subsequent nodes
|
|
109
|
+
|
|
110
|
+
### Wait Nodes
|
|
111
|
+
|
|
112
|
+
- Pauses execution for specified duration
|
|
113
|
+
- Duration specified in milliseconds
|
|
114
|
+
|
|
115
|
+
### Assertion Nodes
|
|
116
|
+
|
|
117
|
+
- Currently structure is in place but evaluation needs implementation
|
|
118
|
+
- Will evaluate assertions with access to all previous endpoint responses
|
|
119
|
+
|
|
120
|
+
### Secrets Resolution
|
|
121
|
+
|
|
122
|
+
If a test plan contains secret references (created using the `secret()` function in the DSL), they are resolved before execution begins. Secrets can be used in endpoint headers and request bodies.
|
|
123
|
+
|
|
124
|
+
**Supported Providers:**
|
|
125
|
+
|
|
126
|
+
- **Environment Variables**: Read from process environment
|
|
127
|
+
- **AWS Secrets Manager**: Retrieve secrets from AWS Secrets Manager
|
|
128
|
+
- **HashiCorp Vault**: Retrieve secrets from Vault KV secrets engine
|
|
129
|
+
|
|
130
|
+
**Example test plan with secrets:**
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"name": "authenticated-check",
|
|
135
|
+
"nodes": [
|
|
136
|
+
{
|
|
137
|
+
"id": "api_call",
|
|
138
|
+
"type": "endpoint",
|
|
139
|
+
"headers": {
|
|
140
|
+
"Authorization": {
|
|
141
|
+
"$secret": {
|
|
142
|
+
"provider": "env",
|
|
143
|
+
"ref": "API_TOKEN"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The executor will resolve all secrets before executing the plan. If any secret cannot be resolved, execution fails immediately with a clear error message.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axios.d.ts","sourceRoot":"","sources":["../../src/adapters/axios.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhF,qBAAa,YAAa,YAAW,iBAAiB;IAC9C,OAAO,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;CAoCvD"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
export class AxiosAdapter {
|
|
3
|
+
async request(req) {
|
|
4
|
+
try {
|
|
5
|
+
const response = await axios({
|
|
6
|
+
method: req.method,
|
|
7
|
+
url: req.url,
|
|
8
|
+
headers: req.headers,
|
|
9
|
+
data: req.body,
|
|
10
|
+
timeout: req.timeout,
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
status: response.status,
|
|
14
|
+
statusText: response.statusText,
|
|
15
|
+
data: response.data,
|
|
16
|
+
headers: response.headers,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// Re-throw axios errors with additional context
|
|
21
|
+
if (axios.isAxiosError(error)) {
|
|
22
|
+
if (error.code === "ECONNREFUSED") {
|
|
23
|
+
throw new Error(`Connection refused - is the server running on ${req.url}?`);
|
|
24
|
+
}
|
|
25
|
+
if (error.code === "ETIMEDOUT") {
|
|
26
|
+
throw new Error(`Request timed out after ${req.timeout}ms`);
|
|
27
|
+
}
|
|
28
|
+
if (error.response) {
|
|
29
|
+
throw new Error(`HTTP ${error.response.status}: ${error.response.statusText}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=axios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"axios.js","sourceRoot":"","sources":["../../src/adapters/axios.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,MAAM,OAAO,YAAY;IACvB,KAAK,CAAC,OAAO,CAAC,GAAgB;QAC5B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC;gBAC3B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,GAAG,CAAC,OAAO;aACrB,CAAC,CAAC;YAEH,OAAO;gBACL,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;gBAC/B,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,OAAO,EAAE,QAAQ,CAAC,OAAiC;aACpD,CAAC;QACJ,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,gDAAgD;YAChD,IAAI,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC9B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CACb,iDAAiD,GAAG,CAAC,GAAG,GAAG,CAC5D,CAAC;gBACJ,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC/B,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;gBAC9D,CAAC;gBACD,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;oBACnB,MAAM,IAAI,KAAK,CACb,QAAQ,KAAK,CAAC,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,CAC9D,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAqB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { HttpClientAdapter, HttpRequest, HttpResponse } from "../types.js";
|
|
2
|
+
export interface StubResponse {
|
|
3
|
+
/** URL pattern to match (string for exact, RegExp for pattern, or function for custom logic) */
|
|
4
|
+
match: string | RegExp | ((req: HttpRequest) => boolean);
|
|
5
|
+
response: HttpResponse;
|
|
6
|
+
}
|
|
7
|
+
export declare class StubAdapter implements HttpClientAdapter {
|
|
8
|
+
private stubs;
|
|
9
|
+
/**
|
|
10
|
+
* Add a stub response for matching requests
|
|
11
|
+
* @param stub - The stub configuration
|
|
12
|
+
* @returns this (for chaining)
|
|
13
|
+
*/
|
|
14
|
+
addStub(stub: StubResponse): this;
|
|
15
|
+
/**
|
|
16
|
+
* Clear all configured stubs
|
|
17
|
+
*/
|
|
18
|
+
clearStubs(): void;
|
|
19
|
+
request(req: HttpRequest): Promise<HttpResponse>;
|
|
20
|
+
private matches;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=stub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.d.ts","sourceRoot":"","sources":["../../src/adapters/stub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhF,MAAM,WAAW,YAAY;IAC3B,gGAAgG;IAChG,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC;IACzD,QAAQ,EAAE,YAAY,CAAC;CACxB;AAED,qBAAa,WAAY,YAAW,iBAAiB;IACnD,OAAO,CAAC,KAAK,CAAsB;IAEnC;;;;OAIG;IACH,OAAO,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI;IAKjC;;OAEG;IACH,UAAU,IAAI,IAAI;IAIZ,OAAO,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAStD,OAAO,CAAC,OAAO;CAShB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export class StubAdapter {
|
|
2
|
+
stubs = [];
|
|
3
|
+
/**
|
|
4
|
+
* Add a stub response for matching requests
|
|
5
|
+
* @param stub - The stub configuration
|
|
6
|
+
* @returns this (for chaining)
|
|
7
|
+
*/
|
|
8
|
+
addStub(stub) {
|
|
9
|
+
this.stubs.push(stub);
|
|
10
|
+
return this;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Clear all configured stubs
|
|
14
|
+
*/
|
|
15
|
+
clearStubs() {
|
|
16
|
+
this.stubs = [];
|
|
17
|
+
}
|
|
18
|
+
async request(req) {
|
|
19
|
+
for (const stub of this.stubs) {
|
|
20
|
+
if (this.matches(stub.match, req)) {
|
|
21
|
+
return stub.response;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
throw new Error(`No stub matched request: ${req.method} ${req.url}`);
|
|
25
|
+
}
|
|
26
|
+
matches(match, req) {
|
|
27
|
+
if (typeof match === "string") {
|
|
28
|
+
return req.url === match;
|
|
29
|
+
}
|
|
30
|
+
if (match instanceof RegExp) {
|
|
31
|
+
return match.test(req.url);
|
|
32
|
+
}
|
|
33
|
+
return match(req);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.js","sourceRoot":"","sources":["../../src/adapters/stub.ts"],"names":[],"mappings":"AAQA,MAAM,OAAO,WAAW;IACd,KAAK,GAAmB,EAAE,CAAC;IAEnC;;;;OAIG;IACH,OAAO,CAAC,IAAkB;QACxB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,UAAU;QACR,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAgB;QAC5B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9B,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,QAAQ,CAAC;YACvB,CAAC;QACH,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;IACvE,CAAC;IAEO,OAAO,CAAC,KAA4B,EAAE,GAAgB;QAC5D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,OAAO,GAAG,CAAC,GAAG,KAAK,KAAK,CAAC;QAC3B,CAAC;QACD,IAAI,KAAK,YAAY,MAAM,EAAE,CAAC;YAC5B,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC;IACpB,CAAC;CACF"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ExecutionEvent } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pluggable event emitter interface for execution observability.
|
|
4
|
+
*
|
|
5
|
+
* Implementations can be synchronous (local) or asynchronous (durable bus).
|
|
6
|
+
* The executor treats emission as best-effort and non-blocking.
|
|
7
|
+
*/
|
|
8
|
+
export interface ExecutionEventEmitter {
|
|
9
|
+
/**
|
|
10
|
+
* Emit a single event. May be synchronous or asynchronous depending on implementation.
|
|
11
|
+
* Errors during emission should not propagate to the executor.
|
|
12
|
+
*/
|
|
13
|
+
emit(event: ExecutionEvent): void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Optional: flush any buffered events (for batching implementations).
|
|
16
|
+
* Called at the end of execution to ensure all events are delivered.
|
|
17
|
+
*/
|
|
18
|
+
flush?(): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional: cleanup resources (timers, connections, etc.).
|
|
21
|
+
*/
|
|
22
|
+
destroy?(): void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* In-memory event emitter for local/development use.
|
|
26
|
+
* Events are delivered synchronously to all subscribers.
|
|
27
|
+
*/
|
|
28
|
+
export declare class LocalEventEmitter implements ExecutionEventEmitter {
|
|
29
|
+
private listeners;
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to all execution events.
|
|
32
|
+
* @returns Unsubscribe function
|
|
33
|
+
*/
|
|
34
|
+
subscribe(listener: (event: ExecutionEvent) => void): () => void;
|
|
35
|
+
emit(event: ExecutionEvent): void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Adapter interface for durable event buses (SQS, Kafka, Redis Streams, etc.).
|
|
39
|
+
* Implementations provided by callers based on their infrastructure.
|
|
40
|
+
*/
|
|
41
|
+
export interface DurableEventBusAdapter {
|
|
42
|
+
/**
|
|
43
|
+
* Publish a batch of events to the durable bus.
|
|
44
|
+
* Should handle serialization, retries, and error handling internally.
|
|
45
|
+
*/
|
|
46
|
+
publish(events: ExecutionEvent[]): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Event emitter that batches events for efficient delivery to durable event buses.
|
|
50
|
+
* Flushes on interval and when batch size is reached.
|
|
51
|
+
*/
|
|
52
|
+
export declare class DurableEventEmitter implements ExecutionEventEmitter {
|
|
53
|
+
private adapter;
|
|
54
|
+
private options;
|
|
55
|
+
private buffer;
|
|
56
|
+
private flushInterval;
|
|
57
|
+
private isDestroyed;
|
|
58
|
+
constructor(adapter: DurableEventBusAdapter, options?: {
|
|
59
|
+
/** Number of events to batch before auto-flushing (default: 50) */
|
|
60
|
+
batchSize?: number;
|
|
61
|
+
/** Milliseconds between auto-flushes (default: 100) */
|
|
62
|
+
flushIntervalMs?: number;
|
|
63
|
+
});
|
|
64
|
+
emit(event: ExecutionEvent): void;
|
|
65
|
+
flush(): Promise<void>;
|
|
66
|
+
destroy(): void;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.d.ts","sourceRoot":"","sources":["../../src/events/emitter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElD;;;OAGG;IACH,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB;;OAEG;IACH,OAAO,CAAC,IAAI,IAAI,CAAC;CAClB;AAED;;;GAGG;AACH,qBAAa,iBAAkB,YAAW,qBAAqB;IAC7D,OAAO,CAAC,SAAS,CAA8C;IAE/D;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI;IAOhE,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;CAUlC;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,qBAAqB;IAM7D,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;IANjB,OAAO,CAAC,MAAM,CAAwB;IACtC,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,WAAW,CAAS;gBAGlB,OAAO,EAAE,sBAAsB,EAC/B,OAAO,GAAE;QACf,mEAAmE;QACnE,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,uDAAuD;QACvD,eAAe,CAAC,EAAE,MAAM,CAAC;KACrB;IAUR,IAAI,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAgB3B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAc5B,OAAO,IAAI,IAAI;CAOhB"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory event emitter for local/development use.
|
|
3
|
+
* Events are delivered synchronously to all subscribers.
|
|
4
|
+
*/
|
|
5
|
+
export class LocalEventEmitter {
|
|
6
|
+
listeners = [];
|
|
7
|
+
/**
|
|
8
|
+
* Subscribe to all execution events.
|
|
9
|
+
* @returns Unsubscribe function
|
|
10
|
+
*/
|
|
11
|
+
subscribe(listener) {
|
|
12
|
+
this.listeners.push(listener);
|
|
13
|
+
return () => {
|
|
14
|
+
this.listeners = this.listeners.filter((l) => l !== listener);
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
emit(event) {
|
|
18
|
+
for (const listener of this.listeners) {
|
|
19
|
+
try {
|
|
20
|
+
listener(event);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
// Don't let listener errors break execution
|
|
24
|
+
console.error("Error in event listener:", error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Event emitter that batches events for efficient delivery to durable event buses.
|
|
31
|
+
* Flushes on interval and when batch size is reached.
|
|
32
|
+
*/
|
|
33
|
+
export class DurableEventEmitter {
|
|
34
|
+
adapter;
|
|
35
|
+
options;
|
|
36
|
+
buffer = [];
|
|
37
|
+
flushInterval = null;
|
|
38
|
+
isDestroyed = false;
|
|
39
|
+
constructor(adapter, options = {}) {
|
|
40
|
+
this.adapter = adapter;
|
|
41
|
+
this.options = options;
|
|
42
|
+
const intervalMs = options.flushIntervalMs ?? 100;
|
|
43
|
+
this.flushInterval = setInterval(() => {
|
|
44
|
+
this.flush().catch((error) => {
|
|
45
|
+
console.error("Error flushing events:", error);
|
|
46
|
+
});
|
|
47
|
+
}, intervalMs);
|
|
48
|
+
}
|
|
49
|
+
emit(event) {
|
|
50
|
+
if (this.isDestroyed) {
|
|
51
|
+
console.warn("Cannot emit event: emitter is destroyed");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.buffer.push(event);
|
|
55
|
+
// Auto-flush when batch size is reached
|
|
56
|
+
if (this.buffer.length >= (this.options.batchSize ?? 50)) {
|
|
57
|
+
this.flush().catch((error) => {
|
|
58
|
+
console.error("Error auto-flushing events:", error);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async flush() {
|
|
63
|
+
if (this.buffer.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
const events = this.buffer;
|
|
66
|
+
this.buffer = [];
|
|
67
|
+
try {
|
|
68
|
+
await this.adapter.publish(events);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error("Failed to publish events:", error);
|
|
72
|
+
// Events are lost on failure - caller adapter should implement retries if needed
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
destroy() {
|
|
76
|
+
this.isDestroyed = true;
|
|
77
|
+
if (this.flushInterval) {
|
|
78
|
+
clearInterval(this.flushInterval);
|
|
79
|
+
this.flushInterval = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.js","sourceRoot":"","sources":["../../src/events/emitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitter.test.d.ts","sourceRoot":"","sources":["../../src/events/emitter.test.ts"],"names":[],"mappings":""}
|