Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/backtest-kit/backtest-kit-minio-s3-docker/llms.txt

Use this file to discover all available pages before exploring further.

The src/config/ directory is the single place where environment variables are translated into live service clients. It exports four modules: params.ts reads raw process.env values with fallback defaults, minio.ts wraps those values into a minio.Client factory, redis.ts wraps them into a singleshot ioredis connection, and setup.ts wires the backtest-kit persist adapters to the IoC container’s data services. Nothing in the rest of the codebase reaches directly into process.env; all configuration flows through these four files.

params.ts — environment variable constants

params.ts reads every environment variable the package needs and exports them as typed constants. A local parseInt override at the top of the file ensures numeric variables are parsed as numbers even when they arrive as strings from the environment.
declare function parseInt(value: unknown): number;

export const CC_REDIS_HOST = process.env.CC_REDIS_HOST || "127.0.0.1";
export const CC_REDIS_PORT = parseInt(process.env.CC_REDIS_PORT) || 6379;
export const CC_REDIS_USER = process.env.CC_REDIS_USER || "default";
export const CC_REDIS_PASSWORD = process.env.CC_REDIS_PASSWORD || "mysecurepassword";

export const CC_MONGO_CONNECTION_STRING = process.env.CC_MONGO_CONNECTION_STRING || "mongodb://localhost:27017/backtest-pro?wtimeoutMS=15000";

export const CC_MINIO_ENDPOINT = process.env.CC_MINIO_ENDPOINT || "localhost";
export const CC_MINIO_PORT = parseInt(process.env.CC_MINIO_PORT) || 9000;
export const CC_MINIO_ACCESSKEY = process.env.CC_MINIO_ACCESSKEY || "minioadmin";
export const CC_MINIO_SECRETKEY = process.env.CC_MINIO_SECRETKEY || "minioadmin";
All nine constants are re-exported individually so that minio.ts, redis.ts, and any other consumers can import only what they need via named imports, keeping the dependency graph explicit.

getMinio() — MinIO client factory

minio.ts exports a plain function getMinio() that constructs and returns a new minio.Client instance on every call. SSL is always disabled because the package is designed to operate inside a private Docker network where TLS termination is handled externally.
import { Client } from "minio";

import {
  CC_MINIO_ACCESSKEY,
  CC_MINIO_ENDPOINT,
  CC_MINIO_PORT,
  CC_MINIO_SECRETKEY,
} from "./params";

export const getMinio = () => {
  return new Client({
    endPoint: CC_MINIO_ENDPOINT,
    port: CC_MINIO_PORT,
    accessKey: CC_MINIO_ACCESSKEY,
    secretKey: CC_MINIO_SECRETKEY,
    useSSL: false,
  });
};
getMinio() is intentionally not memoized here. Client-per-call is safe because the memoization is performed one layer up — MinioService.getClient() wraps getMinio() with memoize keyed on bucketName, so the actual number of live minio.Client instances is bounded by the number of distinct buckets, not the number of callers.

getRedis() — singleshot Redis connection

redis.ts exports a singleshot factory for the ioredis Redis instance. The singleshot utility from functools-kit ensures the function body executes only once no matter how many times getRedis() is called, and every caller receives the same Redis object.
import { singleshot } from "functools-kit";
import { Redis } from "ioredis";
import {
  CC_REDIS_HOST,
  CC_REDIS_PASSWORD,
  CC_REDIS_PORT,
  CC_REDIS_USER,
} from "./params";

export const getRedis = singleshot(() => {
  const redis = new Redis({
    host: CC_REDIS_HOST,
    port: CC_REDIS_PORT,
    username: CC_REDIS_USER,
    password: CC_REDIS_PASSWORD,
  });

  setInterval(async () => {
    await redis.ping();
  }, 30000);

  process.on("SIGINT", async () => {
    await redis.disconnect(false);
  });

  return redis;
});
Two side-effects are registered alongside the connection:
  • Keep-alive ping — a setInterval fires every 30 seconds and sends a PING command to prevent the connection from being closed by idle-timeout policies on the Redis server or any intermediate proxy.
  • SIGINT cleanup — a process.on("SIGINT") handler disconnects cleanly when the process receives Ctrl-C or a Docker stop signal, flushing any in-flight commands before exit.
getRedis is a singleshot — it creates exactly one Redis instance for the lifetime of the process, regardless of how many callers invoke it. BaseMap, RedisService, and all three connection services share this single connection. There is no connection pool.
The setInterval ping registers a recurring timer that keeps the Node.js event loop alive indefinitely. This is intentional for long-running server processes but means that calling getRedis() in a short-lived script will prevent the script from exiting naturally. Always pair with the registered SIGINT handler or call redis.disconnect(false) explicitly when the process is done.

MinioService — memoized client per bucket

MinioService wraps getMinio() with bucket-level memoization using memoize from functools-kit. The first time getClient(bucketName) is called for a given bucket name, it creates a fresh minio.Client, checks whether the bucket exists, and creates it with makeBucket if it does not. Subsequent calls for the same bucket name skip all I/O and return the cached client immediately.
import { memoize } from "functools-kit";
import { getMinio } from "../../../config/minio";

export class MinioService {
  public getClient = memoize(
    (bucketName) => bucketName,
    async (bucketName: string) => {
      const minioClient = getMinio();
      if (await minioClient.bucketExists(bucketName)) {
        return minioClient;
      }
      await minioClient.makeBucket(bucketName);
      return minioClient;
    }
  );
}
The memoize key function is (bucketName) => bucketName, so the cache is keyed on the bucket name string. Each data service that uses a different bucket (e.g. backtest-kit) gets one client; all services sharing the same bucket reuse the same cached instance.

RedisService — waitForInit()

RedisService exposes a single waitForInit() method that acts as a readiness gate for the Redis connection. It is itself wrapped in singleshot, so only the first caller awaits the connection event — subsequent callers return instantly. If Redis does not become ready within 15 seconds (CONNECTION_TIMEOUT = 15_000), the singleshot is cleared and an error is thrown, allowing the caller to retry.
export class RedisService {
  readonly loggerService = inject<LoggerService>(TYPES.loggerService);

  public waitForInit = singleshot(async () => {
    this.loggerService.log("redisService waitForInit");
    const redis = await getRedis();
    if (redis.status === "ready") {
      return redis;
    }
    const result = await Promise.race([
      waitForConnect(redis, this),
      sleep(CONNECTION_TIMEOUT).then(() => TIMEOUT_SYMBOL),
    ]);
    if (result === TIMEOUT_SYMBOL) {
      this.waitForInit.clear();
      throw new Error("Redis connection timeout");
    }
    return redis;
  });
}
setup.ts calls waitForInfra() (a singleshot that delegates to ioc.redisService.waitForInit()) inside every persist adapter’s waitForInit(initial) hook, but only when initial === true. This means the Redis readiness check runs once per process — on the first adapter instantiation — and all subsequent adapters skip it.

Build docs developers (and LLMs) love