@glubean/sdk 0.1.39 → 0.2.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/dist/configure.d.ts +4 -21
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +19 -145
- package/dist/configure.js.map +1 -1
- package/dist/contract-core.d.ts +98 -0
- package/dist/contract-core.d.ts.map +1 -0
- package/dist/contract-core.js +749 -0
- package/dist/contract-core.js.map +1 -0
- package/dist/contract-http/adapter.d.ts +52 -0
- package/dist/contract-http/adapter.d.ts.map +1 -0
- package/dist/contract-http/adapter.js +650 -0
- package/dist/contract-http/adapter.js.map +1 -0
- package/dist/contract-http/factory.d.ts +32 -0
- package/dist/contract-http/factory.d.ts.map +1 -0
- package/dist/contract-http/factory.js +83 -0
- package/dist/contract-http/factory.js.map +1 -0
- package/dist/contract-http/flow-helpers.d.ts +12 -0
- package/dist/contract-http/flow-helpers.d.ts.map +1 -0
- package/dist/contract-http/flow-helpers.js +34 -0
- package/dist/contract-http/flow-helpers.js.map +1 -0
- package/dist/contract-http/index.d.ts +16 -0
- package/dist/contract-http/index.d.ts.map +1 -0
- package/dist/contract-http/index.js +15 -0
- package/dist/contract-http/index.js.map +1 -0
- package/dist/contract-http/markdown.d.ts +10 -0
- package/dist/contract-http/markdown.d.ts.map +1 -0
- package/dist/contract-http/markdown.js +21 -0
- package/dist/contract-http/markdown.js.map +1 -0
- package/dist/contract-http/openapi.d.ts +15 -0
- package/dist/contract-http/openapi.d.ts.map +1 -0
- package/dist/contract-http/openapi.js +38 -0
- package/dist/contract-http/openapi.js.map +1 -0
- package/dist/contract-http/types.d.ts +252 -0
- package/dist/contract-http/types.d.ts.map +1 -0
- package/dist/contract-http/types.js +13 -0
- package/dist/contract-http/types.js.map +1 -0
- package/dist/contract-types.d.ts +420 -467
- package/dist/contract-types.d.ts.map +1 -1
- package/dist/contract-types.js +16 -4
- package/dist/contract-types.js.map +1 -1
- package/dist/each-builder.d.ts +244 -0
- package/dist/each-builder.d.ts.map +1 -0
- package/dist/each-builder.js +268 -0
- package/dist/each-builder.js.map +1 -0
- package/dist/index.d.ts +30 -513
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -826
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/runtime-carrier.d.ts +142 -0
- package/dist/runtime-carrier.d.ts.map +1 -0
- package/dist/runtime-carrier.js +148 -0
- package/dist/runtime-carrier.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +2 -1
- package/dist/session.js.map +1 -1
- package/dist/test-builder.d.ts +249 -0
- package/dist/test-builder.d.ts.map +1 -0
- package/dist/test-builder.js +265 -0
- package/dist/test-builder.js.map +1 -0
- package/dist/test-extend.d.ts +59 -0
- package/dist/test-extend.d.ts.map +1 -0
- package/dist/test-extend.js +111 -0
- package/dist/test-extend.js.map +1 -0
- package/dist/test-utils.d.ts +39 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +91 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/types.d.ts +41 -122
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/contract.d.ts +0 -64
- package/dist/contract.d.ts.map +0 -1
- package/dist/contract.js +0 -793
- package/dist/contract.js.map +0 -1
package/dist/contract-types.d.ts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Core types for the protocol-agnostic contract system.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
4
|
+
* This file defines:
|
|
5
|
+
* - Protocol-agnostic enums (lifecycle / severity / requires / defaultRun)
|
|
6
|
+
* - Failure classification types
|
|
7
|
+
* - ContractProtocolAdapter<Spec, Rt, RtM, Sf, SfM> interface
|
|
8
|
+
* - ContractProjection<RuntimeSchemas, RuntimeMeta> — Runtime (live objects OK)
|
|
9
|
+
* - ExtractedContractProjection<SafeSchemas, SafeMeta> — JSON-safe (downstream)
|
|
10
|
+
* - ProtocolContract<Spec, PayloadSchemas, Meta> — runtime carrier
|
|
11
|
+
* - Flow types — ContractCaseRef / FlowBuilder / FlowContract etc.
|
|
12
|
+
*
|
|
13
|
+
* Protocol-specific types (HttpContractSpec / HttpPayloadSchemas / etc.) live
|
|
14
|
+
* in `./contract-http/types.ts`. Core code never imports from there; other
|
|
15
|
+
* adapter plugins (gRPC / GraphQL / etc.) will follow the same pattern.
|
|
16
|
+
*
|
|
17
|
+
* See `internal/40-discovery/proposals/contract-generics-complete.md` v5
|
|
18
|
+
* and `internal/40-discovery/proposals/contract-flow.md` v9 for design.
|
|
13
19
|
*/
|
|
14
|
-
|
|
15
|
-
type: "apiKey";
|
|
16
|
-
name: string;
|
|
17
|
-
in: "header" | "query";
|
|
18
|
-
} | {
|
|
19
|
-
type: "oauth2";
|
|
20
|
-
flows: Record<string, unknown>;
|
|
21
|
-
} | null;
|
|
20
|
+
import type { Test, TestContext } from "./types.js";
|
|
22
21
|
/**
|
|
23
22
|
* Case lifecycle.
|
|
24
23
|
*
|
|
@@ -35,25 +34,38 @@ export type CaseLifecycle = "active" | "deferred" | "deprecated";
|
|
|
35
34
|
* - `"info"` — informational check, failure does not trigger alerts
|
|
36
35
|
*/
|
|
37
36
|
export type CaseSeverity = "critical" | "warning" | "info";
|
|
37
|
+
/**
|
|
38
|
+
* Physical capability required by a case or flow.
|
|
39
|
+
*
|
|
40
|
+
* - `"headless"` — fully automated, no human in loop (default)
|
|
41
|
+
* - `"browser"` — needs a real browser (OAuth code flow, checkout, captcha)
|
|
42
|
+
* - `"out-of-band"` — needs an out-of-band channel (email, SMS, webhook tunnel)
|
|
43
|
+
*/
|
|
44
|
+
export type CaseRequires = "headless" | "browser" | "out-of-band";
|
|
45
|
+
/**
|
|
46
|
+
* Default run policy for a case or flow.
|
|
47
|
+
*
|
|
48
|
+
* - `"always"` — run whenever the runner satisfies `requires` (default)
|
|
49
|
+
* - `"opt-in"` — skip unless explicitly requested (`--include-opt-in`)
|
|
50
|
+
*/
|
|
51
|
+
export type CaseDefaultRun = "always" | "opt-in";
|
|
52
|
+
/**
|
|
53
|
+
* OpenAPI-style extensions. Keys MUST start with "x-".
|
|
54
|
+
* Non-x keys are rejected at the type level.
|
|
55
|
+
*/
|
|
56
|
+
export type Extensions = Record<`x-${string}`, unknown>;
|
|
38
57
|
/**
|
|
39
58
|
* Standardized failure classification kind. Protocol-agnostic.
|
|
40
59
|
*
|
|
41
60
|
* Open string type — adapters can extend with custom values.
|
|
42
|
-
* Recommended
|
|
43
|
-
* -
|
|
44
|
-
* - `"permission"` — authorization failure (403-class)
|
|
45
|
-
* - `"not-found"` — resource does not exist
|
|
46
|
-
* - `"schema"` — response shape mismatch
|
|
47
|
-
* - `"timeout"` — request or execution timeout
|
|
48
|
-
* - `"transport"` — network/connection error
|
|
49
|
-
* - `"rate-limit"` — throttled
|
|
50
|
-
* - `"business-rule"` — business logic assertion failure
|
|
51
|
-
* - `"unknown"` — cannot classify
|
|
61
|
+
* Recommended: "auth", "permission", "not-found", "schema", "timeout",
|
|
62
|
+
* "transport", "rate-limit", "business-rule", "unknown".
|
|
52
63
|
*/
|
|
53
64
|
export type FailureKind = string;
|
|
54
65
|
/**
|
|
55
66
|
* Standardized failure classification.
|
|
56
|
-
* Produced by protocol adapters or core. Consumed by repair loop, Cloud,
|
|
67
|
+
* Produced by protocol adapters or core. Consumed by repair loop, Cloud,
|
|
68
|
+
* runner summary.
|
|
57
69
|
*/
|
|
58
70
|
export interface FailureClassification {
|
|
59
71
|
kind: FailureKind;
|
|
@@ -62,539 +74,480 @@ export interface FailureClassification {
|
|
|
62
74
|
message?: string;
|
|
63
75
|
}
|
|
64
76
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
77
|
+
* Protocol-agnostic case metadata. `schemas` is plugin-defined payload shape,
|
|
78
|
+
* opaque to core.
|
|
79
|
+
*
|
|
80
|
+
* @template PayloadSchemas Plugin-defined payload shape (e.g. HttpPayloadSchemas).
|
|
81
|
+
* Same shape for every case in a contract; differences between cases live in
|
|
82
|
+
* the values, not in the type.
|
|
83
|
+
* @template Meta Plugin-defined free-form metadata (opaque to core).
|
|
72
84
|
*/
|
|
73
|
-
export
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
export interface CaseMeta<PayloadSchemas = unknown, Meta = unknown> {
|
|
86
|
+
key: string;
|
|
87
|
+
description?: string;
|
|
88
|
+
lifecycle: CaseLifecycle;
|
|
89
|
+
severity: CaseSeverity;
|
|
90
|
+
deferredReason?: string;
|
|
91
|
+
deprecatedReason?: string;
|
|
92
|
+
requires?: CaseRequires;
|
|
93
|
+
defaultRun?: CaseDefaultRun;
|
|
80
94
|
tags?: string[];
|
|
81
|
-
/** Default feature grouping key */
|
|
82
|
-
feature?: string;
|
|
83
|
-
/** OpenAPI extensions (x-* keys). Inherited by all contracts in this instance. */
|
|
84
95
|
extensions?: Extensions;
|
|
96
|
+
/** Plugin-defined payload shape. Opaque to core. */
|
|
97
|
+
schemas?: PayloadSchemas;
|
|
98
|
+
/** Plugin-defined free-form metadata. Opaque to core. */
|
|
99
|
+
meta?: Meta;
|
|
85
100
|
}
|
|
86
101
|
/**
|
|
87
|
-
*
|
|
88
|
-
*
|
|
102
|
+
* Runtime contract projection — adapter.project() output.
|
|
103
|
+
* Allowed to contain live objects (Zod schemas, class instances). Never crosses
|
|
104
|
+
* a serialization boundary; adapter.normalize() converts to Extracted.
|
|
89
105
|
*/
|
|
90
|
-
export interface
|
|
91
|
-
|
|
106
|
+
export interface ContractProjection<PayloadSchemas = unknown, Meta = unknown> {
|
|
107
|
+
protocol: string;
|
|
108
|
+
target: string;
|
|
109
|
+
description?: string;
|
|
110
|
+
feature?: string;
|
|
111
|
+
instanceName?: string;
|
|
112
|
+
tags?: string[];
|
|
113
|
+
extensions?: Extensions;
|
|
114
|
+
deprecated?: string;
|
|
115
|
+
cases: Array<CaseMeta<PayloadSchemas, Meta>>;
|
|
116
|
+
/** Contract-level schemas (e.g. HTTP request body shared across cases). */
|
|
117
|
+
schemas?: PayloadSchemas;
|
|
118
|
+
/** Contract-level free-form metadata. */
|
|
119
|
+
meta?: Meta;
|
|
92
120
|
}
|
|
93
121
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
122
|
+
* JSON-safe case metadata. Shape-identical to CaseMeta but `schemas` / `meta`
|
|
123
|
+
* must be JSON-safe (plain objects / arrays / primitives). Produced by
|
|
124
|
+
* adapter.normalize().
|
|
97
125
|
*/
|
|
98
|
-
export
|
|
99
|
-
<Cases extends Record<string, ContractCase<any, any>>>(id: string, spec: HttpContractSpec<Cases>): HttpContract;
|
|
100
|
-
with(name: string, defaults: HttpContractDefaults): HttpContractFactory;
|
|
101
|
-
}
|
|
126
|
+
export type ExtractedCaseMeta<SafeSchemas = unknown, SafeMeta = unknown> = CaseMeta<SafeSchemas, SafeMeta>;
|
|
102
127
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* - `"headless"` — fully automated, no human in loop (default)
|
|
106
|
-
* - `"browser"` — needs a real browser (OAuth code flow, checkout, captcha)
|
|
107
|
-
* - `"out-of-band"` — needs an out-of-band channel (email, SMS, push, webhook tunnel)
|
|
128
|
+
* JSON-safe contract projection. Downstream (scanner / MCP / CLI / Cloud)
|
|
129
|
+
* consume this. Includes `id` (injected by core).
|
|
108
130
|
*/
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
* - `"always"` — run whenever the runner satisfies `requires` (default)
|
|
114
|
-
* - `"opt-in"` — skip unless explicitly requested (e.g. `--include-opt-in`)
|
|
115
|
-
*
|
|
116
|
-
* Use `"opt-in"` for cases that are expensive, have real side effects,
|
|
117
|
-
* or are slow (Twilio SMS, Stripe charges, long stress tests).
|
|
118
|
-
*/
|
|
119
|
-
export type CaseDefaultRun = "always" | "opt-in";
|
|
120
|
-
/**
|
|
121
|
-
* Normalized response headers shape.
|
|
122
|
-
* HTTP allows multi-value headers (e.g. Set-Cookie), so values can be string or string[].
|
|
123
|
-
* Keys are always lowercase (runtime normalization).
|
|
124
|
-
*/
|
|
125
|
-
export type NormalizedHeaders = Record<string, string | string[]>;
|
|
126
|
-
/**
|
|
127
|
-
* A single example entry for OpenAPI docs.
|
|
128
|
-
*/
|
|
129
|
-
export interface ContractExample<T = unknown> {
|
|
130
|
-
value: T;
|
|
131
|
-
summary?: string;
|
|
131
|
+
export interface ExtractedContractProjection<SafeSchemas = unknown, SafeMeta = unknown> {
|
|
132
|
+
id: string;
|
|
133
|
+
protocol: string;
|
|
134
|
+
target: string;
|
|
132
135
|
description?: string;
|
|
136
|
+
feature?: string;
|
|
137
|
+
instanceName?: string;
|
|
138
|
+
tags?: string[];
|
|
139
|
+
extensions?: Extensions;
|
|
140
|
+
deprecated?: string;
|
|
141
|
+
cases: Array<ExtractedCaseMeta<SafeSchemas, SafeMeta>>;
|
|
142
|
+
schemas?: SafeSchemas;
|
|
143
|
+
meta?: SafeMeta;
|
|
133
144
|
}
|
|
134
145
|
/**
|
|
135
|
-
*
|
|
146
|
+
* Protocol adapter interface. Implement to add support for a new protocol
|
|
147
|
+
* (HTTP is built-in; gRPC / GraphQL etc. will be external plugins).
|
|
136
148
|
*
|
|
137
|
-
* @template
|
|
149
|
+
* @template Spec Adapter's input spec type (e.g. HttpContractSpec).
|
|
150
|
+
* @template RuntimeSchemas Runtime payload shape (live objects allowed).
|
|
151
|
+
* @template RuntimeMeta Runtime free-form meta (live objects allowed).
|
|
152
|
+
* @template SafeSchemas JSON-safe payload shape (after normalize).
|
|
153
|
+
* @template SafeMeta JSON-safe free-form meta.
|
|
138
154
|
*/
|
|
139
|
-
export interface
|
|
140
|
-
/** Expected HTTP status code */
|
|
141
|
-
status: number;
|
|
142
|
-
/** Zod/Valibot schema to validate response body. Parsed value is passed to verify(). */
|
|
143
|
-
schema?: SchemaLike<T>;
|
|
155
|
+
export interface ContractProtocolAdapter<Spec = unknown, RuntimeSchemas = unknown, RuntimeMeta = unknown, SafeSchemas = unknown, SafeMeta = unknown> {
|
|
144
156
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
157
|
+
* Execute a single case. Called by the core dispatcher for each spec case.
|
|
158
|
+
* Adapter does the full case lifecycle: setup → request/invoke → expect →
|
|
159
|
+
* verify → teardown.
|
|
147
160
|
*/
|
|
148
|
-
|
|
161
|
+
execute: (ctx: TestContext, caseSpec: unknown, contractSpec: Spec) => Promise<void>;
|
|
149
162
|
/**
|
|
150
|
-
*
|
|
151
|
-
* before validation (HTTP spec: header names are case-insensitive).
|
|
152
|
-
* Values are `string | string[]` to support multi-value headers (e.g. Set-Cookie).
|
|
163
|
+
* Project the spec to a Runtime projection (may contain live schemas).
|
|
153
164
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
*/
|
|
160
|
-
headers?: SchemaLike<NormalizedHeaders>;
|
|
161
|
-
/**
|
|
162
|
-
* Single response example (shorthand). Equivalent to examples: { default: { value } }.
|
|
163
|
-
* Not used at runtime — only for OpenAPI documentation.
|
|
164
|
-
*/
|
|
165
|
-
example?: T;
|
|
166
|
-
/**
|
|
167
|
-
* Multiple named response examples. Mapped to OpenAPI responses[status].content.examples.
|
|
168
|
-
* Not used at runtime — only for OpenAPI documentation.
|
|
169
|
-
*/
|
|
170
|
-
examples?: Record<string, ContractExample<T>>;
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Path or query parameter value.
|
|
174
|
-
* String shorthand: just the value. Object form: value + OpenAPI metadata.
|
|
175
|
-
*/
|
|
176
|
-
export type ParamValue = string | {
|
|
177
|
-
/** The actual value used for URL substitution / query string construction */
|
|
178
|
-
value: string;
|
|
179
|
-
/** OpenAPI parameter schema (not used at runtime, see Part 5 of proposal) */
|
|
180
|
-
schema?: SchemaLike<unknown>;
|
|
181
|
-
/** Parameter description for OpenAPI docs */
|
|
182
|
-
description?: string;
|
|
183
|
-
/** Whether the parameter is required (default: true for path, false for query) */
|
|
184
|
-
required?: boolean;
|
|
185
|
-
/** Whether the parameter is deprecated */
|
|
186
|
-
deprecated?: boolean;
|
|
187
|
-
};
|
|
188
|
-
/**
|
|
189
|
-
* A single spec case within a contract.
|
|
190
|
-
*
|
|
191
|
-
* @template T The parsed response type (inferred from expect.schema)
|
|
192
|
-
* @template S The setup return type
|
|
193
|
-
*/
|
|
194
|
-
export interface ContractCase<T = unknown, S = void> {
|
|
195
|
-
/**
|
|
196
|
-
* HTTP client to use for this case.
|
|
197
|
-
* Each client can have different auth, base URL, headers, etc.
|
|
198
|
-
* Created via configure() — contract doesn't care about the auth strategy.
|
|
165
|
+
* **Invariant**: `project().cases[].key` must 1:1 match `spec.cases` keys.
|
|
166
|
+
* Core validates this at registration time:
|
|
167
|
+
* - projected key not in spec.cases → hard error
|
|
168
|
+
* - spec.cases key not in projection → hard error
|
|
169
|
+
* - duplicate key → hard error
|
|
199
170
|
*
|
|
200
|
-
*
|
|
171
|
+
* The returned projection does NOT include `id` — core injects it.
|
|
201
172
|
*/
|
|
202
|
-
|
|
203
|
-
/** Why this case exists — business logic, boundary condition, or intent. Required. */
|
|
204
|
-
description: string;
|
|
205
|
-
/** Expected response */
|
|
206
|
-
expect: ContractExpect<T>;
|
|
173
|
+
project: (spec: Spec) => ContractProjection<RuntimeSchemas, RuntimeMeta>;
|
|
207
174
|
/**
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
175
|
+
* Optional: normalize a Runtime projection to JSON-safe Extracted form.
|
|
176
|
+
* Called by scanner / MCP / CLI / Cloud.
|
|
177
|
+
*
|
|
178
|
+
* **Input is already `id`-injected** by core. Adapter just passes `id`
|
|
179
|
+
* through to the returned object.
|
|
180
|
+
*
|
|
181
|
+
* If adapter does NOT implement normalize, downstream consumers see only a
|
|
182
|
+
* protocol-agnostic skeleton (no `schemas` / `meta`). See `contract-flow.md`
|
|
183
|
+
* §3.5.3 rule 3.
|
|
211
184
|
*/
|
|
212
|
-
|
|
185
|
+
normalize?: (projection: ContractProjection<RuntimeSchemas, RuntimeMeta> & {
|
|
186
|
+
id: string;
|
|
187
|
+
}) => ExtractedContractProjection<SafeSchemas, SafeMeta>;
|
|
213
188
|
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* Supported: "application/json", "multipart/form-data", "application/x-www-form-urlencoded",
|
|
217
|
-
* "text/plain", "application/octet-stream".
|
|
189
|
+
* Optional: classify failure from error + event log.
|
|
190
|
+
* Consumers: repair loop, Cloud alerts, runner summary.
|
|
218
191
|
*/
|
|
219
|
-
|
|
192
|
+
classifyFailure?: (input: {
|
|
193
|
+
error?: unknown;
|
|
194
|
+
events: Array<{
|
|
195
|
+
type: string;
|
|
196
|
+
data: Record<string, unknown>;
|
|
197
|
+
}>;
|
|
198
|
+
}) => FailureClassification | undefined;
|
|
220
199
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
200
|
+
* Optional: render contract as an OpenAPI fragment. Input is the Extracted
|
|
201
|
+
* projection (after normalize). Used by MCP `glubean_openapi` tool.
|
|
223
202
|
*/
|
|
224
|
-
|
|
203
|
+
toOpenApi?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>) => Record<string, unknown> | undefined;
|
|
225
204
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
205
|
+
* Optional: render contract as Markdown documentation. Used by
|
|
206
|
+
* CLI `glubean contracts --format md-outline` and
|
|
207
|
+
* MCP `glubean_project_contracts`.
|
|
228
208
|
*/
|
|
229
|
-
|
|
230
|
-
/** Request headers (merged with client headers) — static object or function deriving from setup state */
|
|
231
|
-
headers?: Record<string, string> | ((state: S) => Record<string, string>);
|
|
209
|
+
toMarkdown?: (projection: ExtractedContractProjection<SafeSchemas, SafeMeta>) => string;
|
|
232
210
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
211
|
+
* Optional: render the `target` string for display. HTTP: "POST /users"
|
|
212
|
+
* stays as-is. gRPC: "Greeter/SayHello" might become "Greeter.SayHello()".
|
|
235
213
|
*/
|
|
236
|
-
|
|
214
|
+
renderTarget?: (target: string) => string;
|
|
237
215
|
/**
|
|
238
|
-
*
|
|
216
|
+
* Optional: produce a high-level payload summary for indexing / UI.
|
|
217
|
+
* Input: already-normalized SafeSchemas. Used when full schemas would be
|
|
218
|
+
* too heavy (e.g. index views).
|
|
239
219
|
*/
|
|
240
|
-
|
|
220
|
+
describePayload?: (schemas: SafeSchemas) => PayloadDescriptor | undefined;
|
|
241
221
|
/**
|
|
242
|
-
*
|
|
243
|
-
* `res` is the schema-parsed response (typed) if schema was provided,
|
|
244
|
-
* otherwise the raw parsed JSON (unknown).
|
|
222
|
+
* Optional: execute a single case as a flow step.
|
|
245
223
|
*
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
* Mark this case as not yet executable. Reason is shown in skip message
|
|
252
|
-
* and coverage reports. Remove this field to activate the case.
|
|
253
|
-
*/
|
|
254
|
-
deferred?: string;
|
|
255
|
-
/** Additional tags for this case (merged with contract-level tags) */
|
|
256
|
-
tags?: string[];
|
|
257
|
-
/**
|
|
258
|
-
* Physical capability this case requires to execute.
|
|
224
|
+
* Core has already:
|
|
225
|
+
* 1. Computed `resolvedInputs` via `step.bindings.in(state)` (may be partial)
|
|
226
|
+
* 2. Prepared current flow state
|
|
227
|
+
* 3. Passed the live contract instance (access merged scoped-factory state
|
|
228
|
+
* via `contract._spec`)
|
|
259
229
|
*
|
|
260
|
-
*
|
|
230
|
+
* Adapter responsibilities:
|
|
231
|
+
* 1. Deep-merge `resolvedInputs` into the case's static input fields
|
|
232
|
+
* 2. Run case setup / request / expect / verify / case teardown
|
|
233
|
+
* (Rule 1: case teardown is step-local finally — see contract-flow §7.3)
|
|
234
|
+
* 3. Return adapter-specific CaseOutput shape (HTTP: { status, headers, body })
|
|
261
235
|
*
|
|
262
|
-
*
|
|
263
|
-
|
|
264
|
-
|
|
236
|
+
* Not implemented = this protocol cannot be referenced in a flow.
|
|
237
|
+
*/
|
|
238
|
+
executeCaseInFlow?: (input: {
|
|
239
|
+
ctx: TestContext;
|
|
240
|
+
contract: ProtocolContract<Spec, SafeSchemas, SafeMeta>;
|
|
241
|
+
caseKey: string;
|
|
242
|
+
resolvedInputs: unknown;
|
|
243
|
+
}) => Promise<unknown>;
|
|
244
|
+
/**
|
|
245
|
+
* Optional: validate that a case can be referenced in a flow. Called by
|
|
246
|
+
* ProtocolContract.case(key). HTTP's implementation rejects cases with
|
|
247
|
+
* function-valued input fields (body/params/query/headers) because those
|
|
248
|
+
* depend on case-local setup state unavailable in flow mode. See
|
|
249
|
+
* contract-flow v9 §5.1.1 for rationale.
|
|
265
250
|
*
|
|
266
|
-
*
|
|
267
|
-
* `defaultRun: "opt-in"`.
|
|
251
|
+
* Throws on invalid case; returns undefined on success.
|
|
268
252
|
*/
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
253
|
+
validateCaseForFlow?: (spec: Spec, caseKey: string, contractId: string) => void;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Protocol-agnostic payload summary. Adapter-provided via describePayload.
|
|
257
|
+
* Free-form — common keys: "hasRequest", "hasResponse", "contentType",
|
|
258
|
+
* "messageCount", "streaming".
|
|
259
|
+
*/
|
|
260
|
+
export interface PayloadDescriptor {
|
|
261
|
+
hasRequest?: boolean;
|
|
262
|
+
hasResponse?: boolean;
|
|
263
|
+
[key: string]: unknown;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Runtime contract object returned by `contract[protocol](id, spec)`.
|
|
267
|
+
* Extends Array<Test> so runner/resolve iterate it directly.
|
|
268
|
+
*
|
|
269
|
+
* @template Spec Adapter's spec type — executable info stored in `_spec`.
|
|
270
|
+
* @template PayloadSchemas Runtime (live) payload shape.
|
|
271
|
+
* @template Meta Runtime free-form meta.
|
|
272
|
+
*/
|
|
273
|
+
export interface ProtocolContract<Spec = unknown, PayloadSchemas = unknown, Meta = unknown> extends Array<Test> {
|
|
284
274
|
/**
|
|
285
|
-
*
|
|
286
|
-
* Default: `"warning"`.
|
|
275
|
+
* Runtime projection with `id` injected by core. Consumers duck-type this.
|
|
287
276
|
*/
|
|
288
|
-
|
|
277
|
+
readonly _projection: ContractProjection<PayloadSchemas, Meta> & {
|
|
278
|
+
id: string;
|
|
279
|
+
};
|
|
289
280
|
/**
|
|
290
|
-
*
|
|
291
|
-
*
|
|
281
|
+
* Adapter-private runtime spec carrier. Holds the merged executable spec
|
|
282
|
+
* (scoped-factory defaults + contract spec) used by `executeCaseInFlow`
|
|
283
|
+
* and any adapter-internal helpers.
|
|
292
284
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
285
|
+
* Core never inspects this field. Adapter writes it at construction, reads
|
|
286
|
+
* it during execution.
|
|
295
287
|
*/
|
|
296
|
-
|
|
288
|
+
readonly _spec: Spec;
|
|
297
289
|
/**
|
|
298
|
-
*
|
|
299
|
-
*
|
|
290
|
+
* Return a ContractCaseRef for use in `contract.flow(...).step(...)`.
|
|
291
|
+
*
|
|
292
|
+
* Runtime validation: adapter's `.case(key)` implementation MUST fail-fast
|
|
293
|
+
* if the case contains function-valued input fields (body/params/query/
|
|
294
|
+
* headers as functions). Function fields reference case-local setup state
|
|
295
|
+
* which is not available in flow mode. See contract-flow §5.1.1.
|
|
300
296
|
*/
|
|
301
|
-
|
|
297
|
+
case(key: string): ContractCaseRef<InferInputs<PayloadSchemas>, InferOutput<PayloadSchemas>>;
|
|
302
298
|
}
|
|
303
299
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
*
|
|
300
|
+
* Adapter-defined helper: extract the "case inputs" shape from PayloadSchemas.
|
|
301
|
+
* Each adapter exports its own version (HTTP adapter defines InferHttpInputs).
|
|
302
|
+
* Used by `.case()` return type to give lens functions TS autocomplete.
|
|
303
|
+
*
|
|
304
|
+
* The contract-level `PayloadSchemas` is the same for every case; I/O shape
|
|
305
|
+
* differences between cases live in values, not types.
|
|
306
|
+
*
|
|
307
|
+
* This is a default fallback — adapters override via module augmentation or
|
|
308
|
+
* direct typing of their own ContractCaseRef.
|
|
307
309
|
*/
|
|
308
|
-
export type
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
contentType?: string;
|
|
312
|
-
/** Request headers schema (OpenAPI docs only, not runtime validated on request). */
|
|
313
|
-
headers?: SchemaLike<Record<string, string>>;
|
|
314
|
-
/** Single example value */
|
|
315
|
-
example?: unknown;
|
|
316
|
-
/** Named examples */
|
|
317
|
-
examples?: Record<string, ContractExample<unknown>>;
|
|
318
|
-
};
|
|
310
|
+
export type InferInputs<_PayloadSchemas> = unknown;
|
|
311
|
+
/** Adapter-defined helper: extract the "case output" shape from PayloadSchemas. */
|
|
312
|
+
export type InferOutput<_PayloadSchemas> = unknown;
|
|
319
313
|
/**
|
|
320
|
-
*
|
|
314
|
+
* Opaque reference to a single case of a contract. Produced by
|
|
315
|
+
* `ProtocolContract.case(key)`. Used as input to `FlowBuilder.step(...)`.
|
|
321
316
|
*
|
|
322
|
-
*
|
|
317
|
+
* The generic parameters carry type information for lens TS inference; at
|
|
318
|
+
* runtime the ref only holds identification strings + the live contract ref.
|
|
323
319
|
*/
|
|
324
|
-
export interface
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
320
|
+
export interface ContractCaseRef<CaseInputs = unknown, CaseOutput = unknown> {
|
|
321
|
+
readonly __glubean_type: "contract-case-ref";
|
|
322
|
+
readonly contractId: string;
|
|
323
|
+
readonly caseKey: string;
|
|
324
|
+
readonly protocol: string;
|
|
325
|
+
readonly target: string;
|
|
326
|
+
/** Live ProtocolContract instance — flow runtime uses this, not contractId lookup. */
|
|
327
|
+
readonly contract: ProtocolContract<any, any, any>;
|
|
328
|
+
/** Phantom fields — do not populate at runtime. TS-only. */
|
|
329
|
+
readonly __phantom_inputs?: CaseInputs;
|
|
330
|
+
readonly __phantom_output?: CaseOutput;
|
|
331
|
+
}
|
|
332
|
+
/** Contract-level metadata for a flow (set via `.meta()`). */
|
|
333
|
+
export interface FlowMeta {
|
|
334
|
+
id: string;
|
|
335
|
+
name?: string;
|
|
328
336
|
description?: string;
|
|
337
|
+
tags?: string[];
|
|
338
|
+
extensions?: Extensions;
|
|
329
339
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
340
|
+
* Mark this flow as skipped at run time. Value is the skip reason
|
|
341
|
+
* displayed in reports. Useful for illustrative examples that should
|
|
342
|
+
* be discoverable (for scanner extraction / docs rendering) but must
|
|
343
|
+
* not attempt live HTTP calls.
|
|
333
344
|
*
|
|
334
|
-
*
|
|
335
|
-
* Good: "用户注册", "User Registration"
|
|
336
|
-
* Bad: "POST /users endpoint"
|
|
345
|
+
* Mirrors `TestMeta.skip`.
|
|
337
346
|
*/
|
|
338
|
-
|
|
347
|
+
skip?: string;
|
|
339
348
|
/**
|
|
340
|
-
*
|
|
341
|
-
*
|
|
349
|
+
* Mark this flow as focused. When any flows/tests in a run are `only`,
|
|
350
|
+
* non-focused ones may be excluded. Mirrors `TestMeta.only`.
|
|
342
351
|
*/
|
|
343
|
-
|
|
352
|
+
only?: boolean;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Field dependency / mapping produced by Proxy dry-run of a lens function.
|
|
356
|
+
* Consumed by downstream (MCP/CLI/Cloud) to render flow data-flow diagrams.
|
|
357
|
+
*/
|
|
358
|
+
export interface FieldMapping {
|
|
359
|
+
/** Destination path (within step inputs or flow state). */
|
|
360
|
+
target: string;
|
|
361
|
+
/** Source — a path in state/response, a literal, or pass-through. */
|
|
362
|
+
source: {
|
|
363
|
+
kind: "path";
|
|
364
|
+
path: string;
|
|
365
|
+
} | {
|
|
366
|
+
kind: "literal";
|
|
367
|
+
value: unknown;
|
|
368
|
+
} | {
|
|
369
|
+
kind: "pass-through";
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Runtime flow step — discriminated union.
|
|
374
|
+
* kind "contract-call" = a ContractCaseRef with bindings (Rule 1 teardown applies).
|
|
375
|
+
* kind "compute" = a pure sync data-transform function (no adapter, no teardown).
|
|
376
|
+
*/
|
|
377
|
+
export type RuntimeFlowStep = RuntimeContractCallStep | RuntimeComputeStep;
|
|
378
|
+
export interface RuntimeContractCallStep {
|
|
379
|
+
kind: "contract-call";
|
|
380
|
+
name?: string;
|
|
381
|
+
ref: ContractCaseRef;
|
|
382
|
+
caseKey: string;
|
|
383
|
+
/** Live contract instance (mirrors ref.contract, kept for direct access). */
|
|
384
|
+
contract: ProtocolContract<any, any, any>;
|
|
385
|
+
bindings?: {
|
|
386
|
+
in?: (state: any) => any;
|
|
387
|
+
out?: (state: any, response: any) => any;
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
export interface RuntimeComputeStep {
|
|
391
|
+
kind: "compute";
|
|
392
|
+
name?: string;
|
|
344
393
|
/**
|
|
345
|
-
*
|
|
346
|
-
*
|
|
394
|
+
* Synchronous pure function. NOT subject to lens Proxy purity — may use
|
|
395
|
+
* template literals / method calls / .map(). MUST be synchronous and
|
|
396
|
+
* MUST NOT return a thenable (enforced at runtime).
|
|
347
397
|
*/
|
|
348
|
-
|
|
349
|
-
|
|
398
|
+
fn: (state: any) => any;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Runtime flow projection. Carries live callbacks + live contract refs.
|
|
402
|
+
* Never crosses serialization boundaries. `normalizeFlow()` converts to
|
|
403
|
+
* ExtractedFlowProjection for downstream consumers.
|
|
404
|
+
*/
|
|
405
|
+
export interface RuntimeFlowProjection<State = unknown> {
|
|
406
|
+
protocol: "flow";
|
|
407
|
+
description?: string;
|
|
350
408
|
tags?: string[];
|
|
351
|
-
/**
|
|
352
|
-
* Mark the entire endpoint as deprecated. Value is the deprecation reason.
|
|
353
|
-
* Propagates to all cases: every case lifecycle becomes "deprecated" unless
|
|
354
|
-
* the case explicitly sets its own `deprecated` reason.
|
|
355
|
-
* Maps to OpenAPI operation `deprecated: true` + `x-deprecated-reason`.
|
|
356
|
-
*/
|
|
357
|
-
deprecated?: string;
|
|
358
|
-
/**
|
|
359
|
-
* OpenAPI extensions (x-* keys). Merged over instance-level defaults.extensions.
|
|
360
|
-
* Precedence: defaults < contract < case.
|
|
361
|
-
*/
|
|
362
409
|
extensions?: Extensions;
|
|
363
|
-
/**
|
|
364
|
-
|
|
410
|
+
/** Live flow-level setup callback (only I/O-capable callback in flow). */
|
|
411
|
+
setup?: (ctx: TestContext) => Promise<State>;
|
|
412
|
+
/** Live flow-level teardown callback. Rule 2: outer finally. */
|
|
413
|
+
teardown?: (ctx: TestContext, state: State) => Promise<void>;
|
|
414
|
+
steps: RuntimeFlowStep[];
|
|
365
415
|
}
|
|
366
416
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
* Extends Array<Test> so runner/resolve can iterate it directly.
|
|
370
|
-
* Adds contract-level properties and interop methods.
|
|
417
|
+
* Extracted flow step — discriminated union, JSON-safe.
|
|
371
418
|
*/
|
|
372
|
-
export
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
readonly requestExample?: unknown;
|
|
393
|
-
/** Named request examples for OpenAPI docs */
|
|
394
|
-
readonly requestExamples?: Record<string, ContractExample<unknown>>;
|
|
395
|
-
/** Contract-level deprecation reason (propagates to all cases) */
|
|
396
|
-
readonly deprecated?: string;
|
|
397
|
-
/**
|
|
398
|
-
* Contract-level merged extensions (defaults < contract).
|
|
399
|
-
* Case-level merged extensions live on _caseSchemas[key].extensions.
|
|
400
|
-
*/
|
|
401
|
-
readonly extensions?: Extensions;
|
|
402
|
-
/**
|
|
403
|
-
* Per-case metadata for runtime extraction (OpenAPI generation, projection).
|
|
404
|
-
* Maps case key → full case metadata including schema and gating fields.
|
|
405
|
-
*/
|
|
406
|
-
readonly _caseSchemas?: Record<string, {
|
|
407
|
-
expectStatus?: number;
|
|
408
|
-
responseSchema?: SchemaLike<unknown>;
|
|
409
|
-
/** Response headers schema (OpenAPI + runtime validation) */
|
|
410
|
-
responseHeaders?: SchemaLike<NormalizedHeaders>;
|
|
411
|
-
/** Response content-type (default: application/json) */
|
|
412
|
-
responseContentType?: string;
|
|
413
|
-
/** Single example (OpenAPI docs) */
|
|
414
|
-
example?: unknown;
|
|
415
|
-
/** Named examples (OpenAPI docs) */
|
|
416
|
-
examples?: Record<string, ContractExample<unknown>>;
|
|
417
|
-
/** Per-path-param metadata (schema, description, required, deprecated) */
|
|
418
|
-
paramSchemas?: Record<string, {
|
|
419
|
-
schema?: SchemaLike<unknown>;
|
|
420
|
-
description?: string;
|
|
421
|
-
required?: boolean;
|
|
422
|
-
deprecated?: boolean;
|
|
423
|
-
}>;
|
|
424
|
-
/** Per-query-param metadata */
|
|
425
|
-
querySchemas?: Record<string, {
|
|
426
|
-
schema?: SchemaLike<unknown>;
|
|
427
|
-
description?: string;
|
|
428
|
-
required?: boolean;
|
|
429
|
-
deprecated?: boolean;
|
|
430
|
-
}>;
|
|
431
|
-
description?: string;
|
|
432
|
-
deferred?: string;
|
|
433
|
-
deprecated?: string;
|
|
434
|
-
severity?: CaseSeverity;
|
|
435
|
-
lifecycle: CaseLifecycle;
|
|
436
|
-
requires?: CaseRequires;
|
|
437
|
-
defaultRun?: CaseDefaultRun;
|
|
438
|
-
/** Fully merged extensions (defaults < contract < case) */
|
|
439
|
-
extensions?: Extensions;
|
|
440
|
-
}>;
|
|
441
|
-
/**
|
|
442
|
-
* Inject all cases as steps into a test builder.
|
|
443
|
-
* Usage: test("e2e").use(myContract.asSteps()).step(...).build()
|
|
444
|
-
*/
|
|
445
|
-
asSteps(): <S>(b: import("./index.js").TestBuilder<S>) => import("./index.js").TestBuilder<S>;
|
|
446
|
-
/**
|
|
447
|
-
* Inject a single case as a step into a test builder.
|
|
448
|
-
* Defaults to the first non-deferred case if caseKey is omitted.
|
|
449
|
-
*/
|
|
450
|
-
asStep(caseKey?: string): <S>(b: import("./index.js").TestBuilder<S>) => import("./index.js").TestBuilder<S>;
|
|
419
|
+
export type ExtractedFlowStep = ExtractedContractCallStep | ExtractedComputeStep;
|
|
420
|
+
export interface ExtractedContractCallStep {
|
|
421
|
+
kind: "contract-call";
|
|
422
|
+
name?: string;
|
|
423
|
+
contractId: string;
|
|
424
|
+
caseKey: string;
|
|
425
|
+
protocol: string;
|
|
426
|
+
target: string;
|
|
427
|
+
/** Proxy dry-run output — input mappings. */
|
|
428
|
+
inputs?: FieldMapping[];
|
|
429
|
+
/** Proxy dry-run output — state update mappings. */
|
|
430
|
+
outputs?: FieldMapping[];
|
|
431
|
+
}
|
|
432
|
+
export interface ExtractedComputeStep {
|
|
433
|
+
kind: "compute";
|
|
434
|
+
name?: string;
|
|
435
|
+
/** Top-level state paths read (from Proxy dry-run). */
|
|
436
|
+
reads: string[];
|
|
437
|
+
/** Top-level state keys written (keys of returned object). */
|
|
438
|
+
writes: string[];
|
|
451
439
|
}
|
|
452
440
|
/**
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* Flow steps are verification, not spec: each step has one fixed expected
|
|
456
|
-
* outcome (expect), not multiple possible responses.
|
|
457
|
-
*
|
|
458
|
-
* @template T Response type (inferred from expect.schema)
|
|
459
|
-
* @template S Incoming state type from previous step
|
|
441
|
+
* JSON-safe flow projection. Downstream (scanner / MCP / CLI / Cloud) consume
|
|
442
|
+
* this. Produced by `normalizeFlow(runtime)`.
|
|
460
443
|
*/
|
|
461
|
-
export interface
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
/** Optional description of this step's purpose. */
|
|
444
|
+
export interface ExtractedFlowProjection {
|
|
445
|
+
id: string;
|
|
446
|
+
protocol: "flow";
|
|
465
447
|
description?: string;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
params?: Record<string, string> | ((state: S) => Record<string, string>);
|
|
472
|
-
/** Query parameters */
|
|
473
|
-
query?: Record<string, string> | ((state: S) => Record<string, string>);
|
|
474
|
-
/** Request headers — static object or derived from previous step's state */
|
|
475
|
-
headers?: Record<string, string> | ((state: S) => Record<string, string>);
|
|
476
|
-
/** Request body — static or derived from state */
|
|
477
|
-
body?: unknown | ((state: S) => unknown);
|
|
478
|
-
/**
|
|
479
|
-
* Business logic verification — runs after status and schema validation.
|
|
480
|
-
* Receives schema-parsed value or raw JSON.
|
|
481
|
-
*/
|
|
482
|
-
verify?: (ctx: TestContext, res: T) => Promise<void>;
|
|
483
|
-
/**
|
|
484
|
-
* Extract state from response for the next step.
|
|
485
|
-
* Receives the response and current state. Output replaces state.
|
|
486
|
-
* If omitted, state passes through unchanged.
|
|
487
|
-
*/
|
|
488
|
-
returns?: (res: T, state: S) => unknown;
|
|
448
|
+
tags?: string[];
|
|
449
|
+
extensions?: Extensions;
|
|
450
|
+
/** Present when flow has a setup callback (state source is dynamic). */
|
|
451
|
+
setupDynamic?: true;
|
|
452
|
+
steps: ExtractedFlowStep[];
|
|
489
453
|
}
|
|
490
454
|
/**
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* Breaking change from v1: `metadata()` replaced by `project()`.
|
|
495
|
-
* No v1 compat — no third-party plugins exist yet.
|
|
455
|
+
* Builder for `contract.flow(id)`. State chain threads through `.step()` /
|
|
456
|
+
* `.compute()` via TypeScript generics.
|
|
496
457
|
*/
|
|
497
|
-
export interface
|
|
498
|
-
|
|
499
|
-
|
|
458
|
+
export interface FlowBuilder<State = unknown> {
|
|
459
|
+
readonly __glubean_type: "flow-builder";
|
|
460
|
+
meta(m: Omit<FlowMeta, "id">): FlowBuilder<State>;
|
|
500
461
|
/**
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
* **Invariant:** `project().cases[].key` must 1:1 match `spec.cases` keys.
|
|
505
|
-
* `contract.register()` validates this at registration time:
|
|
506
|
-
* - projected key not in spec.cases → hard error
|
|
507
|
-
* - spec.cases key not in projection → hard error
|
|
508
|
-
* - duplicate key → hard error
|
|
462
|
+
* Flow-level setup — the ONLY I/O-capable callback in a flow. May be async,
|
|
463
|
+
* may read ctx, may call external services. Returns the initial state.
|
|
509
464
|
*/
|
|
510
|
-
|
|
465
|
+
setup<NewState>(fn: (ctx: TestContext) => Promise<NewState>): FlowBuilder<NewState>;
|
|
511
466
|
/**
|
|
512
|
-
*
|
|
513
|
-
*
|
|
467
|
+
* Add a contract-call step. `bindings.in` and `bindings.out` MUST be pure
|
|
468
|
+
* lens functions (select / repack only; no I/O, no method calls, no
|
|
469
|
+
* branching). Lens purity is enforced at Proxy dry-run time during
|
|
470
|
+
* projection extraction.
|
|
514
471
|
*/
|
|
515
|
-
|
|
472
|
+
step<CaseInputs, CaseOutput, NewState = State>(ref: ContractCaseRef<CaseInputs, CaseOutput>, bindings?: {
|
|
473
|
+
in?: (state: State) => CaseInputs;
|
|
474
|
+
out?: (state: State, response: CaseOutput) => NewState;
|
|
475
|
+
name?: string;
|
|
476
|
+
}): FlowBuilder<NewState>;
|
|
516
477
|
/**
|
|
517
|
-
*
|
|
518
|
-
*
|
|
478
|
+
* Add a pure synchronous data-transform step. Accepts any synchronous TS
|
|
479
|
+
* expression (template literals, method calls, .map()). Projection records
|
|
480
|
+
* only read/write dependencies, NOT the formula.
|
|
481
|
+
*
|
|
482
|
+
* Runtime enforcement: throws if `fn` is async or returns a thenable.
|
|
519
483
|
*/
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
}
|
|
528
|
-
/**
|
|
529
|
-
* Return type of `ContractProtocolAdapter.project()`.
|
|
530
|
-
* Aligns with NormalizedContractMeta in scanner.
|
|
531
|
-
*/
|
|
532
|
-
export interface ContractProjection {
|
|
533
|
-
protocol: string;
|
|
534
|
-
target: string;
|
|
535
|
-
description?: string;
|
|
536
|
-
feature?: string;
|
|
537
|
-
instanceName?: string;
|
|
538
|
-
security?: unknown;
|
|
539
|
-
schemaMount?: string;
|
|
540
|
-
requestSchema?: unknown | null;
|
|
541
|
-
cases: Array<{
|
|
542
|
-
key: string;
|
|
543
|
-
description?: string;
|
|
544
|
-
lifecycle: CaseLifecycle;
|
|
545
|
-
severity: CaseSeverity;
|
|
546
|
-
deferredReason?: string;
|
|
547
|
-
deprecatedReason?: string;
|
|
548
|
-
requires?: CaseRequires;
|
|
549
|
-
defaultRun?: CaseDefaultRun;
|
|
550
|
-
schemaMount?: string;
|
|
551
|
-
protocolExpect?: Record<string, unknown>;
|
|
552
|
-
responseSchema?: unknown | null;
|
|
553
|
-
protocolMeta?: Record<string, unknown>;
|
|
554
|
-
}>;
|
|
555
|
-
protocolMeta?: Record<string, unknown>;
|
|
484
|
+
compute<NewState>(fn: (state: State) => NewState): FlowBuilder<NewState>;
|
|
485
|
+
/**
|
|
486
|
+
* Flow-level teardown — runs in Rule 2 outer-finally, receives last-
|
|
487
|
+
* committed state. If flow.setup threw, teardown does NOT run.
|
|
488
|
+
*/
|
|
489
|
+
teardown(fn: (ctx: TestContext, state: State) => Promise<void>): FlowBuilder<State>;
|
|
490
|
+
build(): FlowContract<State>;
|
|
556
491
|
}
|
|
557
492
|
/**
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
* `_projection` extends ContractProjection with `id` — injected by `contract.register()`
|
|
562
|
-
* since the adapter's `project()` doesn't know the user-supplied contract id.
|
|
493
|
+
* Runtime flow contract. Extends Array<Test> so runner iterates directly.
|
|
494
|
+
* The single Test inside orchestrates setup → steps → teardown via runFlow.
|
|
563
495
|
*/
|
|
564
|
-
export interface
|
|
565
|
-
|
|
566
|
-
readonly _projection: ContractProjection & {
|
|
496
|
+
export interface FlowContract<State = unknown> extends Array<Test> {
|
|
497
|
+
readonly _flow: RuntimeFlowProjection<State> & {
|
|
567
498
|
id: string;
|
|
568
499
|
};
|
|
500
|
+
/**
|
|
501
|
+
* Pre-computed JSON-safe extracted projection. Populated by the flow
|
|
502
|
+
* builder via `normalizeFlow(_flow)` so downstream consumers (scanner,
|
|
503
|
+
* CLI, MCP, Cloud) don't need to import the SDK to get field mappings
|
|
504
|
+
* for `.step()` lenses and reads/writes for `.compute()` nodes.
|
|
505
|
+
*/
|
|
506
|
+
readonly _extracted: ExtractedFlowProjection;
|
|
569
507
|
}
|
|
570
|
-
/**
|
|
508
|
+
/**
|
|
509
|
+
* Contract registry metadata attached to tests produced by `contract[protocol]()`.
|
|
510
|
+
* Mirrored by `RegisteredTestMeta.contract` in types.ts.
|
|
511
|
+
*/
|
|
571
512
|
export interface ContractRegistryMeta {
|
|
572
|
-
/** Protocol-agnostic target. HTTP: "POST /users", gRPC: "Greeter/SayHello" */
|
|
513
|
+
/** Protocol-agnostic target. HTTP: "POST /users", gRPC: "Greeter/SayHello". */
|
|
573
514
|
target: string;
|
|
574
|
-
/** Protocol identifier */
|
|
515
|
+
/** Protocol identifier. */
|
|
575
516
|
protocol: string;
|
|
576
|
-
/** Case key within the contract */
|
|
517
|
+
/** Case key within the contract. */
|
|
577
518
|
caseKey: string;
|
|
578
|
-
/** Case lifecycle */
|
|
579
519
|
lifecycle: CaseLifecycle;
|
|
580
|
-
/** Case severity */
|
|
581
520
|
severity: CaseSeverity;
|
|
582
|
-
/** Whether response schema is defined */
|
|
583
|
-
hasSchema: boolean;
|
|
584
|
-
/** Instance name from contract.http.with("name", ...) */
|
|
585
521
|
instanceName?: string;
|
|
586
522
|
/**
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
* gRPC: { expect: { code: 0 } }
|
|
523
|
+
* Adapter.describePayload() output — protocol-agnostic payload overview.
|
|
524
|
+
* Optional because describePayload is optional.
|
|
590
525
|
*/
|
|
591
|
-
|
|
526
|
+
payloadSummary?: PayloadDescriptor;
|
|
527
|
+
/** Plugin-defined free-form meta; core does not inspect. */
|
|
528
|
+
meta?: unknown;
|
|
592
529
|
}
|
|
593
530
|
/**
|
|
594
|
-
*
|
|
595
|
-
*
|
|
531
|
+
* Flow registry metadata attached to the single Test generated by
|
|
532
|
+
* `contract.flow()`. Mirrored by `RegisteredTestMeta.flow` in types.ts.
|
|
596
533
|
*/
|
|
597
|
-
export
|
|
598
|
-
|
|
599
|
-
|
|
534
|
+
export interface FlowRegistryMeta {
|
|
535
|
+
id: string;
|
|
536
|
+
description?: string;
|
|
537
|
+
tags?: string[];
|
|
538
|
+
/** Flattened step descriptors (same shape as ExtractedFlowStep). */
|
|
539
|
+
steps: Array<{
|
|
540
|
+
kind: "contract-call" | "compute";
|
|
541
|
+
name?: string;
|
|
542
|
+
contractId?: string;
|
|
543
|
+
caseKey?: string;
|
|
544
|
+
protocol?: string;
|
|
545
|
+
target?: string;
|
|
546
|
+
inputs?: FieldMapping[];
|
|
547
|
+
outputs?: FieldMapping[];
|
|
548
|
+
reads?: string[];
|
|
549
|
+
writes?: string[];
|
|
550
|
+
}>;
|
|
551
|
+
setupDynamic?: true;
|
|
552
|
+
}
|
|
600
553
|
//# sourceMappingURL=contract-types.d.ts.map
|