config Service

The config service provides centralized configuration management for Pinstripe applications. It loads and normalizes configuration from pinstripe.config.js files, providing defaults and validation for database, mail, server, and custom configuration options.

Interface

The config service returns a promise that resolves to a configuration object with the following structure:

{
    database: DatabaseConfig,
    mail: MailConfig,
    server: ServerConfig,
    // ... any custom configuration properties
}

DatabaseConfig

{
    adapter: 'sqlite' | 'mysql',
    
    // For SQLite (default)
    filename: string, // absolute path to database file
    
    // For MySQL
    host: string,
    user: string,
    password: string,
    database: string
}

MailConfig

{
    adapter: 'dummy' | 'smtp',
    defaults: object,
    
    // For SMTP adapter
    host: string,
    port: number,
    secure: boolean,
    auth: {
        user: string,
        pass: string
    }
}

ServerConfig

{
    limits: {
        bodySize: number,         // Default: 100MB
        rawBodySize: number,      // Default: 1MB
        fieldNameSize: number,    // Default: 100
        fieldSize: number,        // Default: 1MB
        fields: number,          // Default: Infinity
        fileSize: number,        // Default: 10MB
        files: number,           // Default: Infinity
        parts: number,           // Default: Infinity
        headerPairs: number,     // Default: 2000
        imageWidth: number,      // Default: 1024
        imageHeight: number      // Default: 1024
    }
}

Usage

Basic Configuration Access

// Access the full configuration
const config = await this.config;

// Access specific configuration sections
const databaseConfig = await this.config.database;
const mailConfig = await this.config.mail;
const serverConfig = await this.config.server;

Database Configuration

// Get database configuration for creating a client
export default {
    create() {
        return this.defer(async () =>
            Client.new(await this.config.database)
        );
    }
};

// Extract database config for command operations
const { adapter, ...databaseConfig } = await this.config.database;

if (adapter === 'mysql') {
    // Use MySQL-specific configuration
    const { host, user, password, database } = databaseConfig;
} else {
    // Use SQLite-specific configuration
    const { filename } = databaseConfig;
}

Mail Configuration

// Configure mail service
export default {
    create() {
        return this.defer(async () => {
            const { mail: mailConfig = {} } = await this.config;
            const { adapter = 'dummy', ...adapterConfig } = mailConfig;
            
            if (adapter === 'dummy') return this.createDummy(adapterConfig);
            if (adapter === 'smtp') return this.createSmtp(adapterConfig);
            
            throw new Error(`No such mail adapter '${adapter}' exists.`);
        });
    }
};

Server Configuration

// Access server limits for request processing
export default {
    async handleRequest(request, baseUrl) {
        const limits = await this.config.server.limits;
        const params = await this.extractParams(request, baseUrl, limits);
        // ... process request
    }
};

Custom Configuration

// Access custom configuration properties
const customConfig = await this.config;
const theme = customConfig.theme || {};
const salt = customConfig.salt;
const featureFlags = customConfig.featureFlags;

// Use in feature flag service
let { featureFlags = defaultCallback } = await this.config;
if (typeof featureFlags === 'function') {
    featureFlags = await featureFlags.call(this);
}

Multi-tenant Configuration

// Access tenant-specific configuration
export default {
    create() {
        return this.defer(async () => {
            // Get tenant configuration callback
            let { tenant = defaultCallback } = await this.config;
            
            // Resolve tenant dynamically
            if (typeof tenant === 'function') {
                tenant = await tenant.call(this);
            }
            
            // Use tenant configuration
            return tenant;
        });
    }
};

Configuration File Structure

The config service loads configuration from pinstripe.config.js in the project root:

Basic Configuration

// pinstripe.config.js
export default {
    database: {
        adapter: 'sqlite',
        filename: 'development.db'
    },
    mail: {
        adapter: 'dummy'
    },
    salt: 'your-application-salt'
};

Environment-based Configuration

// pinstripe.config.js
const environment = process.env.NODE_ENV || 'development';

let database;
if (environment === 'production') {
    database = {
        adapter: 'mysql',
        host: 'localhost',
        user: 'root',
        password: process.env.DB_PASSWORD,
        database: `myapp_${environment}`
    };
} else {
    database = {
        adapter: 'sqlite',
        filename: `${environment}.db`
    };
}

let mail;
if (environment === 'production') {
    mail = {
        adapter: 'smtp',
        host: "smtp.example.com",
        port: 465,
        secure: true,
        auth: {
            user: process.env.SMTP_USER,
            pass: process.env.SMTP_PASS
        }
    };
} else {
    mail = {
        adapter: 'dummy'
    };
}

export default {
    database,
    mail,
    salt: process.env.APP_SALT || 'default-salt'
};

Advanced Configuration with Functions

// pinstripe.config.js
export default {
    database: {
        adapter: process.env.DATABASE_ADAPTER || 'sqlite'
    },
    
    // Dynamic feature flags
    featureFlags() {
        const headers = this.initialParams._headers;
        const flags = headers['x-feature-flags'] || '';
        return flags.split(/\s+/)
                   .filter(name => !!name)
                   .reduce((out, name) => ({ ...out, [name]: true }), {});
    },
    
    // Dynamic tenant resolution
    tenant() {
        const headers = this.initialParams._headers;
        const hostname = this.initialParams._url.hostname;
        const host = (headers['host'] || hostname)
                     .replace(/:\d+$/, '')
                     .toLowerCase();
        return this.database.tenants.where({ host }).first();
    },
    
    // Custom theme configuration
    theme: {
        primaryColor: '#007bff',
        fontFamily: 'Arial, sans-serif'
    }
};

Default Values

The config service provides sensible defaults for all configuration sections:

  • Database: SQLite adapter with ${environment}.db filename
  • Mail: Dummy adapter (logs to console)
  • Server: Standard limits for request processing
  • Custom properties: No defaults (undefined unless specified)

Normalization

Configuration values are automatically normalized:

  • Database paths: Relative filenames become absolute paths
  • Database names: Auto-generated from project name and environment for MySQL
  • Server limits: Missing limit values get sensible defaults
  • Mail defaults: Empty defaults object if not specified

Caching

Configuration is loaded once per application lifecycle and cached using the defer mechanism. Subsequent calls to this.config return the same promise, ensuring consistent configuration across services.