@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.
Files changed (114) hide show
  1. package/README.md +152 -0
  2. package/dist/adapters/axios.d.ts +5 -0
  3. package/dist/adapters/axios.d.ts.map +1 -0
  4. package/dist/adapters/axios.js +36 -0
  5. package/dist/adapters/axios.js.map +1 -0
  6. package/dist/adapters/index.d.ts +3 -0
  7. package/dist/adapters/index.d.ts.map +1 -0
  8. package/dist/adapters/index.js +3 -0
  9. package/dist/adapters/index.js.map +1 -0
  10. package/dist/adapters/stub.d.ts +22 -0
  11. package/dist/adapters/stub.d.ts.map +1 -0
  12. package/dist/adapters/stub.js +36 -0
  13. package/dist/adapters/stub.js.map +1 -0
  14. package/dist/events/emitter.d.ts +68 -0
  15. package/dist/events/emitter.d.ts.map +1 -0
  16. package/dist/events/emitter.js +83 -0
  17. package/dist/events/emitter.js.map +1 -0
  18. package/dist/events/emitter.test.d.ts +2 -0
  19. package/dist/events/emitter.test.d.ts.map +1 -0
  20. package/dist/events/emitter.test.js +251 -0
  21. package/dist/events/emitter.test.js.map +1 -0
  22. package/dist/events/index.d.ts +3 -0
  23. package/dist/events/index.d.ts.map +1 -0
  24. package/dist/events/index.js +3 -0
  25. package/dist/events/index.js.map +1 -0
  26. package/dist/events/types.d.ts +109 -0
  27. package/dist/events/types.d.ts.map +1 -0
  28. package/dist/events/types.js +9 -0
  29. package/dist/events/types.js.map +1 -0
  30. package/dist/executor.d.ts +4 -0
  31. package/dist/executor.d.ts.map +1 -0
  32. package/dist/executor.js +732 -0
  33. package/dist/executor.js.map +1 -0
  34. package/dist/executor.test.d.ts +2 -0
  35. package/dist/executor.test.d.ts.map +1 -0
  36. package/dist/executor.test.js +1524 -0
  37. package/dist/executor.test.js.map +1 -0
  38. package/dist/index.d.ts +8 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +12 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/secrets/index.d.ts +14 -0
  43. package/dist/secrets/index.d.ts.map +1 -0
  44. package/dist/secrets/index.js +18 -0
  45. package/dist/secrets/index.js.map +1 -0
  46. package/dist/secrets/providers/aws.d.ts +63 -0
  47. package/dist/secrets/providers/aws.d.ts.map +1 -0
  48. package/dist/secrets/providers/aws.js +111 -0
  49. package/dist/secrets/providers/aws.js.map +1 -0
  50. package/dist/secrets/providers/env.d.ts +36 -0
  51. package/dist/secrets/providers/env.d.ts.map +1 -0
  52. package/dist/secrets/providers/env.js +37 -0
  53. package/dist/secrets/providers/env.js.map +1 -0
  54. package/dist/secrets/providers/index.d.ts +7 -0
  55. package/dist/secrets/providers/index.d.ts.map +1 -0
  56. package/dist/secrets/providers/index.js +7 -0
  57. package/dist/secrets/providers/index.js.map +1 -0
  58. package/dist/secrets/providers/vault.d.ts +75 -0
  59. package/dist/secrets/providers/vault.d.ts.map +1 -0
  60. package/dist/secrets/providers/vault.js +143 -0
  61. package/dist/secrets/providers/vault.js.map +1 -0
  62. package/dist/secrets/registry.d.ts +61 -0
  63. package/dist/secrets/registry.d.ts.map +1 -0
  64. package/dist/secrets/registry.js +182 -0
  65. package/dist/secrets/registry.js.map +1 -0
  66. package/dist/secrets/resolver.d.ts +40 -0
  67. package/dist/secrets/resolver.d.ts.map +1 -0
  68. package/dist/secrets/resolver.js +178 -0
  69. package/dist/secrets/resolver.js.map +1 -0
  70. package/dist/secrets/secrets.test.d.ts +2 -0
  71. package/dist/secrets/secrets.test.d.ts.map +1 -0
  72. package/dist/secrets/secrets.test.js +243 -0
  73. package/dist/secrets/secrets.test.js.map +1 -0
  74. package/dist/secrets/types.d.ts +71 -0
  75. package/dist/secrets/types.d.ts.map +1 -0
  76. package/dist/secrets/types.js +38 -0
  77. package/dist/secrets/types.js.map +1 -0
  78. package/dist/shared.d.ts +8 -0
  79. package/dist/shared.d.ts.map +1 -0
  80. package/dist/shared.js +30 -0
  81. package/dist/shared.js.map +1 -0
  82. package/dist/test-plan-types.d.ts +43 -0
  83. package/dist/test-plan-types.d.ts.map +1 -0
  84. package/dist/test-plan-types.js +2 -0
  85. package/dist/test-plan-types.js.map +1 -0
  86. package/dist/types.d.ts +77 -0
  87. package/dist/types.d.ts.map +1 -0
  88. package/dist/types.js +3 -0
  89. package/dist/types.js.map +1 -0
  90. package/package.json +35 -0
  91. package/src/adapters/axios.ts +41 -0
  92. package/src/adapters/index.ts +2 -0
  93. package/src/adapters/stub.ts +47 -0
  94. package/src/events/emitter.test.ts +316 -0
  95. package/src/events/emitter.ts +133 -0
  96. package/src/events/index.ts +2 -0
  97. package/src/events/types.ts +132 -0
  98. package/src/executor.test.ts +1674 -0
  99. package/src/executor.ts +986 -0
  100. package/src/index.ts +69 -0
  101. package/src/secrets/index.ts +41 -0
  102. package/src/secrets/providers/aws.ts +179 -0
  103. package/src/secrets/providers/env.ts +66 -0
  104. package/src/secrets/providers/index.ts +15 -0
  105. package/src/secrets/providers/vault.ts +257 -0
  106. package/src/secrets/registry.ts +234 -0
  107. package/src/secrets/resolver.ts +249 -0
  108. package/src/secrets/secrets.test.ts +318 -0
  109. package/src/secrets/types.ts +105 -0
  110. package/src/shared.ts +46 -0
  111. package/src/test-plan-types.ts +49 -0
  112. package/src/types.ts +95 -0
  113. package/tsconfig.json +20 -0
  114. package/vitest.config.ts +14 -0
@@ -0,0 +1,318 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { isSecretRef } from "./types.js";
3
+ import { SecretProviderRegistry } from "./registry.js";
4
+ import { EnvSecretProvider } from "./providers/env.js";
5
+ import {
6
+ resolveSecretsInPlan,
7
+ collectSecretsFromPlan,
8
+ planHasSecrets,
9
+ } from "./resolver.js";
10
+ import { NodeType, ResponseFormat, HttpMethod } from "griffin/schema";
11
+ import { TestPlanV1 } from "griffin/types";
12
+
13
+ // Helper to create a secret ref (mirrors the DSL's secret function)
14
+ function createSecretRef(path: string) {
15
+ const colonIndex = path.indexOf(":");
16
+ const provider = path.slice(0, colonIndex);
17
+ const ref = path.slice(colonIndex + 1);
18
+ return { $secret: { provider, ref } };
19
+ }
20
+
21
+ describe("Secret Types", () => {
22
+ describe("isSecretRef", () => {
23
+ it("should return true for valid secret refs", () => {
24
+ expect(
25
+ isSecretRef({ $secret: { provider: "env", ref: "API_KEY" } }),
26
+ ).toBe(true);
27
+ expect(
28
+ isSecretRef({
29
+ $secret: { provider: "aws", ref: "my-secret", version: "1" },
30
+ }),
31
+ ).toBe(true);
32
+ });
33
+
34
+ it("should return false for non-secret values", () => {
35
+ expect(isSecretRef("string")).toBe(false);
36
+ expect(isSecretRef(123)).toBe(false);
37
+ expect(isSecretRef(null)).toBe(false);
38
+ expect(isSecretRef(undefined)).toBe(false);
39
+ expect(isSecretRef({})).toBe(false);
40
+ expect(isSecretRef({ secret: { provider: "env", ref: "KEY" } })).toBe(
41
+ false,
42
+ ); // wrong key
43
+ });
44
+ });
45
+ });
46
+
47
+ describe("SecretProviderRegistry", () => {
48
+ let registry: SecretProviderRegistry;
49
+
50
+ beforeEach(() => {
51
+ registry = new SecretProviderRegistry();
52
+ });
53
+
54
+ it("should register and retrieve providers", () => {
55
+ const envProvider = new EnvSecretProvider();
56
+ registry.register(envProvider);
57
+
58
+ expect(registry.has("env")).toBe(true);
59
+ expect(registry.get("env")).toBe(envProvider);
60
+ expect(registry.getProviderNames()).toEqual(["env"]);
61
+ });
62
+
63
+ it("should throw when getting unregistered provider", () => {
64
+ expect(() => registry.get("unknown")).toThrow(/not configured/);
65
+ });
66
+
67
+ it("should throw when registering duplicate provider", () => {
68
+ const envProvider = new EnvSecretProvider();
69
+ registry.register(envProvider);
70
+ expect(() => registry.register(envProvider)).toThrow(/already registered/);
71
+ });
72
+
73
+ it("should resolve secrets using the correct provider", async () => {
74
+ const envProvider = new EnvSecretProvider({
75
+ env: { TEST_SECRET: "secret-value" },
76
+ });
77
+ registry.register(envProvider);
78
+
79
+ const result = await registry.resolve({
80
+ provider: "env",
81
+ ref: "TEST_SECRET",
82
+ });
83
+
84
+ expect(result).toBe("secret-value");
85
+ });
86
+ });
87
+
88
+ describe("EnvSecretProvider", () => {
89
+ it("should resolve environment variables", async () => {
90
+ const provider = new EnvSecretProvider({
91
+ env: { MY_API_KEY: "test-key-123" },
92
+ });
93
+
94
+ const result = await provider.resolve("MY_API_KEY");
95
+ expect(result).toBe("test-key-123");
96
+ });
97
+
98
+ it("should throw for missing environment variables", async () => {
99
+ const provider = new EnvSecretProvider({ env: {} });
100
+
101
+ await expect(provider.resolve("MISSING_VAR")).rejects.toThrow(/not set/);
102
+ });
103
+
104
+ it("should support prefix", async () => {
105
+ const provider = new EnvSecretProvider({
106
+ env: { APP_API_KEY: "prefixed-value" },
107
+ prefix: "APP_",
108
+ });
109
+
110
+ const result = await provider.resolve("API_KEY");
111
+ expect(result).toBe("prefixed-value");
112
+ });
113
+ });
114
+
115
+ describe("Plan Secret Resolution", () => {
116
+ const createTestPlan = (
117
+ headers?: Record<string, any>,
118
+ body?: any,
119
+ ): TestPlanV1 => ({
120
+ id: "test-plan-1",
121
+ name: "Test Plan",
122
+ version: "1.0",
123
+ environment: "default",
124
+ nodes: [
125
+ {
126
+ id: "endpoint-1",
127
+ type: NodeType.ENDPOINT,
128
+ method: HttpMethod.GET,
129
+ path: "/api/test",
130
+ base: { type: "target", key: "api-gateway" },
131
+ response_format: ResponseFormat.JSON,
132
+ headers,
133
+ body,
134
+ },
135
+ ],
136
+ edges: [
137
+ { from: "__START__", to: "endpoint-1" },
138
+ { from: "endpoint-1", to: "__END__" },
139
+ ],
140
+ });
141
+
142
+ describe("planHasSecrets", () => {
143
+ it("should return false for plans without secrets", () => {
144
+ const plan = createTestPlan({ "Content-Type": "application/json" });
145
+ expect(planHasSecrets(plan)).toBe(false);
146
+ });
147
+
148
+ it("should return true for plans with secret refs in headers", () => {
149
+ const plan = createTestPlan({
150
+ Authorization: createSecretRef("env:API_KEY"),
151
+ });
152
+ expect(planHasSecrets(plan)).toBe(true);
153
+ });
154
+
155
+ it("should return true for plans with secret refs in body", () => {
156
+ const plan = createTestPlan(undefined, {
157
+ token: createSecretRef("env:TOKEN"),
158
+ });
159
+ expect(planHasSecrets(plan)).toBe(true);
160
+ });
161
+ });
162
+
163
+ describe("collectSecretsFromPlan", () => {
164
+ it("should collect secrets from headers", () => {
165
+ const plan = createTestPlan({
166
+ Authorization: createSecretRef("env:API_KEY"),
167
+ "X-Custom": createSecretRef("aws:custom-secret"),
168
+ });
169
+
170
+ const collected = collectSecretsFromPlan(plan);
171
+
172
+ expect(collected.refs).toHaveLength(2);
173
+ expect(collected.refs).toContainEqual({
174
+ provider: "env",
175
+ ref: "API_KEY",
176
+ });
177
+ expect(collected.refs).toContainEqual({
178
+ provider: "aws",
179
+ ref: "custom-secret",
180
+ });
181
+ });
182
+
183
+ it("should collect secrets from nested body", () => {
184
+ const plan = createTestPlan(undefined, {
185
+ auth: {
186
+ token: createSecretRef("env:TOKEN"),
187
+ },
188
+ items: [{ key: createSecretRef("env:ITEM_KEY") }],
189
+ });
190
+
191
+ const collected = collectSecretsFromPlan(plan);
192
+
193
+ expect(collected.refs).toHaveLength(2);
194
+ expect(collected.refs).toContainEqual({ provider: "env", ref: "TOKEN" });
195
+ expect(collected.refs).toContainEqual({
196
+ provider: "env",
197
+ ref: "ITEM_KEY",
198
+ });
199
+ });
200
+
201
+ it("should deduplicate secret refs", () => {
202
+ const plan = createTestPlan({
203
+ Authorization: createSecretRef("env:API_KEY"),
204
+ "X-Backup-Auth": createSecretRef("env:API_KEY"),
205
+ });
206
+
207
+ const collected = collectSecretsFromPlan(plan);
208
+
209
+ expect(collected.refs).toHaveLength(1);
210
+ expect(collected.paths).toHaveLength(2);
211
+ });
212
+ });
213
+
214
+ describe("resolveSecretsInPlan", () => {
215
+ it("should resolve secrets in headers", async () => {
216
+ const plan = createTestPlan({
217
+ Authorization: createSecretRef("env:API_KEY"),
218
+ "Content-Type": "application/json",
219
+ });
220
+
221
+ const registry = new SecretProviderRegistry();
222
+ registry.register(
223
+ new EnvSecretProvider({
224
+ env: { API_KEY: "Bearer secret-token" },
225
+ }),
226
+ );
227
+
228
+ const resolved = await resolveSecretsInPlan(plan, registry);
229
+
230
+ const endpoint = resolved.nodes[0];
231
+ if (endpoint.type !== NodeType.ENDPOINT) {
232
+ throw new Error("Endpoint not found");
233
+ }
234
+ expect(endpoint.headers?.Authorization).toBe("Bearer secret-token");
235
+ expect(endpoint.headers?.["Content-Type"]).toBe("application/json");
236
+ });
237
+
238
+ it("should resolve secrets in body", async () => {
239
+ const plan = createTestPlan(undefined, {
240
+ token: createSecretRef("env:TOKEN"),
241
+ data: "plain-value",
242
+ });
243
+
244
+ const registry = new SecretProviderRegistry();
245
+ registry.register(
246
+ new EnvSecretProvider({
247
+ env: { TOKEN: "resolved-token" },
248
+ }),
249
+ );
250
+
251
+ const resolved = await resolveSecretsInPlan(plan, registry);
252
+
253
+ const endpoint = resolved.nodes[0];
254
+ if (endpoint.type !== NodeType.ENDPOINT) {
255
+ throw new Error("Endpoint not found");
256
+ }
257
+ expect(endpoint.body.token).toBe("resolved-token");
258
+ expect(endpoint.body.data).toBe("plain-value");
259
+ });
260
+
261
+ it("should not modify original plan", async () => {
262
+ const plan = createTestPlan({
263
+ Authorization: createSecretRef("env:API_KEY"),
264
+ });
265
+
266
+ const registry = new SecretProviderRegistry();
267
+ registry.register(
268
+ new EnvSecretProvider({
269
+ env: { API_KEY: "secret" },
270
+ }),
271
+ );
272
+
273
+ const resolved = await resolveSecretsInPlan(plan, registry);
274
+
275
+ // Original should still have secret ref
276
+ const originalEndpoint = plan.nodes[0];
277
+ if (originalEndpoint.type !== NodeType.ENDPOINT) {
278
+ throw new Error("Endpoint not found");
279
+ }
280
+ expect(originalEndpoint.headers?.Authorization).toEqual(
281
+ createSecretRef("env:API_KEY"),
282
+ );
283
+
284
+ // Resolved should have string
285
+ const resolvedEndpoint = resolved.nodes[0];
286
+ if (resolvedEndpoint.type !== NodeType.ENDPOINT) {
287
+ throw new Error("Endpoint not found");
288
+ }
289
+ expect(resolvedEndpoint.headers?.Authorization).toBe("secret");
290
+ });
291
+
292
+ it("should throw for unregistered provider", async () => {
293
+ const plan = createTestPlan({
294
+ Authorization: createSecretRef("unknown:API_KEY"),
295
+ });
296
+
297
+ const registry = new SecretProviderRegistry();
298
+ registry.register(new EnvSecretProvider({ env: {} }));
299
+
300
+ await expect(resolveSecretsInPlan(plan, registry)).rejects.toThrow(
301
+ /not configured/,
302
+ );
303
+ });
304
+
305
+ it("should throw for missing secret", async () => {
306
+ const plan = createTestPlan({
307
+ Authorization: createSecretRef("env:MISSING_KEY"),
308
+ });
309
+
310
+ const registry = new SecretProviderRegistry();
311
+ registry.register(new EnvSecretProvider({ env: {} }));
312
+
313
+ await expect(resolveSecretsInPlan(plan, registry)).rejects.toThrow(
314
+ /not set/,
315
+ );
316
+ });
317
+ });
318
+ });
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Secret provider types for griffin plan executor.
3
+ */
4
+
5
+ /**
6
+ * Data structure for a secret reference as it appears in a plan.
7
+ */
8
+ export interface SecretRefData {
9
+ provider: string;
10
+ ref: string;
11
+ version?: string;
12
+ field?: string;
13
+ }
14
+
15
+ /**
16
+ * Secret reference marker in plan JSON.
17
+ */
18
+ export interface SecretRef {
19
+ $secret: SecretRefData;
20
+ }
21
+
22
+ /**
23
+ * Options passed to secret resolution.
24
+ */
25
+ export interface SecretResolveOptions {
26
+ version?: string;
27
+ field?: string;
28
+ }
29
+
30
+ /**
31
+ * Interface that all secret providers must implement.
32
+ */
33
+ export interface SecretProvider {
34
+ /** Unique name of the provider (e.g., "env", "aws", "vault") */
35
+ readonly name: string;
36
+
37
+ /**
38
+ * Resolve a single secret reference.
39
+ * @param ref - The secret path/identifier within this provider
40
+ * @param options - Optional version or field extraction
41
+ * @returns The resolved secret value
42
+ * @throws Error if secret cannot be resolved
43
+ */
44
+ resolve(ref: string, options?: SecretResolveOptions): Promise<string>;
45
+
46
+ /**
47
+ * Optional batch resolution for efficiency.
48
+ * Default implementation calls resolve() for each ref.
49
+ */
50
+ resolveMany?(
51
+ refs: Array<{ ref: string; options?: SecretResolveOptions }>,
52
+ ): Promise<Map<string, string>>;
53
+
54
+ /**
55
+ * Optional validation/health check.
56
+ * Called during provider initialization.
57
+ */
58
+ validate?(): Promise<void>;
59
+ }
60
+
61
+ /**
62
+ * Error thrown when secret resolution fails.
63
+ */
64
+ export class SecretResolutionError extends Error {
65
+ public readonly provider: string;
66
+ public readonly ref: string;
67
+ public readonly cause?: Error;
68
+
69
+ constructor(
70
+ message: string,
71
+ details: { provider: string; ref: string; cause?: unknown },
72
+ ) {
73
+ super(message);
74
+ this.name = "SecretResolutionError";
75
+ this.provider = details.provider;
76
+ this.ref = details.ref;
77
+ if (details.cause instanceof Error) {
78
+ this.cause = details.cause;
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Type guard to check if a value is a secret reference.
85
+ */
86
+ export function isSecretRef(value: unknown): value is SecretRef {
87
+ if (typeof value !== "object" || value === null) {
88
+ return false;
89
+ }
90
+
91
+ const obj = value as Record<string, unknown>;
92
+ if (
93
+ !("$secret" in obj) ||
94
+ typeof obj.$secret !== "object" ||
95
+ obj.$secret === null
96
+ ) {
97
+ return false;
98
+ }
99
+
100
+ const secretData = obj.$secret as Record<string, unknown>;
101
+ return (
102
+ typeof secretData.provider === "string" &&
103
+ typeof secretData.ref === "string"
104
+ );
105
+ }
package/src/shared.ts ADDED
@@ -0,0 +1,46 @@
1
+ import {
2
+ TSchema,
3
+ TSchemaOptions,
4
+ TUnsafe,
5
+ Static,
6
+ Unsafe,
7
+ Type,
8
+ } from "typebox";
9
+ import { Value } from "typebox/value";
10
+ import { Memory } from "typebox/system";
11
+
12
+ export function Ref<T extends TSchema>(
13
+ t: T,
14
+ options: TSchemaOptions = {},
15
+ ): TUnsafe<Static<T>> {
16
+ const id = (t as unknown as Record<string, string | undefined>).$id;
17
+ if (!id) {
18
+ throw new Error("missing ID on schema");
19
+ }
20
+ return Unsafe<Static<T>>({ ...t, $ref: id, $id: undefined, ...options });
21
+ }
22
+
23
+ export class TUnionOneOf<Types extends TSchema[] = TSchema[]> extends Type.Base<
24
+ TSchema[]
25
+ > {
26
+ public oneOf: Types;
27
+ constructor(oneOf: Types) {
28
+ super();
29
+ this.oneOf = oneOf;
30
+ }
31
+ }
32
+ export function UnionOneOf(oneOf: TSchema[]): TUnionOneOf {
33
+ return new TUnionOneOf(oneOf);
34
+ }
35
+
36
+ //export interface TUnionOneOf<Types extends TSchema[] = TSchema[]> extends TSchema {
37
+ // '~kind': 'UnionOneOf'
38
+ // //static: { [K in keyof Types]: Static<Types[K]> }[number]
39
+ // oneOf: Types
40
+ //}
41
+ //
42
+ //export function UnionOneOf<Types extends TSchema[]>(oneOf: [...Types], options: TSchemaOptions = {}) {
43
+ // return { ...options, ["~kind"]: 'UnionOneOf', oneOf } as TUnionOneOf<Types>
44
+ //
45
+ // ///return Memory.Create({ '~kind': 'UnionOneOf' }, { oneOf }, options) as never
46
+ //}
@@ -0,0 +1,49 @@
1
+ export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
2
+ export type ResponseFormat = "JSON" | "XML" | "TEXT";
3
+
4
+ export interface Endpoint {
5
+ id: string;
6
+ type: "endpoint";
7
+ method: HttpMethod;
8
+ path: string;
9
+ response_format: ResponseFormat;
10
+ headers?: Record<string, string>;
11
+ body?: any;
12
+ }
13
+
14
+ export interface WaitNode {
15
+ id: string;
16
+ type: "wait";
17
+ duration_ms: number;
18
+ }
19
+
20
+ export interface AssertionNode {
21
+ id: string;
22
+ type: "assertion";
23
+ assertions: Assertion[];
24
+ }
25
+
26
+ export interface Assertion {
27
+ type: string;
28
+ expected?: any;
29
+ actual?: any;
30
+ message?: string;
31
+ }
32
+
33
+ export interface Edge {
34
+ from: string;
35
+ to: string;
36
+ }
37
+
38
+ export interface Frequency {
39
+ every: number;
40
+ unit: "minute" | "hour" | "day";
41
+ }
42
+
43
+ export interface TestPlan {
44
+ name: string;
45
+ endpoint_host: string;
46
+ frequency?: Frequency;
47
+ nodes: (Endpoint | WaitNode | AssertionNode)[];
48
+ edges: Edge[];
49
+ }
package/src/types.ts ADDED
@@ -0,0 +1,95 @@
1
+ import type { ExecutionEventEmitter } from "./events/index.js";
2
+ import type { SecretProviderRegistry } from "./secrets/index.js";
3
+
4
+ export const START = "__START__" as const;
5
+ export const END = "__END__" as const;
6
+
7
+ export type JSONValue =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | null
12
+ | { [key: string]: JSONValue }
13
+ | JSONValue[];
14
+
15
+ /**
16
+ * Complete response data from an endpoint execution
17
+ */
18
+ export interface NodeResponseData {
19
+ body: JSONValue;
20
+ headers: Record<string, string>;
21
+ status: number;
22
+ }
23
+
24
+ export interface HttpRequest {
25
+ method: string;
26
+ url: string;
27
+ headers?: Record<string, string>;
28
+ body?: unknown;
29
+ timeout?: number;
30
+ }
31
+
32
+ export interface HttpResponse {
33
+ status: number;
34
+ statusText: string;
35
+ data: unknown;
36
+ headers?: Record<string, string>;
37
+ }
38
+
39
+ export interface HttpClientAdapter {
40
+ request(req: HttpRequest): Promise<HttpResponse>;
41
+ }
42
+
43
+ export interface ExecutionOptions {
44
+ mode: "local" | "remote";
45
+ timeout?: number;
46
+ httpClient: HttpClientAdapter;
47
+
48
+ /** Optional event emitter for execution observability */
49
+ eventEmitter?: ExecutionEventEmitter;
50
+
51
+ /** Unique execution ID for correlating events (generated if not provided) */
52
+ executionId?: string;
53
+
54
+ /**
55
+ * Optional secret provider registry for resolving secret references.
56
+ * If provided, secrets in the plan will be resolved before execution.
57
+ * If not provided, any secret references in the plan will cause an error.
58
+ */
59
+ secretRegistry?: SecretProviderRegistry;
60
+
61
+ /**
62
+ * Target resolver for mapping target keys to base URLs.
63
+ * Required when plan contains target references.
64
+ */
65
+ targetResolver: (key: string) => Promise<string | undefined>;
66
+ }
67
+
68
+ export interface NodeResult {
69
+ nodeId: string;
70
+ success: boolean;
71
+ response?: JSONValue;
72
+ error?: string;
73
+ duration_ms: number;
74
+ }
75
+
76
+ export interface ExecutionResult {
77
+ success: boolean;
78
+ results: NodeResult[];
79
+ errors: string[];
80
+ totalDuration_ms: number;
81
+ }
82
+
83
+ export interface EndpointResult {
84
+ success: boolean;
85
+ response?: JSONValue;
86
+ headers?: Record<string, string>;
87
+ status?: number;
88
+ error?: string;
89
+ duration_ms: number;
90
+ }
91
+
92
+ export interface WaitResult {
93
+ success: boolean;
94
+ duration_ms: number;
95
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "declaration": true,
9
+ "declarationMap": true,
10
+ "sourceMap": true,
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "moduleResolution": "bundler"
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: "node",
7
+ include: ["src/**/*.{test,spec}.ts"],
8
+ coverage: {
9
+ provider: "v8",
10
+ reporter: ["text", "json", "html"],
11
+ exclude: ["node_modules/", "dist/", "**/*.d.ts", "**/*.config.*"],
12
+ },
13
+ },
14
+ });