function wdm(compiler, options = {}) {
  validate(/** @type {Schema} */ (schema), options, {
    name: "Dev Middleware",
    baseDataPath: "options",
  });

  const { mimeTypes } = options;

  if (mimeTypes) {
    const { types } = mime;

    // mimeTypes from user provided options should take priority
    // over existing, known types
    // @ts-expect-error
    mime.types = { ...types, ...mimeTypes };
  }

  /**
   * @type {WithOptional<Context<RequestInternal, ResponseInternal>, "watching" | "outputFileSystem">}
   */
  const context = {
    state: false,

    stats: undefined,
    callbacks: [],
    options,
    compiler,
    logger: compiler.getInfrastructureLogger("webpack-dev-middleware"),
  };

  setupHooks(context);

  if (options.writeToDisk) {
    setupWriteToDisk(context);
  }

  setupOutputFileSystem(context);

  // Start watching
  if (/** @type {Compiler} */ (context.compiler).watching) {
    context.watching = /** @type {Compiler} */ (context.compiler).watching;
  } else {
    /**
     * @param {Error | null | undefined} error error
     */
    const errorHandler = (error) => {
      if (error) {
        // TODO: improve that in future
        // For example - `writeToDisk` can throw an error and right now it is ends watching.
        // We can improve that and keep watching active, but it is require API on webpack side.
        // Let's implement that in webpack@5 because it is rare case.
        context.logger.error(error);
      }
    };

    if (
      Array.isArray(/** @type {MultiCompiler} */ (context.compiler).compilers)
    ) {
      const compilers = /** @type {MultiCompiler} */ (context.compiler);
      const watchOptions = compilers.compilers.map(
        (childCompiler) => childCompiler.options.watchOptions || {},
      );

      context.watching = compiler.watch(watchOptions, errorHandler);
    } else {
      const oneCompiler = /** @type {Compiler} */ (context.compiler);
      const watchOptions = oneCompiler.options.watchOptions || {};

      context.watching = compiler.watch(watchOptions, errorHandler);
    }
  }

  const filledContext =
    /** @type {FilledContext<RequestInternal, ResponseInternal>} */
    (context);

  const instance =
    /** @type {API<RequestInternal, ResponseInternal>} */
    (middleware(filledContext));

  // API
  instance.getFilenameFromUrl = (url, extra) =>
    getFilenameFromUrl(filledContext, url, extra);

  instance.waitUntilValid = (callback = noop) => {
    ready(filledContext, callback);
  };

  instance.invalidate = (callback = noop) => {
    ready(filledContext, callback);

    filledContext.watching.invalidate();
  };

  instance.close = (callback = noop) => {
    filledContext.watching.close(callback);
  };

  instance.context = filledContext;

  return instance;
}
@template {IncomingMessage} [RequestInternal=IncomingMessage]
@template {ServerResponse} [ResponseInternal=ServerResponse]
@param {Compiler | MultiCompiler} compiler compiler
@param {Options<RequestInternal, ResponseInternal>=} options options
@returns {API<RequestInternal, ResponseInternal>} webpack dev middleware