@vitest-agent/mcp 3.0.4 → 4.0.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/README.md +7 -6
- package/annotations.js +18 -0
- package/bin/vitest-agent-mcp.js +5 -140
- package/{middleware/idempotency.js → idempotency.js} +48 -49
- package/index.d.ts +5610 -1549
- package/index.js +35 -17
- package/main.d.ts +31 -0
- package/main.js +144 -0
- package/package.json +9 -6
- package/prompts/layer.js +108 -0
- package/register-toolkit.js +311 -0
- package/server.js +24 -894
- package/{context.js → session.js} +38 -22
- package/toolkit.js +86 -0
- package/tools/acceptance-metrics.js +26 -14
- package/tools/cache-health.js +28 -17
- package/tools/commit-changes.js +33 -10
- package/tools/configure.js +33 -10
- package/tools/coverage.js +34 -5
- package/tools/errors.js +36 -26
- package/tools/failure-signature-get.js +33 -10
- package/tools/file-coverage.js +37 -13
- package/tools/help.js +45 -4
- package/tools/history.js +39 -19
- package/tools/hypothesis.js +118 -102
- package/tools/inventory.js +149 -146
- package/tools/note.js +131 -113
- package/tools/overview.js +33 -10
- package/tools/ping.js +22 -10
- package/tools/register-agent.js +88 -62
- package/tools/run-tests.js +76 -23
- package/tools/settings-list.js +27 -10
- package/tools/status.js +35 -10
- package/tools/tdd-artifact.js +37 -22
- package/tools/tdd-behavior.js +120 -108
- package/tools/tdd-goal.js +98 -85
- package/tools/tdd-phase-transition-request.js +201 -166
- package/tools/tdd-progress-push.js +102 -0
- package/tools/tdd-task.js +140 -138
- package/tools/test.js +152 -138
- package/tools/trends.js +37 -18
- package/tools/triage-brief.js +34 -15
- package/tools/turn-search.js +39 -16
- package/tools/wrapup-prompt.js +37 -17
- package/utils/crash-guards.js +0 -22
- package/utils/replay-marker.js +12 -0
- package/utils/safe-format-fatal-error.js +0 -16
- package/utils/tool-error-envelope.js +3 -3
- package/version.js +13 -0
- package/layers/McpLive.js +0 -29
- package/prompts/index.js +0 -89
- package/router.js +0 -74
- package/session-env.js +0 -112
- package/utils/effect-to-zod.js +0 -158
package/README.md
CHANGED
|
@@ -6,16 +6,17 @@
|
|
|
6
6
|
|
|
7
7
|
> **Part of the [vitest-agent](https://vitest-agent.dev) ecosystem.** Most users want **[@vitest-agent/plugin](https://www.npmjs.com/package/@vitest-agent/plugin)**, which pulls this package in automatically. Install `@vitest-agent/mcp` directly only if you run the MCP server standalone.
|
|
8
8
|
|
|
9
|
-
The `vitest-agent-mcp` MCP server bin. Exposes action-keyed tools over stdio that give LLM agents structured access to test data, coverage, history, failure signatures, TDD lifecycle state and more. Also surfaces six framing-only prompts.
|
|
9
|
+
The `vitest-agent-mcp` MCP server bin, built on Effect's native `McpServer` (`effect/unstable/ai`). Exposes action-keyed tools over stdio that give LLM agents structured access to test data, coverage, history, failure signatures, TDD lifecycle state and more. Also surfaces six framing-only prompts.
|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
|
-
- **
|
|
13
|
+
- **30 action-keyed tools** — one `Tool.make` per file, assembled into a single `Toolkit`; per-CRUD families collapse into single tools dispatching on an `action` discriminator; covers `test_status`, `test_overview`, `test_coverage`, `test_errors`, `run_tests`, `note`, `hypothesis`, `tdd_task`, `tdd_goal`, `tdd_behavior`, `tdd_progress_push` and more
|
|
14
14
|
- **Six framing prompts** — `triage`, `why-flaky`, `regression-since-pass`, `explain-failure`, `tdd-resume`, `wrapup`
|
|
15
|
-
- **
|
|
16
|
-
- **Strict tool inputs** — an unknown key is rejected with an error naming it and listing the accepted params, instead of being stripped and running a wider query than the caller asked for
|
|
17
|
-
- **Session-surviving error handling** — a
|
|
18
|
-
- **Programmatic API** — `
|
|
15
|
+
- **Idempotent writes** — `tdd_task`, `tdd_goal`, `tdd_behavior` and `hypothesis` create-actions are idempotent on derived keys via the `withIdempotency` combinator; a replay carries `_idempotentReplay: true`
|
|
16
|
+
- **Strict tool inputs** — every served `inputSchema` is strict at every object level; an unknown key is rejected with an error naming it and listing the accepted params, instead of being stripped and running a wider query than the caller asked for
|
|
17
|
+
- **Session-surviving error handling** — a handler that dies returns a structured `UnexpectedToolError` envelope, every log line goes to stderr (stdout is the JSON-RPC wire), and a stray unhandled rejection after the transport connects is logged rather than killing the server mid-session
|
|
18
|
+
- **Programmatic API** — `ServerLayer({ version })` is the whole server as an Effect `Layer` over any `Stdio` implementation (the test harness runs it over in-memory queues); `Kit`, `toolHandlers`, `PromptsLayer`, `registerStrictToolkit` and `McpSession` are exported for embedding or extension
|
|
19
|
+
- **No MCP SDK, tRPC or zod** — the wire protocol, JSON Schema generation and input validation all come from `effect`; tool inputs and outputs are Effect `Schema` values end to end
|
|
19
20
|
|
|
20
21
|
## Install
|
|
21
22
|
|
package/annotations.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Context } from "effect";
|
|
2
|
+
|
|
3
|
+
//#region src/annotations.ts
|
|
4
|
+
/**
|
|
5
|
+
* Renders a tool's encoded result as the human-readable `content[0].text`
|
|
6
|
+
* channel (markdown, typically). Returning `undefined` — or leaving the
|
|
7
|
+
* annotation unset — falls back to `JSON.stringify(encoded)`.
|
|
8
|
+
*
|
|
9
|
+
* Attach with `tool.annotate(RenderText, (encoded) => ...)`; the renderer
|
|
10
|
+
* receives the wire-encoded result (the same value that becomes
|
|
11
|
+
* `structuredContent`), never the decoded domain value.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
const RenderText = Context.Reference("@vitest-agent/mcp/RenderText", { defaultValue: () => void 0 });
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { RenderText };
|
package/bin/vitest-agent-mcp.js
CHANGED
|
@@ -1,149 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { McpLive } from "../layers/McpLive.js";
|
|
4
|
-
import { startMcpServer } from "../server.js";
|
|
5
|
-
import { recoverSessionContextFromSessionEnv } from "../session-env.js";
|
|
6
|
-
import { shouldExitOnUncaughtException } from "../utils/crash-guards.js";
|
|
7
|
-
import { safeFormatFatalError } from "../utils/safe-format-fatal-error.js";
|
|
8
|
-
import { Effect, ManagedRuntime } from "effect";
|
|
9
|
-
import * as NodeServices from "@effect/platform-node/NodeServices";
|
|
10
|
-
import { PathResolutionLive, resolveDataPath, resolveLogFile, resolveLogLevel } from "@vitest-agent/sdk";
|
|
2
|
+
import { main } from "../main.js";
|
|
11
3
|
|
|
12
4
|
//#region src/bin.ts
|
|
13
5
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* {@link shouldExitOnUncaughtException} — see that function's doc
|
|
17
|
-
* comment for the judgment call this flag backs (issue #191).
|
|
18
|
-
*/
|
|
19
|
-
let transportConnected = false;
|
|
20
|
-
/**
|
|
21
|
-
* Issue #191, sub-item A: the MCP server used to be a bare
|
|
22
|
-
* `main().catch(...)` with no process-level guards. Under Node >=15 an
|
|
23
|
-
* unhandled promise rejection anywhere in the async chain — a
|
|
24
|
-
* fire-and-forgotten Effect fiber, a background timer, anything that
|
|
25
|
-
* never flows through a tool call's own await chain — crashes the
|
|
26
|
-
* process by default, closing the stdio transport and silently
|
|
27
|
-
* deregistering every tool from the client's perspective mid session,
|
|
28
|
-
* with no recovery path. Register both guards at module scope, before
|
|
29
|
-
* any async work in `main()` starts, so they also cover the dbPath
|
|
30
|
-
* resolution / ManagedRuntime construction phase.
|
|
31
|
-
*
|
|
32
|
-
* Every throw or rejection *inside* a single tool call is already
|
|
33
|
-
* caught by the MCP SDK's own `CallToolRequestSchema` handler (see
|
|
34
|
-
* `server.ts`'s `registerTool` shadow for the structured-envelope
|
|
35
|
-
* layer on top of that). Anything that reaches these handlers
|
|
36
|
-
* therefore originated outside any tool-call boundary and, by
|
|
37
|
-
* definition, has no in-flight caller waiting on it — logging and
|
|
38
|
-
* continuing is safe because there is no request context to roll back
|
|
39
|
-
* and no benefit to dropping every *other* in-flight and future tool
|
|
40
|
-
* call over it.
|
|
41
|
-
*/
|
|
42
|
-
process.on("unhandledRejection", (reason) => {
|
|
43
|
-
process.stderr.write(`vitest-agent-mcp: unhandledRejection: ${safeFormatFatalError(reason)}\n`);
|
|
44
|
-
});
|
|
45
|
-
/**
|
|
46
|
-
* Node's own guidance for `uncaughtException` is "do not resume normal
|
|
47
|
-
* operation" — arbitrary in-process state may be corrupt. This process
|
|
48
|
-
* accepts that residual risk once the transport is connected: see
|
|
49
|
-
* `shouldExitOnUncaughtException`'s doc comment for the reasoning
|
|
50
|
-
* (no long-lived mutable state outside SQLite's own transactions, and
|
|
51
|
-
* silent process death mid-session is strictly worse). Before the
|
|
52
|
-
* transport connects there is no client session to preserve by staying
|
|
53
|
-
* alive, so this exits loudly instead.
|
|
54
|
-
*/
|
|
55
|
-
process.on("uncaughtException", (err, origin) => {
|
|
56
|
-
process.stderr.write(`vitest-agent-mcp: uncaughtException (${origin}): ${safeFormatFatalError(err)}\n`);
|
|
57
|
-
if (shouldExitOnUncaughtException(transportConnected)) {
|
|
58
|
-
process.exitCode = 1;
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
/**
|
|
63
|
-
* Test-only crash injection, gated by an env var so it can never fire
|
|
64
|
-
* in a normal install. Exists so `__test__/bin-crash-resilience.e2e.test.ts`
|
|
65
|
-
* can exercise the guards above against a *real* child process (a
|
|
66
|
-
* crash in the test's own process is not something a unit test can
|
|
67
|
-
* safely simulate). Fires exactly once, on the next event-loop turn
|
|
68
|
-
* after the transport is known to be connected, so ordering relative
|
|
69
|
-
* to `transportConnected` is deterministic regardless of client-side
|
|
70
|
-
* handshake timing.
|
|
71
|
-
*/
|
|
72
|
-
function scheduleTestCrashInjection() {
|
|
73
|
-
const kind = process.env.VITEST_AGENT_MCP_TEST_INJECT_CRASH;
|
|
74
|
-
if (kind !== "unhandledRejection" && kind !== "uncaughtException") return;
|
|
75
|
-
setImmediate(() => {
|
|
76
|
-
if (kind === "unhandledRejection") Promise.reject(/* @__PURE__ */ new Error("[test-injected] unhandledRejection"));
|
|
77
|
-
else throw new Error("[test-injected] uncaughtException");
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Resolve the user's project directory.
|
|
82
|
-
*
|
|
83
|
-
* Precedence (most explicit wins):
|
|
84
|
-
*
|
|
85
|
-
* 1. `VITEST_AGENT_REPORTER_PROJECT_DIR` — set by the Claude Code plugin
|
|
86
|
-
* loader (`plugins/claude-code/bin/mcp-server.mjs`) to the resolved project root.
|
|
87
|
-
* The loader controls this end-to-end so the value is reliable when
|
|
88
|
-
* set.
|
|
89
|
-
* 2. `CLAUDE_PROJECT_DIR` — exported by Claude Code for hook scripts and
|
|
90
|
-
* (per docs hints) MCP server subprocesses. Used when the loader is
|
|
91
|
-
* bypassed (e.g. someone wires the MCP binary up manually).
|
|
92
|
-
* 3. `process.cwd()` — fall-through for direct invocation outside Claude
|
|
93
|
-
* Code, where the user is presumably running from their project root.
|
|
94
|
-
*/
|
|
95
|
-
function resolveProjectDir() {
|
|
96
|
-
return process.env.VITEST_AGENT_REPORTER_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Optional first positional argument: an initial Claude Code chat UUID
|
|
100
|
-
* (the host's `chatId`) to seed the MCP server's session association.
|
|
6
|
+
* MCP server entry point for vitest-agent. Thin shim over `main.ts` — the
|
|
7
|
+
* assembled program that owns the process.
|
|
101
8
|
*
|
|
102
|
-
*
|
|
103
|
-
* via Claude Code variable substitution if such a variable exists for
|
|
104
|
-
* sessions (the documented substitutions are `${CLAUDE_PLUGIN_ROOT}` and
|
|
105
|
-
* `${CLAUDE_PLUGIN_DATA}`; testing whether `${CLAUDE_SESSION_ID}` or a
|
|
106
|
-
* similar name is honored in `mcpServers.args` is part of the reason
|
|
107
|
-
* this seed path exists). When the seed is empty the agent is expected
|
|
108
|
-
* to recover the chat id at boot. The legacy `set_current_session_id`
|
|
109
|
-
* MCP tool was removed in Phase 3.
|
|
9
|
+
* @packageDocumentation
|
|
110
10
|
*/
|
|
111
|
-
|
|
112
|
-
const argv = process.argv[2];
|
|
113
|
-
if (argv === void 0) return null;
|
|
114
|
-
const trimmed = argv.trim();
|
|
115
|
-
if (trimmed.length === 0) return null;
|
|
116
|
-
if (trimmed.startsWith("${") && trimmed.endsWith("}")) return null;
|
|
117
|
-
return trimmed;
|
|
118
|
-
}
|
|
119
|
-
async function main() {
|
|
120
|
-
const projectDir = resolveProjectDir();
|
|
121
|
-
const initialSessionId = resolveInitialSessionId();
|
|
122
|
-
const dbPath = await Effect.runPromise(resolveDataPath(projectDir).pipe(Effect.provide(PathResolutionLive(projectDir)), Effect.provide(NodeServices.layer)));
|
|
123
|
-
const logLevel = resolveLogLevel();
|
|
124
|
-
const logFile = resolveLogFile();
|
|
125
|
-
const runtime = ManagedRuntime.make(McpLive(dbPath, logLevel, logFile));
|
|
126
|
-
const recoveredContext = sessionContextFromEnv(process.env);
|
|
127
|
-
const ctx = {
|
|
128
|
-
runtime,
|
|
129
|
-
cwd: projectDir,
|
|
130
|
-
currentSessionId: createCurrentSessionIdRef(initialSessionId ?? recoveredContext?.chatId ?? null),
|
|
131
|
-
sessionContext: createSessionContextRef(recoveredContext, () => recoverSessionContextFromSessionEnv({ projectDir }))
|
|
132
|
-
};
|
|
133
|
-
const chatIdResolved = initialSessionId ?? recoveredContext?.chatId ?? null;
|
|
134
|
-
console.error("[vitest-agent-mcp] Starting...");
|
|
135
|
-
console.error(`[vitest-agent-mcp] Project: ${projectDir}`);
|
|
136
|
-
console.error(`[vitest-agent-mcp] Database: ${dbPath}`);
|
|
137
|
-
console.error(`[vitest-agent-mcp] Initial chat id: ${chatIdResolved !== null ? "(set)" : "(none — SessionStart hook had not written CLAUDE_ENV_FILE yet)"}`);
|
|
138
|
-
if (recoveredContext !== null) console.error("[vitest-agent-mcp] Recovered session context: agent=(set) conversation=(set)");
|
|
139
|
-
await startMcpServer(ctx);
|
|
140
|
-
transportConnected = true;
|
|
141
|
-
scheduleTestCrashInjection();
|
|
142
|
-
}
|
|
143
|
-
main().catch((err) => {
|
|
144
|
-
process.stderr.write(`vitest-agent-mcp: ${safeFormatFatalError(err)}\n`);
|
|
145
|
-
process.exit(1);
|
|
146
|
-
});
|
|
11
|
+
main();
|
|
147
12
|
|
|
148
13
|
//#endregion
|
|
149
14
|
export { };
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import { middleware, publicProcedure } from "../context.js";
|
|
2
1
|
import { Effect, Option } from "effect";
|
|
3
|
-
import { DataReader, DataStore } from "@vitest-agent/
|
|
2
|
+
import { DataReader, DataStore } from "@vitest-agent/engine";
|
|
4
3
|
|
|
5
|
-
//#region src/
|
|
4
|
+
//#region src/idempotency.ts
|
|
6
5
|
/**
|
|
7
|
-
* Registered idempotency specs for mutation
|
|
6
|
+
* Registered idempotency specs for mutation tools.
|
|
8
7
|
*
|
|
9
8
|
* `hypothesis validate` is covered (key: `validate:${id}:${outcome}`).
|
|
10
9
|
* `hypothesis record` is deliberately not covered — see the note in the
|
|
11
10
|
* hypothesis spec below.
|
|
12
11
|
*
|
|
13
12
|
* Add an entry here whenever a new idempotent mutation is introduced.
|
|
13
|
+
*
|
|
14
|
+
* @public
|
|
14
15
|
*/
|
|
15
16
|
const idempotencyKeys = [
|
|
16
17
|
{
|
|
@@ -68,61 +69,59 @@ const idempotencyKeys = [
|
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
];
|
|
71
|
-
/** Lookup table keyed by
|
|
72
|
+
/** Lookup table keyed by tool name for O(1) spec retrieval. */
|
|
72
73
|
const keySpecByPath = new Map(idempotencyKeys.map((s) => [s.procedurePath, s]));
|
|
74
|
+
/** Merges the `_idempotentReplay` marker into an object payload; passes any other shape through unchanged. */
|
|
75
|
+
const withReplayMarker = (parsed) => parsed !== null && typeof parsed === "object" ? {
|
|
76
|
+
...parsed,
|
|
77
|
+
_idempotentReplay: true
|
|
78
|
+
} : parsed;
|
|
73
79
|
/**
|
|
74
|
-
*
|
|
80
|
+
* Wraps `handler` with idempotent-response caching keyed on `path`.
|
|
81
|
+
*
|
|
82
|
+
* Semantics (unchanged from the retired tRPC middleware):
|
|
75
83
|
*
|
|
76
|
-
*
|
|
77
|
-
* `
|
|
84
|
+
* 1. Look up the `IdempotencyKeySpec` registered for `path`, and
|
|
85
|
+
* derive a key from `params` (already decoded — strict registration
|
|
86
|
+
* guarantees no key was stripped). No registered spec, or a `null`
|
|
87
|
+
* key, runs `handler` untouched with nothing cached.
|
|
88
|
+
* 2. Cache HIT — `DataReader.findIdempotentResponse` returns the stored
|
|
89
|
+
* JSON. A row that parses falls through to step 2a; a row that fails
|
|
90
|
+
* to parse (corrupt or truncated) is treated as step 3, a cache MISS —
|
|
91
|
+
* it is not a reason to fail the call.
|
|
92
|
+
* 2a. The parsed value is merged with `_idempotentReplay: true` for an
|
|
93
|
+
* object payload (a non-object payload passes through unchanged).
|
|
94
|
+
* `handler` does not run.
|
|
95
|
+
* 3. Cache MISS — run `handler`, then persist the result via
|
|
96
|
+
* `DataStore.recordIdempotentResponse` best-effort: a persistence
|
|
97
|
+
* failure (or a cache-lookup failure) never surfaces to the caller, it
|
|
98
|
+
* is swallowed so a transient DB failure doesn't turn into a tool
|
|
99
|
+
* error.
|
|
78
100
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* - Cache MISS → call `next()` to run the handler, then persist the result
|
|
83
|
-
* via `DataStore.recordIdempotentResponse`. Persistence errors are
|
|
84
|
-
* swallowed (best-effort) so a transient DB failure doesn't surface to
|
|
85
|
-
* the caller as a tool error.
|
|
101
|
+
* The combinator reads no ambient state — `path` and `params` are its
|
|
102
|
+
* only inputs beyond the `DataReader` / `DataStore` services it adds to
|
|
103
|
+
* `handler`'s requirements.
|
|
86
104
|
*
|
|
87
|
-
*
|
|
88
|
-
* `null`, pass straight through to `next()` without any caching.
|
|
105
|
+
* @public
|
|
89
106
|
*/
|
|
90
|
-
const
|
|
91
|
-
const { ctx, path, type, getRawInput, next } = opts;
|
|
92
|
-
if (type !== "mutation") return next();
|
|
107
|
+
const withIdempotency = (path, handler) => (params) => Effect.gen(function* () {
|
|
93
108
|
const spec = keySpecByPath.get(path);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
if (key === null) return next();
|
|
98
|
-
const cached = await ctx.runtime.runPromise(Effect.gen(function* () {
|
|
99
|
-
return yield* (yield* DataReader).findIdempotentResponse(path, key);
|
|
100
|
-
}));
|
|
109
|
+
const key = spec === void 0 ? null : spec.deriveKey(params);
|
|
110
|
+
if (key === null) return yield* handler(params);
|
|
111
|
+
const cached = yield* (yield* DataReader).findIdempotentResponse(path, key).pipe(Effect.orElseSucceed(() => Option.none()));
|
|
101
112
|
if (Option.isSome(cached)) {
|
|
102
|
-
const parsed = JSON.parse(cached.value);
|
|
103
|
-
return
|
|
104
|
-
ok: true,
|
|
105
|
-
data: parsed !== null && typeof parsed === "object" ? {
|
|
106
|
-
...parsed,
|
|
107
|
-
_idempotentReplay: true
|
|
108
|
-
} : parsed,
|
|
109
|
-
marker: "middlewareMarker",
|
|
110
|
-
ctx
|
|
111
|
-
};
|
|
113
|
+
const parsed = yield* Effect.try(() => JSON.parse(cached.value)).pipe(Effect.map(Option.some), Effect.orElseSucceed(() => Option.none()));
|
|
114
|
+
if (Option.isSome(parsed)) return withReplayMarker(parsed.value);
|
|
112
115
|
}
|
|
113
|
-
const result =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
});
|
|
121
|
-
}).pipe(Effect.orElseSucceed(() => void 0)));
|
|
116
|
+
const result = yield* handler(params);
|
|
117
|
+
yield* (yield* DataStore).recordIdempotentResponse({
|
|
118
|
+
procedurePath: path,
|
|
119
|
+
key,
|
|
120
|
+
resultJson: JSON.stringify(result),
|
|
121
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
122
|
+
}).pipe(Effect.orElseSucceed(() => void 0));
|
|
122
123
|
return result;
|
|
123
124
|
});
|
|
124
|
-
/** Drop-in replacement for `publicProcedure` on idempotent mutations. */
|
|
125
|
-
const idempotentProcedure = publicProcedure.use(idempotent);
|
|
126
125
|
|
|
127
126
|
//#endregion
|
|
128
|
-
export { idempotencyKeys,
|
|
127
|
+
export { idempotencyKeys, withIdempotency };
|