@griffin-app/griffin-ts 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/ASSERTIONS_QUICK_REF.md +161 -0
- package/README.md +297 -0
- package/dist/assertions.d.ts +136 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +230 -0
- package/dist/assertions.js.map +1 -0
- package/dist/builder.d.ts +168 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +165 -0
- package/dist/builder.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +6 -0
- package/dist/constants.js.map +1 -0
- package/dist/example-sequential.d.ts +11 -0
- package/dist/example-sequential.d.ts.map +1 -0
- package/dist/example-sequential.js +160 -0
- package/dist/example-sequential.js.map +1 -0
- package/dist/example.d.ts +9 -0
- package/dist/example.d.ts.map +1 -0
- package/dist/example.js +36 -0
- package/dist/example.js.map +1 -0
- package/dist/frequency.d.ts +15 -0
- package/dist/frequency.d.ts.map +1 -0
- package/dist/frequency.js +34 -0
- package/dist/frequency.js.map +1 -0
- package/dist/http-methods.d.ts +6 -0
- package/dist/http-methods.d.ts.map +1 -0
- package/dist/http-methods.js +9 -0
- package/dist/http-methods.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/index.js.map +1 -0
- package/dist/response-formats.d.ts +4 -0
- package/dist/response-formats.d.ts.map +1 -0
- package/dist/response-formats.js +7 -0
- package/dist/response-formats.js.map +1 -0
- package/dist/schema-exports.d.ts +6 -0
- package/dist/schema-exports.d.ts.map +1 -0
- package/dist/schema-exports.js +43 -0
- package/dist/schema-exports.js.map +1 -0
- package/dist/schema.d.ts +311 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +214 -0
- package/dist/schema.js.map +1 -0
- package/dist/secrets.d.ts +56 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +74 -0
- package/dist/secrets.js.map +1 -0
- package/dist/sequential-builder.d.ts +127 -0
- package/dist/sequential-builder.d.ts.map +1 -0
- package/dist/sequential-builder.js +128 -0
- package/dist/sequential-builder.js.map +1 -0
- package/dist/shared.d.ts +8 -0
- package/dist/shared.d.ts.map +1 -0
- package/dist/shared.js +36 -0
- package/dist/shared.js.map +1 -0
- package/dist/target.d.ts +33 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +53 -0
- package/dist/target.js.map +1 -0
- package/dist/type-exports.d.ts +6 -0
- package/dist/type-exports.d.ts.map +1 -0
- package/dist/type-exports.js +7 -0
- package/dist/type-exports.js.map +1 -0
- package/dist/wait.d.ts +9 -0
- package/dist/wait.d.ts.map +1 -0
- package/dist/wait.js +8 -0
- package/dist/wait.js.map +1 -0
- package/package.json +43 -0
- package/src/assertions.ts +327 -0
- package/src/builder.ts +336 -0
- package/src/constants.ts +5 -0
- package/src/example-sequential.ts +191 -0
- package/src/example.ts +55 -0
- package/src/frequency.ts +38 -0
- package/src/http-methods.ts +5 -0
- package/src/index.ts +70 -0
- package/src/response-formats.ts +3 -0
- package/src/schema-exports.ts +43 -0
- package/src/schema.ts +289 -0
- package/src/secrets.ts +112 -0
- package/src/sequential-builder.ts +254 -0
- package/src/shared.ts +46 -0
- package/src/target.ts +55 -0
- package/src/type-exports.ts +20 -0
- package/src/wait.ts +4 -0
- package/tsconfig.json +20 -0
package/src/secrets.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret reference types and utilities for griffin DSL.
|
|
3
|
+
*
|
|
4
|
+
* Secrets are referenced by provider and path, and resolved at runtime
|
|
5
|
+
* by the plan executor using configured secret providers.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface SecretRefData {
|
|
9
|
+
provider: string;
|
|
10
|
+
ref: string;
|
|
11
|
+
version?: string;
|
|
12
|
+
field?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SecretRef {
|
|
16
|
+
$secret: SecretRefData;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SecretOptions {
|
|
20
|
+
/** Pin to a specific version (provider-dependent) */
|
|
21
|
+
version?: string;
|
|
22
|
+
/** Extract a specific field from a JSON secret */
|
|
23
|
+
field?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a secret reference for use in endpoint headers or body.
|
|
28
|
+
*
|
|
29
|
+
* @param path - Provider-qualified path in format "provider:path"
|
|
30
|
+
* Examples:
|
|
31
|
+
* - "env:API_KEY" - Environment variable
|
|
32
|
+
* - "aws:prod/api-key" - AWS Secrets Manager
|
|
33
|
+
* - "vault:secret/data/api" - HashiCorp Vault
|
|
34
|
+
* - "doppler:backend/prod/API_KEY" - Doppler
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional version pinning or field extraction
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* builder.addEndpoint("call", {
|
|
41
|
+
* method: GET,
|
|
42
|
+
* path: "/api/data",
|
|
43
|
+
* headers: {
|
|
44
|
+
* "Authorization": secret("aws:prod/api-key"),
|
|
45
|
+
* "X-API-Key": secret("env:LOCAL_API_KEY"),
|
|
46
|
+
* },
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export function secret(path: string, options?: SecretOptions): SecretRef {
|
|
51
|
+
const colonIndex = path.indexOf(":");
|
|
52
|
+
|
|
53
|
+
if (colonIndex === -1) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`Secret path must include provider: "provider:path" (e.g., "aws:my-secret", "env:API_KEY"). Got: "${path}"`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (colonIndex === 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Secret path must have a provider name before the colon. Got: "${path}"`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (colonIndex === path.length - 1) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`Secret path must have a reference after the colon. Got: "${path}"`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const provider = path.slice(0, colonIndex);
|
|
72
|
+
const ref = path.slice(colonIndex + 1);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
$secret: {
|
|
76
|
+
provider,
|
|
77
|
+
ref,
|
|
78
|
+
...(options?.version !== undefined && { version: options.version }),
|
|
79
|
+
...(options?.field !== undefined && { field: options.field }),
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Type guard to check if a value is a secret reference.
|
|
86
|
+
*/
|
|
87
|
+
export function isSecretRef(value: unknown): value is SecretRef {
|
|
88
|
+
if (typeof value !== "object" || value === null) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const obj = value as Record<string, unknown>;
|
|
93
|
+
if (
|
|
94
|
+
!("$secret" in obj) ||
|
|
95
|
+
typeof obj.$secret !== "object" ||
|
|
96
|
+
obj.$secret === null
|
|
97
|
+
) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const secretData = obj.$secret as Record<string, unknown>;
|
|
102
|
+
return (
|
|
103
|
+
typeof secretData.provider === "string" &&
|
|
104
|
+
typeof secretData.ref === "string"
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Type that allows either a literal value or a secret reference.
|
|
110
|
+
* Used for headers and other fields that may contain secrets.
|
|
111
|
+
*/
|
|
112
|
+
export type SecretOrValue<T> = T | SecretRef;
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Endpoint,
|
|
3
|
+
Wait,
|
|
4
|
+
Assertion,
|
|
5
|
+
type EndpointConfig,
|
|
6
|
+
type WaitDuration,
|
|
7
|
+
} from "./builder";
|
|
8
|
+
import { START, END } from "./constants";
|
|
9
|
+
import { TEST_PLAN_VERSION, Edge, Node, Frequency, TestPlanV1 } from "./schema";
|
|
10
|
+
import {
|
|
11
|
+
createStateProxy,
|
|
12
|
+
type SerializedAssertion,
|
|
13
|
+
type StateProxy,
|
|
14
|
+
} from "./assertions";
|
|
15
|
+
|
|
16
|
+
type RawPlan = Omit<TestPlanV1, "id" | "environment">;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Callback type for building assertions with type-safe state access
|
|
20
|
+
*/
|
|
21
|
+
export type AssertionCallback<NodeNames extends string> = (
|
|
22
|
+
state: StateProxy<NodeNames>,
|
|
23
|
+
) => SerializedAssertion[];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* SequentialTestBuilder provides a simplified DSL for creating linear test flows.
|
|
27
|
+
* Edges are automatically created based on the order nodes are added.
|
|
28
|
+
* No manual edge management required - just add steps in sequence.
|
|
29
|
+
*
|
|
30
|
+
* @template NodeNames - Union of all registered node names for type-safe state access
|
|
31
|
+
*/
|
|
32
|
+
export interface SequentialTestBuilder<NodeNames extends string = never> {
|
|
33
|
+
/**
|
|
34
|
+
* Adds an endpoint request to the sequence.
|
|
35
|
+
*
|
|
36
|
+
* @param name - Unique name for this node
|
|
37
|
+
* @param config - Endpoint configuration
|
|
38
|
+
* @returns Updated builder with node name registered
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* builder.request("create_user", {
|
|
43
|
+
* method: POST,
|
|
44
|
+
* path: "/api/v1/users",
|
|
45
|
+
* response_format: Json,
|
|
46
|
+
* body: { name: "Test User" }
|
|
47
|
+
* })
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
request<Name extends string>(
|
|
51
|
+
name: Name,
|
|
52
|
+
config: EndpointConfig,
|
|
53
|
+
): SequentialTestBuilder<NodeNames | Name>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Adds a wait/delay to the sequence.
|
|
57
|
+
*
|
|
58
|
+
* @param name - Unique name for this node
|
|
59
|
+
* @param duration - How long to wait
|
|
60
|
+
* @returns Updated builder with node name registered
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* builder.wait("pause", Wait.seconds(5))
|
|
65
|
+
* // or with raw milliseconds:
|
|
66
|
+
* builder.wait("pause", 5000)
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
wait<Name extends string>(
|
|
70
|
+
name: Name,
|
|
71
|
+
duration: WaitDuration,
|
|
72
|
+
): SequentialTestBuilder<NodeNames | Name>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Adds assertions to the sequence using a callback with type-safe state access.
|
|
76
|
+
*
|
|
77
|
+
* The callback receives a state proxy that provides autocomplete for all registered
|
|
78
|
+
* node names and allows building rich JsonPath-based assertions.
|
|
79
|
+
*
|
|
80
|
+
* @param callback - Function that receives state proxy and returns assertions
|
|
81
|
+
* @returns Updated builder (no new node name registered - uses auto-generated name)
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* builder.assert((state) => [
|
|
86
|
+
* Assert(state["create_user"].status).equals(201),
|
|
87
|
+
* Assert(state["create_user"].body["data"]["id"]).not.isNull(),
|
|
88
|
+
* Assert(state["create_user"].headers["content-type"]).contains("application/json"),
|
|
89
|
+
* ])
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
assert(
|
|
93
|
+
callback: AssertionCallback<NodeNames>,
|
|
94
|
+
): SequentialTestBuilder<NodeNames>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Builds the final test plan.
|
|
98
|
+
* Automatically connects all nodes in sequence: START → node1 → node2 → ... → END
|
|
99
|
+
*
|
|
100
|
+
* @returns The completed TestPlan
|
|
101
|
+
*/
|
|
102
|
+
build(): RawPlan;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Internal implementation class for SequentialTestBuilder.
|
|
107
|
+
*/
|
|
108
|
+
class SequentialTestBuilderImpl<
|
|
109
|
+
NodeNames extends string = never,
|
|
110
|
+
> implements SequentialTestBuilder<NodeNames> {
|
|
111
|
+
private nodes: Node[] = [];
|
|
112
|
+
private nodeNames: string[] = [];
|
|
113
|
+
private nodeCounter = 0;
|
|
114
|
+
|
|
115
|
+
constructor(
|
|
116
|
+
private config: {
|
|
117
|
+
name: string;
|
|
118
|
+
frequency?: Frequency;
|
|
119
|
+
locations?: string[];
|
|
120
|
+
},
|
|
121
|
+
) {}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generates a unique auto-generated node name
|
|
125
|
+
*/
|
|
126
|
+
private generateNodeName(): string {
|
|
127
|
+
return `step_${this.nodeCounter++}`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
request<Name extends string>(
|
|
131
|
+
name: Name,
|
|
132
|
+
config: EndpointConfig,
|
|
133
|
+
): SequentialTestBuilder<NodeNames | Name> {
|
|
134
|
+
const node = Endpoint(config);
|
|
135
|
+
this.nodes.push({ ...node, id: name } as Node);
|
|
136
|
+
this.nodeNames.push(name);
|
|
137
|
+
return this as unknown as SequentialTestBuilder<NodeNames | Name>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
wait<Name extends string>(
|
|
141
|
+
name: Name,
|
|
142
|
+
duration: WaitDuration,
|
|
143
|
+
): SequentialTestBuilder<NodeNames | Name> {
|
|
144
|
+
const node = Wait(duration);
|
|
145
|
+
this.nodes.push({ ...node, id: name } as Node);
|
|
146
|
+
this.nodeNames.push(name);
|
|
147
|
+
return this as unknown as SequentialTestBuilder<NodeNames | Name>;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
assert(
|
|
151
|
+
callback: AssertionCallback<NodeNames>,
|
|
152
|
+
): SequentialTestBuilder<NodeNames> {
|
|
153
|
+
const stateProxy = createStateProxy(this.nodeNames as NodeNames[]);
|
|
154
|
+
const serializedAssertions = callback(stateProxy);
|
|
155
|
+
|
|
156
|
+
const nodeName = this.generateNodeName();
|
|
157
|
+
const node = Assertion(serializedAssertions);
|
|
158
|
+
this.nodes.push({ ...node, id: nodeName } as Node);
|
|
159
|
+
this.nodeNames.push(nodeName);
|
|
160
|
+
return this as unknown as SequentialTestBuilder<NodeNames>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
build(): RawPlan {
|
|
164
|
+
const { name, frequency, locations } = this.config;
|
|
165
|
+
const edges: Edge[] = [];
|
|
166
|
+
|
|
167
|
+
// If no nodes, return empty plan with just START->END
|
|
168
|
+
if (this.nodes.length === 0) {
|
|
169
|
+
return {
|
|
170
|
+
name,
|
|
171
|
+
frequency,
|
|
172
|
+
locations,
|
|
173
|
+
version: TEST_PLAN_VERSION,
|
|
174
|
+
nodes: [],
|
|
175
|
+
edges: [{ from: START, to: END }],
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Connect START to first node
|
|
180
|
+
edges.push({ from: START, to: this.nodes[0].id });
|
|
181
|
+
|
|
182
|
+
// Connect consecutive nodes
|
|
183
|
+
for (let i = 0; i < this.nodes.length - 1; i++) {
|
|
184
|
+
edges.push({
|
|
185
|
+
from: this.nodes[i].id,
|
|
186
|
+
to: this.nodes[i + 1].id,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Connect last node to END
|
|
191
|
+
edges.push({ from: this.nodes[this.nodes.length - 1].id, to: END });
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
name,
|
|
195
|
+
version: TEST_PLAN_VERSION,
|
|
196
|
+
frequency,
|
|
197
|
+
locations,
|
|
198
|
+
nodes: this.nodes,
|
|
199
|
+
edges,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Creates a sequential test builder for simple linear test flows.
|
|
206
|
+
*
|
|
207
|
+
* Unlike createGraphBuilder, this builder automatically manages edges
|
|
208
|
+
* based on the order in which nodes are added. Perfect for straightforward
|
|
209
|
+
* sequential tests where you don't need complex branching logic.
|
|
210
|
+
*
|
|
211
|
+
* @param config - Test configuration
|
|
212
|
+
* @param config.name - Name of the test
|
|
213
|
+
* @param config.frequency - Optional frequency for scheduled execution
|
|
214
|
+
* @param config.locations - Optional array of location identifiers where this test should run
|
|
215
|
+
* @returns A new SequentialTestBuilder instance
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* ```typescript
|
|
219
|
+
* import { createTestBuilder, POST, GET, Json, Frequency, Assert } from "griffin-ts";
|
|
220
|
+
*
|
|
221
|
+
* const plan = createTestBuilder({
|
|
222
|
+
* name: "create-and-verify-user",
|
|
223
|
+
* frequency: Frequency.every(5).minute(),
|
|
224
|
+
* locations: ["us-east-1", "eu-west-1"]
|
|
225
|
+
* })
|
|
226
|
+
* .request("create_user", {
|
|
227
|
+
* method: POST,
|
|
228
|
+
* path: "/api/v1/users",
|
|
229
|
+
* response_format: Json,
|
|
230
|
+
* body: { name: "Test User", email: "test@example.com" }
|
|
231
|
+
* })
|
|
232
|
+
* .assert((state) => [
|
|
233
|
+
* Assert(state["create_user"].status).equals(201),
|
|
234
|
+
* Assert(state["create_user"].body["data"]["id"]).not.isNull(),
|
|
235
|
+
* ])
|
|
236
|
+
* .request("get_user", {
|
|
237
|
+
* method: GET,
|
|
238
|
+
* path: "/api/v1/users/123",
|
|
239
|
+
* response_format: Json
|
|
240
|
+
* })
|
|
241
|
+
* .assert((state) => [
|
|
242
|
+
* Assert(state["get_user"].status).equals(200),
|
|
243
|
+
* Assert(state["get_user"].body["data"]["name"]).equals("Test User"),
|
|
244
|
+
* ])
|
|
245
|
+
* .build();
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export function createTestBuilder(config: {
|
|
249
|
+
name: string;
|
|
250
|
+
frequency?: Frequency;
|
|
251
|
+
locations?: string[];
|
|
252
|
+
}): SequentialTestBuilder {
|
|
253
|
+
return new SequentialTestBuilderImpl(config);
|
|
254
|
+
}
|
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
|
+
//}
|
package/src/target.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target reference utilities for griffin DSL.
|
|
3
|
+
*
|
|
4
|
+
* Targets are resolved at runtime by the runner based on the execution environment.
|
|
5
|
+
* This allows the same test to run against different environments (staging, production, etc.)
|
|
6
|
+
* without changing the test code.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TargetRef } from "./schema.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Create a target reference for endpoint base URLs.
|
|
13
|
+
*
|
|
14
|
+
* The runner will resolve this to the appropriate base URL based on the
|
|
15
|
+
* execution environment (passed as a per-run parameter).
|
|
16
|
+
*
|
|
17
|
+
* @param key - The target identifier (e.g., "billing-service", "api-gateway")
|
|
18
|
+
* @returns A target reference object
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* builder.addNode("billing", Endpoint({
|
|
23
|
+
* method: GET,
|
|
24
|
+
* path: "/invoices",
|
|
25
|
+
* base: target("billing-service"),
|
|
26
|
+
* response_format: JSON
|
|
27
|
+
* }));
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function target(key: string): TargetRef {
|
|
31
|
+
if (!key || typeof key !== "string") {
|
|
32
|
+
throw new Error(`Target key must be a non-empty string. Got: ${key}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (key.trim() === "") {
|
|
36
|
+
throw new Error("Target key cannot be empty or whitespace only");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
type: "target",
|
|
41
|
+
key: key.trim(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Type guard to check if a value is a target reference.
|
|
47
|
+
*/
|
|
48
|
+
export function isTargetRef(value: unknown): value is TargetRef {
|
|
49
|
+
if (typeof value !== "object" || value === null) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const obj = value as Record<string, unknown>;
|
|
54
|
+
return obj.type === "target" && typeof obj.key === "string";
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static types derived from TypeBox schemas.
|
|
3
|
+
* Import from "griffin/types" for TypeScript type checking.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
TargetRef,
|
|
8
|
+
Endpoint,
|
|
9
|
+
Frequency,
|
|
10
|
+
Wait,
|
|
11
|
+
BinaryPredicate,
|
|
12
|
+
JSONAssertion,
|
|
13
|
+
XMLAssertion,
|
|
14
|
+
TextAssertion,
|
|
15
|
+
Assertion,
|
|
16
|
+
Assertions,
|
|
17
|
+
Node,
|
|
18
|
+
Edge,
|
|
19
|
+
TestPlanV1,
|
|
20
|
+
} from "./schema.js";
|
package/src/wait.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "commonjs",
|
|
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": "node"
|
|
17
|
+
},
|
|
18
|
+
"include": ["src/**/*"],
|
|
19
|
+
"exclude": ["node_modules", "dist"]
|
|
20
|
+
}
|