cube-js/cube · error · Error

CreateOptions.driverFactory function must return either Base

Error message

CreateOptions.driverFactory function must return either BaseDriver or DriverConfig.

What it means

assertDriverFactoryResult remembers whether your driverFactory has returned a BaseDriver instance or a plain DriverConfig ({ type, ... }) object. If the factory already returned a DriverConfig and later returns a BaseDriver instance (or vice versa), it throws, because mixing return shapes across calls (per data source / per pre-aggregation context) is unsupported. The check enforces a consistent factory contract.

Source

Thrown at packages/cubejs-server-core/src/core/OptsHandler.ts:101

      throw new Error(
        'Either CUBEJS_DB_TYPE or CreateOptions.driverFactory must be specified'
      );
    }

    return validated;
  }

  /**
   * Assert value returned from the driver factory.
   */
  private assertDriverFactoryResult(
    val: DriverConfig | BaseDriver,
  ) {
    if (isDriver(val)) {
      if (!this.driverFactoryType) {
        this.driverFactoryType = 'BaseDriver';
      } else if (this.driverFactoryType !== 'BaseDriver') {
        throw new Error(
          'CreateOptions.driverFactory function must return either ' +
          'BaseDriver or DriverConfig.'
        );
      }
      return <BaseDriver>val;
    } else if (
      val && (<DriverConfig>val).type && typeof (<DriverConfig>val).type === 'string'
    ) {
      if (!this.driverFactoryType) {
        this.driverFactoryType = 'DriverConfig';
      } else if (this.driverFactoryType !== 'DriverConfig') {
        throw new Error(
          'CreateOptions.driverFactory function must return either ' +
          'BaseDriver or DriverConfig.'
        );
      }
      return <DriverConfig>val;
    } else {

View on GitHub (pinned to 7d981676b3)

Solutions

  1. Make the driverFactory always return the same shape — prefer DriverConfig objects ({ type, ... }) everywhere
  2. Or always return driver instances everywhere, but never mix
  3. Centralize per-data-source config as data (objects) rather than instantiated drivers
  4. Test all data sources / contexts so every code path of the factory is exercised

Example fix

// before
driverFactory: async (ctx) => ctx.dataSource === 'a' ? new PostgresDriver(opts) : { type: 'mysql' }
// after
driverFactory: async (ctx) => ctx.dataSource === 'a' ? { type: 'postgres', ...opts } : { type: 'mysql' }
Defensive patterns

Strategy: type-guard

Validate before calling

import { isDriver } from '@cubejs-backend/shared'; // or a duck-type check
function assertConsistentFactoryShape(factory, ctxs) {
  const shapes = ctxs.map(c => typeof factory(c) === 'object' && 'type' in factory(c) ? 'DriverConfig' : 'BaseDriver');
  if (new Set(shapes).size > 1) throw new Error('driverFactory must consistently return one shape');
}

Type guard

function isDriverConfig(val) {
  return val != null && typeof val === 'object' && !('testConnection' in val) && typeof val.type === 'string';
}

Try / catch

try {
  await driverFactory(ctx);
} catch (e) {
  if (e.message.includes('must return either BaseDriver or DriverConfig')) {
    console.error('driverFactory returned mixed shapes across calls; unify to DriverConfig.');
  }
  throw e;
}

Prevention

When it happens

Trigger: A driverFactory that conditionally returns an instantiated driver for one dataSource and a DriverConfig object for another, e.g. driverFactory: async (ctx) => ctx.dataSource === 'ds1' ? new PostgresDriver({...}) : { type: 'mysql' }.

Common situations: Multi-data-source setups assembled from per-source legacy configs; a factory that falls back to `new SomeDriver()` on error but returns configs normally; refactoring part of a codebase from driver instances to DriverConfig.

Related errors


AI-assisted analysis of cube-js/cube@7d981676b3 (2026-09-02). Data as JSON: /api/errors/8d1016cf04083737. Report an issue: GitHub.