diff --git a/.changeset/config.json b/.changeset/config.json
index 1bd913bad..21e54fbf7 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -8,4 +8,4 @@
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["@better-t-stack/backend", "web"]
-}
\ No newline at end of file
+}
diff --git a/.changeset/kind-geese-sell.md b/.changeset/kind-geese-sell.md
new file mode 100644
index 000000000..85fbc14d3
--- /dev/null
+++ b/.changeset/kind-geese-sell.md
@@ -0,0 +1,5 @@
+---
+"create-better-t-stack": minor
+---
+
+Add SingleStore Helios database support
diff --git a/apps/cli/README.md b/apps/cli/README.md
index 419039be4..00bcb3f3d 100644
--- a/apps/cli/README.md
+++ b/apps/cli/README.md
@@ -36,9 +36,9 @@ Follow the prompts to configure your project or use the `--yes` flag for default
| **Backend** | • Hono
• Express
• Elysia
• Next.js API routes
• Convex
• Fastify
• None |
| **API Layer** | • tRPC (type-safe APIs)
• oRPC (OpenAPI-compatible type-safe APIs)
• None |
| **Runtime** | • Bun
• Node.js
• Cloudflare Workers
• None |
-| **Database** | • SQLite
• PostgreSQL
• MySQL
• MongoDB
• None |
+| **Database** | • SQLite
• PostgreSQL
• MySQL
• MongoDB
• SingleStore
• None |
| **ORM** | • Drizzle (TypeScript-first)
• Prisma (feature-rich)
• Mongoose (for MongoDB)
• None |
-| **Database Setup** | • Turso (SQLite)
• Cloudflare D1 (SQLite)
• Neon (PostgreSQL)
• Supabase (PostgreSQL)
• Prisma Postgres (via Prisma Accelerate)
• MongoDB Atlas
• None (manual setup) |
+| **Database Setup** | • Turso (SQLite)
• Cloudflare D1 (SQLite)
• Neon (PostgreSQL)
• Supabase (PostgreSQL)
• Prisma Postgres (via Prisma Accelerate)
• MongoDB Atlas
• SingleStore Helios (cloud-hosted SingleStore)
• None (manual setup) |
| **Authentication** | Better-Auth (email/password, with more options coming soon) |
| **Styling** | Tailwind CSS with shadcn/ui components |
| **Addons** | • PWA support
• Tauri (desktop applications)
• Starlight (documentation site)
• Biome (linting and formatting)
• Husky (Git hooks)
• Turborepo (optimized builds) |
@@ -53,7 +53,7 @@ Usage: create-better-t-stack [project-directory] [options]
Options:
-V, --version Output the version number
-y, --yes Use default configuration
- --database Database type (none, sqlite, postgres, mysql, mongodb)
+ --database Database type (none, sqlite, postgres, mysql, mongodb, singlestore)
--orm ORM type (none, drizzle, prisma, mongoose)
--auth Include authentication
--no-auth Exclude authentication
@@ -65,7 +65,7 @@ Options:
--package-manager Package manager (npm, pnpm, bun)
--install Install dependencies
--no-install Skip installing dependencies
- --db-setup Database setup (turso, d1, neon, supabase, prisma-postgres, mongodb-atlas, docker, none)
+ --db-setup Database setup (turso, d1, neon, supabase, prisma-postgres, mongodb-atlas, singlestore-helios, docker, none)
--web-deploy Web deployment (workers, none)
--backend Backend framework (hono, express, elysia, next, convex, fastify, none)
--runtime Runtime (bun, node, workers, none)
@@ -173,6 +173,11 @@ Create a Cloudflare Workers project:
```bash
npx create-better-t-stack my-app --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
+
+Create a SingleStore project with Helios cloud setup:
+
+```bash
+npx create-better-t-stack my-app --backend hono --runtime node --database singlestore --orm drizzle --db-setup singlestore-helios --api trpc
```
Create a minimal API-only project:
@@ -191,6 +196,7 @@ npx create-better-t-stack my-app --frontend none --backend hono --api trpc --dat
- **ORM 'none'**: Can be used when you want to handle database operations manually or use a different ORM.
- **Runtime 'none'**: Only available with Convex backend or when backend is 'none'.
- **Cloudflare Workers runtime**: Only compatible with Hono backend, Drizzle ORM (or no ORM), and SQLite database (with D1 setup). Not compatible with MongoDB.
+- **SingleStore database**: Only compatible with Drizzle ORM. Always uses SingleStore Helios setup (no manual setup option).
- **Addons 'none'**: Skips all addons (PWA, Tauri, Starlight, Biome, Husky, Turborepo).
- **Examples 'none'**: Skips all example implementations (todo, AI chat).
- **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer
diff --git a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts
new file mode 100644
index 000000000..b06e08f2d
--- /dev/null
+++ b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts
@@ -0,0 +1,83 @@
+import path from "node:path";
+import { log } from "@clack/prompts";
+import fs from "fs-extra";
+import pc from "picocolors";
+import type { ProjectConfig } from "../../types";
+import {
+ addEnvVariablesToFile,
+ type EnvVariable,
+} from "../project-generation/env-setup";
+
+type SingleStoreHeliosConfig = {
+ connectionString: string;
+};
+
+async function writeEnvFile(
+ projectDir: string,
+ config?: SingleStoreHeliosConfig,
+) {
+ try {
+ const envPath = path.join(projectDir, "apps/server", ".env");
+ const variables: EnvVariable[] = [
+ {
+ key: "DATABASE_URL",
+ value:
+ config?.connectionString ??
+ "singlestore://username:password@host:port/database?ssl={}",
+ condition: true,
+ },
+ ];
+ await addEnvVariablesToFile(envPath, variables);
+ } catch (_error) {
+ log.error("Failed to update environment configuration");
+ }
+}
+
+function displayManualSetupInstructions() {
+ log.info(`
+${pc.green("SingleStore Helios Manual Setup Instructions:")}
+
+1. Sign up for SingleStore Cloud at:
+ ${pc.blue("https://www.singlestore.com/cloud")}
+
+2. Create a new workspace from the dashboard
+
+3. Get your connection string from the workspace details:
+ Format: ${pc.dim("singlestore://USERNAME:PASSWORD@HOST:PORT/DATABASE?ssl={}")}
+
+4. Add the connection string to your .env file:
+ ${pc.dim('DATABASE_URL="your_connection_string"')}
+
+${pc.yellow("Important:")}
+- The connection string MUST include ${pc.bold("ssl={}")} at the end
+- Use the singlestore:// protocol for SingleStore connections
+- SingleStore requires SSL connections for cloud deployments`);
+}
+
+export async function setupSingleStoreHelios(config: ProjectConfig) {
+ const { projectDir } = config;
+
+ try {
+ const serverDir = path.join(projectDir, "apps/server");
+ await fs.ensureDir(serverDir);
+ await writeEnvFile(projectDir);
+
+ log.success(
+ pc.green(
+ "SingleStore Helios setup complete! Please update the connection string in .env file.",
+ ),
+ );
+
+ displayManualSetupInstructions();
+ } catch (error) {
+ log.error(pc.red("SingleStore Helios setup failed"));
+ if (error instanceof Error) {
+ log.error(pc.red(error.message));
+ }
+
+ try {
+ await writeEnvFile(projectDir);
+ displayManualSetupInstructions();
+ } catch {}
+ }
+}
diff --git a/apps/cli/src/helpers/project-generation/env-setup.ts b/apps/cli/src/helpers/project-generation/env-setup.ts
index 210c4caff..5c919bd2f 100644
--- a/apps/cli/src/helpers/project-generation/env-setup.ts
+++ b/apps/cli/src/helpers/project-generation/env-setup.ts
@@ -185,6 +185,7 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
dbSetup === "mongodb-atlas" ||
dbSetup === "neon" ||
dbSetup === "supabase" ||
+ dbSetup === "singlestore-helios" ||
dbSetup === "d1" ||
dbSetup === "docker";
diff --git a/apps/cli/src/helpers/setup/db-setup.ts b/apps/cli/src/helpers/setup/db-setup.ts
index 48d47e17e..a8be87e0c 100644
--- a/apps/cli/src/helpers/setup/db-setup.ts
+++ b/apps/cli/src/helpers/setup/db-setup.ts
@@ -10,6 +10,7 @@ import { setupDockerCompose } from "../database-providers/docker-compose-setup";
import { setupMongoDBAtlas } from "../database-providers/mongodb-atlas-setup";
import { setupNeonPostgres } from "../database-providers/neon-setup";
import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup";
+import { setupSingleStoreHelios } from "../database-providers/singlestore-helios-setup";
import { setupSupabase } from "../database-providers/supabase-setup";
import { setupTurso } from "../database-providers/turso-setup";
@@ -68,6 +69,12 @@ export async function setupDatabase(config: ProjectConfig) {
devDependencies: ["drizzle-kit"],
projectDir: serverDir,
});
+ } else if (database === "singlestore") {
+ await addPackageDependency({
+ dependencies: ["drizzle-orm", "mysql2"],
+ devDependencies: ["drizzle-kit"],
+ projectDir: serverDir,
+ });
}
} else if (orm === "mongoose") {
await addPackageDependency({
@@ -93,6 +100,8 @@ export async function setupDatabase(config: ProjectConfig) {
}
} else if (database === "mongodb" && dbSetup === "mongodb-atlas") {
await setupMongoDBAtlas(config);
+ } else if (database === "singlestore" && dbSetup === "singlestore-helios") {
+ await setupSingleStoreHelios(config);
}
} catch (error) {
s.stop(pc.red("Failed to set up database"));
diff --git a/apps/cli/src/prompts/database-setup.ts b/apps/cli/src/prompts/database-setup.ts
index 2d27d7c47..a21f3038a 100644
--- a/apps/cli/src/prompts/database-setup.ts
+++ b/apps/cli/src/prompts/database-setup.ts
@@ -91,6 +91,8 @@ export async function getDBSetupChoice(
},
{ value: "none" as const, label: "None", hint: "Manual setup" },
];
+ } else if (databaseType === "singlestore") {
+ return "singlestore-helios";
} else {
return "none";
}
diff --git a/apps/cli/src/prompts/database.ts b/apps/cli/src/prompts/database.ts
index 16c717d44..6e0f645e4 100644
--- a/apps/cli/src/prompts/database.ts
+++ b/apps/cli/src/prompts/database.ts
@@ -47,6 +47,11 @@ export async function getDatabaseChoice(
label: "MongoDB",
hint: "open-source NoSQL database that stores data in JSON-like documents called BSON",
});
+ databaseOptions.push({
+ value: "singlestore",
+ label: "SingleStore",
+ hint: "high-performance distributed SQL database for real-time analytics",
+ });
}
const response = await select({
diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts
index a769345ff..30dab67f7 100644
--- a/apps/cli/src/types.ts
+++ b/apps/cli/src/types.ts
@@ -1,7 +1,7 @@
import z from "zod";
export const DatabaseSchema = z
- .enum(["none", "sqlite", "postgres", "mysql", "mongodb"])
+ .enum(["none", "sqlite", "postgres", "mysql", "mongodb", "singlestore"])
.describe("Database type");
export type Database = z.infer;
@@ -70,6 +70,7 @@ export const DatabaseSetupSchema = z
"prisma-postgres",
"mongodb-atlas",
"supabase",
+ "singlestore-helios",
"d1",
"docker",
"none",
diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts
index c7b4b6f8c..33fa7c645 100644
--- a/apps/cli/src/utils/compatibility-rules.ts
+++ b/apps/cli/src/utils/compatibility-rules.ts
@@ -129,6 +129,64 @@ export function validateWorkersCompatibility(
"Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
);
}
+
+ if (
+ providedFlags.has("runtime") &&
+ options.runtime === "workers" &&
+ config.database === "singlestore"
+ ) {
+ exitWithError(
+ "SingleStore is not supported on Cloudflare Workers. Use Bun or Node.js runtimes.",
+ );
+ }
+
+ if (
+ providedFlags.has("database") &&
+ config.database === "singlestore" &&
+ config.runtime === "workers"
+ ) {
+ exitWithError(
+ "SingleStore is not supported on Cloudflare Workers. Use Bun or Node.js runtimes.",
+ );
+ }
+}
+
+export function validateSingleStoreCompatibility(
+ providedFlags: Set,
+ options: CLIInput,
+ config: Partial,
+) {
+ if (
+ providedFlags.has("database") &&
+ options.database === "singlestore" &&
+ config.orm &&
+ config.orm !== "drizzle"
+ ) {
+ exitWithError(
+ `SingleStore database requires Drizzle ORM. Current ORM: ${config.orm}. Please use '--orm drizzle' or choose a different database.`,
+ );
+ }
+
+ if (
+ providedFlags.has("orm") &&
+ config.orm &&
+ config.orm !== "drizzle" &&
+ config.database === "singlestore"
+ ) {
+ exitWithError(
+ `ORM '${config.orm}' is not compatible with SingleStore database. SingleStore requires Drizzle ORM. Please use '--orm drizzle' or choose a different database.`,
+ );
+ }
+
+ if (
+ providedFlags.has("dbSetup") &&
+ options.dbSetup === "singlestore-helios" &&
+ config.database !== "singlestore"
+ ) {
+ exitWithError(
+ `SingleStore Helios setup (--db-setup singlestore-helios) requires SingleStore database. Current database: ${config.database}. Please use '--database singlestore' or choose a different database setup.`,
+ );
+ }
}
export function coerceBackendPresets(config: Partial) {
diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts
index b04dc292a..613153d5a 100644
--- a/apps/cli/src/validation.ts
+++ b/apps/cli/src/validation.ts
@@ -20,6 +20,7 @@ import {
validateAddonsAgainstFrontends,
validateApiFrontendCompatibility,
validateExamplesCompatibility,
+ validateSingleStoreCompatibility,
validateWebDeployRequiresWebFrontend,
validateWorkersCompatibility,
} from "./utils/compatibility-rules";
@@ -247,6 +248,18 @@ export function processAndValidateFlags(
);
}
+ if (
+ providedFlags.has("database") &&
+ providedFlags.has("orm") &&
+ config.database === "singlestore" &&
+ config.orm &&
+ config.orm !== "drizzle"
+ ) {
+ exitWithError(
+ "SingleStore database requires Drizzle ORM. Please use '--orm drizzle' or choose a different database.",
+ );
+ }
+
if (
providedFlags.has("database") &&
providedFlags.has("orm") &&
@@ -349,6 +362,28 @@ export function processAndValidateFlags(
);
}
+ if (
+ providedFlags.has("dbSetup") &&
+ (config.database ? providedFlags.has("database") : true) &&
+ config.dbSetup === "singlestore-helios" &&
+ config.database !== "singlestore"
+ ) {
+ exitWithError(
+ "SingleStore Helios setup requires SingleStore database. Please use '--database singlestore' or choose a different setup.",
+ );
+ }
+
+ if (
+ providedFlags.has("database") &&
+ providedFlags.has("dbSetup") &&
+ config.database === "singlestore" &&
+ config.dbSetup === "none"
+ ) {
+ exitWithError(
+ "SingleStore database requires SingleStore Helios setup. Please use '--db-setup singlestore-helios' or omit the --db-setup flag.",
+ );
+ }
+
if (config.dbSetup === "d1") {
if (
(providedFlags.has("dbSetup") && providedFlags.has("database")) ||
@@ -396,6 +431,7 @@ export function processAndValidateFlags(
}
validateWorkersCompatibility(providedFlags, options, config);
+ validateSingleStoreCompatibility(providedFlags, options, config);
const hasWebFrontendFlag = (config.frontend ?? []).some((f) =>
isWebFrontend(f),
diff --git a/apps/cli/templates/db/drizzle/singlestore/drizzle.config.ts.hbs b/apps/cli/templates/db/drizzle/singlestore/drizzle.config.ts.hbs
new file mode 100644
index 000000000..581473b56
--- /dev/null
+++ b/apps/cli/templates/db/drizzle/singlestore/drizzle.config.ts.hbs
@@ -0,0 +1,10 @@
+import { defineConfig } from "drizzle-kit";
+
+export default defineConfig({
+ schema: "./src/db/schema",
+ out: "./src/db/migrations",
+ dialect: "singlestore",
+ dbCredentials: {
+ url: process.env.DATABASE_URL || "",
+ },
+});
\ No newline at end of file
diff --git a/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs b/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs
new file mode 100644
index 000000000..a0be3fdb1
--- /dev/null
+++ b/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs
@@ -0,0 +1,6 @@
+import mysql from "mysql2/promise";
+import { drizzle } from "drizzle-orm/singlestore";
+
+const pool = mysql.createPool(process.env.DATABASE_URL || "");
+
+export const db = drizzle({ client: pool });
diff --git a/apps/cli/templates/examples/todo/server/drizzle/singlestore/src/db/schema/todo.ts b/apps/cli/templates/examples/todo/server/drizzle/singlestore/src/db/schema/todo.ts
new file mode 100644
index 000000000..025d6c29f
--- /dev/null
+++ b/apps/cli/templates/examples/todo/server/drizzle/singlestore/src/db/schema/todo.ts
@@ -0,0 +1,7 @@
+import { singlestoreTable, varchar, bigint, boolean } from "drizzle-orm/singlestore-core";
+
+export const todo = singlestoreTable("todo", {
+ id: bigint("id", { mode: "number" }).primaryKey().autoincrement(),
+ text: varchar("text", { length: 255 }).notNull(),
+ completed: boolean("completed").default(false).notNull(),
+});
diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts
index 8fe3c1080..6eb50c60d 100644
--- a/apps/cli/test/cli.smoke.test.ts
+++ b/apps/cli/test/cli.smoke.test.ts
@@ -1146,6 +1146,313 @@ describe("create-better-t-stack smoke", () => {
orm: "mongoose",
});
});
+
+ it("scaffolds with SingleStore + Drizzle", async () => {
+ const projectName = "app-singlestore-drizzle";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ assertScaffoldedProject(projectDir);
+ assertProjectStructure(projectDir, {
+ hasWeb: true,
+ hasServer: true,
+ hasDatabase: true,
+ });
+ assertBtsConfig(projectDir, {
+ database: "singlestore",
+ orm: "drizzle",
+ });
+ });
+
+ it("SingleStore project generates correct drizzle setup and dependencies", async () => {
+ const projectName = "app-singlestore-setup";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ const serverDir = join(projectDir, "apps", "server");
+ const drizzleConfigPath = join(serverDir, "drizzle.config.ts");
+ expect(existsSync(drizzleConfigPath)).toBe(true);
+ const drizzleConfig = readFileSync(drizzleConfigPath, "utf8");
+ expect(drizzleConfig).toContain('dialect: "singlestore"');
+ const dbIndexPath = join(serverDir, "src", "db", "index.ts");
+ expect(existsSync(dbIndexPath)).toBe(true);
+ const dbIndex = readFileSync(dbIndexPath, "utf8");
+ expect(dbIndex).toContain("drizzle-orm/singlestore");
+ expect(dbIndex).toContain("mysql2/promise");
+ const packageJsonPath = join(serverDir, "package.json");
+ expect(existsSync(packageJsonPath)).toBe(true);
+ const packageJson = readJsonSync(packageJsonPath);
+ expect(packageJson.dependencies).toHaveProperty("mysql2");
+ });
+
+ it("SingleStore project generates proper schema structure", async () => {
+ const projectName = "app-singlestore-schema";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "todo",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ const serverDir = join(projectDir, "apps", "server");
+ const schemaDir = join(serverDir, "src", "db", "schema");
+ expect(existsSync(schemaDir)).toBe(true);
+ const schemaFiles = require("node:fs").readdirSync(schemaDir);
+ const schemaFile = schemaFiles.find((file: string) =>
+ file.endsWith(".ts"),
+ );
+
+ if (schemaFile) {
+ const schemaPath = join(schemaDir, schemaFile);
+ const schemaContent = readFileSync(schemaPath, "utf8");
+ expect(schemaContent).toContain("singlestoreTable");
+ expect(schemaContent).toContain('from "drizzle-orm/singlestore-core"');
+ expect(schemaContent).not.toContain('from "drizzle-orm/singlestore"');
+ }
+ });
+
+ it("SingleStore project generates .env with correct SSL configuration", async () => {
+ const projectName = "app-singlestore-env";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ const serverDir = join(projectDir, "apps", "server");
+ const envPath = join(serverDir, ".env");
+ expect(existsSync(envPath)).toBe(true);
+ const envContent = readFileSync(envPath, "utf8");
+ expect(envContent).toContain("DATABASE_URL");
+ const lines = envContent.split("\n");
+ const databaseUrlLine = lines.find((line) =>
+ line.startsWith("DATABASE_URL="),
+ );
+ expect(databaseUrlLine).toBeTruthy();
+
+ const databaseUrl = databaseUrlLine
+ ?.split("=", 2)[1]
+ ?.replace(/['"]/g, "");
+ expect(databaseUrl).toBeTruthy();
+ expect(databaseUrl).toMatch(/singlestore:\/\/.*\?ssl/);
+ expect(envContent).toContain("?ssl=");
+ });
+
+ describe("SingleStore compatibility matrix", () => {
+ it("scaffolds SingleStore with authentication enabled", async () => {
+ const projectName = "singlestore-auth";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--auth",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ assertScaffoldedProject(projectDir);
+ assertProjectStructure(projectDir, {
+ hasWeb: true,
+ hasServer: true,
+ hasDatabase: true,
+ hasAuth: true,
+ });
+ assertBtsConfig(projectDir, {
+ database: "singlestore",
+ orm: "drizzle",
+ auth: true,
+ });
+ });
+
+ it("scaffolds SingleStore with TODO example", async () => {
+ const projectName = "singlestore-todo";
+ await runCli(
+ [
+ projectName,
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "trpc",
+ "--no-auth",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "todo",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+
+ const projectDir = join(workdir, projectName);
+ assertScaffoldedProject(projectDir);
+ assertProjectStructure(projectDir, {
+ hasWeb: true,
+ hasServer: true,
+ hasDatabase: true,
+ });
+ assertBtsConfig(projectDir, {
+ database: "singlestore",
+ orm: "drizzle",
+ examples: ["todo"],
+ });
+
+ const todoSchemaPath = join(
+ projectDir,
+ "apps",
+ "server",
+ "src",
+ "db",
+ "schema",
+ "todo.ts",
+ );
+ expect(existsSync(todoSchemaPath)).toBe(true);
+ const todoSchemaContent = readFileSync(todoSchemaPath, "utf8");
+ expect(todoSchemaContent).toContain("singlestoreTable");
+ expect(todoSchemaContent).toContain("bigint(");
+ });
+ });
});
describe("addon combinations", () => {
@@ -1391,6 +1698,138 @@ describe("create-better-t-stack smoke", () => {
);
});
+ it("rejects SingleStore with Prisma ORM", async () => {
+ await runCliExpectingError(
+ [
+ "invalid-singlestore-prisma",
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "prisma",
+ "--api",
+ "none",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "none",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+ });
+
+ it("rejects SingleStore with Mongoose ORM", async () => {
+ await runCliExpectingError(
+ [
+ "invalid-singlestore-mongoose",
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "singlestore",
+ "--orm",
+ "mongoose",
+ "--api",
+ "none",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "none",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+ });
+
+ it("rejects singlestore-helios setup with non-SingleStore database", async () => {
+ await runCliExpectingError(
+ [
+ "invalid-helios-postgres",
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "bun",
+ "--database",
+ "postgres",
+ "--orm",
+ "prisma",
+ "--api",
+ "none",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "singlestore-helios",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+ });
+
+ it("rejects SingleStore database with none db-setup", async () => {
+ await runCliExpectingError(
+ [
+ "invalid-singlestore-none",
+ "--yes",
+ "--frontend",
+ "tanstack-router",
+ "--backend",
+ "hono",
+ "--runtime",
+ "node",
+ "--database",
+ "singlestore",
+ "--orm",
+ "drizzle",
+ "--api",
+ "none",
+ "--no-auth",
+ "--addons",
+ "none",
+ "--db-setup",
+ "none",
+ "--examples",
+ "none",
+ "--package-manager",
+ "bun",
+ "--no-install",
+ "--no-git",
+ ],
+ workdir,
+ );
+ });
+
it("rejects incompatible frontend and API combinations", async () => {
await runCliExpectingError(
[
@@ -2437,6 +2876,7 @@ describe("create-better-t-stack smoke", () => {
"app-sqlite-drizzle",
"app-postgres-prisma",
"app-mongo-mongoose",
+ "app-singlestore-drizzle",
"app-biome",
"app-multi-addons",
"app-trpc",
@@ -2459,6 +2899,7 @@ describe("create-better-t-stack smoke", () => {
"app-orpc-solid",
"app-backend-next",
"app-node-runtime",
+ "singlestore-hono-node",
].forEach((n) => projectNames.add(n));
const detectPackageManager = (
diff --git a/apps/cli/test/programmatic-api.test.ts b/apps/cli/test/programmatic-api.test.ts
index 3fe7c4540..e93a35689 100644
--- a/apps/cli/test/programmatic-api.test.ts
+++ b/apps/cli/test/programmatic-api.test.ts
@@ -172,6 +172,23 @@ describe("Programmatic API - Fast Tests", () => {
});
}, 15000);
+ test("creates project with SingleStore + Drizzle", async () => {
+ const result = await init("singlestore-app", {
+ yes: true,
+ database: "singlestore",
+ orm: "drizzle",
+ dbSetup: "singlestore-helios",
+ install: false,
+ git: false,
+ });
+
+ expect(result.success).toBe(true);
+ assertBtsConfig(result.projectDirectory, {
+ database: "singlestore",
+ orm: "drizzle",
+ });
+ }, 15000);
+
test("creates project with oRPC API", async () => {
const result = await init("orpc-app", {
yes: true,
@@ -261,6 +278,19 @@ describe("Programmatic API - Fast Tests", () => {
).rejects.toThrow(/requires Mongoose or Prisma/);
});
+ test("handles incompatible SingleStore + Prisma combination", async () => {
+ await expect(
+ init("singlestore-prisma", {
+ yes: true,
+ database: "singlestore",
+ orm: "prisma",
+ install: false,
+ git: false,
+ yolo: false,
+ }),
+ ).rejects.toThrow(/SingleStore database requires Drizzle/);
+ });
+
test("handles auth without database", async () => {
await expect(
init("auth-no-db", {
diff --git a/apps/web/content/docs/cli/compatibility.mdx b/apps/web/content/docs/cli/compatibility.mdx
index b05811f0f..a5628b039 100644
--- a/apps/web/content/docs/cli/compatibility.mdx
+++ b/apps/web/content/docs/cli/compatibility.mdx
@@ -17,11 +17,13 @@ The CLI validates option combinations to ensure generated projects work correctl
| `postgres` | `drizzle`, `prisma` | Advanced relational database |
| `mysql` | `drizzle`, `prisma` | Traditional relational database |
| `mongodb` | `mongoose`, `prisma` | Document database, requires specific ORMs |
+| `singlestore` | `drizzle` | High-performance distributed SQL database |
| `none` | `none` | No database setup |
### Restrictions
- **MongoDB + Drizzle**: ❌ Not supported - Drizzle doesn't support MongoDB
+- **SingleStore + Prisma/Mongoose**: ❌ Not supported - SingleStore only supports Drizzle ORM
- **Database without ORM**: ❌ Not supported - Database requires an ORM for code generation
- **ORM without Database**: ❌ Not supported - ORM requires a database target
@@ -104,7 +106,7 @@ create-better-t-stack --frontend nuxt --api orpc
### Frontend Restrictions
- **Multiple Web Frontends**: ❌ Only one web framework allowed
-- **Multiple Native Frontends**: ❌ Only one native framework allowed
+- **Multiple Native Frontends**: ❌ Only one native framework allowed
- **Web + Native**: ✅ One web and one native framework allowed
```bash
@@ -127,6 +129,7 @@ create-better-t-stack --frontend next native-nativewind
| `supabase` | `postgres` | PostgreSQL with additional features |
| `prisma-postgres` | `postgres` | Managed PostgreSQL via Prisma |
| `mongodb-atlas` | `mongodb` | Managed MongoDB |
+| `singlestore-helios` | `singlestore` | Cloud-hosted SingleStore database |
| `docker` | `postgres`, `mysql`, `mongodb` | Not compatible with `sqlite` or Workers |
### Special Cases
@@ -177,7 +180,7 @@ create-better-t-stack --auth --database postgres --orm drizzle --backend hono
- Requires a database when backend is present (except Convex)
- Cannot be used with `--backend none` and `--database none`
-### AI Example
+### AI Example
- Not compatible with `--backend elysia`
- Not compatible with `--frontend solid`
diff --git a/apps/web/content/docs/cli/index.mdx b/apps/web/content/docs/cli/index.mdx
index b423509cd..7dec6ba49 100644
--- a/apps/web/content/docs/cli/index.mdx
+++ b/apps/web/content/docs/cli/index.mdx
@@ -30,10 +30,10 @@ create-better-t-stack [project-directory] [options]
- `--frontend `: Web and/or native frameworks (see [Options](/docs/cli/options#frontend))
- `--backend `: `hono`, `express`, `fastify`, `elysia`, `next`, `convex`, `none`
- `--runtime `: `bun`, `node`, `workers` (`none` only with `--backend convex` or `--backend none`)
-- `--database `: `none`, `sqlite`, `postgres`, `mysql`, `mongodb`
+- `--database `: `none`, `sqlite`, `postgres`, `mysql`, `mongodb`, `singlestore`
- `--orm `: `none`, `drizzle`, `prisma`, `mongoose`
- `--api `: `none`, `trpc`, `orpc`
-- `--db-setup `: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `docker`
+- `--db-setup `: `none`, `turso`, `d1`, `neon`, `supabase`, `prisma-postgres`, `mongodb-atlas`, `singlestore-helios`, `docker`
- `--examples `: `none`, `todo`, `ai`
- `--web-deploy `: `none`, `workers`
- `--directory-conflict `: `merge`, `overwrite`, `increment`, `error`
diff --git a/apps/web/content/docs/cli/options.mdx b/apps/web/content/docs/cli/options.mdx
index 7650925bf..96bcdf17a 100644
--- a/apps/web/content/docs/cli/options.mdx
+++ b/apps/web/content/docs/cli/options.mdx
@@ -87,9 +87,10 @@ Database type to use:
- `none`: No database
- `sqlite`: SQLite database
-- `postgres`: PostgreSQL database
+- `postgres`: PostgreSQL database
- `mysql`: MySQL database
- `mongodb`: MongoDB database
+- `singlestore`: SingleStore database
```bash
create-better-t-stack --database postgres
@@ -119,6 +120,7 @@ Database hosting/setup provider:
- `supabase`: Supabase (PostgreSQL)
- `prisma-postgres`: Prisma Postgres via Prisma Accelerate
- `mongodb-atlas`: MongoDB Atlas
+- `singlestore-helios`: SingleStore Helios (cloud-hosted SingleStore)
- `docker`: Local Docker containers
```bash
diff --git a/apps/web/content/docs/cli/programmatic-api.mdx b/apps/web/content/docs/cli/programmatic-api.mdx
index 3538e6559..d3f7397a3 100644
--- a/apps/web/content/docs/cli/programmatic-api.mdx
+++ b/apps/web/content/docs/cli/programmatic-api.mdx
@@ -132,7 +132,7 @@ interface CreateInput {
yes?: boolean; // Skip prompts, use defaults
yolo?: boolean; // Bypass validations (not recommended)
verbose?: boolean; // Show JSON result (CLI only, programmatic always returns result)
- database?: Database; // "none" | "sqlite" | "postgres" | "mysql" | "mongodb"
+ database?: Database; // "none" | "sqlite" | "postgres" | "mysql" | "mongodb" | "singlestore"
orm?: ORM; // "none" | "drizzle" | "prisma" | "mongoose"
auth?: boolean; // Include authentication
frontend?: Frontend[]; // Array of frontend frameworks
diff --git a/apps/web/content/docs/compatibility.mdx b/apps/web/content/docs/compatibility.mdx
index 60b5f9d17..8f6ccb780 100644
--- a/apps/web/content/docs/compatibility.mdx
+++ b/apps/web/content/docs/compatibility.mdx
@@ -11,6 +11,7 @@ description: Valid and invalid combinations across frontend, backend, runtime, d
- **API `none`**: No tRPC/oRPC setup; use framework-native APIs
- **Database `none`**: Disables ORM and authentication
- **ORM `none`**: No ORM setup; manage DB manually
+- **SingleStore database**: Only compatible with Drizzle ORM; requires SingleStore Helios setup
- **Runtime `none`**: Only with Convex backend or when backend is `none`
## Cloudflare Workers
@@ -18,7 +19,7 @@ description: Valid and invalid combinations across frontend, backend, runtime, d
- Backend: `hono` only
- Database: `sqlite` with Cloudflare D1
- ORM: `drizzle` (or none)
-- Not compatible with MongoDB
+- Not compatible with MongoDB, SingleStore
## Framework Notes
diff --git a/apps/web/content/docs/index.mdx b/apps/web/content/docs/index.mdx
index 3e9e76f7c..b89503c08 100644
--- a/apps/web/content/docs/index.mdx
+++ b/apps/web/content/docs/index.mdx
@@ -251,7 +251,7 @@ See the full list in the [CLI Reference](/docs/cli). Key flags:
- `--frontend`: tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none
- `--backend`: hono, express, fastify, elysia, next, convex, none
- `--runtime`: bun, node, workers, none
-- `--database`: sqlite, postgres, mysql, mongodb, none
+- `--database`: sqlite, postgres, mysql, mongodb, singlestore, none
- `--orm`: drizzle, prisma, mongoose, none
- `--api`: trpc, orpc, none
- `--addons`: turborepo, pwa, tauri, biome, husky, starlight, none
diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx
index cc24712e7..abf0e7081 100644
--- a/apps/web/src/app/(home)/_components/stack-builder.tsx
+++ b/apps/web/src/app/(home)/_components/stack-builder.tsx
@@ -382,6 +382,57 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
message: "ORM set to 'Prisma' (MongoDB requires Prisma or Mongoose)",
});
}
+ } else if (nextStack.database === "singlestore") {
+ if (nextStack.orm !== "drizzle") {
+ notes.database.notes.push(
+ "SingleStore requires Drizzle ORM. Drizzle will be selected.",
+ );
+ notes.orm.notes.push(
+ "SingleStore requires Drizzle ORM. It will be selected.",
+ );
+ notes.database.hasIssue = true;
+ notes.orm.hasIssue = true;
+ nextStack.orm = "drizzle";
+ changed = true;
+ changes.push({
+ category: "database",
+ message: "ORM set to 'Drizzle' (SingleStore requires Drizzle)",
+ });
+ }
+ if (nextStack.dbSetup !== "singlestore-helios") {
+ notes.database.notes.push(
+ "SingleStore database selected: DB Setup will be set to 'SingleStore Helios'.",
+ );
+ notes.dbSetup.notes.push(
+ "SingleStore works best with cloud setup. SingleStore Helios will be selected.",
+ );
+ notes.database.hasIssue = true;
+ notes.dbSetup.hasIssue = true;
+ nextStack.dbSetup = "singlestore-helios";
+ changed = true;
+ changes.push({
+ category: "database",
+ message:
+ "DB Setup set to 'SingleStore Helios' (recommended for SingleStore)",
+ });
+ }
+ if (nextStack.runtime === "workers") {
+ notes.runtime.notes.push(
+ "Cloudflare Workers runtime is not compatible with SingleStore. SQLite will be selected.",
+ );
+ notes.database.notes.push(
+ "SingleStore is not compatible with Cloudflare Workers runtime. SQLite will be selected.",
+ );
+ notes.runtime.hasIssue = true;
+ notes.database.hasIssue = true;
+ nextStack.database = "sqlite";
+ changed = true;
+ changes.push({
+ category: "runtime",
+ message:
+ "Database set to 'SQLite' (SingleStore not compatible with Workers)",
+ });
+ }
} else {
if (nextStack.orm === "mongoose") {
notes.database.notes.push(
@@ -516,6 +567,41 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
"Database set to 'PostgreSQL' (required by Supabase setup)",
});
}
+ } else if (nextStack.dbSetup === "singlestore-helios") {
+ if (nextStack.database !== "singlestore") {
+ notes.dbSetup.notes.push(
+ "Requires SingleStore. It will be selected.",
+ );
+ notes.database.notes.push(
+ "SingleStore Helios setup requires SingleStore. It will be selected.",
+ );
+ notes.dbSetup.hasIssue = true;
+ notes.database.hasIssue = true;
+ nextStack.database = "singlestore";
+ changed = true;
+ changes.push({
+ category: "dbSetup",
+ message:
+ "Database set to 'SingleStore' (required by SingleStore Helios setup)",
+ });
+ }
+ if (nextStack.orm !== "drizzle") {
+ notes.dbSetup.notes.push(
+ "SingleStore Helios requires Drizzle ORM. It will be selected.",
+ );
+ notes.orm.notes.push(
+ "SingleStore Helios setup requires Drizzle ORM. It will be selected.",
+ );
+ notes.dbSetup.hasIssue = true;
+ notes.orm.hasIssue = true;
+ nextStack.orm = "drizzle";
+ changed = true;
+ changes.push({
+ category: "dbSetup",
+ message:
+ "ORM set to 'Drizzle' (SingleStore Helios requires Drizzle)",
+ });
+ }
} else if (nextStack.dbSetup === "d1") {
if (nextStack.database !== "sqlite") {
notes.dbSetup.notes.push(
@@ -669,6 +755,24 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
});
}
+ if (nextStack.database === "singlestore") {
+ notes.runtime.notes.push(
+ "Cloudflare Workers runtime is not compatible with SingleStore. SQLite will be selected.",
+ );
+ notes.database.notes.push(
+ "SingleStore is not compatible with Cloudflare Workers runtime. SQLite will be selected.",
+ );
+ notes.runtime.hasIssue = true;
+ notes.database.hasIssue = true;
+ nextStack.database = "sqlite";
+ changed = true;
+ changes.push({
+ category: "runtime",
+ message:
+ "Database set to 'SQLite' (SingleStore not compatible with Workers)",
+ });
+ }
+
if (nextStack.dbSetup === "docker") {
notes.runtime.notes.push(
"Cloudflare Workers runtime does not support Docker setup. D1 will be selected.",
@@ -945,6 +1049,7 @@ const generateCommand = (stackState: StackState): string => {
"supabase",
"prisma-postgres",
"mongodb-atlas",
+ "singlestore-helios",
"docker",
].includes(stackState.dbSetup);
@@ -1635,7 +1740,51 @@ const StackBuilder = () => {
TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || [];
const categoryDisplayName = getCategoryDisplayName(categoryKey);
- const filteredOptions = categoryOptions.filter(() => {
+ const filteredOptions = categoryOptions.filter((option) => {
+ if (categoryKey === "orm") {
+ if (stack.database === "mongodb") {
+ return (
+ option.id === "prisma" ||
+ option.id === "mongoose" ||
+ option.id === "none"
+ );
+ }
+ if (stack.database === "singlestore") {
+ return option.id === "drizzle";
+ }
+ }
+
+ if (categoryKey === "dbSetup") {
+ if (stack.database === "singlestore") {
+ return option.id === "singlestore-helios";
+ }
+ if (stack.database === "sqlite") {
+ return ["turso", "d1", "docker", "none"].includes(
+ option.id,
+ );
+ }
+ if (stack.database === "postgres") {
+ return [
+ "neon",
+ "supabase",
+ "prisma-postgres",
+ "docker",
+ "none",
+ ].includes(option.id);
+ }
+ if (stack.database === "mysql") {
+ return ["docker", "none"].includes(option.id);
+ }
+ if (stack.database === "mongodb") {
+ return ["mongodb-atlas", "docker", "none"].includes(
+ option.id,
+ );
+ }
+ if (stack.database === "none") {
+ return option.id === "none";
+ }
+ }
+
return true;
});
diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts
index 792de3448..0a35e33ef 100644
--- a/apps/web/src/lib/constant.ts
+++ b/apps/web/src/lib/constant.ts
@@ -246,6 +246,13 @@ export const TECH_OPTIONS: Record<
icon: `${ICON_BASE_URL}/mongodb.svg`,
color: "from-green-400 to-green-600",
},
+ {
+ id: "singlestore",
+ name: "SingleStore",
+ description: "High-performance distributed SQL database",
+ icon: `${ICON_BASE_URL}/singlestore.svg`,
+ color: "from-purple-500 to-purple-700",
+ },
{
id: "none",
name: "No Database",
@@ -328,6 +335,13 @@ export const TECH_OPTIONS: Record<
icon: `${ICON_BASE_URL}/supabase.svg`,
color: "from-emerald-400 to-emerald-600",
},
+ {
+ id: "singlestore-helios",
+ name: "SingleStore Helios",
+ description: "Cloud-hosted SingleStore database on Helios",
+ icon: `${ICON_BASE_URL}/singlestore.svg`,
+ color: "from-purple-500 to-purple-700",
+ },
{
id: "docker",
name: "Docker",
diff --git a/bun.lock b/bun.lock
index 728366ad2..150fc0c5c 100644
--- a/bun.lock
+++ b/bun.lock
@@ -14,7 +14,7 @@
},
"apps/cli": {
"name": "create-better-t-stack",
- "version": "2.33.6",
+ "version": "2.33.8",
"bin": {
"create-better-t-stack": "dist/cli.js",
},