counterfact 2.9.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,34 +1,57 @@
1
1
  <div align="center" markdown="1">
2
2
 
3
- <img src="./counterfact.svg" alt="Counterfact" border=0>
3
+ <h1><img src="./counterfact.svg" alt="Counterfact" border=0></h1>
4
4
 
5
5
  <br>
6
6
 
7
- ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![TypeScript](./typescript-badge.png)](https://github.com/ellerbrock/typescript-badges/) [![Coverage Status](https://coveralls.io/repos/github/counterfact/api-simulator/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact)
7
+ ![MIT License](https://img.shields.io/badge/license-MIT-blue) [![Coverage Status](https://coveralls.io/repos/github/counterfact/api-simulator/badge.svg)](https://coveralls.io/github/pmcelhaney/counterfact) ![friction 0%](https://img.shields.io/badge/friction-0%25-brightgreen)
8
8
 
9
9
  </div>
10
10
 
11
- You've used mock servers. You know where they stop being useful: static responses, no shared state, no way to inject a failure mid-run, no control without restarting. Counterfact picks up where they leave off.
11
+ <div align="center" markdown="1">
12
+ <h2>Mock servers work—until you need state, failures, or control mid-run.</h2>
13
+ </div
14
+
15
+ Static responses aren’t enough. There’s no shared state. You can’t inject failures. You can’t test real workflows.<br>
16
+ Mock servers make it easy to get started, but hard to keep going.<br>
17
+ Counterfact is an API simulator without those limits.
18
+
19
+ Point it at an [OpenAPI](https://www.openapis.org) document and get a live, stateful API in seconds.
20
+ - Type-safe TypeScript handlers for every endpoint
21
+ - Hot reloading as you edit
22
+ - Shared state across routes
23
+ - **A built-in REPL to control behavior at runtime**
24
+ - Optional proxying to real backends
25
+
26
+ Flexbile for humans. Stable for [AI agents](https://github.com/counterfact/api-simulator/blob/main/docs/patterns/ai-assisted-implementation.md).
27
+
28
+ You’re in control—without restarting.
29
+
30
+ For a *frontend developer* waiting on a backend,<br>
31
+ a *test engineer* who needs clean, reproducible state,<br>
32
+ or an *AI agent* that needs a stable API
33
+
34
+ Real enough to be useful. Fake enough to be usable.
35
+
12
36
 
13
- Point it at an OpenAPI spec and it generates TypeScript handlers for every endpoint—type-safe, hot-reloading, sharing state across routes. A built-in REPL gives you a live control surface: seed data, trigger error conditions, proxy individual routes to a real backend, all on a running server. Whether you're a frontend developer waiting on a backend, a test engineer who needs clean reproducible state, or an AI agent that needs a stable API to work against, Counterfact is the simulator that doesn't plateau.
37
+ ## Try it now
14
38
 
15
39
  ```sh
16
40
  npx counterfact@latest https://petstore3.swagger.io/api/v3/openapi.json api
17
41
  ```
18
42
 
43
+ > Starts a local server with a live REPL to inspect and control API behavior
19
44
  > Requires Node ≥ 22.0.0
20
45
 
21
46
  ## Go deeper
22
47
 
23
- | | |
24
- |---|---|
25
- | [Getting started](./docs/getting-started.md) | Detailed walkthrough with state, REPL, and proxy |
26
- | [Usage](./docs/usage.md) | Feature index: routes, context, REPL, proxy, middleware, and more |
27
- | [Patterns](./docs/patterns/index.md) | Failures, latency, AI sandboxes, integration tests |
28
- | [Reference](./docs/reference.md) | `$` API, CLI flags, architecture |
29
- | [How it compares](./docs/comparison.md) | json-server, WireMock, Prism, Microcks, MSW |
30
- | [FAQ](./docs/faq.md) | State, types, regeneration |
31
- | [Petstore example](https://github.com/counterfact/example-petstore) | Full working example |
48
+ - [Getting started](./docs/getting-started.md) – Detailed walkthrough with state, REPL, and proxy
49
+ - [Patterns](./docs/patterns/index.md) – How Counterfact transforms your workflow
50
+ - [Example repo](https://github.com/counterfact/example-petstore) Using Counterfact to implement the Swagger Petstore
51
+ - [How it compares](./docs/comparison.md) json-server, WireMock, Prism, Microcks, MSW
52
+ - [Usage](./docs/usage.md) Explore features and how to use them
53
+ - [Reference](./docs/reference.md) `$` API, CLI flags, architecture
54
+ - [FAQ](./docs/faq.md) State, types, regeneration
32
55
 
33
56
  <div align="center" markdown="1">
34
57
 
@@ -64,6 +64,11 @@ export class ApiRunner {
64
64
  * Defaults to `""` (no subdirectory).
65
65
  */
66
66
  group;
67
+ /**
68
+ * Optional version label for this runner's spec (e.g. `"v1"`, `"v2"`).
69
+ * Defaults to `""` (unversioned).
70
+ */
71
+ version;
67
72
  /**
68
73
  * The subdirectory path segment derived from {@link group}.
69
74
  * Returns `""` when `group` is empty, otherwise `"/${group}"`.
@@ -72,8 +77,9 @@ export class ApiRunner {
72
77
  return this.group ? `/${this.group}` : "";
73
78
  }
74
79
  config;
75
- constructor(config, nativeTs, openApiDocument, group) {
80
+ constructor(config, nativeTs, openApiDocument, group, version = "", versions = []) {
76
81
  this.group = group;
82
+ this.version = version;
77
83
  const modulesPath = this.group
78
84
  ? pathJoin(config.basePath, this.group)
79
85
  : config.basePath;
@@ -87,8 +93,8 @@ export class ApiRunner {
87
93
  this.contextRegistry = new ContextRegistry();
88
94
  this.scenarioRegistry = new ScenarioRegistry();
89
95
  this.scenarioFileGenerator = new ScenarioFileGenerator(modulesPath);
90
- this.codeGenerator = new CodeGenerator(this.openApiPath, config.basePath + this.subdirectory, config.generate);
91
- this.dispatcher = new Dispatcher(this.registry, this.contextRegistry, openApiDocument, config);
96
+ this.codeGenerator = new CodeGenerator(this.openApiPath, config.basePath + this.subdirectory, config.generate, version);
97
+ this.dispatcher = new Dispatcher(this.registry, this.contextRegistry, openApiDocument, config, version, versions);
92
98
  this.transpiler = new Transpiler(pathJoin(modulesPath, "routes"), compiledPathsDirectory, "commonjs");
93
99
  this.moduleLoader = new ModuleLoader(compiledPathsDirectory, this.registry, this.contextRegistry, pathJoin(modulesPath, "scenarios"), this.scenarioRegistry);
94
100
  }
@@ -101,8 +107,10 @@ export class ApiRunner {
101
107
  *
102
108
  * @param config - Runtime configuration for this runner instance.
103
109
  * @param group - Optional group name placing generated code in a subdirectory (default `""`).
110
+ * @param version - Optional version label for this spec (e.g. `"v1"`, `"v2"`).
111
+ * @param versions - Optional ordered list of all version labels in this group (oldest first).
104
112
  */
105
- static async create(config, group = "") {
113
+ static async create(config, group = "", version = "", versions = []) {
106
114
  const nativeTs = await runtimeCanExecuteErasableTs();
107
115
  const modulesPath = group
108
116
  ? pathJoin(config.basePath, group)
@@ -114,7 +122,7 @@ export class ApiRunner {
114
122
  const openApiDocument = config.openApiPath === "_"
115
123
  ? undefined
116
124
  : await loadOpenApiDocument(config.openApiPath);
117
- return new ApiRunner(config, nativeTs, openApiDocument, group);
125
+ return new ApiRunner(config, nativeTs, openApiDocument, group, version, versions);
118
126
  }
119
127
  /**
120
128
  * Generates TypeScript route stubs and type files from the OpenAPI spec.
@@ -123,11 +131,15 @@ export class ApiRunner {
123
131
  * - Routes and types are only generated when `config.openApiPath` is not `"_"`.
124
132
  * - The scenario context type file is always generated when
125
133
  * `config.generate.types` is `true`, even without a spec.
134
+ *
135
+ * @param repository - Optional shared repository. Pass a shared instance
136
+ * when multiple versioned specs in the same group should merge their types
137
+ * into the same output tree.
126
138
  */
127
- async generate() {
139
+ async generate(repository) {
128
140
  if (this.config.openApiPath !== "_" &&
129
141
  (this.config.generate.routes || this.config.generate.types)) {
130
- await this.codeGenerator.generate();
142
+ await this.codeGenerator.generate(repository);
131
143
  }
132
144
  if (this.config.generate.types) {
133
145
  await this.scenarioFileGenerator.generate();
package/dist/app.js CHANGED
@@ -1,8 +1,13 @@
1
+ import fs from "node:fs/promises";
2
+ import nodePath from "node:path";
1
3
  import { createHttpTerminator } from "http-terminator";
2
4
  import { ApiRunner } from "./api-runner.js";
3
5
  import { startRepl as startReplServer } from "./repl/repl.js";
4
6
  import { createRouteFunction } from "./repl/route-builder.js";
5
7
  import { createKoaApp } from "./server/web-server/create-koa-app.js";
8
+ import { Repository } from "./typescript-generator/repository.js";
9
+ import { ensureDirectoryExists } from "./util/ensure-directory-exists.js";
10
+ import { generateVersionsTsContent } from "./typescript-generator/versions-ts-generator.js";
6
11
  export { loadOpenApiDocument } from "./server/load-openapi-document.js";
7
12
  export { createMswHandlers, handleMswRequest, } from "./msw.js";
8
13
  export async function runStartupScenario(scenarioRegistry, contextRegistry, config, openApiDocument) {
@@ -18,19 +23,47 @@ export async function runStartupScenario(scenarioRegistry, contextRegistry, conf
18
23
  };
19
24
  await indexModule["startup"](scenario$);
20
25
  }
26
+ /**
27
+ * Derives the URL prefix for a spec entry.
28
+ *
29
+ * Applies the following precedence rules:
30
+ * 1. Explicit `prefix` (even `""`) → returned as-is.
31
+ * 2. `group` + `version` both present → `/<group>/<version>`.
32
+ * 3. `group` present (no `version`) → `/<group>`.
33
+ * 4. Neither → `""` (root).
34
+ */
35
+ function derivePrefix(spec) {
36
+ if (spec.prefix !== undefined) {
37
+ return spec.prefix;
38
+ }
39
+ if (spec.group && spec.version) {
40
+ return `/${spec.group}/${spec.version}`;
41
+ }
42
+ if (spec.group) {
43
+ return `/${spec.group}`;
44
+ }
45
+ return "";
46
+ }
21
47
  /**
22
48
  * Normalises the spec configuration to an array.
23
49
  *
24
- * When `specs` is provided it is returned as-is. When it is omitted, a
25
- * single-entry array is constructed from `config.openApiPath`,
26
- * `config.prefix`, and `group = ""` so that the rest of the code never
27
- * needs to branch on single-vs-multiple specs.
50
+ * When `specs` is provided, each entry's `prefix` is resolved via
51
+ * {@link derivePrefix} so the rest of the code can assume `prefix` is always
52
+ * a string. When `specs` is omitted, a single-entry array is constructed from
53
+ * `config.openApiPath`, `config.prefix`, and `group = ""`.
28
54
  */
29
55
  function normalizeSpecs(config, specs) {
30
56
  if (specs !== undefined) {
31
- return specs;
57
+ return specs.map((spec) => ({ ...spec, prefix: derivePrefix(spec) }));
32
58
  }
33
- return [{ source: config.openApiPath, prefix: config.prefix, group: "" }];
59
+ return [
60
+ {
61
+ source: config.openApiPath,
62
+ prefix: config.prefix,
63
+ group: "",
64
+ version: "",
65
+ },
66
+ ];
34
67
  }
35
68
  function validateSpecGroups(specs) {
36
69
  if (specs.length <= 1) {
@@ -41,20 +74,26 @@ function validateSpecGroups(specs) {
41
74
  .filter(({ group }) => group === "")
42
75
  .map(({ index }) => String(index + 1));
43
76
  if (invalidSpecNumbers.length === 0) {
44
- const seenGroups = new Set();
45
- const duplicateGroupNames = new Set();
77
+ const seenKeys = new Set();
78
+ const duplicateKeys = new Set();
46
79
  for (const spec of specs) {
47
80
  const group = spec.group.trim();
48
- if (seenGroups.has(group)) {
49
- duplicateGroupNames.add(group);
81
+ const version = spec.version?.trim() ?? "";
82
+ // Use group@version as the uniqueness key so that the same group can
83
+ // appear with different versions (e.g. v1 and v2 of the same API).
84
+ // The empty-group case is already rejected above, so `group` is always
85
+ // non-empty here and the `@version` suffix remains unambiguous.
86
+ const key = version ? `${group}@${version}` : group;
87
+ if (seenKeys.has(key)) {
88
+ duplicateKeys.add(key);
50
89
  continue;
51
90
  }
52
- seenGroups.add(group);
91
+ seenKeys.add(key);
53
92
  }
54
- if (duplicateGroupNames.size === 0) {
93
+ if (duplicateKeys.size === 0) {
55
94
  return;
56
95
  }
57
- throw new Error(`Each spec must define a unique group when multiple APIs are configured (duplicate groups: ${[...duplicateGroupNames].join(", ")}).`);
96
+ throw new Error(`Each spec must define a unique group (and version) when multiple APIs are configured (duplicates: ${[...duplicateKeys].join(", ")}).`);
58
97
  }
59
98
  throw new Error(`Each spec must define a non-empty group when multiple APIs are configured (invalid spec entries: ${invalidSpecNumbers.join(", ")}).`);
60
99
  }
@@ -81,7 +120,18 @@ function validateSpecGroups(specs) {
81
120
  export async function counterfact(config, specs) {
82
121
  const normalizedSpecs = normalizeSpecs({ openApiPath: config.openApiPath, prefix: config.prefix }, specs);
83
122
  validateSpecGroups(normalizedSpecs);
84
- const runners = await Promise.all(normalizedSpecs.map((spec) => ApiRunner.create({ ...config, openApiPath: spec.source, prefix: spec.prefix }, spec.group)));
123
+ // Compute the ordered versions per group (oldest first, as declared in specs).
124
+ // This list is passed to each runner so that $.minVersion() can compare
125
+ // version positions at runtime.
126
+ const versionsByGroup = new Map();
127
+ for (const spec of normalizedSpecs) {
128
+ const version = spec.version ?? "";
129
+ if (version) {
130
+ const existing = versionsByGroup.get(spec.group) ?? [];
131
+ versionsByGroup.set(spec.group, [...existing, version]);
132
+ }
133
+ }
134
+ const runners = await Promise.all(normalizedSpecs.map((spec) => ApiRunner.create({ ...config, openApiPath: spec.source, prefix: spec.prefix }, spec.group, spec.version ?? "", versionsByGroup.get(spec.group) ?? [])));
85
135
  const koaApp = createKoaApp({
86
136
  runners,
87
137
  config,
@@ -89,7 +139,61 @@ export async function counterfact(config, specs) {
89
139
  // The REPL is configured using the first runner.
90
140
  const primaryRunner = runners[0];
91
141
  async function start(options) {
92
- await Promise.all(runners.map((runner) => runner.generate()));
142
+ // Serialize generate() calls within each group to avoid concurrent writes
143
+ // to the same output directory. Runners that share a group share the same
144
+ // basePath subdirectory (and therefore the same counterfact-types
145
+ // destination), so running them in parallel would cause a race when both
146
+ // try to create that directory at startup. Different groups are still
147
+ // generated in parallel.
148
+ //
149
+ // When multiple versioned specs share the same group, they also share a
150
+ // single Repository instance so that the shared `types/paths/…` files
151
+ // accumulate all versions into a merged Versioned<…> type instead of each
152
+ // overwriting the previous version's types.
153
+ const runnersByGroup = new Map();
154
+ for (const runner of runners) {
155
+ const bucket = runnersByGroup.get(runner.group) ?? [];
156
+ bucket.push(runner);
157
+ runnersByGroup.set(runner.group, bucket);
158
+ }
159
+ await Promise.all(Array.from(runnersByGroup.values()).map(async (bucket) => {
160
+ const sharedRepository = bucket.length > 1 ? new Repository() : undefined;
161
+ for (const runner of bucket) {
162
+ await runner.generate(sharedRepository);
163
+ }
164
+ }));
165
+ if (options.generate?.types) {
166
+ // Build a per-group map of unique non-empty version strings in
167
+ // declaration order. new Set() preserves insertion order so the first
168
+ // occurrence of each version is kept and duplicates are dropped without
169
+ // reordering.
170
+ const versionsByGroup = new Map();
171
+ for (const spec of normalizedSpecs) {
172
+ const group = spec.group;
173
+ const version = (spec.version ?? "").trim();
174
+ if (version === "") {
175
+ continue;
176
+ }
177
+ const existing = versionsByGroup.get(group) ?? [];
178
+ if (!existing.includes(version)) {
179
+ existing.push(version);
180
+ }
181
+ versionsByGroup.set(group, existing);
182
+ }
183
+ // Write <basePath>/<group>/types/versions.ts for every group that has
184
+ // at least one versioned spec. When the group is empty the path
185
+ // collapses to <basePath>/types/versions.ts (the single-spec case).
186
+ await Promise.all(Array.from(versionsByGroup.entries()).map(async ([group, versions]) => {
187
+ const content = await generateVersionsTsContent(versions);
188
+ const versionsFilePath = group
189
+ ? nodePath.join(config.basePath, group, "types", "versions.ts")
190
+ : nodePath.join(config.basePath, "types", "versions.ts");
191
+ /* eslint-disable security/detect-non-literal-fs-filename -- path is derived from the caller-supplied basePath and fixed suffixes. */
192
+ await ensureDirectoryExists(versionsFilePath);
193
+ await fs.writeFile(versionsFilePath, content, "utf8");
194
+ /* eslint-enable security/detect-non-literal-fs-filename */
195
+ }));
196
+ }
93
197
  await Promise.all(runners.map((runner) => runner.watch()));
94
198
  await Promise.all(runners.map((runner) => runner.start(options)));
95
199
  let httpTerminator;
@@ -67,7 +67,7 @@ export function createIntroduction(params) {
67
67
  ` API Base URL ${url}`,
68
68
  source === "_" ? undefined : ` Swagger UI ${swaggerUrl}`,
69
69
  "",
70
- " Instructions https://counterfact.dev/docs/usage.html",
70
+ " Instructions https://github.com/counterfact/api-simulator/blob/main/docs/usage.md",
71
71
  " Help/feedback https://github.com/pmcelhaney/counterfact/issues",
72
72
  "",
73
73
  ...telemetryWarning,
package/dist/cli/run.js CHANGED
@@ -22,18 +22,23 @@ const DEFAULT_PORT = 3100;
22
22
  * CLI flag) into an array of {@link SpecConfig} objects, or `undefined` when
23
23
  * the option is a plain string (single OpenAPI document path).
24
24
  *
25
- * - **Array**: each entry is mapped to `{source, prefix, group}` with defaults.
25
+ * - **Array**: each entry is mapped to `{source, prefix, group, version}` with defaults.
26
26
  * - **Object**: wrapped in a single-element array.
27
27
  * - **String / undefined**: returns `undefined` — caller handles the string
28
28
  * case (it shifts the positional argument) and the `undefined` case
29
29
  * (single spec derived from config).
30
+ *
31
+ * Note: `prefix` is intentionally left `undefined` when not supplied so that
32
+ * `normalizeSpecs` (in `app.ts`) can derive it automatically from
33
+ * `group`/`version`.
30
34
  */
31
35
  export function normalizeSpecOption(specOption) {
32
36
  if (Array.isArray(specOption)) {
33
37
  return specOption.map((entry) => ({
34
38
  source: entry.source,
35
- prefix: entry.prefix ?? "",
39
+ prefix: entry.prefix,
36
40
  group: entry.group ?? "",
41
+ version: entry.version,
37
42
  }));
38
43
  }
39
44
  if (typeof specOption === "object" &&
@@ -42,8 +47,9 @@ export function normalizeSpecOption(specOption) {
42
47
  return [
43
48
  {
44
49
  source: specOption.source,
45
- prefix: specOption.prefix ?? "",
50
+ prefix: specOption.prefix,
46
51
  group: specOption.group ?? "",
52
+ version: specOption.version,
47
53
  },
48
54
  ];
49
55
  }
@@ -253,7 +259,7 @@ function buildProgram(version, taglines) {
253
259
  .option("--watch-routes", "generate + watch routes for changes")
254
260
  .option("-s, --serve", "start the server")
255
261
  .option("-b, --build-cache", "builds the cache of compiled routes and types")
256
- .option("--no-admin-api", "disable the admin API at /_counterfact/api/*")
262
+ .option("--admin-api", "enable the admin API at /_counterfact/api/*")
257
263
  .option("-r, --repl", "start the REPL")
258
264
  .option("--proxy-url <string>", "proxy URL")
259
265
  .option("--admin-api-token <string>", "bearer token required for /_counterfact/api/* endpoints (defaults to COUNTERFACT_ADMIN_API_TOKEN)")
@@ -5,9 +5,7 @@ const POSTHOG_HOST = "https://us.i.posthog.com";
5
5
  /**
6
6
  * Returns `true` when telemetry should be sent.
7
7
  *
8
- * Telemetry is disabled in CI, when `COUNTERFACT_TELEMETRY_DISABLED=true`,
9
- * or before the May 2026 rollout date unless the user has explicitly opted
10
- * in with `COUNTERFACT_TELEMETRY_DISABLED=false`.
8
+ * Telemetry is disabled in CI or when `COUNTERFACT_TELEMETRY_DISABLED=true`.
11
9
  */
12
10
  export function isTelemetryEnabled() {
13
11
  if (process.env["CI"])
@@ -15,9 +13,6 @@ export function isTelemetryEnabled() {
15
13
  const telemetryDisabledEnv = process.env["COUNTERFACT_TELEMETRY_DISABLED"];
16
14
  if (telemetryDisabledEnv === "true")
17
15
  return false;
18
- const isBeforeRollout = new Date() < new Date("2026-05-01");
19
- if (isBeforeRollout && telemetryDisabledEnv !== "false")
20
- return false;
21
16
  return true;
22
17
  }
23
18
  /**
@@ -67,7 +67,7 @@ async function buildTypeNameMapping(specification) {
67
67
  return;
68
68
  }
69
69
  // Create the type coder to get the correct type name
70
- const typeCoder = new OperationTypeCoder(operation, requestMethod, securitySchemes);
70
+ const typeCoder = new OperationTypeCoder(operation, "", requestMethod, securitySchemes);
71
71
  // Get the type name (first from the names generator)
72
72
  const typeName = typeCoder.names().next().value;
73
73
  methodMap.set(requestMethod.toUpperCase(), typeName);
package/dist/repl/repl.js CHANGED
@@ -194,9 +194,6 @@ export function startRepl(contextRegistry, registry, config, print = printToStdo
194
194
  }
195
195
  seenGroups.add(binding.key);
196
196
  }
197
- if (duplicateGroups.size > 0) {
198
- throw new Error(`Duplicate API groups are not allowed when multiple APIs are configured (duplicate groups: ${[...duplicateGroups].join(", ")}).`);
199
- }
200
197
  }
201
198
  const rootBinding = groupedBindings[0];
202
199
  if (rootBinding === undefined) {
@@ -268,7 +265,7 @@ export function startRepl(contextRegistry, registry, config, print = printToStdo
268
265
  print("- context: the root context ( same as loadContext('/') )");
269
266
  print("- route('/some/path'): create a request builder for the given path");
270
267
  print("");
271
- print("For more information, see https://counterfact.dev/docs/usage.html");
268
+ print("For more information, see https://github.com/counterfact/api-simulator/blob/main/docs/usage.md");
272
269
  print("");
273
270
  this.clearBufferedCommand();
274
271
  this.displayPrompt();
@@ -157,8 +157,7 @@ export type GenericResponseBuilder<
157
157
  : object extends OmitValueWhenNever<Omit<Response, "examples">>
158
158
  ? COUNTERFACT_RESPONSE
159
159
  : keyof OmitValueWhenNever<Omit<Response, "examples">> extends "headers"
160
- ? {
161
- ALL_REMAINING_HEADERS_ARE_OPTIONAL: COUNTERFACT_RESPONSE;
160
+ ? COUNTERFACT_RESPONSE & {
162
161
  header: HeaderFunction<Response>;
163
162
  }
164
163
  : GenericResponseBuilderInner<Response>;
@@ -5,12 +5,15 @@
5
5
  * argument object.
6
6
  */
7
7
  export interface OpenApiParameters {
8
+ explode?: boolean;
8
9
  in: "body" | "cookie" | "formData" | "header" | "path" | "query";
9
10
  name: string;
10
11
  required?: boolean;
11
12
  schema?: {
12
13
  [key: string]: unknown;
14
+ properties?: Record<string, unknown>;
13
15
  type?: string;
14
16
  };
17
+ style?: string;
15
18
  type?: "string" | "number" | "integer" | "boolean";
16
19
  }