Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/amanvarshney01/create-better-t-stack/llms.txt

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

The programmatic API uses typed errors with the Result type for type-safe error handling.

Error Types

All errors extend TaggedError from better-result and include a tag field for type discrimination.

CreateError

Union type for errors returned by create() and createVirtual():
type CreateError = UserCancelledError | CLIError | ProjectCreationError
Source: ~/workspace/source/apps/cli/src/index.ts:164

Error Classes

UserCancelledError

Thrown when the user cancels an operation.
class UserCancelledError extends TaggedError("UserCancelledError") {
  tag: "UserCancelledError";
  message: string;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:13 Example:
const result = await create("my-app", { /* ... */ });

if (result.isErr() && result.error.tag === "UserCancelledError") {
  console.log("Operation cancelled by user");
}

CLIError

General CLI error for validation failures, invalid flags, etc.
class CLIError extends TaggedError("CLIError") {
  tag: "CLIError";
  message: string;
  cause?: unknown;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:24 Example:
const result = await create("my-app", {
  database: "postgres",
  orm: "none", // Invalid: postgres requires ORM
});

if (result.isErr() && result.error.tag === "CLIError") {
  console.error(`Configuration error: ${result.error.message}`);
}

ProjectCreationError

Error during project scaffolding.
class ProjectCreationError extends TaggedError("ProjectCreationError") {
  tag: "ProjectCreationError";
  phase: string;    // Which phase failed (e.g., "templates", "dependencies")
  message: string;
  cause?: unknown;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:72 Example:
const result = await create("my-app", { /* ... */ });

if (result.isErr() && result.error.tag === "ProjectCreationError") {
  console.error(`Failed during ${result.error.phase}: ${result.error.message}`);
  if (result.error.cause) {
    console.error("Caused by:", result.error.cause);
  }
}

ValidationError

Validation error for config/flag validation failures.
class ValidationError extends TaggedError("ValidationError") {
  tag: "ValidationError";
  field?: string;
  value?: unknown;
  message: string;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:31 Example:
import { ValidationError } from "create-better-t-stack";

if (error instanceof ValidationError) {
  console.error(`Invalid ${error.field}: ${error.message}`);
  console.error(`Received value:`, error.value);
}

CompatibilityError

Error for incompatible option combinations.
class CompatibilityError extends TaggedError("CompatibilityError") {
  tag: "CompatibilityError";
  options: string[];  // Conflicting options
  message: string;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:43 Example:
import { CompatibilityError } from "create-better-t-stack";

if (error instanceof CompatibilityError) {
  console.error(`Incompatible options: ${error.options.join(", ")}`);
  console.error(error.message);
}

DirectoryConflictError

Error when target directory exists and is not empty.
class DirectoryConflictError extends TaggedError("DirectoryConflictError") {
  tag: "DirectoryConflictError";
  directory: string;
  message: string;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:55 Example:
const result = await create("existing-dir", {
  directoryConflict: "error", // Default
});

if (result.isErr() && result.error.tag === "DirectoryConflictError") {
  console.error(`Directory exists: ${result.error.directory}`);
  console.error("Use directoryConflict: 'overwrite', 'merge', or 'increment'");
}

DatabaseSetupError

Error during database configuration.
class DatabaseSetupError extends TaggedError("DatabaseSetupError") {
  tag: "DatabaseSetupError";
  provider: string;  // Database provider (e.g., "postgres")
  message: string;
  cause?: unknown;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:83 Example:
import { DatabaseSetupError } from "create-better-t-stack";

if (error instanceof DatabaseSetupError) {
  console.error(`Failed to setup ${error.provider}: ${error.message}`);
}

AddonSetupError

Error during addon configuration.
class AddonSetupError extends TaggedError("AddonSetupError") {
  tag: "AddonSetupError";
  addon: string;     // Addon name (e.g., "biome")
  message: string;
  cause?: unknown;
}
Source: ~/workspace/source/apps/cli/src/utils/errors.ts:96 Example:
import { AddonSetupError } from "create-better-t-stack";

if (error instanceof AddonSetupError) {
  console.error(`Failed to setup ${error.addon}: ${error.message}`);
}

GeneratorError

Error during template generation (used by createVirtual()).
class GeneratorError extends TaggedError("GeneratorError") {
  tag: "GeneratorError";
  message: string;
  phase?: string;    // Generation phase that failed
  cause?: unknown;
}
Source: ~/workspace/source/packages/template-generator/src/types.ts:41 Example:
const result = await createVirtual({ /* ... */ });

if (result.isErr() && result.error.tag === "GeneratorError") {
  console.error(`Generation failed: ${result.error.message}`);
  if (result.error.phase) {
    console.error(`During phase: ${result.error.phase}`);
  }
}

Error Handling Patterns

Using match()

The recommended pattern for handling errors:
import { create } from "create-better-t-stack";

const result = await create("my-app", {
  frontend: ["tanstack-router"],
  backend: "hono",
});

result.match({
  ok: (data) => {
    console.log(`Success: ${data.projectDirectory}`);
  },
  err: (error) => {
    console.error(`Error: ${error.message}`);
    process.exit(1);
  },
});

Using isErr() / isOk()

import { create } from "create-better-t-stack";

const result = await create("my-app", { /* ... */ });

if (result.isErr()) {
  console.error(result.error.message);
  process.exit(1);
}

// TypeScript knows result.value is available here
console.log(result.value.projectDirectory);

Type Discrimination with Tag

import { create } from "create-better-t-stack";

const result = await create("my-app", { /* ... */ });

if (result.isErr()) {
  const error = result.error;
  
  switch (error.tag) {
    case "UserCancelledError":
      console.log("Operation cancelled");
      process.exit(0);
      
    case "DirectoryConflictError":
      console.error(`Directory exists: ${error.directory}`);
      console.error("Use --directory-conflict to handle this");
      process.exit(1);
      
    case "ValidationError":
      console.error(`Invalid ${error.field}: ${error.message}`);
      process.exit(1);
      
    case "ProjectCreationError":
      console.error(`Failed during ${error.phase}: ${error.message}`);
      process.exit(1);
      
    default:
      console.error(error.message);
      process.exit(1);
  }
}

Using instanceof

import {
  create,
  UserCancelledError,
  DirectoryConflictError,
  ValidationError,
} from "create-better-t-stack";

const result = await create("my-app", { /* ... */ });

if (result.isErr()) {
  const error = result.error;
  
  if (error instanceof UserCancelledError) {
    console.log("Cancelled");
  } else if (error instanceof DirectoryConflictError) {
    console.error(`Directory conflict: ${error.directory}`);
  } else if (error instanceof ValidationError) {
    console.error(`Validation failed: ${error.field}`);
  } else {
    console.error(error.message);
  }
}

Using unwrapOr()

import { create } from "create-better-t-stack";

const result = await create("my-app", { /* ... */ });

// Get value or null on error
const data = result.unwrapOr(null);

if (data) {
  console.log(data.projectDirectory);
} else {
  console.error("Failed to create project");
}

Retrying on Error

import { create } from "create-better-t-stack";

async function createWithRetry(name: string, options: any, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const result = await create(name, options);
    
    if (result.isOk()) {
      return result;
    }
    
    if (result.error.tag === "UserCancelledError") {
      // Don't retry user cancellations
      return result;
    }
    
    console.error(`Attempt ${i + 1} failed: ${result.error.message}`);
    
    if (i < maxRetries - 1) {
      console.log(`Retrying...`);
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
}

Logging Errors

import { create } from "create-better-t-stack";
import fs from "node:fs";

const result = await create("my-app", { /* ... */ });

if (result.isErr()) {
  const error = result.error;
  
  // Log detailed error information
  const errorLog = {
    timestamp: new Date().toISOString(),
    type: error.tag,
    message: error.message,
    details: {
      ...error,
    },
  };
  
  fs.appendFileSync(
    "error.log",
    JSON.stringify(errorLog, null, 2) + "\n"
  );
  
  console.error(`Error logged. See error.log for details.`);
  process.exit(1);
}

Common Error Scenarios

Directory Already Exists

const result = await create("existing-dir", {
  directoryConflict: "error", // Will throw DirectoryConflictError
});

// Solution: Use a different conflict strategy
const result2 = await create("existing-dir", {
  directoryConflict: "increment", // Creates existing-dir-1, existing-dir-2, etc.
});

Invalid Configuration

const result = await create("my-app", {
  database: "postgres",
  orm: "none", // Invalid: postgres requires an ORM
});

// Will return CLIError or ValidationError

Missing Dependencies

const result = await create("my-app", {
  auth: "better-auth",
  database: "none", // Invalid: better-auth requires a database
});

// Will return CompatibilityError

Best Practices

Always Handle Errors

Never ignore the Result type. Always check isOk() or use match() to handle both success and error cases.

Use Type Discrimination

Use the tag field or instanceof to handle different error types appropriately.

Provide User Feedback

For user-facing applications, provide clear error messages and suggested fixes.

Log Detailed Errors

In production, log the full error object (including cause and phase) for debugging.

Build docs developers (and LLMs) love