@jterrazz/test 10.1.0 → 12.0.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/dist/index.d.ts CHANGED
@@ -10,12 +10,14 @@ import { DeepMockProxy } from "vitest-mock-extended";
10
10
  * (`expected/*.txt`). One grammar, one vocabulary (CONVENTIONS D4).
11
11
  */
12
12
  /** The frozen token vocabulary (CONVENTIONS D4) plus the code-only kinds. */
13
- type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
13
+ type MatcherKind = 'any' | 'base64' | 'date' | 'duration' | 'email' | 'float' | 'hex' | 'includes' | 'int' | 'ip' | 'iso8601' | 'number' | 'path' | 'port' | 'ref' | 'regex' | 'semver' | 'sha' | 'string' | 'time' | 'ulid' | 'url' | 'uuid' | 'workdir';
14
14
  /**
15
15
  * A dynamic-value matcher. Created via the {@link match} factories — never
16
16
  * constructed directly by user code.
17
17
  */
18
18
  declare class Matcher {
19
+ /** For kind 'includes': the substring the actual value must contain. */
20
+ readonly includes?: string;
19
21
  readonly kind: MatcherKind;
20
22
  /** For kind 'ref': the capture to assert inequality against. */
21
23
  readonly notRef?: string;
@@ -24,6 +26,7 @@ declare class Matcher {
24
26
  /** For kind 'regex': the pattern the actual value must match. */
25
27
  readonly regex?: RegExp;
26
28
  constructor(kind: MatcherKind, options?: {
29
+ includes?: string;
27
30
  notRef?: string;
28
31
  refName?: string;
29
32
  regex?: RegExp;
@@ -61,6 +64,13 @@ declare const match: {
61
64
  float: () => Matcher;
62
65
  /** Matches a hexadecimal string. */
63
66
  hex: () => Matcher;
67
+ /**
68
+ * Matches a string CONTAINING the given substring. Code-only, like
69
+ * {@link match.regex} — the `{{token}}` fixture vocabulary does not grow.
70
+ * The explicit escape hatch now that contract string filters
71
+ * (`openai.chat({ user })`) mean exact equality.
72
+ */
73
+ includes: (substring: string) => Matcher;
64
74
  /** Matches an integer (or an integer string in text contexts). */
65
75
  int: () => Matcher;
66
76
  /** Matches an IPv4 address. */
@@ -389,43 +399,48 @@ interface CliPort {
389
399
  //#endregion
390
400
  //#region src/core/contracts/types.d.ts
391
401
  /**
392
- * The observed outgoing request, reduced to what trigger matchers inspect.
393
- * Built once per request by the MSW integration and handed to
394
- * {@link InterceptTrigger.match}.
402
+ * The observed outgoing request, reduced to what contract matchers inspect.
403
+ * Built once per request by the engine (MSW on api/jobs, the stub backend on
404
+ * website/mobile) and handed to {@link ContractRequest.match} and to a
405
+ * {@link ContractResponder}.
395
406
  */
396
407
  interface MatchableRequest {
397
408
  /** Parsed JSON body when the payload is JSON, the raw text otherwise, or `null` when absent. */
398
409
  body: unknown;
399
410
  /** Request headers, keyed by lowercased header name. */
400
411
  headers: Record<string, string>;
401
- /** The fully-qualified request URL (including any query string). */
412
+ /** Uppercased HTTP method of the observed request. */
413
+ method: string;
414
+ /** The request URL — fully-qualified, or origin-relative for the stub backend. */
402
415
  url: string;
403
416
  }
404
417
  /**
405
- * An intercept trigger describes which HTTP request to match.
418
+ * The request half of a contract: which outgoing call it speaks for.
419
+ *
420
+ * `url` is either an absolute URL (string or RegExp), or a PATH FORM starting
421
+ * with `/` (`/articles/{{uuid}}`) which matches any origin — the shape a
422
+ * website/mobile app's own backend calls take. `{{token}}` segments compare
423
+ * structurally, declared query params are a subset of the observed ones.
406
424
  */
407
- interface InterceptTrigger {
408
- /** Adapter name - must match the folder prefix in file-based intercepts. */
425
+ interface ContractRequest {
426
+ /** Adapter name — `http` | `openai` | `anthropic`. */
409
427
  adapter: string;
410
- /** HTTP method to match. */
428
+ /** HTTP method to match. `*` matches any method. */
411
429
  method: string;
412
- /** URL pattern to match (string for exact prefix, RegExp for pattern). */
430
+ /** Absolute URL (string | RegExp) or an any-origin path form (`/articles/{{uuid}}`). */
413
431
  url: RegExp | string;
414
- /** Optional request matcher - the handler only fires if this returns true. */
432
+ /** Optional request matcher — the contract only fires if this returns true. */
415
433
  match?: (request: MatchableRequest) => boolean;
416
- /**
417
- * Transform raw JSON data into a provider-specific response envelope.
418
- * Called when .intercept(trigger, 'adapter/file.json') loads a file.
419
- */
420
- wrap: (data: unknown) => InterceptResponse;
434
+ /** Transform raw data into a provider-specific response envelope. */
435
+ wrap: (data: unknown) => ContractResponse;
421
436
  }
422
437
  /**
423
- * An intercept response describes what to return when the trigger matches.
438
+ * The response half of a contract: what to reply when the request matches.
424
439
  */
425
- interface InterceptResponse {
440
+ interface ContractResponse {
426
441
  /** HTTP status code (default: 200). */
427
442
  status?: number;
428
- /** Response body (will be JSON.stringified). */
443
+ /** Response body — an object is JSON, a string is text, `null`/`undefined` is empty. */
429
444
  body: unknown;
430
445
  /** Response headers. */
431
446
  headers?: Record<string, string>;
@@ -434,67 +449,101 @@ interface InterceptResponse {
434
449
  }
435
450
  /**
436
451
  * A dynamic response: computed from the observed request at the moment the
437
- * intercept is consumed, rather than fixed ahead of time. Handed the same
438
- * {@link MatchableRequest} the trigger matched on, so the reply can echo or
439
- * derive from the request body/headers/url.
452
+ * contract is served, rather than fixed ahead of time. Handed the same
453
+ * {@link MatchableRequest} the request half matched on, so the reply can echo
454
+ * or derive from the body/headers/url.
440
455
  */
441
- type InterceptResponder = (request: MatchableRequest) => InterceptResponse;
456
+ type ContractResponder = (request: MatchableRequest) => ContractResponse;
442
457
  /**
443
- * What an intercept replies with: either a fixed {@link InterceptResponse} or
444
- * an {@link InterceptResponder} evaluated per consumed request.
458
+ * What a contract replies with: either a fixed {@link ContractResponse} or a
459
+ * {@link ContractResponder} evaluated per served request.
445
460
  */
446
- type InterceptResponseValue = InterceptResponder | InterceptResponse;
447
- /**
448
- * A fully resolved intercept entry ready to be registered with MSW.
449
- */
450
- interface InterceptEntry {
451
- trigger: InterceptTrigger;
452
- response: InterceptResponseValue;
453
- }
461
+ type ContractResponseValue = ContractResponder | ContractResponse;
454
462
  //#endregion
455
463
  //#region src/core/contracts/contract.d.ts
456
464
  /**
457
- * A declared external interaction: what to match and what to reply, together
458
- * in one named artifact. Contracts live in flat TypeScript files under
459
- * `contracts/` next to the tests that use them — `<name>.<provider>.ts` with
460
- * `provider ∈ { openai, anthropic, http }` (CONVENTIONS C4) — so the business
461
- * payload (prompts, JSON responses) is visible at a glance while the real
462
- * HTTP call stays mocked underneath (MSW).
465
+ * A declared external interaction: what to match (`request`) and what to reply
466
+ * (`response`), together in one named artifact. Contracts live in TypeScript
467
+ * files under `contracts/` next to the tests that use them, so the business
468
+ * payload (prompts, JSON responses) is visible at a glance while the real HTTP
469
+ * call stays mocked underneath.
463
470
  */
464
- interface InterceptContract {
465
- trigger: InterceptTrigger;
471
+ interface Contract {
472
+ /** Which outgoing call this contract speaks for. */
473
+ request: ContractRequest;
466
474
  /**
467
- * The reply — a fixed {@link InterceptResponse}, or a function
468
- * `(request) => InterceptResponse` evaluated per consumed request when the
475
+ * The reply — a fixed {@link ContractResponse}, or a function
476
+ * `(request) => ContractResponse` evaluated per served request when the
469
477
  * response must derive from the incoming payload.
470
478
  */
471
- response: InterceptResponseValue;
479
+ response: ContractResponseValue;
480
+ /**
481
+ * How many times this contract may serve. Omitted = unlimited (a
482
+ * re-render or a retry replays it). `n` = exhausted after n serves, so an
483
+ * ordered sequence is a finite contract followed by an unlimited tail.
484
+ */
485
+ times?: number;
486
+ /**
487
+ * When true, the chain FAILS unless the contract was actually requested:
488
+ * at least once, or exactly `times` times when `times` is set. Turns a
489
+ * silently-unused declaration into a spec failure.
490
+ */
491
+ required?: boolean;
472
492
  }
473
493
  /**
474
- * Declare an intercept contract. Identity function — its value is the
475
- * enforced shape and the naming convention:
494
+ * A composite of contracts — the unit tests import. Flat, ordered, immutable;
495
+ * `.with()` derives a variant without touching the original.
496
+ */
497
+ interface Contracts {
498
+ /** The flattened contracts, in selection order. */
499
+ readonly contracts: readonly Contract[];
500
+ /**
501
+ * Derive a new composite: every base contract sharing a route with an
502
+ * override is REPLACED, and the overrides are PREPENDED — so a
503
+ * more-specific override wins first-match selection over a generic base
504
+ * route it does not replace.
505
+ */
506
+ with: (...overrides: (Contract | Contract[] | Contracts)[]) => Contracts;
507
+ }
508
+ /** Any accepted contract input: one, a list, or a composite. */
509
+ type ContractInput = Contract | Contract[] | Contracts;
510
+ /**
511
+ * Declare one contract. Identity function — its value is the enforced shape
512
+ * and the naming convention:
476
513
  *
477
514
  * @example
478
- * // specs/api/reports/contracts/classify-article.openai.ts
515
+ * // specs/api/reports/contracts/openai/classify-article.ts
479
516
  * import { defineContract, openai } from '@jterrazz/test';
480
517
  *
481
518
  * export default defineContract({
482
- * trigger: openai.responses({ user: /Report Ingestion/, tools: ['classify'] }),
519
+ * request: openai.responses({ user: PROMPT, tools: ['classify'] }),
483
520
  * response: openai.reply({ categories: ['TECH'] }),
484
521
  * });
485
522
  *
486
523
  * // Dynamic — the response is computed from the observed request:
487
524
  * export default defineContract({
488
- * trigger: http.post('https://api.example.com/echo'),
525
+ * request: http.post('https://api.example.com/echo'),
489
526
  * response: (request) => http.json({ received: request.body }),
490
527
  * });
528
+ */
529
+ declare function defineContract(contract: Contract): Contract;
530
+ /**
531
+ * Compose contracts into the artifact a test imports — it's contracts all the
532
+ * way down: a composite may extend contracts, lists, and other composites,
533
+ * recursively, order preserved.
491
534
  *
492
- * // specs/api/reports/reports.test.ts
493
- * import classifyArticle from '../../spec/intercept/contracts/classify-article.openai.js';
535
+ * `.with(...)` derives a scenario: contracts whose route the overrides claim
536
+ * are removed from the base, and the overrides are prepended. Under
537
+ * first-match selection a more specific override (`/articles/gone-1`) also
538
+ * wins over a generic base route (`/articles/{{uuid}}`) it does not replace.
494
539
  *
495
- * const result = await jobs.intercept(classifyArticle).trigger('report-ingestion');
540
+ * @example
541
+ * // contracts/newsroom.contracts.ts
542
+ * export default defineContracts(events, articleById);
543
+ * export const withArticleGone = (id: string) =>
544
+ * newsroom.with(articleGone(id));
496
545
  */
497
- declare function defineContract(contract: InterceptContract): InterceptContract;
546
+ declare function defineContracts(...items: ContractInput[]): Contracts;
498
547
  //#endregion
499
548
  //#region src/core/ports/browser.port.d.ts
500
549
  /**
@@ -975,30 +1024,6 @@ declare class PageResult extends BaseResult {
975
1024
  get url(): string;
976
1025
  }
977
1026
  //#endregion
978
- //#region src/core/http-files/intercept-file.d.ts
979
- /** The declared request half of an exchange — the matching trigger. */
980
- interface ExchangeRequest {
981
- /** Declared header subset — names case-insensitive, values may carry `{{token}}`s. */
982
- headers: Record<string, string>;
983
- method: string;
984
- /** Path as written, query string included (`/articles?event_id=…`). */
985
- path: string;
986
- }
987
- /** The declared response half of an exchange — the stubbed reply. */
988
- interface ExchangeResponse {
989
- /** Parsed JSON body — or the raw text when the body is not valid JSON. */
990
- body: unknown;
991
- /** True when a body section is present. */
992
- hasBody: boolean;
993
- headers: Record<string, string>;
994
- status: number;
995
- }
996
- /** One declared request/response pair of an `intercepts/*.http` file. */
997
- interface InterceptExchange {
998
- request: ExchangeRequest;
999
- response: ExchangeResponse;
1000
- }
1001
- //#endregion
1002
1027
  //#region src/core/specification/shared/stub-backend.d.ts
1003
1028
  /** Options for the stub backend server. */
1004
1029
  interface StubBackendOptions {
@@ -1009,11 +1034,10 @@ interface StubBackendOptions {
1009
1034
  port?: number;
1010
1035
  }
1011
1036
  declare class StubBackend {
1012
- private consumed;
1013
- private entries;
1014
- /** Guarded once the current chain declared at least one intercept. */
1037
+ /** Guarded once the current chain declared at least one contract. */
1015
1038
  private guarded;
1016
1039
  private readonly options;
1040
+ private queue;
1017
1041
  private server;
1018
1042
  private readonly unmatchedRequests;
1019
1043
  private url;
@@ -1023,28 +1047,23 @@ declare class StubBackend {
1023
1047
  /** Stop the server (idempotent). */
1024
1048
  stop(): Promise<void>;
1025
1049
  /**
1026
- * Arm the stub for one chain: the declared exchanges replace the previous
1027
- * chain's wholesale, consumption restarts, and the unmatched log clears —
1050
+ * Arm the stub for one chain: the declared contracts replace the previous
1051
+ * chain's wholesale, the queue restarts, and the unmatched log clears —
1028
1052
  * the reset-between-chains databases already follow.
1029
1053
  */
1030
- beginChain(exchanges: InterceptExchange[]): void;
1054
+ beginChain(contracts: readonly Contract[]): void;
1031
1055
  /**
1032
- * The strict failure for the chain, or null — non-null when the chain
1033
- * declared at least one intercept AND an unmatched request was recorded.
1034
- * Enumerates every unmatched request (method, path, count) plus the
1035
- * declared routes, so the missing exchange writes itself.
1056
+ * The strict failure for the chain, or null. Non-null when the chain
1057
+ * declared at least one contract AND either an unmatched request was
1058
+ * recorded, or a `required` contract was never satisfied. Enumerates every
1059
+ * unmatched request (method, path, count) plus the declared routes, so the
1060
+ * missing contract writes itself.
1036
1061
  */
1037
1062
  violation(): Error | null;
1038
1063
  private corsHeaders;
1039
1064
  private describeRoutes;
1040
1065
  private handle;
1041
1066
  private record;
1042
- /**
1043
- * FIFO with a sticky tail: the first unconsumed matching exchange is
1044
- * consumed; when every matching exchange is spent, the LAST one keeps
1045
- * replying.
1046
- */
1047
- private take;
1048
1067
  }
1049
1068
  //#endregion
1050
1069
  //#region src/core/specification/shared/builder.d.ts
@@ -1068,7 +1087,7 @@ interface DockerSpecConfig {
1068
1087
  interface SpecificationConfig {
1069
1088
  /**
1070
1089
  * The declared stub backend (website/mobile facets) — armed with the
1071
- * chain's `.http` intercept exchanges before every terminal action.
1090
+ * chain's contracts before every terminal action.
1072
1091
  */
1073
1092
  backend?: StubBackend;
1074
1093
  /**
@@ -1136,6 +1155,11 @@ interface SpecificationConfig {
1136
1155
  */
1137
1156
  transform?: (text: string) => string;
1138
1157
  }
1158
+ /**
1159
+ * The `.intercept()` overload set, identical on every facet: one contract, a
1160
+ * list, a composite — or the inline `(request, response)` pair for a one-off.
1161
+ */
1162
+ type InterceptMethod<T> = ((contracts: ContractInput) => T) & ((request: ContractRequest, response: ContractResponder | ContractResponse) => T);
1139
1163
  /**
1140
1164
  * The `api` facet — HTTP chain entry handed out by `specification.api()`.
1141
1165
  * Setup methods chain; action methods are terminal: they execute the spec
@@ -1147,8 +1171,8 @@ interface SpecificationConfig {
1147
1171
  interface ApiSpecification<DatabaseKey extends string = string> {
1148
1172
  /** Set HTTP headers for the request. Multiple calls merge. */
1149
1173
  headers: (headers: Record<string, string>) => ApiSpecification<DatabaseKey>;
1150
- /** Intercept an outgoing HTTP call — a contract, an array of contracts, an `intercepts/*.http` file, or a trigger + response pair. */
1151
- intercept: ((contractOrContracts: InterceptContract | InterceptContract[] | string) => ApiSpecification<DatabaseKey>) & ((trigger: InterceptTrigger, response: InterceptResponder | InterceptResponse | string) => ApiSpecification<DatabaseKey>);
1174
+ /** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
1175
+ intercept: InterceptMethod<ApiSpecification<DatabaseKey>>;
1152
1176
  /** Queue a SQL seed file from `seeds/` to run before the action. */
1153
1177
  seed: (file: string, options?: {
1154
1178
  database?: DatabaseKey;
@@ -1169,8 +1193,8 @@ interface ApiSpecification<DatabaseKey extends string = string> {
1169
1193
  * Jobs run in-process by definition (CONVENTIONS A5/A8).
1170
1194
  */
1171
1195
  interface JobsSpecification<DatabaseKey extends string = string> {
1172
- /** Intercept an outgoing HTTP call — a contract, an array of contracts, an `intercepts/*.http` file, or a trigger + response pair. */
1173
- intercept: ((contractOrContracts: InterceptContract | InterceptContract[] | string) => JobsSpecification<DatabaseKey>) & ((trigger: InterceptTrigger, response: InterceptResponder | InterceptResponse | string) => JobsSpecification<DatabaseKey>);
1196
+ /** Declare outgoing calls — a contract, a list, a composite, or an inline request + response pair. */
1197
+ intercept: InterceptMethod<JobsSpecification<DatabaseKey>>;
1174
1198
  /** Queue a SQL seed file from `seeds/` to run before the action. */
1175
1199
  seed: (file: string, options?: {
1176
1200
  database?: DatabaseKey;
@@ -1217,11 +1241,10 @@ interface WebsiteSpecification {
1217
1241
  /** Set HTTP headers for the exchange (incl. User-Agent overrides). Multiple calls merge. */
1218
1242
  headers: (headers: Record<string, string>) => WebsiteSpecification;
1219
1243
  /**
1220
- * Declare the chain's backend exchanges from an `intercepts/<name>.http`
1221
- * file — served by the declared stub backend (requires the runner's
1222
- * `backend` option). Multiple calls layer in order.
1244
+ * Declare the chain's backend contracts — served by the declared stub
1245
+ * backend (requires the runner's `backend` option). Multiple calls append.
1223
1246
  */
1224
- intercept: (file: string) => WebsiteSpecification;
1247
+ intercept: InterceptMethod<WebsiteSpecification>;
1225
1248
  /** Perform one raw HTTP GET — redirects surface as 3xx results, never followed. */
1226
1249
  fetch: (path: string) => Promise<FetchResult>;
1227
1250
  /**
@@ -1239,11 +1262,10 @@ interface WebsiteSpecification {
1239
1262
  */
1240
1263
  interface MobileSpecification {
1241
1264
  /**
1242
- * Declare the chain's backend exchanges from an `intercepts/<name>.http`
1243
- * file — served by the declared stub backend (requires the runner's
1244
- * `backend` option). Multiple calls layer in order.
1265
+ * Declare the chain's backend contracts — served by the declared stub
1266
+ * backend (requires the runner's `backend` option). Multiple calls append.
1245
1267
  */
1246
- intercept: (file: string) => MobileSpecification;
1268
+ intercept: InterceptMethod<MobileSpecification>;
1247
1269
  /**
1248
1270
  * Relaunch the app and resolve with the captured screen. With a deep
1249
1271
  * link, the app opens on it; with a scenario, the visitor interacts
@@ -1390,7 +1412,15 @@ interface OrchestratorOptions {
1390
1412
  */
1391
1413
  services: Record<string, ServiceHandle>;
1392
1414
  mode: 'e2e' | 'integration';
1393
- root?: string;
1415
+ /**
1416
+ * The project root, already resolved. REQUIRED: the constructors derive it
1417
+ * by walking up from the calling specification file (CONVENTIONS A9), and
1418
+ * the orchestrator is handed the answer. Defaulting to `process.cwd()` here
1419
+ * meant one silent second opinion about what the project is — the runner
1420
+ * resolving from the caller, this from wherever the runner happened to be
1421
+ * launched, which in a workspace is rarely the same directory.
1422
+ */
1423
+ root: string;
1394
1424
  /** Compose project name — used for per-worker stack isolation. */
1395
1425
  projectName?: string;
1396
1426
  }
@@ -1626,8 +1656,8 @@ interface MobileSpecificationOptions {
1626
1656
  bundleId: string;
1627
1657
  };
1628
1658
  /**
1629
- * Declared stub backend: started with the runner, serving the exchanges
1630
- * each chain declares via `.intercept('<name>.http')`. The handle gains
1659
+ * Declared stub backend: started with the runner, serving the contracts
1660
+ * each chain declares via `.intercept(...)`. The handle gains
1631
1661
  * `backendUrl` — inject it into your bundler env yourself.
1632
1662
  */
1633
1663
  backend?: MobileBackendOptions;
@@ -1685,8 +1715,8 @@ interface ServeOptions {
1685
1715
  /**
1686
1716
  * The declared stub backend behind the site under test — started before the
1687
1717
  * server command, torn down with the runner. Its URL is injected into the
1688
- * server child's environment under `env`; the chain's
1689
- * `.intercept('<name>.http')` exchanges are what it serves.
1718
+ * server child's environment under `env`; the contracts each chain declares
1719
+ * via `.intercept(...)` are what it serves.
1690
1720
  */
1691
1721
  interface WebsiteBackendOptions {
1692
1722
  /** Env var receiving the stub's URL in the server child (e.g. `'API_URL'`). */
@@ -1718,7 +1748,7 @@ type WebsiteSpecificationOptions = {
1718
1748
  /**
1719
1749
  * Declared stub backend: started BEFORE the server command, its
1720
1750
  * URL injected into the child env under `backend.env`. Serves the
1721
- * exchanges each chain declares via `.intercept('<name>.http')`.
1751
+ * contracts each chain declares via `.intercept(...)`.
1722
1752
  */
1723
1753
  backend?: WebsiteBackendOptions;
1724
1754
  /**
@@ -1994,8 +2024,9 @@ interface SqliteOptions {
1994
2024
  */
1995
2025
  init?: string;
1996
2026
  /**
1997
- * Path to a Prisma schema directory or file.
1998
- * The adapter runs `prisma db push` to create the template.
2027
+ * Path to a Prisma schema directory or file, resolved against the current
2028
+ * working directory. The adapter runs `prisma db push --schema <path>` to
2029
+ * create the template, so the schema needs no Prisma config at the cwd.
1999
2030
  * Mutually exclusive with `init`.
2000
2031
  */
2001
2032
  prismaSchema?: string;
@@ -2013,6 +2044,7 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
2013
2044
  private workerDbPath;
2014
2045
  private initSql;
2015
2046
  private prismaSchema;
2047
+ private readonly options;
2016
2048
  constructor(options?: SqliteOptions);
2017
2049
  buildConnectionString(): string;
2018
2050
  createDatabaseAdapter(): DatabasePort;
@@ -2040,20 +2072,38 @@ declare class SqliteHandle implements DatabasePort, ServiceHandle {
2040
2072
  */
2041
2073
  declare function sqlite(options?: SqliteOptions): SqliteHandle;
2042
2074
  //#endregion
2075
+ //#region src/core/contracts/filters.d.ts
2076
+ /**
2077
+ * A text filter on a provider request builder (`openai.chat({ user })`,
2078
+ * `anthropic.messages({ system })`, …).
2079
+ *
2080
+ * A **string is EXACT**. That is deliberate: a substring filter silently
2081
+ * cross-matches — two prompts sharing a preamble both satisfy the first
2082
+ * contract, and the spec goes green on the wrong reply. Looser matching is
2083
+ * explicit, and says which looseness it means:
2084
+ *
2085
+ * - a `RegExp` — pattern matching;
2086
+ * - `match.includes('…')` — containment, the code-only matcher (it never joins
2087
+ * the `{{token}}` file vocabulary, CONVENTIONS D4);
2088
+ * - any other `match.*` matcher, evaluated by the same structural engine the
2089
+ * assertions use.
2090
+ */
2091
+ type TextFilter = Matcher | RegExp | string;
2092
+ //#endregion
2043
2093
  //#region src/integrations/anthropic/anthropic.d.ts
2044
2094
  interface AnthropicMessagesFilter {
2045
- model?: RegExp | string;
2046
- system?: RegExp | string;
2047
- user?: RegExp | string;
2095
+ model?: TextFilter;
2096
+ system?: TextFilter;
2097
+ user?: TextFilter;
2048
2098
  tools?: string[];
2049
2099
  }
2050
- declare function buildReply(data: unknown): InterceptResponse;
2100
+ declare function buildReply(data: unknown): ContractResponse;
2051
2101
  /**
2052
2102
  * Anthropic API intercept helpers.
2053
2103
  */
2054
2104
  declare const anthropic: {
2055
2105
  /**
2056
- * Trigger: match Messages API requests, optionally routed through a
2106
+ * Request: match Messages API calls, optionally routed through a
2057
2107
  * custom gateway URL. When used with a JSON fixture file, the data is
2058
2108
  * returned as-is (no wrapping) because Anthropic fixtures are typically
2059
2109
  * already in the Messages API response shape.
@@ -2061,15 +2111,16 @@ declare const anthropic: {
2061
2111
  * @example
2062
2112
  * anthropic.messages()
2063
2113
  * anthropic.messages({ system: /classify/ })
2114
+ * anthropic.messages({ user: buildPrompt() }) // string = EXACT equality
2064
2115
  * anthropic.messages({ user: /classify/ }, GATEWAY)
2065
2116
  */
2066
- messages(filter?: AnthropicMessagesFilter, url?: string): InterceptTrigger;
2117
+ messages(filter?: AnthropicMessagesFilter, url?: string): ContractRequest;
2067
2118
  /** Response: wrap data in Anthropic messages format. */
2068
2119
  reply: typeof buildReply;
2069
2120
  /** Response: return an Anthropic error. */
2070
- error(status: number, message?: string): InterceptResponse;
2121
+ error(status: number, message?: string): ContractResponse;
2071
2122
  /** Response: simulate a timeout. */
2072
- timeout(): InterceptResponse;
2123
+ timeout(): ContractResponse;
2073
2124
  };
2074
2125
  //#endregion
2075
2126
  //#region src/core/contracts/http.d.ts
@@ -2077,7 +2128,7 @@ declare const anthropic: {
2077
2128
  * Request filters for the generic HTTP provider. Every field is a subset
2078
2129
  * constraint — a request matches when all provided fields match.
2079
2130
  */
2080
- interface HttpInterceptFilter {
2131
+ interface HttpContractFilter {
2081
2132
  /**
2082
2133
  * Body constraint. An object is a deep SUBSET match (toMatchObject-style)
2083
2134
  * whose leaf values may be `match.*` matchers; a string is a containment
@@ -2089,58 +2140,78 @@ interface HttpInterceptFilter {
2089
2140
  /** Query-param subset. string = exact value, RegExp = `test()`. */
2090
2141
  query?: Record<string, RegExp | string>;
2091
2142
  }
2143
+ /** Init options shared by the response builders. */
2144
+ interface HttpResponseInit {
2145
+ /** Delay in ms before responding (for timeout testing). */
2146
+ delay?: number;
2147
+ /** Response headers, merged over the builder's own. */
2148
+ headers?: Record<string, string>;
2149
+ /** HTTP status code. */
2150
+ status?: number;
2151
+ }
2092
2152
  /**
2093
- * Generic HTTP intercept helpers for any URL. An optional {@link
2094
- * HttpInterceptFilter} narrows matching by request body, headers, or query —
2095
- * a request that hits the URL/method but fails the filter counts as unmatched
2096
- * (strict intercepts, CONVENTIONS D7).
2153
+ * Generic HTTP contract helpers for any URL. The url is absolute (string or
2154
+ * RegExp), or a PATH FORM starting with `/` — `http.get('/articles/{{uuid}}')`
2155
+ * matches that path on ANY origin, which is what an app calling its own
2156
+ * backend needs. An optional {@link HttpContractFilter} narrows matching by
2157
+ * body, headers, or query — a request that hits the URL/method but fails the
2158
+ * filter counts as unmatched (strict contracts, CONVENTIONS D7).
2097
2159
  *
2098
2160
  * @example
2099
- * .intercept(http.get('https://api.example.com/data'), 'http/response.json')
2100
- * .intercept(http.post(URL, { body: { user: 'alice' } }), http.json({ ok: true }))
2161
+ * defineContract({ request: http.get('/articles/{{uuid}}'), response: http.json(article) })
2162
+ * defineContract({ request: http.post(URL, { body: { user: 'alice' } }), response: http.empty() })
2101
2163
  */
2102
2164
  declare const http: {
2103
- get(url: RegExp | string, filter?: HttpInterceptFilter): InterceptTrigger;
2104
- post(url: RegExp | string, filter?: HttpInterceptFilter): InterceptTrigger;
2105
- put(url: RegExp | string, filter?: HttpInterceptFilter): InterceptTrigger;
2106
- delete(url: RegExp | string, filter?: HttpInterceptFilter): InterceptTrigger;
2107
- any(url: RegExp | string, filter?: HttpInterceptFilter): InterceptTrigger;
2108
- /** Response: simple JSON success. */
2109
- json(data: unknown, status?: number): InterceptResponse;
2110
- /** Response: error with message. */
2111
- error(status: number, message?: string): InterceptResponse;
2165
+ any(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2166
+ delete(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2167
+ get(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2168
+ patch(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2169
+ post(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2170
+ put(url: RegExp | string, filter?: HttpContractFilter): ContractRequest;
2171
+ /** Response: a body-less reply (204 by default). */
2172
+ empty(status?: number): ContractResponse;
2173
+ /** Response: an error status. Without a body, `{ error: 'HTTP <status>' }`. */
2174
+ error(status: number, body?: unknown): ContractResponse;
2175
+ /** Response: a JSON body (200 by default). */
2176
+ json(body: unknown, init?: HttpResponseInit): ContractResponse;
2177
+ /** Response: a text body, served as `text/plain` (200 by default). */
2178
+ text(body: string, init?: HttpResponseInit): ContractResponse;
2112
2179
  };
2113
2180
  //#endregion
2114
2181
  //#region src/integrations/openai/openai.d.ts
2115
2182
  interface OpenAIChatFilter {
2116
- model?: RegExp | string;
2117
- system?: RegExp | string;
2118
- user?: RegExp | string;
2183
+ model?: TextFilter;
2184
+ system?: TextFilter;
2185
+ user?: TextFilter;
2119
2186
  tools?: string[];
2120
2187
  temperature?: number;
2121
2188
  }
2122
2189
  interface OpenAIResponsesFilter {
2123
- model?: RegExp | string;
2124
- system?: RegExp | string;
2125
- user?: RegExp | string;
2190
+ model?: TextFilter;
2191
+ system?: TextFilter;
2192
+ user?: TextFilter;
2126
2193
  tools?: string[];
2127
2194
  }
2128
- declare function buildChatReply(data: unknown): InterceptResponse;
2195
+ declare function buildChatReply(data: unknown): ContractResponse;
2129
2196
  /**
2130
2197
  * OpenAI API intercept helpers.
2131
2198
  */
2132
2199
  declare const openai: {
2133
2200
  /**
2134
- * Trigger: match Chat Completions API requests.
2201
+ * Request: match Chat Completions API calls. STRING filters mean EXACT
2202
+ * equality (pass the app's own prompt builder); loosen deliberately with a
2203
+ * RegExp or `match.includes(...)`.
2135
2204
  *
2136
2205
  * @example
2137
- * openai.chat() // any chat call
2138
- * openai.chat({ model: 'gpt-4o' }) // specific model
2139
- * openai.chat({ system: /classify/ }) // system prompt match
2206
+ * openai.chat() // any chat call
2207
+ * openai.chat({ model: 'gpt-4o' }) // exact model
2208
+ * openai.chat({ system: buildPrompt() }) // exact system prompt
2209
+ * openai.chat({ system: /classify/ }) // pattern
2140
2210
  */
2141
- chat(filter?: OpenAIChatFilter): InterceptTrigger;
2211
+ chat(filter?: OpenAIChatFilter): ContractRequest;
2142
2212
  /**
2143
- * Trigger: match Responses API requests (AI SDK v5+) with auto-wrapping.
2213
+ * Request: match Responses API calls (AI SDK v5+) with auto-wrapping.
2214
+ * String filters mean EXACT equality (see {@link openai.chat}).
2144
2215
  * When used with a JSON file, the data is automatically wrapped in the
2145
2216
  * Responses API envelope.
2146
2217
  *
@@ -2150,7 +2221,7 @@ declare const openai: {
2150
2221
  * @example
2151
2222
  * openai.responses({ user: /Report Ingestion/ }, GATEWAY)
2152
2223
  */
2153
- responses(filter?: OpenAIResponsesFilter, url?: string): InterceptTrigger;
2224
+ responses(filter?: OpenAIResponsesFilter, url?: string): ContractRequest;
2154
2225
  /**
2155
2226
  * Response: wrap data in Chat Completions format.
2156
2227
  *
@@ -2159,11 +2230,11 @@ declare const openai: {
2159
2230
  */
2160
2231
  reply: typeof buildChatReply;
2161
2232
  /** Response: return an OpenAI error. */
2162
- error(status: number, message?: string): InterceptResponse;
2233
+ error(status: number, message?: string): ContractResponse;
2163
2234
  /** Response: return malformed content. */
2164
- malformed(content: string): InterceptResponse;
2235
+ malformed(content: string): ContractResponse;
2165
2236
  /** Response: simulate a timeout. */
2166
- timeout(): InterceptResponse;
2237
+ timeout(): ContractResponse;
2167
2238
  };
2168
2239
  //#endregion
2169
2240
  //#region src/vitest/mock-of.d.ts
@@ -2229,4 +2300,4 @@ declare module 'vitest' {
2229
2300
  }
2230
2301
  }
2231
2302
  //#endregion
2232
- export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, DirectoryAccessor, type DockerSpecConfig, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, HttpResult, type InterceptContract, type InterceptEntry, type InterceptResponder, type InterceptResponse, type InterceptResponseValue, type InterceptTrigger, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, content, contentinfo, defineContract, field, findContainersByLabel, form, heading, http, inspectContainer, link, main, match, mockOf, mockOfDate, navigation, openai, postgres, redis, region, removeContainers, search, specification, sqlite, testId, text, within };
2303
+ export { type ApiHandle, type ApiSpecification, type ApiSpecificationOptions, BaseResult, type BrowserConsoleMessage, type BrowserLinkElement, type BrowserMetaElement, type BrowserOpenOptions, type BrowserPage, type BrowserPort, type CaptureScope, type CliEnv, type CliHandle, type CliOutput, type CliPort, CliResult, type CliSpecification, type CliSpecificationOptions, ContainerAccessor, type ContainerPort, type Contract, type ContractInput, type ContractRequest, type ContractResponder, type ContractResponse, type ContractResponseValue, type Contracts, type DatabaseKeys, type DatabasePort, type DeviceOpenOptions, type DevicePort, type DeviceScreen, DirectoryAccessor, type DockerSpecConfig, type ElementKind, type ElementMatch, type ElementOptions, type ElementRef, type ExecOptions, FetchResult, type FileAccessor, FilesystemAccessor, type HonoApp, type HttpContractFilter, type HttpResponseInit, HttpResult, type IsolationStrategy, type JobHandle, type JobsHandle, type JobsSpecification, type JobsSpecificationOptions, JsonAccessor, type LandmarkKind, type MatchFixtureOptions, type MatchableRequest, Matcher, type MatcherKind, type MobileBackendOptions, type MobileElementKind, type MobileElementMatch, type MobileElementRef, type MobileHandle, type MobileScenario, type MobileSpecification, type MobileSpecificationOptions, type MobileVisitor, type MockDatePort, type MockPort, Orchestrator, PageResult, type PostgresOptions, type RedisOptions, ResponseAccessor, type ScreenNode, ScreenResult, type ServeOptions, type ServerPort, type ServerResponse, type ServiceHandle, type ServiceRecord, type SpecificationConfig, type SpecificationMode, type SqliteOptions, TableAccessor, TextAccessor, type TextFilter, type VisitScenario, type Visitor, type WebsiteBackendOptions, type WebsiteHandle, type WebsiteSpecification, type WebsiteSpecificationOptions, anthropic, banner, button, complementary, content, contentinfo, defineContract, defineContracts, field, findContainersByLabel, form, heading, http, inspectContainer, link, main, match, mockOf, mockOfDate, navigation, openai, postgres, redis, region, removeContainers, search, specification, sqlite, testId, text, within };