Repository: storybookjs/storybook
File: code/core/src/common/utils/file-cache.ts (TypeScript)

Code before:

    const fileName = this.generateHash(name);
    const fileData = this.parseSetData(name, data, opts);
    const fileOptions = { encoding: opts.encoding || 'utf8' };
    const SAFE_WRITE_ATTEMPTS = 5;

    for (let attempt = 1; attempt <= SAFE_WRITE_ATTEMPTS; attempt++) {
      try {
        await writeFile(fileName, fileData, fileOptions);
        return;
      } catch (err) {
        // If an EBUSY error occurs on any attempt except
        // the last, then wait for a bit and try again.
        // https://github.com/storybookjs/storybook/issues/23131
        if (
          attempt < SAFE_WRITE_ATTEMPTS &&
          err instanceof Error &&
          'code' in err &&
          err.code === 'EBUSY'
        ) {
          await new Promise((resolve) => setTimeout(resolve, 100));
        } else {
          throw err;
        }
      }
    }
  }

  public setSync<T>(name: string, data: T, orgOpts: CacheSetOptions | number = {}): void {
    const opts: CacheSetOptions = typeof orgOpts === 'number' ? { ttl: orgOpts } : orgOpts;
    mkdirSync(this.cache_dir, { recursive: true });
    writeFileSync(this.generateHash(name), this.parseSetData(name, data, opts), {
      encoding: opts.encoding || 'utf8',
    });
  }

  public async setMany(items: CacheItem[], options?: CacheSetOptions): Promise<void> {
    await Promise.all(items.map((item) => this.set(item.key, item.content ?? item.value, options)));
  }

  public setManySync(items: CacheItem[], options?: CacheSetOptions): void {
    items.forEach((item) => this.setSync(item.key, item.content ?? item.value, options));
  }

  public async remove(name: string): Promise<void> {
    await rm(this.generateHash(name), { force: true });
  }

  public removeSync(name: string): void {
    rmSync(this.generateHash(name), { force: true });
  }

Review comment (performance):
This retry a write mechanism, seems something that belongs in a common helper function, not implemented in the file-cache mechanism directly.

Do you agree?

Then we can make the unit test purely test the retry-mechanism.

Code after:
    const opts: CacheSetOptions = typeof orgOpts === 'number' ? { ttl: orgOpts } : orgOpts;
    await mkdir(this.cache_dir, { recursive: true });
    await writeFileWithRetry(this.generateHash(name), this.parseSetData(name, data, opts), {
      encoding: opts.encoding || 'utf8',
    });
  }

  public setSync<T>(name: string, data: T, orgOpts: CacheSetOptions | number = {}): void {
    const opts: CacheSetOptions = typeof orgOpts === 'number' ? { ttl: orgOpts } : orgOpts;
    mkdirSync(this.cache_dir, { recursive: true });
    writeFileSync(this.generateHash(name), this.parseSetData(name, data, opts), {
      encoding: opts.encoding || 'utf8',
    });
  }

  public async setMany(items: CacheItem[], options?: CacheSetOptions): Promise<void> {
    await Promise.all(items.map((item) => this.set(item.key, item.content ?? item.value, options)));
  }

  public setManySync(items: CacheItem[], options?: CacheSetOptions): void {
    items.forEach((item) => this.setSync(item.key, item.content ?? item.value, options));
  }

  public async remove(name: string): Promise<void> {
    await rm(this.generateHash(name), { force: true });
  }

  public removeSync(name: string): void {
    rmSync(this.generateHash(name), { force: true });
  }

  public async clear(): Promise<void> {
    const files = await readdir(this.cache_dir);
    await Promise.all(
      files
        .filter((f) => f.startsWith(this.prefix))
        .map((f) => rm(join(this.cache_dir, f), { force: true }))
    );
  }

  public clearSync(): void {
    readdirSync(this.cache_dir)
      .filter((f) => f.startsWith(this.prefix))
      .forEach((f) => rmSync(join(this.cache_dir, f), { force: true }));
  }

  public async getAll(): Promise<CacheItem[]> {
    const now = Date.now();
    const files = await readdir(this.cache_dir);
    const items = await Promise.all(
      files