diff --git a/apps/cli/src/helpers/core/install-dependencies.ts b/apps/cli/src/helpers/core/install-dependencies.ts
index 6433cd539..c6832bef0 100644
--- a/apps/cli/src/helpers/core/install-dependencies.ts
+++ b/apps/cli/src/helpers/core/install-dependencies.ts
@@ -66,6 +66,19 @@ export function getInstallArgs(packageManager: PackageManager): string[] {
return ["install"];
}
+export function getInstallRetryArgs(
+ packageManager: PackageManager,
+ error: unknown,
+): string[] | null {
+ if (packageManager !== "bun") return null;
+
+ const stderr = (error as { stderr?: unknown })?.stderr;
+ const output = typeof stderr === "string" ? stderr : toErrorMessage(error);
+ return /No version matching .+ found for specifier .+\(but package exists\)/is.test(output)
+ ? ["install", "--force"]
+ : null;
+}
+
export async function installDependencies({
projectDir,
packageManager,
@@ -81,14 +94,23 @@ export async function installDependencies({
s.start(`Running ${packageManager} install...`);
const installArgs = getInstallArgs(packageManager);
- await $({
- cwd: projectDir,
- env: {
- ...process.env,
- ...getInstallEnvironment(packageManager),
- },
- stderr: ["inherit", "pipe"],
- })`${packageManager} ${installArgs}`;
+ const runInstall = async (args: string[]) =>
+ $({
+ cwd: projectDir,
+ env: {
+ ...process.env,
+ ...getInstallEnvironment(packageManager),
+ },
+ stderr: ["inherit", "pipe"],
+ })`${packageManager} ${args}`;
+
+ try {
+ await runInstall(installArgs);
+ } catch (error) {
+ const retryArgs = getInstallRetryArgs(packageManager, error);
+ if (!retryArgs) throw error;
+ await runInstall(retryArgs);
+ }
s.stop("Dependencies installed successfully");
return { step, success: true };
diff --git a/apps/cli/test/generation/install-dependencies.test.ts b/apps/cli/test/generation/install-dependencies.test.ts
index 0ce5fd44f..4f68d39ea 100644
--- a/apps/cli/test/generation/install-dependencies.test.ts
+++ b/apps/cli/test/generation/install-dependencies.test.ts
@@ -8,6 +8,7 @@ import { setupDatabase } from "@/helpers/core/db-setup";
import {
getInstallArgs,
getInstallEnvironment,
+ getInstallRetryArgs,
installDependencies,
} from "@/helpers/core/install-dependencies";
@@ -38,6 +39,22 @@ describe("getInstallArgs", () => {
});
});
+describe("getInstallRetryArgs", () => {
+ it("refreshes Bun metadata after a registry propagation miss", () => {
+ const error = {
+ stderr:
+ 'error: No version matching "4.0.71" found for specifier "@ai-sdk/gateway" (but package exists)',
+ };
+
+ expect(getInstallRetryArgs("bun", error)).toEqual(["install", "--force"]);
+ expect(getInstallRetryArgs("npm", error)).toBeNull();
+ });
+
+ it("does not retry unrelated Bun install failures", () => {
+ expect(getInstallRetryArgs("bun", new Error("network unavailable"))).toBeNull();
+ });
+});
+
describe("installDependencies", () => {
it("returns a failure result instead of swallowing the error when install fails", async () => {
// A non-existent cwd makes the install command fail fast (ENOENT) without
diff --git a/apps/cli/test/support/__snapshots__/template-snapshots.test.ts.snap b/apps/cli/test/support/__snapshots__/template-snapshots.test.ts.snap
index a3a217f57..15a72346d 100644
--- a/apps/cli/test/support/__snapshots__/template-snapshots.test.ts.snap
+++ b/apps/cli/test/support/__snapshots__/template-snapshots.test.ts.snap
@@ -47,6 +47,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: tanstack-ro
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -112,6 +114,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: next-self-f
"packages/db/src/index.ts",
"packages/db/src/schema/auth.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -155,6 +159,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: astro-react
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -198,6 +204,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: nuxt-standa
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -250,6 +258,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: solid-start
"packages/db/src/index.ts",
"packages/db/src/schema/auth.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -363,6 +373,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: hono-bun-or
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -428,6 +440,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: hono-openap
"packages/db/src/index.ts",
"packages/db/src/schema/auth.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -492,6 +506,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: hono-apollo
"packages/db/src/index.ts",
"packages/db/src/schema/auth.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -633,6 +649,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: xendit-sign
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -773,6 +791,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: better-auth
"packages/db/src/index.ts",
"packages/db/src/schema/auth.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1101,6 +1121,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: algolia-sea
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1159,6 +1181,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: opensearch-
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1217,6 +1241,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: nango-integ
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1275,6 +1301,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: medusa-ecom
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1335,6 +1363,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: cms-keystat
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1395,6 +1425,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: i18n-paragl
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1453,6 +1485,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: ai-cli-root
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1511,6 +1545,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: nx-root-too
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1608,6 +1644,8 @@ exports[`Template Snapshots File Structure Snapshots file structure: native-reac
"packages/db/src/index.ts",
"packages/db/src/schema/example.ts",
"packages/db/src/schema/index.ts",
+ "packages/db/src/schema/task.ts",
+ "packages/db/src/tasks.ts",
"packages/db/tsconfig.json",
"packages/env/package.json",
"packages/env/src/native.ts",
@@ -1731,7 +1769,7 @@ exports[`Template Snapshots File Structure Snapshots file structure: java-spring
exports[`Template Snapshots Key File Content Snapshots key files: tanstack-router-minimal 1`] = `
{
- "fileCount": 57,
+ "fileCount": 59,
"files": [
{
"content": "[exists]",
@@ -2014,52 +2052,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -2273,6 +2409,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-tanstack-router-minimal/db/tasks";
//
@@ -2285,6 +2429,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -2433,6 +2589,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -2441,6 +2598,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -2523,7 +2688,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: next-self-fullstack 1`] = `
{
- "fileCount": 66,
+ "fileCount": 68,
"files": [
{
"content": "[exists]",
@@ -2942,6 +3107,14 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
protectedProcedure, publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-next-self-fullstack/db/tasks";
//
@@ -2954,6 +3127,20 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: protectedProcedure.query(({ ctx }) => listTasks(ctx.session.user.id)),
+ create: protectedProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ ctx, input }) => createTask(ctx.session.user.id, input.title)),
+ setCompleted: protectedProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ ctx, input }) =>
+ setTaskCompleted(ctx.session.user.id, input.id, input.completed),
+ ),
+ remove: protectedProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ ctx, input }) => deleteTask(ctx.session.user.id, input.id)),
+ }),
privateData: protectedProcedure.query(({ ctx }) => {
return {
message: "This is private",
@@ -3285,6 +3472,7 @@ export const accountRelations = relations(account, ({ one }) => ({
{
"content":
"export * from "./auth";
+export * from "./task";
//
@@ -3293,6 +3481,14 @@ export const accountRelations = relations(account, ({ one }) => ({
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -3376,7 +3572,7 @@ export const accountRelations = relations(account, ({ one }) => ({
exports[`Template Snapshots Key File Content Snapshots key files: astro-react-integration 1`] = `
{
- "fileCount": 43,
+ "fileCount": 45,
"files": [
{
"content": "[exists]",
@@ -3782,6 +3978,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -3790,6 +3987,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -3871,7 +4076,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: nuxt-standalone 1`] = `
{
- "fileCount": 43,
+ "fileCount": 45,
"files": [
{
"content": "[exists]",
@@ -4316,6 +4521,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -4324,6 +4530,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -4407,7 +4621,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: solid-start-v2-fullstack 1`] = `
{
- "fileCount": 52,
+ "fileCount": 54,
"files": [
{
"content": "[exists]",
@@ -5170,6 +5384,7 @@ export const accountRelations = relations(account, ({ one }) => ({
{
"content":
"export * from "./auth";
+export * from "./task";
//
@@ -5178,6 +5393,14 @@ export const accountRelations = relations(account, ({ one }) => ({
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -5544,52 +5767,47 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { trpc } from "@/utils/trpc";
import { useQuery } from "@tanstack/react-query";
+import { trpc } from "@/utils/trpc";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -6022,7 +6240,7 @@ export default prisma;
exports[`Template Snapshots Key File Content Snapshots key files: hono-bun-orpc 1`] = `
{
- "fileCount": 57,
+ "fileCount": 59,
"files": [
{
"content": "[exists]",
@@ -6345,52 +6563,47 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { orpc } from "@/utils/orpc";
import { useQuery } from "@tanstack/react-query";
+import { orpc } from "@/utils/orpc";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery(orpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -6754,6 +6967,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -6762,6 +6976,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -6844,7 +7066,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: hono-openapi 1`] = `
{
- "fileCount": 66,
+ "fileCount": 68,
"files": [
{
"content": "[exists]",
@@ -7202,28 +7424,14 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { env } from "@snapshot-hono-openapi/env/web";
import { useEffect, useState } from "react";
+import { env } from "@snapshot-hono-openapi/env/web";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const [healthCheck, setHealthCheck] = useState({ isLoading: true, data: false });
@@ -7242,25 +7450,34 @@ function HomeComponent() {
}, []);
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -7904,6 +8121,7 @@ export const accountRelations = relations(account, ({ one }) => ({
{
"content":
"export * from "./auth";
+export * from "./task";
//
@@ -7912,6 +8130,14 @@ export const accountRelations = relations(account, ({ one }) => ({
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -7994,7 +8220,7 @@ export const accountRelations = relations(account, ({ one }) => ({
exports[`Template Snapshots Key File Content Snapshots key files: hono-apollo-server 1`] = `
{
- "fileCount": 65,
+ "fileCount": 67,
"files": [
{
"content": "[exists]",
@@ -8296,28 +8522,14 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { graphqlFetch } from "@/utils/graphql";
import { useQuery } from "@tanstack/react-query";
+import { graphqlFetch } from "@/utils/graphql";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery({
@@ -8326,25 +8538,34 @@ function HomeComponent() {
});
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -8996,6 +9217,7 @@ export const accountRelations = relations(account, ({ one }) => ({
{
"content":
"export * from "./auth";
+export * from "./task";
//
@@ -9004,6 +9226,14 @@ export const accountRelations = relations(account, ({ one }) => ({
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -9086,7 +9316,7 @@ export const accountRelations = relations(account, ({ one }) => ({
exports[`Template Snapshots Key File Content Snapshots key files: better-auth-full 1`] = `
{
- "fileCount": 67,
+ "fileCount": 69,
"files": [
{
"content": "[exists]",
@@ -9396,52 +9626,180 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { Link } from "@tanstack/react-router";
+import { useState } from "react";
+
+import { authClient } from "@/lib/auth-client";
+import { trpc, trpcClient } from "@/utils/trpc";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const { data: session, isPending: sessionPending } = authClient.useSession();
+ const userId = session?.user.id;
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ // The user id is part of the cache key, so a session change can never render
+ // rows that were fetched for the previous account.
+ const tasksQueryKey = ["tasks", "list", userId] as const;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+ const tasks = useQuery({
+ queryKey: tasksQueryKey,
+ queryFn: () => trpcClient.tasks.list.query(),
+ enabled: Boolean(userId),
+ });
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
+
+ if (sessionPending) {
+ return Checking your session...
;
+ }
+
+ if (!userId) {
+ return (
+
+
+ Tasks are stored per account. Sign in to create your first one.
+
+
+ Sign in
+
+
+ );
+ }
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -9682,6 +10040,14 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
protectedProcedure, publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-better-auth-full/db/tasks";
//
@@ -9694,6 +10060,20 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: protectedProcedure.query(({ ctx }) => listTasks(ctx.session.user.id)),
+ create: protectedProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ ctx, input }) => createTask(ctx.session.user.id, input.title)),
+ setCompleted: protectedProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ ctx, input }) =>
+ setTaskCompleted(ctx.session.user.id, input.id, input.completed),
+ ),
+ remove: protectedProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ ctx, input }) => deleteTask(ctx.session.user.id, input.id)),
+ }),
privateData: protectedProcedure.query(({ ctx }) => {
return {
message: "This is private",
@@ -10031,6 +10411,7 @@ export const accountRelations = relations(account, ({ one }) => ({
{
"content":
"export * from "./auth";
+export * from "./task";
//
@@ -10039,6 +10420,14 @@ export const accountRelations = relations(account, ({ one }) => ({
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -10307,51 +10696,46 @@ if (!rootElement.innerHTML) {
"content":
"import { createFileRoute } from "@tanstack/react-router";
import { useQuery } from "convex/react";
+
import { api } from "@snapshot-convex-clerk/backend/convex/_generated/api";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery(api.healthCheck.get);
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck === undefined
- ? "Checking..."
- : healthCheck === "OK"
- ? "Connected"
- : "Error"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -12266,52 +12650,47 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { trpc } from "@/utils/trpc";
import { useQuery } from "@tanstack/react-query";
+import { trpc } from "@/utils/trpc";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -13005,52 +13384,47 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
-import { trpc } from "@/utils/trpc";
import { useQuery } from "@tanstack/react-query";
+import { trpc } from "@/utils/trpc";
+
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
-
-
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -13483,7 +13857,7 @@ export default prisma;
exports[`Template Snapshots Key File Content Snapshots key files: algolia-search-hono 1`] = `
{
- "fileCount": 58,
+ "fileCount": 60,
"files": [
{
"content": "[exists]",
@@ -13771,52 +14145,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -14030,6 +14502,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-algolia-search-hono/db/tasks";
//
@@ -14042,6 +14522,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -14190,6 +14682,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -14198,6 +14691,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -14280,7 +14781,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: opensearch-search-hono 1`] = `
{
- "fileCount": 58,
+ "fileCount": 60,
"files": [
{
"content": "[exists]",
@@ -14568,52 +15069,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -14827,6 +15426,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-opensearch-search-hono/db/tasks";
//
@@ -14839,6 +15446,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -14987,6 +15606,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -14995,6 +15615,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -15077,7 +15705,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: cms-keystatic-next 1`] = `
{
- "fileCount": 60,
+ "fileCount": 62,
"files": [
{
"content": "[exists]",
@@ -15467,6 +16095,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-cms-keystatic-next/db/tasks";
//
@@ -15479,6 +16115,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -15627,6 +16275,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -15635,6 +16284,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -15718,7 +16375,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: i18n-paraglide-tanstack-router 1`] = `
{
- "fileCount": 60,
+ "fileCount": 62,
"files": [
{
"content": "[exists]",
@@ -16014,52 +16671,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -16279,6 +17034,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-i18n-paraglide-tanstack-router/db/tasks";
//
@@ -16291,6 +17054,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -16439,6 +17214,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -16447,6 +17223,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -16529,7 +17313,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: ai-cli-root-tooling 1`] = `
{
- "fileCount": 58,
+ "fileCount": 60,
"files": [
{
"content": "[exists]",
@@ -16816,52 +17600,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -17081,6 +17963,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-ai-cli-root-tooling/db/tasks";
//
@@ -17093,6 +17983,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -17241,6 +18143,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -17249,6 +18152,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -17331,7 +18242,7 @@ export const db = drizzle({ client, schema });
exports[`Template Snapshots Key File Content Snapshots key files: nx-root-tooling 1`] = `
{
- "fileCount": 58,
+ "fileCount": 60,
"files": [
{
"content": "[exists]",
@@ -17614,52 +18525,150 @@ if (!rootElement.innerHTML) {
{
"content":
"import { createFileRoute } from "@tanstack/react-router";
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { useState } from "react";
+
import { trpc } from "@/utils/trpc";
-import { useQuery } from "@tanstack/react-query";
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ const tasksQueryKey = trpc.tasks.list.queryKey();
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
-function HomeComponent() {
- const healthCheck = useQuery(trpc.healthCheck.queryOptions());
return (
-
-
{TITLE_TEXT}
-
-
- API Status
-
-
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
-
-
+
+
+ ))}
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+
+function HomeComponent() {
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
+
+ return (
+
+
+
+
+
+
);
}
@@ -17878,6 +18887,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-nx-root-tooling/db/tasks";
//
@@ -17890,6 +18907,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -18038,6 +19067,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -18046,6 +19076,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
@@ -18295,32 +19333,28 @@ export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = \`
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- \`;
function HomeComponent() {
return (
-
-
{TITLE_TEXT}
-
-
-
+
+
+ snapshot-frontend-only-no-backend
+
+ TanStack Router.
+
+
+
+
+ Tasks
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+
+
+
);
}
@@ -18533,7 +19567,7 @@ export default defineConfig({
exports[`Template Snapshots Key File Content Snapshots key files: native-react-native 1`] = `
{
- "fileCount": 67,
+ "fileCount": 69,
"files": [
{
"content": "[exists]",
@@ -19109,6 +20143,14 @@ export const publicProcedure = t.procedure;
publicProcedure,
router,
} from "../index";
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@snapshot-native-react-native/db/tasks";
//
@@ -19121,6 +20163,18 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ tasks: router({
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ }),
});
export type AppRouter = typeof appRouter;
"
@@ -19269,6 +20323,7 @@ export const db = drizzle({ client, schema });
{
"content":
"export * from "./example";
+export * from "./task";
//
@@ -19277,6 +20332,14 @@ export const db = drizzle({ client, schema });
,
"path": "packages/db/src/schema/index.ts",
},
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/schema/task.ts",
+ },
+ {
+ "content": "[exists]",
+ "path": "packages/db/src/tasks.ts",
+ },
{
"content":
"{
diff --git a/packages/template-generator/templates/api/trpc/server/src/routers/index.ts.hbs b/packages/template-generator/templates/api/trpc/server/src/routers/index.ts.hbs
index 45a511fb9..cadf32e98 100644
--- a/packages/template-generator/templates/api/trpc/server/src/routers/index.ts.hbs
+++ b/packages/template-generator/templates/api/trpc/server/src/routers/index.ts.hbs
@@ -29,6 +29,16 @@ import {
{{#if (isBetterAuth auth)}}protectedProcedure, {{/if}}publicProcedure,
router,
} from "../index{{#if (eq backend "adonisjs")}}.js{{/if}}";
+{{#if (and (eq orm "drizzle") (or (isBetterAuth auth) (eq auth "none")))}}
+import { z } from "zod";
+
+import {
+ createTask,
+ deleteTask,
+ listTasks,
+ setTaskCompleted,
+} from "@{{projectName}}/db/tasks";
+{{/if}}
//
@@ -41,6 +51,35 @@ export const appRouter = router({
healthCheck: publicProcedure.query(() => {
return "OK";
}),
+ {{#if (and (eq orm "drizzle") (or (isBetterAuth auth) (eq auth "none")))}}
+ tasks: router({
+ {{#if (isBetterAuth auth)}}
+ list: protectedProcedure.query(({ ctx }) => listTasks(ctx.session.user.id)),
+ create: protectedProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ ctx, input }) => createTask(ctx.session.user.id, input.title)),
+ setCompleted: protectedProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ ctx, input }) =>
+ setTaskCompleted(ctx.session.user.id, input.id, input.completed),
+ ),
+ remove: protectedProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ ctx, input }) => deleteTask(ctx.session.user.id, input.id)),
+ {{else}}
+ list: publicProcedure.query(() => listTasks()),
+ create: publicProcedure
+ .input(z.object({ title: z.string().trim().min(1).max(255) }))
+ .mutation(({ input }) => createTask(input.title)),
+ setCompleted: publicProcedure
+ .input(z.object({ id: z.number().int(), completed: z.boolean() }))
+ .mutation(({ input }) => setTaskCompleted(input.id, input.completed)),
+ remove: publicProcedure
+ .input(z.object({ id: z.number().int() }))
+ .mutation(({ input }) => deleteTask(input.id)),
+ {{/if}}
+ }),
+ {{/if}}
{{#if (isBetterAuth auth)}}
privateData: protectedProcedure.query(({ ctx }) => {
return {
diff --git a/packages/template-generator/templates/db/drizzle/base/src/schema/index.ts.hbs b/packages/template-generator/templates/db/drizzle/base/src/schema/index.ts.hbs
index 9a6aa9b34..da8b6d612 100644
--- a/packages/template-generator/templates/db/drizzle/base/src/schema/index.ts.hbs
+++ b/packages/template-generator/templates/db/drizzle/base/src/schema/index.ts.hbs
@@ -1,7 +1,10 @@
{{#if (or (isBetterAuth auth) (eq auth "nextauth"))}}
-export * from "./auth";
+export * from "./auth{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{else}}
-export * from "./example";
+export * from "./example{{#if (eq backend "adonisjs")}}.js{{/if}}";
+{{/if}}
+{{#if (or (isBetterAuth auth) (eq auth "none"))}}
+export * from "./task{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{/if}}
//
diff --git a/packages/template-generator/templates/db/drizzle/base/src/tasks.ts.hbs b/packages/template-generator/templates/db/drizzle/base/src/tasks.ts.hbs
new file mode 100644
index 000000000..74cd7d07d
--- /dev/null
+++ b/packages/template-generator/templates/db/drizzle/base/src/tasks.ts.hbs
@@ -0,0 +1,65 @@
+{{#if (or (isBetterAuth auth) (eq auth "none"))}}
+import { {{#if (isBetterAuth auth)}}and, {{/if}}desc, eq } from "drizzle-orm";
+
+import { db } from "./index{{#if (eq backend "adonisjs")}}.js{{/if}}";
+import { task } from "./schema/task{{#if (eq backend "adonisjs")}}.js{{/if}}";
+
+export type Task = typeof task.$inferSelect;
+
+{{#if (isBetterAuth auth)}}
+export function listTasks(userId: string) {
+ return db
+ .select()
+ .from(task)
+ .where(eq(task.userId, userId))
+ .orderBy(desc(task.createdAt));
+}
+
+export async function createTask(userId: string, title: string): Promise {
+ {{#if (eq database "mysql")}}
+ const [inserted] = await db.insert(task).values({ title, userId }).$returningId();
+ if (!inserted) throw new Error("Could not create the task");
+ const [created] = await db.select().from(task).where(eq(task.id, inserted.id));
+ {{else}}
+ const [created] = await db.insert(task).values({ title, userId }).returning();
+ {{/if}}
+ if (!created) throw new Error("Could not create the task");
+ return created;
+}
+
+export async function setTaskCompleted(userId: string, id: number, completed: boolean) {
+ await db
+ .update(task)
+ .set({ completed })
+ .where(and(eq(task.id, id), eq(task.userId, userId)));
+}
+
+export async function deleteTask(userId: string, id: number) {
+ await db.delete(task).where(and(eq(task.id, id), eq(task.userId, userId)));
+}
+{{else}}
+export function listTasks() {
+ return db.select().from(task).orderBy(desc(task.createdAt));
+}
+
+export async function createTask(title: string): Promise {
+ {{#if (eq database "mysql")}}
+ const [inserted] = await db.insert(task).values({ title }).$returningId();
+ if (!inserted) throw new Error("Could not create the task");
+ const [created] = await db.select().from(task).where(eq(task.id, inserted.id));
+ {{else}}
+ const [created] = await db.insert(task).values({ title }).returning();
+ {{/if}}
+ if (!created) throw new Error("Could not create the task");
+ return created;
+}
+
+export async function setTaskCompleted(id: number, completed: boolean) {
+ await db.update(task).set({ completed }).where(eq(task.id, id));
+}
+
+export async function deleteTask(id: number) {
+ await db.delete(task).where(eq(task.id, id));
+}
+{{/if}}
+{{/if}}
diff --git a/packages/template-generator/templates/db/drizzle/mysql/src/index.ts.hbs b/packages/template-generator/templates/db/drizzle/mysql/src/index.ts.hbs
index f040ddc92..d658afe0e 100644
--- a/packages/template-generator/templates/db/drizzle/mysql/src/index.ts.hbs
+++ b/packages/template-generator/templates/db/drizzle/mysql/src/index.ts.hbs
@@ -1,6 +1,6 @@
{{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "none"))}}
import { env } from "@{{projectName}}/env/server";
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{#if (eq dbSetup "planetscale")}}
import { drizzle } from "drizzle-orm/planetscale-serverless";
@@ -25,7 +25,7 @@ export const db = drizzle({
{{/if}}
{{#if (eq runtime "workers")}}
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{#if (eq dbSetup "planetscale")}}
import { drizzle } from "drizzle-orm/planetscale-serverless";
diff --git a/packages/template-generator/templates/db/drizzle/mysql/src/schema/task.ts.hbs b/packages/template-generator/templates/db/drizzle/mysql/src/schema/task.ts.hbs
new file mode 100644
index 000000000..c45fd4755
--- /dev/null
+++ b/packages/template-generator/templates/db/drizzle/mysql/src/schema/task.ts.hbs
@@ -0,0 +1,29 @@
+{{#if (or (isBetterAuth auth) (eq auth "none"))}}
+{{#if (isBetterAuth auth)}}
+import { boolean, index, mysqlTable, serial, timestamp, varchar } from "drizzle-orm/mysql-core";
+
+import { user } from "./auth{{#if (eq backend "adonisjs")}}.js{{/if}}";
+{{else}}
+import { boolean, mysqlTable, serial, timestamp, varchar } from "drizzle-orm/mysql-core";
+{{/if}}
+
+export const task = mysqlTable(
+ "task",
+ {
+ id: serial("id").primaryKey(),
+ title: varchar("title", { length: 255 }).notNull(),
+ completed: boolean("completed").default(false).notNull(),
+ {{#if (isBetterAuth auth)}}
+ userId: varchar("user_id", { length: 36 })
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ {{/if}}
+ createdAt: timestamp("created_at", { fsp: 3 }).defaultNow().notNull(),
+ updatedAt: timestamp("updated_at", { fsp: 3 })
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ }{{#if (isBetterAuth auth)}},
+ (table) => [index("task_user_id_idx").on(table.userId)]{{/if}}
+);
+{{/if}}
diff --git a/packages/template-generator/templates/db/drizzle/postgres/src/index.ts.hbs b/packages/template-generator/templates/db/drizzle/postgres/src/index.ts.hbs
index 1e2540ade..44ff2b9a9 100644
--- a/packages/template-generator/templates/db/drizzle/postgres/src/index.ts.hbs
+++ b/packages/template-generator/templates/db/drizzle/postgres/src/index.ts.hbs
@@ -1,6 +1,6 @@
{{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "none"))}}
import { env } from "@{{projectName}}/env/server";
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{#if (eq dbSetup "neon")}}
import { neon } from '@neondatabase/serverless';
@@ -16,7 +16,7 @@ export const db = drizzle(env.DATABASE_URL, { schema });
{{/if}}
{{#if (eq runtime "workers")}}
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{#if (eq dbSetup "neon")}}
import { neon } from '@neondatabase/serverless';
diff --git a/packages/template-generator/templates/db/drizzle/postgres/src/schema/task.ts.hbs b/packages/template-generator/templates/db/drizzle/postgres/src/schema/task.ts.hbs
new file mode 100644
index 000000000..fd805f0c6
--- /dev/null
+++ b/packages/template-generator/templates/db/drizzle/postgres/src/schema/task.ts.hbs
@@ -0,0 +1,29 @@
+{{#if (or (isBetterAuth auth) (eq auth "none"))}}
+{{#if (isBetterAuth auth)}}
+import { boolean, index, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
+
+import { user } from "./auth{{#if (eq backend "adonisjs")}}.js{{/if}}";
+{{else}}
+import { boolean, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
+{{/if}}
+
+export const task = pgTable(
+ "task",
+ {
+ id: serial("id").primaryKey(),
+ title: text("title").notNull(),
+ completed: boolean("completed").default(false).notNull(),
+ {{#if (isBetterAuth auth)}}
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ {{/if}}
+ createdAt: timestamp("created_at").defaultNow().notNull(),
+ updatedAt: timestamp("updated_at")
+ .defaultNow()
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ }{{#if (isBetterAuth auth)}},
+ (table) => [index("task_user_id_idx").on(table.userId)]{{/if}}
+);
+{{/if}}
diff --git a/packages/template-generator/templates/db/drizzle/sqlite/src/index.ts.hbs b/packages/template-generator/templates/db/drizzle/sqlite/src/index.ts.hbs
index b50a73b5b..d548d97de 100644
--- a/packages/template-generator/templates/db/drizzle/sqlite/src/index.ts.hbs
+++ b/packages/template-generator/templates/db/drizzle/sqlite/src/index.ts.hbs
@@ -1,6 +1,6 @@
{{#if (or (eq runtime "bun") (eq runtime "node") (eq runtime "none"))}}
import { env } from "@{{projectName}}/env/server";
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";
@@ -15,7 +15,7 @@ export const db = drizzle({ client, schema });
{{/if}}
{{#if (eq runtime "workers")}}
-import * as schema from "./schema/index";
+import * as schema from "./schema/index{{#if (eq backend "adonisjs")}}.js{{/if}}";
{{#if (eq dbSetup "d1")}}
import { drizzle } from "drizzle-orm/d1";
diff --git a/packages/template-generator/templates/db/drizzle/sqlite/src/schema/task.ts.hbs b/packages/template-generator/templates/db/drizzle/sqlite/src/schema/task.ts.hbs
new file mode 100644
index 000000000..185d514d6
--- /dev/null
+++ b/packages/template-generator/templates/db/drizzle/sqlite/src/schema/task.ts.hbs
@@ -0,0 +1,32 @@
+{{#if (or (isBetterAuth auth) (eq auth "none"))}}
+import { sql } from "drizzle-orm";
+{{#if (isBetterAuth auth)}}
+import { sqliteTable, integer, text, index } from "drizzle-orm/sqlite-core";
+
+import { user } from "./auth{{#if (eq backend "adonisjs")}}.js{{/if}}";
+{{else}}
+import { sqliteTable, integer, text } from "drizzle-orm/sqlite-core";
+{{/if}}
+
+export const task = sqliteTable(
+ "task",
+ {
+ id: integer("id").primaryKey({ autoIncrement: true }),
+ title: text("title").notNull(),
+ completed: integer("completed", { mode: "boolean" }).default(false).notNull(),
+ {{#if (isBetterAuth auth)}}
+ userId: text("user_id")
+ .notNull()
+ .references(() => user.id, { onDelete: "cascade" }),
+ {{/if}}
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
+ .notNull(),
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
+ .$onUpdate(() => /* @__PURE__ */ new Date())
+ .notNull(),
+ }{{#if (isBetterAuth auth)}},
+ (table) => [index("task_user_id_idx").on(table.userId)]{{/if}}
+);
+{{/if}}
diff --git a/packages/template-generator/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs b/packages/template-generator/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs
index 4e3362e02..23f8e2061 100644
--- a/packages/template-generator/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs
+++ b/packages/template-generator/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs
@@ -1,45 +1,195 @@
-import { createFileRoute } from "@tanstack/react-router";
{{#if (eq backend "convex")}}
+import { createFileRoute } from "@tanstack/react-router";
import { useQuery } from "convex/react";
-import { api } from "@{{ projectName }}/backend/convex/_generated/api";
-{{else if (eq api "orpc")}}
-import { orpc } from "@/utils/orpc";
+
+import { api } from "@{{projectName}}/backend/convex/_generated/api";
+{{else}}
+import { createFileRoute } from "@tanstack/react-router";
+{{#if (eq api "orpc")}}
import { useQuery } from "@tanstack/react-query";
+
+import { orpc } from "@/utils/orpc";
{{else if (eq api "trpc")}}
-import { trpc } from "@/utils/trpc";
+{{#if (and (eq orm "drizzle") (or (isBetterAuth auth) (eq auth "none")))}}
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+{{#if (isBetterAuth auth)}}
+import { Link } from "@tanstack/react-router";
+{{/if}}
+import { useState } from "react";
+
+{{#if (isBetterAuth auth)}}
+import { authClient } from "@/lib/auth-client";
+{{/if}}
+import { trpc{{#if (isBetterAuth auth)}}, trpcClient{{/if}} } from "@/utils/trpc";
+{{else}}
import { useQuery } from "@tanstack/react-query";
+
+import { trpc } from "@/utils/trpc";
+{{/if}}
{{else if (eq api "ts-rest")}}
import { tsr } from "@/utils/ts-rest";
{{else if (eq api "garph")}}
-import { query, resolved } from "@/utils/garph";
import { useQuery } from "@tanstack/react-query";
+
+import { query, resolved } from "@/utils/garph";
{{else if (or (eq api "graphql-yoga") (eq api "apollo-server"))}}
-import { graphqlFetch } from "@/utils/graphql";
import { useQuery } from "@tanstack/react-query";
+
+import { graphqlFetch } from "@/utils/graphql";
{{else if (eq api "openapi")}}
-import { env } from "@{{projectName}}/env/web";
import { useEffect, useState } from "react";
+
+import { env } from "@{{projectName}}/env/web";
+{{/if}}
{{/if}}
export const Route = createFileRoute("/")({
component: HomeComponent,
});
-const TITLE_TEXT = `
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
-
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
- `;
+{{#if (and (eq api "trpc") (eq orm "drizzle") (or (isBetterAuth auth) (eq auth "none")))}}
+function TaskPanel() {
+ const queryClient = useQueryClient();
+ const [title, setTitle] = useState("");
+ {{#if (isBetterAuth auth)}}
+ const { data: session, isPending: sessionPending } = authClient.useSession();
+ const userId = session?.user.id;
+
+ // The user id is part of the cache key, so a session change can never render
+ // rows that were fetched for the previous account.
+ const tasksQueryKey = ["tasks", "list", userId] as const;
+
+ const tasks = useQuery({
+ queryKey: tasksQueryKey,
+ queryFn: () => trpcClient.tasks.list.query(),
+ enabled: Boolean(userId),
+ });
+ {{else}}
+ const tasksQueryKey = trpc.tasks.list.queryKey();
+
+ const tasks = useQuery(trpc.tasks.list.queryOptions());
+ {{/if}}
+
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: tasksQueryKey });
+
+ const create = useMutation({
+ ...trpc.tasks.create.mutationOptions(),
+ onSuccess: () => {
+ setTitle("");
+ return invalidate();
+ },
+ });
+ const setCompleted = useMutation({
+ ...trpc.tasks.setCompleted.mutationOptions(),
+ onSuccess: invalidate,
+ });
+ const remove = useMutation({
+ ...trpc.tasks.remove.mutationOptions(),
+ onSuccess: invalidate,
+ });
+
+ const mutationError = create.error ?? setCompleted.error ?? remove.error;
+
+ {{#if (isBetterAuth auth)}}
+ if (sessionPending) {
+ return Checking your session...
;
+ }
+
+ if (!userId) {
+ return (
+
+
+ Tasks are stored per account. Sign in to create your first one.
+
+
+ Sign in
+
+
+ );
+ }
+ {{/if}}
+
+ return (
+
+
+
+ {mutationError ? (
+
+ {mutationError.message}
+
+ ) : null}
+
+ {tasks.isPending ? (
+
Loading tasks...
+ ) : tasks.isError ? (
+
+ Could not load tasks: {tasks.error.message}
+
+ ) : tasks.data && tasks.data.length > 0 ? (
+
+ ) : (
+
+ No tasks yet. Add one above, then reload the page. It is stored in your database.
+
+ )}
+
+ );
+}
+{{/if}}
function HomeComponent() {
{{#if (eq backend "convex")}}
@@ -78,42 +228,53 @@ function HomeComponent() {
{{/if}}
return (
-
-
{TITLE_TEXT}
-
-
- API Status
- {{#if (eq backend "convex")}}
-
-
-
- {healthCheck === undefined
- ? "Checking..."
- : healthCheck === "OK"
- ? "Connected"
- : "Error"}
-
-
- {{else}}
- {{#unless (eq api "none")}}
-
-
-
- {healthCheck.isLoading
- ? "Checking..."
- : healthCheck.data
- ? "Connected"
- : "Disconnected"}
-
-
- {{/unless}}
- {{/if}}
-
-
+
+
+ {{projectName}}
+
+ TanStack Router{{#unless (eq backend "none")}} and {{backend}}{{/unless}}{{#unless (eq database "none")}}, {{database}}{{#unless (eq orm "none")}} through {{orm}}{{/unless}}{{/unless}}{{#if (isBetterAuth auth)}}, Better Auth{{/if}}.
+
+
+
+
+ Tasks
+ {{#if (and (eq api "trpc") (eq orm "drizzle") (or (isBetterAuth auth) (eq auth "none")))}}
+
+ {{else}}
+
+ This stack has no database-backed task API yet. Replace this panel with your first
+ feature.
+
+ {{/if}}
+
+
+
);
}
diff --git a/packages/template-generator/test/output/generator-output-cleanliness.test.ts b/packages/template-generator/test/output/generator-output-cleanliness.test.ts
index 7d00543c9..49f64cb2a 100644
--- a/packages/template-generator/test/output/generator-output-cleanliness.test.ts
+++ b/packages/template-generator/test/output/generator-output-cleanliness.test.ts
@@ -1,12 +1,12 @@
import { parseStackPartSpecs, type ProjectConfig } from "@better-fullstack/types";
+import { makeConfig } from "@test/_fixtures/config-factory";
import { describe, expect, it } from "bun:test";
import type { VirtualFile, VirtualNode } from "@/types";
+import { dependencyVersionMap } from "@/dependencies/add-deps";
import { generateVirtualProject } from "@/generator";
import { EMBEDDED_TEMPLATES } from "@/templates.generated";
-import { dependencyVersionMap } from "@/dependencies/add-deps";
-import { makeConfig } from "@test/_fixtures/config-factory";
function listFiles(node: VirtualNode): VirtualFile[] {
return node.type === "file" ? [node] : node.children.flatMap(listFiles);
@@ -58,6 +58,23 @@ describe("generated output cleanliness", () => {
});
}
+ for (const database of ["redis", "edgedb"] as const) {
+ it(`does not expose the ORM sentinel for ${database}`, async () => {
+ const result = await generateVirtualProject({
+ config: makeConfig({ database, orm: "none" }),
+ templates: EMBEDDED_TEMPLATES,
+ });
+
+ expect(result.success).toBe(true);
+ const route = listFiles(result.tree!.root).find((file) =>
+ file.path.endsWith("apps/web/src/routes/index.tsx"),
+ );
+
+ expect(route?.content).toContain(`TanStack Router and hono, ${database}.`);
+ expect(route?.content).not.toContain(`${database} through none`);
+ });
+ }
+
it("renders graph-native Blazor and Kotlin application templates", async () => {
const config = makeConfig({
stackParts: parseStackPartSpecs([