From 2cc278bdffe062a17ad2303e2cd678e4b604d425 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:18:47 +0530 Subject: [PATCH 01/18] feat(cli): add SingleStore database and Helios setup support to type system --- apps/cli/src/types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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", From 245fb3fd85dbe51da06ec435fd8f47cb1ed9d63e Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:18:54 +0530 Subject: [PATCH 02/18] feat(cli): add SingleStore compatibility rules and validation --- apps/cli/src/utils/compatibility-rules.ts | 38 +++++++++++++++++++++++ apps/cli/src/validation.ts | 25 +++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index c7b4b6f8c..693dc5d8e 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -131,6 +131,44 @@ export function validateWorkersCompatibility( } } +export function validateSingleStoreCompatibility( + providedFlags: Set, + options: CLIInput, + config: Partial, +) { + if ( + providedFlags.has("database") && + options.database === "singlestore" && + config.orm && + config.orm !== "drizzle" + ) { + exitWithError( + `SingleStore database is only compatible with 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 only supports 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) { if (config.backend === "convex") { config.auth = false; diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts index b04dc292a..22a51a89f 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,17 @@ 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 (config.dbSetup === "d1") { if ( (providedFlags.has("dbSetup") && providedFlags.has("database")) || @@ -396,6 +420,7 @@ export function processAndValidateFlags( } validateWorkersCompatibility(providedFlags, options, config); + validateSingleStoreCompatibility(providedFlags, options, config); const hasWebFrontendFlag = (config.frontend ?? []).some((f) => isWebFrontend(f), From f5fdaa47b8151785ee1bbdb843d4452d7ea6f319 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:01 +0530 Subject: [PATCH 03/18] feat(cli): add SingleStore database prompts and setup options --- apps/cli/src/prompts/database-setup.ts | 9 +++++++++ apps/cli/src/prompts/database.ts | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/apps/cli/src/prompts/database-setup.ts b/apps/cli/src/prompts/database-setup.ts index 2d27d7c47..aa7fdd759 100644 --- a/apps/cli/src/prompts/database-setup.ts +++ b/apps/cli/src/prompts/database-setup.ts @@ -91,6 +91,15 @@ export async function getDBSetupChoice( }, { value: "none" as const, label: "None", hint: "Manual setup" }, ]; + } else if (databaseType === "singlestore") { + options = [ + { + value: "singlestore-helios" as const, + label: "SingleStore Helios", + hint: "Managed SingleStore cloud platform", + }, + { value: "none" as const, label: "None", hint: "Manual setup" }, + ]; } else { return "none"; } diff --git a/apps/cli/src/prompts/database.ts b/apps/cli/src/prompts/database.ts index 16c717d44..f7e69b847 100644 --- a/apps/cli/src/prompts/database.ts +++ b/apps/cli/src/prompts/database.ts @@ -39,6 +39,11 @@ export async function getDatabaseChoice( label: "MySQL", hint: "popular open-source relational database system", }, + { + value: "singlestore", + label: "SingleStore", + hint: "high-performance distributed SQL database for real-time analytics", + }, ]; if (runtime !== "workers") { From d4f6f856ed1979823e70331f2487e076402e823a Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:07 +0530 Subject: [PATCH 04/18] feat(cli): add SingleStore Drizzle templates and example schemas --- .../drizzle/singlestore/drizzle.config.ts.hbs | 10 ++++++++++ .../db/drizzle/singlestore/src/db/index.ts.hbs | 18 ++++++++++++++++++ .../drizzle/singlestore/src/db/schema/todo.ts | 7 +++++++ 3 files changed, 35 insertions(+) create mode 100644 apps/cli/templates/db/drizzle/singlestore/drizzle.config.ts.hbs create mode 100644 apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs create mode 100644 apps/cli/templates/examples/todo/server/drizzle/singlestore/src/db/schema/todo.ts 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..bfc774119 --- /dev/null +++ b/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs @@ -0,0 +1,18 @@ +{{#if (or (eq runtime "bun") (eq runtime "node"))}} +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 }); +{{/if}} + +{{#if (eq runtime "workers")}} +import mysql from "mysql2/promise"; +import { drizzle } from "drizzle-orm/singlestore"; +import { env } from "cloudflare:workers"; + +const pool = mysql.createPool(env.DATABASE_URL || ""); + +export const db = drizzle({ client: pool }); +{{/if}} \ No newline at end of file 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..00e342641 --- /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"; + +export const todo = singlestoreTable("todo", { + id: bigint("id", { mode: "number" }).primaryKey().autoincrement(), + text: varchar("text", { length: 255 }).notNull(), + completed: boolean("completed").default(false).notNull(), +}); \ No newline at end of file From a91f2fe3bdf287813088f279029a44bcf02d46c7 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:15 +0530 Subject: [PATCH 05/18] feat(cli): add SingleStore Helios provider setup and dependency management --- .../singlestore-helios-setup.ts | 80 +++++++++++++++++++ .../helpers/project-generation/env-setup.ts | 1 + apps/cli/src/helpers/setup/db-setup.ts | 9 +++ 3 files changed, 90 insertions(+) create mode 100644 apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts 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..028b4c965 --- /dev/null +++ b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts @@ -0,0 +1,80 @@ +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=require", + 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); + + // For now, we'll create a default .env with placeholder values + // In the future, this could integrate with SingleStore CLI if available + 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 {} + } +} \ No newline at end of file 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..36233501e 100644 --- a/apps/cli/src/helpers/setup/db-setup.ts +++ b/apps/cli/src/helpers/setup/db-setup.ts @@ -12,6 +12,7 @@ import { setupNeonPostgres } from "../database-providers/neon-setup"; import { setupPrismaPostgres } from "../database-providers/prisma-postgres-setup"; import { setupSupabase } from "../database-providers/supabase-setup"; import { setupTurso } from "../database-providers/turso-setup"; +import { setupSingleStoreHelios } from "../database-providers/singlestore-helios-setup"; export async function setupDatabase(config: ProjectConfig) { const { database, orm, dbSetup, backend, projectDir } = config; @@ -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")); From 5d6f2712cadfe2c07eff985e3976191ae20b803d Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:21 +0530 Subject: [PATCH 06/18] test(cli): add comprehensive SingleStore database tests and validation --- apps/cli/test/cli.smoke.test.ts | 414 +++++++++++++++++++++++++ apps/cli/test/programmatic-api.test.ts | 30 ++ 2 files changed, 444 insertions(+) diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 8fe3c1080..1de61a074 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -1146,6 +1146,318 @@ 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"); + + // Check drizzle.config.ts has correct singlestore dialect + const drizzleConfigPath = join(serverDir, "drizzle.config.ts"); + expect(existsSync(drizzleConfigPath)).toBe(true); + const drizzleConfig = readFileSync(drizzleConfigPath, "utf8"); + expect(drizzleConfig).toContain('dialect: "singlestore"'); + + // Check db connection uses mysql2 driver for 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'); + + // Check mysql2 dependency is included + 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"); + + // Check schema directory exists + expect(existsSync(schemaDir)).toBe(true); + + // Look for schema files that should use singlestoreTable + 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"); + } + }); + + 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"); + + // Check .env file exists and has correct SingleStore format + expect(existsSync(envPath)).toBe(true); + const envContent = readFileSync(envPath, "utf8"); + expect(envContent).toContain("DATABASE_URL"); + + // Find the DATABASE_URL line and verify SingleStore format with SSL + 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/); + + // Verify SSL parameter is included (required for SingleStore cloud) + expect(envContent).toContain("?ssl="); + }); + + // Phase 7: Advanced Testing & Integration - SingleStore Matrix Testing + 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"], + }); + + // Check that TODO example schema uses SingleStore table structure + 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 +1703,106 @@ 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 incompatible frontend and API combinations", async () => { await runCliExpectingError( [ @@ -2437,6 +2849,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 +2872,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", { From 1fdac042749642a574cd8b853e0dec8da83c5114 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:28 +0530 Subject: [PATCH 07/18] feat(web): add SingleStore database support to stack builder with option filtering --- .../app/(home)/_components/stack-builder.tsx | 102 +++++++++++++++++- apps/web/src/lib/constant.ts | 14 +++ 2 files changed, 115 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index cc24712e7..434f33869 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -382,6 +382,39 @@ 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 === "none") { + 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)", + }); + } } else { if (nextStack.orm === "mongoose") { notes.database.notes.push( @@ -516,6 +549,39 @@ 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 +735,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { }); } + if (nextStack.dbSetup === "docker") { notes.runtime.notes.push( "Cloudflare Workers runtime does not support Docker setup. D1 will be selected.", @@ -945,6 +1012,7 @@ const generateCommand = (stackState: StackState): string => { "supabase", "prisma-postgres", "mongodb-atlas", + "singlestore-helios", "docker", ].includes(stackState.dbSetup); @@ -1635,7 +1703,39 @@ const StackBuilder = () => { TECH_OPTIONS[categoryKey as keyof typeof TECH_OPTIONS] || []; const categoryDisplayName = getCategoryDisplayName(categoryKey); - const filteredOptions = categoryOptions.filter(() => { + const filteredOptions = categoryOptions.filter((option) => { + // Filter ORM options based on database selection + 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" || option.id === "none"; + } + } + + // Filter dbSetup options based on database selection + if (categoryKey === "dbSetup") { + if (stack.database === "singlestore") { + return option.id === "singlestore-helios" || option.id === "none"; + } + 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..88a78b76c 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 SingleStore database on Helios", + icon: `${ICON_BASE_URL}/singlestore.svg`, + color: "from-purple-500 to-purple-700", + }, { id: "docker", name: "Docker", From b0eae641ae8aa269e4dc67b502554ffe2535091f Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:35 +0530 Subject: [PATCH 08/18] docs(cli): add SingleStore database setup guide and compatibility notes --- apps/cli/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/cli/README.md b/apps/cli/README.md index 419039be4..129e24a31 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 (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. Automatically sets database setup to SingleStore Helios (cloud). - **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 From 4f318a321fc2f08d26ef0916f48a936846e8392f Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:42 +0530 Subject: [PATCH 09/18] docs: add SingleStore database to CLI documentation and compatibility guides --- apps/web/content/docs/cli/compatibility.mdx | 7 +++++-- apps/web/content/docs/cli/index.mdx | 4 ++-- apps/web/content/docs/cli/options.mdx | 2 ++ apps/web/content/docs/cli/programmatic-api.mdx | 2 +- apps/web/content/docs/compatibility.mdx | 1 + apps/web/content/docs/index.mdx | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/web/content/docs/cli/compatibility.mdx b/apps/web/content/docs/cli/compatibility.mdx index b05811f0f..432009b48 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 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..7e88e1f9c 100644 --- a/apps/web/content/docs/cli/options.mdx +++ b/apps/web/content/docs/cli/options.mdx @@ -90,6 +90,7 @@ Database type to use: - `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 (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..b28a3a275 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 - **Runtime `none`**: Only with Convex backend or when backend is `none` ## Cloudflare Workers 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 From da684c94099a3f0c619bd3f3ac5e193cdf89b124 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:19:50 +0530 Subject: [PATCH 10/18] chore: update bun.lock for SingleStore dependencies --- bun.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", }, From 18b6804c738baddf98fbb84178dcc08da42eff8a Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 05:46:36 +0530 Subject: [PATCH 11/18] test(cli): fix SingleStore bigint assertion in TODO smoke test --- .../singlestore-helios-setup.ts | 20 ++++--- apps/cli/src/helpers/setup/db-setup.ts | 2 +- apps/cli/test/cli.smoke.test.ts | 54 +++++++++++-------- .../app/(home)/_components/stack-builder.tsx | 39 ++++++++++---- 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts index 028b4c965..1380a2161 100644 --- a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts +++ b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts @@ -12,13 +12,18 @@ type SingleStoreHeliosConfig = { connectionString: string; }; -async function writeEnvFile(projectDir: string, config?: SingleStoreHeliosConfig) { +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=require", + value: + config?.connectionString ?? + "singlestore://username:password@host:port/database?ssl=require", condition: true, }, ]; @@ -59,13 +64,14 @@ export async function setupSingleStoreHelios(config: ProjectConfig) { // For now, we'll create a default .env with placeholder values // In the future, this could integrate with SingleStore CLI if available await writeEnvFile(projectDir); - + log.success( - pc.green("SingleStore Helios setup complete! Please update the connection string in .env file.") + 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) { @@ -77,4 +83,4 @@ export async function setupSingleStoreHelios(config: ProjectConfig) { displayManualSetupInstructions(); } catch {} } -} \ No newline at end of file +} diff --git a/apps/cli/src/helpers/setup/db-setup.ts b/apps/cli/src/helpers/setup/db-setup.ts index 36233501e..a8be87e0c 100644 --- a/apps/cli/src/helpers/setup/db-setup.ts +++ b/apps/cli/src/helpers/setup/db-setup.ts @@ -10,9 +10,9 @@ 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"; -import { setupSingleStoreHelios } from "../database-providers/singlestore-helios-setup"; export async function setupDatabase(config: ProjectConfig) { const { database, orm, dbSetup, backend, projectDir } = config; diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 1de61a074..858f78372 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -1228,20 +1228,20 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); - + // Check drizzle.config.ts has correct singlestore dialect const drizzleConfigPath = join(serverDir, "drizzle.config.ts"); expect(existsSync(drizzleConfigPath)).toBe(true); const drizzleConfig = readFileSync(drizzleConfigPath, "utf8"); expect(drizzleConfig).toContain('dialect: "singlestore"'); - + // Check db connection uses mysql2 driver for 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'); - + expect(dbIndex).toContain("drizzle-orm/singlestore"); + expect(dbIndex).toContain("mysql2/promise"); + // Check mysql2 dependency is included const packageJsonPath = join(serverDir, "package.json"); expect(existsSync(packageJsonPath)).toBe(true); @@ -1249,7 +1249,6 @@ describe("create-better-t-stack smoke", () => { expect(packageJson.dependencies).toHaveProperty("mysql2"); }); - it("SingleStore project generates proper schema structure", async () => { const projectName = "app-singlestore-schema"; await runCli( @@ -1286,14 +1285,16 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); const schemaDir = join(serverDir, "src", "db", "schema"); - + // Check schema directory exists expect(existsSync(schemaDir)).toBe(true); - + // Look for schema files that should use singlestoreTable const schemaFiles = require("node:fs").readdirSync(schemaDir); - const schemaFile = schemaFiles.find((file: string) => file.endsWith(".ts")); - + const schemaFile = schemaFiles.find((file: string) => + file.endsWith(".ts"), + ); + if (schemaFile) { const schemaPath = join(schemaDir, schemaFile); const schemaContent = readFileSync(schemaPath, "utf8"); @@ -1337,29 +1338,31 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); const envPath = join(serverDir, ".env"); - + // Check .env file exists and has correct SingleStore format expect(existsSync(envPath)).toBe(true); const envContent = readFileSync(envPath, "utf8"); expect(envContent).toContain("DATABASE_URL"); - + // Find the DATABASE_URL line and verify SingleStore format with SSL - const lines = envContent.split('\n'); - const databaseUrlLine = lines.find(line => line.startsWith('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, ''); + + const databaseUrl = databaseUrlLine + ?.split("=", 2)[1] + ?.replace(/['"]/g, ""); expect(databaseUrl).toBeTruthy(); expect(databaseUrl).toMatch(/singlestore:\/\/.*\?ssl/); - + // Verify SSL parameter is included (required for SingleStore cloud) expect(envContent).toContain("?ssl="); }); // Phase 7: Advanced Testing & Integration - SingleStore Matrix Testing describe("SingleStore compatibility matrix", () => { - - it("scaffolds SingleStore with authentication enabled", async () => { const projectName = "singlestore-auth"; await runCli( @@ -1451,11 +1454,19 @@ describe("create-better-t-stack smoke", () => { }); // Check that TODO example schema uses SingleStore table structure - const todoSchemaPath = join(projectDir, "apps", "server", "src", "db", "schema", "todo.ts"); + 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()"); + expect(todoSchemaContent).toContain("bigint("); }); }); }); @@ -1802,7 +1813,6 @@ describe("create-better-t-stack smoke", () => { ); }); - it("rejects incompatible frontend and API combinations", async () => { await runCliExpectingError( [ diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index 434f33869..33a4cdf9f 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -412,7 +412,8 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { changed = true; changes.push({ category: "database", - message: "DB Setup set to 'SingleStore Helios' (recommended for SingleStore)", + message: + "DB Setup set to 'SingleStore Helios' (recommended for SingleStore)", }); } } else { @@ -551,7 +552,9 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { } } else if (nextStack.dbSetup === "singlestore-helios") { if (nextStack.database !== "singlestore") { - notes.dbSetup.notes.push("Requires SingleStore. It will be selected."); + notes.dbSetup.notes.push( + "Requires SingleStore. It will be selected.", + ); notes.database.notes.push( "SingleStore Helios setup requires SingleStore. It will be selected.", ); @@ -735,7 +738,6 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { }); } - if (nextStack.dbSetup === "docker") { notes.runtime.notes.push( "Cloudflare Workers runtime does not support Docker setup. D1 will be selected.", @@ -1707,35 +1709,52 @@ const StackBuilder = () => { // Filter ORM options based on database selection if (categoryKey === "orm") { if (stack.database === "mongodb") { - return option.id === "prisma" || option.id === "mongoose" || option.id === "none"; + return ( + option.id === "prisma" || + option.id === "mongoose" || + option.id === "none" + ); } if (stack.database === "singlestore") { return option.id === "drizzle" || option.id === "none"; } } - + // Filter dbSetup options based on database selection if (categoryKey === "dbSetup") { if (stack.database === "singlestore") { - return option.id === "singlestore-helios" || option.id === "none"; + return ( + option.id === "singlestore-helios" || + option.id === "none" + ); } if (stack.database === "sqlite") { - return ["turso", "d1", "docker", "none"].includes(option.id); + return ["turso", "d1", "docker", "none"].includes( + option.id, + ); } if (stack.database === "postgres") { - return ["neon", "supabase", "prisma-postgres", "docker", "none"].includes(option.id); + 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); + return ["mongodb-atlas", "docker", "none"].includes( + option.id, + ); } if (stack.database === "none") { return option.id === "none"; } } - + return true; }); From 80da37c261c0056d757f48ca5dba6c8085ead93c Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 07:20:24 +0530 Subject: [PATCH 12/18] fix(cli): update SingleStore connection string format and import path in templates --- .../helpers/database-providers/singlestore-helios-setup.ts | 4 ++-- .../todo/server/drizzle/singlestore/src/db/schema/todo.ts | 4 ++-- apps/cli/test/cli.smoke.test.ts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts index 1380a2161..fe5be882a 100644 --- a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts +++ b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts @@ -23,7 +23,7 @@ async function writeEnvFile( key: "DATABASE_URL", value: config?.connectionString ?? - "singlestore://username:password@host:port/database?ssl=require", + "singlestore://username:password@host:port/database?ssl={}", condition: true, }, ]; @@ -48,7 +48,7 @@ ${pc.green("SingleStore Helios Manual Setup Instructions:")} 4. Add the connection string to your .env file: ${pc.dim('DATABASE_URL="your_connection_string"')} -${pc.yellow("Important:")} +${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`); 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 index 00e342641..025d6c29f 100644 --- 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 @@ -1,7 +1,7 @@ -import { singlestoreTable, varchar, bigint, boolean } from "drizzle-orm/singlestore"; +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(), -}); \ No newline at end of file +}); diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 858f78372..661587be3 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -1299,6 +1299,8 @@ describe("create-better-t-stack smoke", () => { 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"'); } }); From 6ad6671897aaa2106d400899c054a371fef9ea70 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 07:28:56 +0530 Subject: [PATCH 13/18] refactor(cli): remove outdated comments in SingleStore setup and smoke tests for clarity --- .../singlestore-helios-setup.ts | 3 --- apps/cli/test/cli.smoke.test.ts | 18 ------------------ .../app/(home)/_components/stack-builder.tsx | 2 -- 3 files changed, 23 deletions(-) diff --git a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts index fe5be882a..b06e08f2d 100644 --- a/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts +++ b/apps/cli/src/helpers/database-providers/singlestore-helios-setup.ts @@ -60,9 +60,6 @@ export async function setupSingleStoreHelios(config: ProjectConfig) { try { const serverDir = path.join(projectDir, "apps/server"); await fs.ensureDir(serverDir); - - // For now, we'll create a default .env with placeholder values - // In the future, this could integrate with SingleStore CLI if available await writeEnvFile(projectDir); log.success( diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 661587be3..25af4c103 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -1228,21 +1228,15 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); - - // Check drizzle.config.ts has correct singlestore dialect const drizzleConfigPath = join(serverDir, "drizzle.config.ts"); expect(existsSync(drizzleConfigPath)).toBe(true); const drizzleConfig = readFileSync(drizzleConfigPath, "utf8"); expect(drizzleConfig).toContain('dialect: "singlestore"'); - - // Check db connection uses mysql2 driver for 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"); - - // Check mysql2 dependency is included const packageJsonPath = join(serverDir, "package.json"); expect(existsSync(packageJsonPath)).toBe(true); const packageJson = readJsonSync(packageJsonPath); @@ -1285,11 +1279,7 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); const schemaDir = join(serverDir, "src", "db", "schema"); - - // Check schema directory exists expect(existsSync(schemaDir)).toBe(true); - - // Look for schema files that should use singlestoreTable const schemaFiles = require("node:fs").readdirSync(schemaDir); const schemaFile = schemaFiles.find((file: string) => file.endsWith(".ts"), @@ -1340,13 +1330,9 @@ describe("create-better-t-stack smoke", () => { const projectDir = join(workdir, projectName); const serverDir = join(projectDir, "apps", "server"); const envPath = join(serverDir, ".env"); - - // Check .env file exists and has correct SingleStore format expect(existsSync(envPath)).toBe(true); const envContent = readFileSync(envPath, "utf8"); expect(envContent).toContain("DATABASE_URL"); - - // Find the DATABASE_URL line and verify SingleStore format with SSL const lines = envContent.split("\n"); const databaseUrlLine = lines.find((line) => line.startsWith("DATABASE_URL="), @@ -1358,12 +1344,9 @@ describe("create-better-t-stack smoke", () => { ?.replace(/['"]/g, ""); expect(databaseUrl).toBeTruthy(); expect(databaseUrl).toMatch(/singlestore:\/\/.*\?ssl/); - - // Verify SSL parameter is included (required for SingleStore cloud) expect(envContent).toContain("?ssl="); }); - // Phase 7: Advanced Testing & Integration - SingleStore Matrix Testing describe("SingleStore compatibility matrix", () => { it("scaffolds SingleStore with authentication enabled", async () => { const projectName = "singlestore-auth"; @@ -1455,7 +1438,6 @@ describe("create-better-t-stack smoke", () => { examples: ["todo"], }); - // Check that TODO example schema uses SingleStore table structure const todoSchemaPath = join( projectDir, "apps", diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index 33a4cdf9f..d230922ff 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -1706,7 +1706,6 @@ const StackBuilder = () => { const categoryDisplayName = getCategoryDisplayName(categoryKey); const filteredOptions = categoryOptions.filter((option) => { - // Filter ORM options based on database selection if (categoryKey === "orm") { if (stack.database === "mongodb") { return ( @@ -1720,7 +1719,6 @@ const StackBuilder = () => { } } - // Filter dbSetup options based on database selection if (categoryKey === "dbSetup") { if (stack.database === "singlestore") { return ( From 66555cc50cacbcd198417764e9d0106d10efa020 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 08:17:06 +0530 Subject: [PATCH 14/18] fix(cli): enforce compatibility rules for SingleStore with Cloudflare Workers and update documentation --- apps/cli/src/utils/compatibility-rules.ts | 20 +++++++++++++++++++ .../drizzle/singlestore/src/db/index.ts.hbs | 12 ----------- apps/web/content/docs/compatibility.mdx | 2 +- .../app/(home)/_components/stack-builder.tsx | 18 +++++++++++++++++ 4 files changed, 39 insertions(+), 13 deletions(-) diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index 693dc5d8e..c4447015e 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -129,6 +129,26 @@ 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( 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 index bfc774119..a0be3fdb1 100644 --- a/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs +++ b/apps/cli/templates/db/drizzle/singlestore/src/db/index.ts.hbs @@ -1,18 +1,6 @@ -{{#if (or (eq runtime "bun") (eq runtime "node"))}} 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 }); -{{/if}} - -{{#if (eq runtime "workers")}} -import mysql from "mysql2/promise"; -import { drizzle } from "drizzle-orm/singlestore"; -import { env } from "cloudflare:workers"; - -const pool = mysql.createPool(env.DATABASE_URL || ""); - -export const db = drizzle({ client: pool }); -{{/if}} \ No newline at end of file diff --git a/apps/web/content/docs/compatibility.mdx b/apps/web/content/docs/compatibility.mdx index b28a3a275..1a25f260d 100644 --- a/apps/web/content/docs/compatibility.mdx +++ b/apps/web/content/docs/compatibility.mdx @@ -19,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/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index d230922ff..b6f5e20fa 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -738,6 +738,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.", From 7e11389b88d79bb0c0db108b90c74e464ea554d7 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 08:32:07 +0530 Subject: [PATCH 15/18] docs(cli): clarify SingleStore database description and update compatibility notes in documentation --- apps/cli/README.md | 2 +- apps/cli/src/prompts/database.ts | 10 +++++----- apps/cli/src/utils/compatibility-rules.ts | 4 ++-- apps/web/content/docs/cli/compatibility.mdx | 2 +- apps/web/content/docs/cli/options.mdx | 4 ++-- apps/web/src/app/(home)/_components/stack-builder.tsx | 7 ++----- apps/web/src/lib/constant.ts | 2 +- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/apps/cli/README.md b/apps/cli/README.md index 129e24a31..f49543577 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -38,7 +38,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default | **Runtime** | • Bun
• Node.js
• Cloudflare Workers
• 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
• SingleStore Helios (SingleStore)
• 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) | diff --git a/apps/cli/src/prompts/database.ts b/apps/cli/src/prompts/database.ts index f7e69b847..6e0f645e4 100644 --- a/apps/cli/src/prompts/database.ts +++ b/apps/cli/src/prompts/database.ts @@ -39,11 +39,6 @@ export async function getDatabaseChoice( label: "MySQL", hint: "popular open-source relational database system", }, - { - value: "singlestore", - label: "SingleStore", - hint: "high-performance distributed SQL database for real-time analytics", - }, ]; if (runtime !== "workers") { @@ -52,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/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index c4447015e..33fa7c645 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -163,7 +163,7 @@ export function validateSingleStoreCompatibility( config.orm !== "drizzle" ) { exitWithError( - `SingleStore database is only compatible with Drizzle ORM. Current ORM: ${config.orm}. Please use '--orm drizzle' or choose a different database.`, + `SingleStore database requires Drizzle ORM. Current ORM: ${config.orm}. Please use '--orm drizzle' or choose a different database.`, ); } @@ -174,7 +174,7 @@ export function validateSingleStoreCompatibility( config.database === "singlestore" ) { exitWithError( - `ORM '${config.orm}' is not compatible with SingleStore database. SingleStore only supports Drizzle ORM. Please use '--orm drizzle' or choose a different database.`, + `ORM '${config.orm}' is not compatible with SingleStore database. SingleStore requires Drizzle ORM. Please use '--orm drizzle' or choose a different database.`, ); } diff --git a/apps/web/content/docs/cli/compatibility.mdx b/apps/web/content/docs/cli/compatibility.mdx index 432009b48..a5628b039 100644 --- a/apps/web/content/docs/cli/compatibility.mdx +++ b/apps/web/content/docs/cli/compatibility.mdx @@ -129,7 +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 SingleStore database | +| `singlestore-helios` | `singlestore` | Cloud-hosted SingleStore database | | `docker` | `postgres`, `mysql`, `mongodb` | Not compatible with `sqlite` or Workers | ### Special Cases diff --git a/apps/web/content/docs/cli/options.mdx b/apps/web/content/docs/cli/options.mdx index 7e88e1f9c..96bcdf17a 100644 --- a/apps/web/content/docs/cli/options.mdx +++ b/apps/web/content/docs/cli/options.mdx @@ -87,7 +87,7 @@ 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 @@ -120,7 +120,7 @@ Database hosting/setup provider: - `supabase`: Supabase (PostgreSQL) - `prisma-postgres`: Prisma Postgres via Prisma Accelerate - `mongodb-atlas`: MongoDB Atlas -- `singlestore-helios`: SingleStore Helios (SingleStore) +- `singlestore-helios`: SingleStore Helios (cloud-hosted SingleStore) - `docker`: Local Docker containers ```bash diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index b6f5e20fa..2147f1ef9 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -1733,16 +1733,13 @@ const StackBuilder = () => { ); } if (stack.database === "singlestore") { - return option.id === "drizzle" || option.id === "none"; + return option.id === "drizzle"; } } if (categoryKey === "dbSetup") { if (stack.database === "singlestore") { - return ( - option.id === "singlestore-helios" || - option.id === "none" - ); + return option.id === "singlestore-helios"; } if (stack.database === "sqlite") { return ["turso", "d1", "docker", "none"].includes( diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index 88a78b76c..0a35e33ef 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -338,7 +338,7 @@ export const TECH_OPTIONS: Record< { id: "singlestore-helios", name: "SingleStore Helios", - description: "Cloud SingleStore database on Helios", + description: "Cloud-hosted SingleStore database on Helios", icon: `${ICON_BASE_URL}/singlestore.svg`, color: "from-purple-500 to-purple-700", }, From fdcd32c03a57f0cbd53c4537d92873ee001fbd26 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 08:38:26 +0530 Subject: [PATCH 16/18] fix(cli): enforce SingleStore Helios setup requirement and update related documentation to omit None --- apps/cli/README.md | 2 +- apps/cli/src/prompts/database-setup.ts | 9 +------ apps/cli/src/validation.ts | 11 +++++++++ apps/cli/test/cli.smoke.test.ts | 33 +++++++++++++++++++++++++ apps/web/content/docs/compatibility.mdx | 2 +- 5 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/cli/README.md b/apps/cli/README.md index f49543577..00bcb3f3d 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -196,7 +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. Automatically sets database setup to SingleStore Helios (cloud). +- **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/prompts/database-setup.ts b/apps/cli/src/prompts/database-setup.ts index aa7fdd759..a21f3038a 100644 --- a/apps/cli/src/prompts/database-setup.ts +++ b/apps/cli/src/prompts/database-setup.ts @@ -92,14 +92,7 @@ export async function getDBSetupChoice( { value: "none" as const, label: "None", hint: "Manual setup" }, ]; } else if (databaseType === "singlestore") { - options = [ - { - value: "singlestore-helios" as const, - label: "SingleStore Helios", - hint: "Managed SingleStore cloud platform", - }, - { value: "none" as const, label: "None", hint: "Manual setup" }, - ]; + return "singlestore-helios"; } else { return "none"; } diff --git a/apps/cli/src/validation.ts b/apps/cli/src/validation.ts index 22a51a89f..613153d5a 100644 --- a/apps/cli/src/validation.ts +++ b/apps/cli/src/validation.ts @@ -373,6 +373,17 @@ export function processAndValidateFlags( ); } + 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")) || diff --git a/apps/cli/test/cli.smoke.test.ts b/apps/cli/test/cli.smoke.test.ts index 25af4c103..6eb50c60d 100644 --- a/apps/cli/test/cli.smoke.test.ts +++ b/apps/cli/test/cli.smoke.test.ts @@ -1797,6 +1797,39 @@ describe("create-better-t-stack smoke", () => { ); }); + 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( [ diff --git a/apps/web/content/docs/compatibility.mdx b/apps/web/content/docs/compatibility.mdx index 1a25f260d..8f6ccb780 100644 --- a/apps/web/content/docs/compatibility.mdx +++ b/apps/web/content/docs/compatibility.mdx @@ -11,7 +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 +- **SingleStore database**: Only compatible with Drizzle ORM; requires SingleStore Helios setup - **Runtime `none`**: Only with Convex backend or when backend is `none` ## Cloudflare Workers From d51747eb42e77fd1f8ce5e0b410cecc8ff691993 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 08:44:36 +0530 Subject: [PATCH 17/18] chore: update changeset configuration and add support for SingleStore Helios database --- .changeset/config.json | 2 +- .changeset/kind-geese-sell.md | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/kind-geese-sell.md 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 From 994377473a6264087ab445af9134894fc69a1cb3 Mon Sep 17 00:00:00 2001 From: theskinnycoder Date: Sat, 16 Aug 2025 08:57:17 +0530 Subject: [PATCH 18/18] fix(cli): enhance compatibility checks for SingleStore with any other provider and update database selection logic --- .../app/(home)/_components/stack-builder.tsx | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/web/src/app/(home)/_components/stack-builder.tsx b/apps/web/src/app/(home)/_components/stack-builder.tsx index 2147f1ef9..abf0e7081 100644 --- a/apps/web/src/app/(home)/_components/stack-builder.tsx +++ b/apps/web/src/app/(home)/_components/stack-builder.tsx @@ -399,7 +399,7 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { message: "ORM set to 'Drizzle' (SingleStore requires Drizzle)", }); } - if (nextStack.dbSetup === "none") { + if (nextStack.dbSetup !== "singlestore-helios") { notes.database.notes.push( "SingleStore database selected: DB Setup will be set to 'SingleStore Helios'.", ); @@ -416,6 +416,23 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => { "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(