better-ts-stack 2.1.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -8
- package/dist/args.js +2 -1
- package/dist/builder/buildPlanner.js +7 -1
- package/dist/builder/configGenerator.js +28 -3
- package/dist/builder/index.js +2 -1
- package/dist/builder/readmeGenerator.js +2 -2
- package/dist/output/nextSteps.js +2 -4
- package/dist/prompts/stack.js +3 -5
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/templates/backend/express/src/env.ts.hbs +13 -2
- package/templates/backend/express/src/index.ts.hbs +31 -2
- package/templates/backend/nestjs/src/app.module.ts.hbs +4 -1
- package/templates/backend/nestjs/src/env.ts.hbs +13 -2
- package/templates/backend/nestjs/src/health/health.controller.ts.hbs +7 -0
- package/templates/backend/nestjs/src/{main.ts → main.ts.hbs} +11 -0
- package/templates/frontend/nextjs/app/globals.css +119 -79
- package/templates/frontend/nextjs/app/layout.tsx +13 -11
- package/templates/frontend/nextjs/app/page.tsx.hbs +63 -43
- package/templates/frontend/nextjs/components/ui/alert.tsx +75 -0
- package/templates/frontend/nextjs/components/ui/badge.tsx +51 -0
- package/templates/frontend/nextjs/components/ui/button.tsx +33 -27
- package/templates/frontend/nextjs/components/ui/card.tsx +76 -34
- package/templates/frontend/nextjs/components/ui/field.tsx +203 -32
- package/templates/frontend/nextjs/components/ui/input.tsx +8 -7
- package/templates/frontend/nextjs/components/ui/label.tsx +19 -0
- package/templates/frontend/nextjs/components/ui/separator.tsx +24 -0
- package/templates/frontend/nextjs/components/ui/skeleton.tsx +13 -0
- package/templates/frontend/nextjs/components/ui/spinner.tsx +10 -0
- package/templates/frontend/nextjs/components.json +11 -5
- package/templates/frontend/nextjs/config.json +4 -3
- package/templates/frontend/nextjs/lib/utils.ts +1 -6
- package/templates/frontend/tanstack/components.json +12 -5
- package/templates/frontend/tanstack/config.json +5 -2
- package/templates/frontend/tanstack/src/components/ui/alert.tsx +75 -0
- package/templates/frontend/tanstack/src/components/ui/badge.tsx +51 -0
- package/templates/frontend/tanstack/src/components/ui/button.tsx +33 -27
- package/templates/frontend/tanstack/src/components/ui/card.tsx +76 -34
- package/templates/frontend/tanstack/src/components/ui/field.tsx +201 -32
- package/templates/frontend/tanstack/src/components/ui/input.tsx +8 -7
- package/templates/frontend/tanstack/src/components/ui/label.tsx +17 -0
- package/templates/frontend/tanstack/src/components/ui/separator.tsx +24 -0
- package/templates/frontend/tanstack/src/components/ui/skeleton.tsx +13 -0
- package/templates/frontend/tanstack/src/components/ui/spinner.tsx +10 -0
- package/templates/frontend/tanstack/src/lib/utils.ts +1 -6
- package/templates/frontend/tanstack/src/routes/index.tsx.hbs +64 -45
- package/templates/frontend/tanstack/src/styles/app.css +100 -55
- package/templates/modules/agents/AGENTS.md.hbs +29 -21
- package/templates/modules/auth/express/config.json +12 -11
- package/templates/modules/auth/express/src/lib/auth.ts.hbs +143 -0
- package/templates/modules/auth/express/src/lib/email.ts +37 -0
- package/templates/modules/auth/express/src/middleware/requireAuth.ts +29 -0
- package/templates/modules/auth/express/src/routes/me.ts +13 -0
- package/templates/modules/auth/nestjs/config.json +13 -11
- package/templates/modules/auth/nestjs/src/auth/auth.module.ts.hbs +64 -14
- package/templates/modules/auth/nestjs/src/auth/auth.ts.hbs +152 -0
- package/templates/modules/auth/nestjs/src/auth/email.ts +37 -0
- package/templates/modules/auth/nestjs/src/me/me.controller.ts +15 -0
- package/templates/modules/auth/nextjs/app/dashboard/page.tsx +101 -45
- package/templates/modules/auth/nextjs/app/forgot-password/page.tsx +11 -0
- package/templates/modules/auth/nextjs/app/reset-password/page.tsx +17 -0
- package/templates/modules/auth/nextjs/app/sign-in/page.tsx +23 -17
- package/templates/modules/auth/nextjs/app/sign-up/page.tsx +25 -19
- package/templates/modules/auth/nextjs/components/auth/change-password-form.tsx +125 -0
- package/templates/modules/auth/nextjs/components/auth/email-verification-card.tsx +91 -0
- package/templates/modules/auth/nextjs/components/auth/forgot-password-form.tsx +133 -0
- package/templates/modules/auth/nextjs/components/auth/reset-password-form.tsx +155 -0
- package/templates/modules/auth/nextjs/components/auth/sign-in-form.tsx +46 -32
- package/templates/modules/auth/nextjs/components/auth/sign-out-button.tsx +6 -5
- package/templates/modules/auth/nextjs/components/auth/sign-up-form.tsx +36 -30
- package/templates/modules/auth/nextjs/components/auth/social-buttons.tsx +73 -0
- package/templates/modules/auth/nextjs/config.json +8 -2
- package/templates/modules/auth/nextjs/lib/auth-schema.ts +27 -2
- package/templates/modules/auth/nextjs/lib/auth.ts.hbs +65 -0
- package/templates/modules/auth/nextjs/lib/email.ts +43 -0
- package/templates/modules/auth/nextjs/lib/session.ts +12 -0
- package/templates/modules/auth/tanstack/config.json +9 -3
- package/templates/modules/auth/tanstack/src/components/auth/change-password-form.tsx +123 -0
- package/templates/modules/auth/tanstack/src/components/auth/email-verification-card.tsx +89 -0
- package/templates/modules/auth/tanstack/src/components/auth/forgot-password-form.tsx +131 -0
- package/templates/modules/auth/tanstack/src/components/auth/reset-password-form.tsx +149 -0
- package/templates/modules/auth/tanstack/src/components/auth/sign-in-form.tsx +44 -35
- package/templates/modules/auth/tanstack/src/components/auth/sign-out-button.tsx +7 -6
- package/templates/modules/auth/tanstack/src/components/auth/sign-up-form.tsx +37 -34
- package/templates/modules/auth/tanstack/src/components/auth/social-buttons.tsx +71 -0
- package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +7 -1
- package/templates/modules/auth/tanstack/src/lib/auth-schema.ts +27 -2
- package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +61 -1
- package/templates/modules/auth/tanstack/src/lib/email.ts +43 -0
- package/templates/modules/auth/tanstack/src/routeTree.gen.ts +61 -3
- package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +40 -32
- package/templates/modules/auth/tanstack/src/routes/forgot-password.tsx +17 -0
- package/templates/modules/auth/tanstack/src/routes/reset-password.tsx +25 -0
- package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +19 -12
- package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +21 -14
- package/templates/modules/drizzle/express/src/lib/schema.ts.hbs +90 -0
- package/templates/modules/drizzle/nestjs/src/database/schema.ts.hbs +90 -0
- package/templates/modules/mongoose/nestjs/config.json +2 -1
- package/templates/modules/prisma/express/prisma/schema.prisma.hbs +92 -0
- package/templates/modules/prisma/nestjs/prisma/schema.prisma.hbs +91 -0
- package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +0 -8
- package/templates/modules/auth/express/src/controllers/authController.ts.hbs +0 -116
- package/templates/modules/auth/express/src/lib/jwt.ts.hbs +0 -27
- package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +0 -25
- package/templates/modules/auth/express/src/routes/auth.ts.hbs +0 -27
- package/templates/modules/auth/express/src/services/userStore.ts.hbs +0 -117
- package/templates/modules/auth/nestjs/src/auth/auth.controller.ts.hbs +0 -61
- package/templates/modules/auth/nestjs/src/auth/auth.guard.ts +0 -36
- package/templates/modules/auth/nestjs/src/auth/auth.schemas.ts +0 -35
- package/templates/modules/auth/nestjs/src/auth/auth.service.ts +0 -70
- package/templates/modules/auth/nestjs/src/auth/jwt.ts +0 -27
- package/templates/modules/auth/nestjs/src/auth/users.service.ts.hbs +0 -144
- package/templates/modules/auth/nestjs/src/auth/zod-validation.pipe.ts +0 -16
- package/templates/modules/drizzle/express/src/lib/schema.ts +0 -12
- package/templates/modules/drizzle/nestjs/src/database/schema.ts +0 -12
- package/templates/modules/prisma/express/prisma/schema.prisma +0 -24
- package/templates/modules/prisma/nestjs/prisma/schema.prisma +0 -23
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { createFileRoute{{#if useAuth}}, Link{{/if}} } from "@tanstack/react-router";
|
|
2
2
|
import { ArrowRight } from "lucide-react";
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { Alert, AlertDescription } from "~/components/ui/alert";
|
|
5
|
+
import { Badge } from "~/components/ui/badge";
|
|
6
|
+
import { Button } from "~/components/ui/button";
|
|
7
|
+
import {
|
|
8
|
+
Card,
|
|
9
|
+
CardContent,
|
|
10
|
+
CardFooter,
|
|
11
|
+
CardHeader,
|
|
12
|
+
CardTitle,
|
|
13
|
+
} from "~/components/ui/card";
|
|
7
14
|
|
|
8
15
|
export const Route = createFileRoute("/")({
|
|
9
16
|
component: Home,
|
|
@@ -13,7 +20,7 @@ const featureList = [
|
|
|
13
20
|
"TanStack Start SSR",
|
|
14
21
|
"TanStack Router and Query",
|
|
15
22
|
"React 19 and TypeScript",
|
|
16
|
-
"Tailwind CSS v4",
|
|
23
|
+
"Tailwind CSS v4 and shadcn/ui on Base UI",
|
|
17
24
|
{{#if useAuth}}"Better Auth-ready session flow",{{/if}}
|
|
18
25
|
{{#if (eq database "prisma")}}"Prisma database tooling",{{/if}}
|
|
19
26
|
{{#if (eq database "drizzle")}}"Drizzle database tooling",{{/if}}
|
|
@@ -22,66 +29,74 @@ const featureList = [
|
|
|
22
29
|
|
|
23
30
|
function Home() {
|
|
24
31
|
return (
|
|
25
|
-
<main className="min-h-
|
|
26
|
-
<div className="mx-auto flex min-h-
|
|
32
|
+
<main className="min-h-svh">
|
|
33
|
+
<div className="mx-auto flex min-h-svh max-w-6xl flex-col justify-center gap-12 px-6 py-16 sm:px-10 lg:px-12">
|
|
27
34
|
<section className="grid gap-10 lg:grid-cols-[1.2fr_0.8fr] lg:items-center">
|
|
28
|
-
<div className="
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
</span>
|
|
32
|
-
<div className="space-y-4">
|
|
35
|
+
<div className="flex flex-col gap-6">
|
|
36
|
+
<Badge variant="outline">Created with better-ts-stack</Badge>
|
|
37
|
+
<div className="flex flex-col gap-4">
|
|
33
38
|
<h1 className="max-w-3xl text-4xl font-semibold tracking-tight sm:text-5xl">
|
|
34
39
|
Ship your stack faster with a polished TypeScript foundation.
|
|
35
40
|
</h1>
|
|
36
41
|
<p className="max-w-2xl text-base leading-7 text-muted-foreground sm:text-lg">
|
|
37
42
|
Your project already includes the core app shell, database wiring,
|
|
38
|
-
and
|
|
39
|
-
|
|
43
|
+
and shadcn/ui components so you can keep building instead of
|
|
44
|
+
setting up boilerplate.
|
|
40
45
|
</p>
|
|
41
46
|
</div>
|
|
42
47
|
<div className="flex flex-col gap-3 sm:flex-row">
|
|
43
48
|
{{#if useAuth}}
|
|
44
|
-
<Link
|
|
45
|
-
to="/sign-up"
|
|
46
|
-
className={cn(buttonVariants({ size: "lg" }), "gap-2")}
|
|
47
|
-
>
|
|
49
|
+
<Button size="lg" nativeButton={false} render={<Link to="/sign-up" />}>
|
|
48
50
|
Create an account
|
|
49
|
-
<ArrowRight
|
|
50
|
-
</
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
<ArrowRight data-icon="inline-end" />
|
|
52
|
+
</Button>
|
|
53
|
+
<Button
|
|
54
|
+
size="lg"
|
|
55
|
+
variant="outline"
|
|
56
|
+
nativeButton={false}
|
|
57
|
+
render={<Link to="/sign-in" />}
|
|
54
58
|
>
|
|
55
59
|
Login
|
|
56
|
-
</
|
|
60
|
+
</Button>
|
|
57
61
|
{{else}}
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
<Button
|
|
63
|
+
size="lg"
|
|
64
|
+
nativeButton={false}
|
|
65
|
+
render={
|
|
66
|
+
<a
|
|
67
|
+
href="https://better-ts-stack.abdullahtech.me/docs"
|
|
68
|
+
target="_blank"
|
|
69
|
+
rel="noreferrer"
|
|
70
|
+
/>
|
|
71
|
+
}
|
|
63
72
|
>
|
|
64
73
|
Explore the docs
|
|
65
|
-
<ArrowRight
|
|
66
|
-
</
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
<ArrowRight data-icon="inline-end" />
|
|
75
|
+
</Button>
|
|
76
|
+
<Button
|
|
77
|
+
size="lg"
|
|
78
|
+
variant="outline"
|
|
79
|
+
nativeButton={false}
|
|
80
|
+
render={
|
|
81
|
+
<a
|
|
82
|
+
href="https://tanstack.com/start/latest"
|
|
83
|
+
target="_blank"
|
|
84
|
+
rel="noreferrer"
|
|
85
|
+
/>
|
|
86
|
+
}
|
|
72
87
|
>
|
|
73
88
|
TanStack guide
|
|
74
|
-
</
|
|
89
|
+
</Button>
|
|
75
90
|
{{/if}}
|
|
76
91
|
</div>
|
|
77
92
|
</div>
|
|
78
93
|
|
|
79
|
-
<Card
|
|
94
|
+
<Card>
|
|
80
95
|
<CardHeader>
|
|
81
96
|
<CardTitle>Included in this starter</CardTitle>
|
|
82
97
|
</CardHeader>
|
|
83
|
-
<CardContent
|
|
84
|
-
<ul className="
|
|
98
|
+
<CardContent>
|
|
99
|
+
<ul className="flex flex-col gap-3 text-muted-foreground">
|
|
85
100
|
{featureList.map((feature) => (
|
|
86
101
|
<li key={feature} className="flex items-center gap-3">
|
|
87
102
|
<span className="size-2 rounded-full bg-primary" />
|
|
@@ -89,15 +104,19 @@ function Home() {
|
|
|
89
104
|
</li>
|
|
90
105
|
))}
|
|
91
106
|
</ul>
|
|
92
|
-
<div className="rounded-2xl border border-border bg-muted px-4 py-3 text-sm text-muted-foreground">
|
|
93
|
-
Start editing{" "}
|
|
94
|
-
<code className="font-mono">src/routes/index.tsx</code> and
|
|
95
|
-
shape the project around your product instead of setup chores.
|
|
96
|
-
</div>
|
|
97
107
|
</CardContent>
|
|
108
|
+
<CardFooter>
|
|
109
|
+
<Alert>
|
|
110
|
+
<AlertDescription>
|
|
111
|
+
Start editing{" "}
|
|
112
|
+
<code className="font-mono">src/routes/index.tsx</code> and
|
|
113
|
+
shape the project around your product instead of setup chores.
|
|
114
|
+
</AlertDescription>
|
|
115
|
+
</Alert>
|
|
116
|
+
</CardFooter>
|
|
98
117
|
</Card>
|
|
99
118
|
</section>
|
|
100
119
|
</div>
|
|
101
120
|
</main>
|
|
102
121
|
);
|
|
103
|
-
}
|
|
122
|
+
}
|
|
@@ -1,49 +1,95 @@
|
|
|
1
1
|
@import "tailwindcss" source("../");
|
|
2
|
+
@import "tw-animate-css";
|
|
3
|
+
@import "shadcn/tailwind.css";
|
|
4
|
+
@import "@fontsource-variable/inter";
|
|
5
|
+
|
|
6
|
+
@custom-variant dark (&:is(.dark *));
|
|
2
7
|
|
|
3
8
|
@theme inline {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
--font-heading: var(--font-sans);
|
|
10
|
+
--font-sans: 'Inter Variable', sans-serif;
|
|
11
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
12
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
13
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
14
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
15
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
16
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
17
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
18
|
+
--color-sidebar: var(--sidebar);
|
|
19
|
+
--color-chart-5: var(--chart-5);
|
|
20
|
+
--color-chart-4: var(--chart-4);
|
|
21
|
+
--color-chart-3: var(--chart-3);
|
|
22
|
+
--color-chart-2: var(--chart-2);
|
|
23
|
+
--color-chart-1: var(--chart-1);
|
|
24
|
+
--color-ring: var(--ring);
|
|
25
|
+
--color-input: var(--input);
|
|
26
|
+
--color-border: var(--border);
|
|
27
|
+
--color-destructive: var(--destructive);
|
|
28
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
29
|
+
--color-accent: var(--accent);
|
|
30
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
31
|
+
--color-muted: var(--muted);
|
|
32
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
33
|
+
--color-secondary: var(--secondary);
|
|
34
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
35
|
+
--color-primary: var(--primary);
|
|
36
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
37
|
+
--color-popover: var(--popover);
|
|
38
|
+
--color-card-foreground: var(--card-foreground);
|
|
39
|
+
--color-card: var(--card);
|
|
40
|
+
--color-foreground: var(--foreground);
|
|
41
|
+
--color-background: var(--background);
|
|
42
|
+
--radius-sm: calc(var(--radius) * 0.6);
|
|
43
|
+
--radius-md: calc(var(--radius) * 0.8);
|
|
44
|
+
--radius-lg: var(--radius);
|
|
45
|
+
--radius-xl: calc(var(--radius) * 1.4);
|
|
46
|
+
--radius-2xl: calc(var(--radius) * 1.8);
|
|
47
|
+
--radius-3xl: calc(var(--radius) * 2.2);
|
|
48
|
+
--radius-4xl: calc(var(--radius) * 2.6);
|
|
20
49
|
}
|
|
21
50
|
|
|
22
51
|
:root {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
--background: oklch(1 0 0);
|
|
53
|
+
--foreground: oklch(0.145 0 0);
|
|
54
|
+
--card: oklch(1 0 0);
|
|
55
|
+
--card-foreground: oklch(0.145 0 0);
|
|
56
|
+
--popover: oklch(1 0 0);
|
|
57
|
+
--popover-foreground: oklch(0.145 0 0);
|
|
58
|
+
--primary: oklch(0.205 0 0);
|
|
59
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
60
|
+
--secondary: oklch(0.97 0 0);
|
|
61
|
+
--secondary-foreground: oklch(0.205 0 0);
|
|
62
|
+
--muted: oklch(0.97 0 0);
|
|
63
|
+
--muted-foreground: oklch(0.556 0 0);
|
|
64
|
+
--accent: oklch(0.97 0 0);
|
|
65
|
+
--accent-foreground: oklch(0.205 0 0);
|
|
66
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
67
|
+
--border: oklch(0.922 0 0);
|
|
68
|
+
--input: oklch(0.922 0 0);
|
|
69
|
+
--ring: oklch(0.708 0 0);
|
|
70
|
+
--chart-1: oklch(0.87 0 0);
|
|
71
|
+
--chart-2: oklch(0.556 0 0);
|
|
72
|
+
--chart-3: oklch(0.439 0 0);
|
|
73
|
+
--chart-4: oklch(0.371 0 0);
|
|
74
|
+
--chart-5: oklch(0.269 0 0);
|
|
75
|
+
--radius: 0.625rem;
|
|
76
|
+
--sidebar: oklch(0.985 0 0);
|
|
77
|
+
--sidebar-foreground: oklch(0.145 0 0);
|
|
78
|
+
--sidebar-primary: oklch(0.205 0 0);
|
|
79
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
80
|
+
--sidebar-accent: oklch(0.97 0 0);
|
|
81
|
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
|
82
|
+
--sidebar-border: oklch(0.922 0 0);
|
|
83
|
+
--sidebar-ring: oklch(0.708 0 0);
|
|
39
84
|
}
|
|
40
85
|
|
|
41
|
-
|
|
42
|
-
:root {
|
|
86
|
+
.dark {
|
|
43
87
|
--background: oklch(0.145 0 0);
|
|
44
88
|
--foreground: oklch(0.985 0 0);
|
|
45
89
|
--card: oklch(0.205 0 0);
|
|
46
90
|
--card-foreground: oklch(0.985 0 0);
|
|
91
|
+
--popover: oklch(0.205 0 0);
|
|
92
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
47
93
|
--primary: oklch(0.922 0 0);
|
|
48
94
|
--primary-foreground: oklch(0.205 0 0);
|
|
49
95
|
--secondary: oklch(0.269 0 0);
|
|
@@ -56,30 +102,29 @@
|
|
|
56
102
|
--border: oklch(1 0 0 / 10%);
|
|
57
103
|
--input: oklch(1 0 0 / 15%);
|
|
58
104
|
--ring: oklch(0.556 0 0);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
105
|
+
--chart-1: oklch(0.87 0 0);
|
|
106
|
+
--chart-2: oklch(0.556 0 0);
|
|
107
|
+
--chart-3: oklch(0.439 0 0);
|
|
108
|
+
--chart-4: oklch(0.371 0 0);
|
|
109
|
+
--chart-5: oklch(0.269 0 0);
|
|
110
|
+
--sidebar: oklch(0.205 0 0);
|
|
111
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
112
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
113
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
114
|
+
--sidebar-accent: oklch(0.269 0 0);
|
|
115
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
116
|
+
--sidebar-border: oklch(1 0 0 / 10%);
|
|
117
|
+
--sidebar-ring: oklch(0.556 0 0);
|
|
70
118
|
}
|
|
71
119
|
|
|
72
120
|
@layer base {
|
|
73
|
-
html {
|
|
74
|
-
color-scheme: light dark;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
121
|
* {
|
|
78
|
-
@apply border-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
html,
|
|
122
|
+
@apply border-border outline-ring/50;
|
|
123
|
+
}
|
|
82
124
|
body {
|
|
83
|
-
@apply bg-
|
|
84
|
-
|
|
125
|
+
@apply bg-background text-foreground;
|
|
126
|
+
}
|
|
127
|
+
html {
|
|
128
|
+
@apply font-sans;
|
|
129
|
+
}
|
|
85
130
|
}
|
|
@@ -11,10 +11,10 @@ Instructions for AI coding agents working in {{projectName}}. Read this before m
|
|
|
11
11
|
- NestJS 12 REST API in TypeScript, using ES modules
|
|
12
12
|
{{/if}}
|
|
13
13
|
{{#if (eq framework 'nextjs')}}
|
|
14
|
-
- Next.js 16 with the App Router, React 19, Tailwind CSS 4, and shadcn/ui
|
|
14
|
+
- Next.js 16 with the App Router, React 19, Tailwind CSS 4, and shadcn/ui on Base UI
|
|
15
15
|
{{/if}}
|
|
16
16
|
{{#if (eq framework 'tanstack')}}
|
|
17
|
-
- TanStack Start with Vite, Nitro, React 19, Tailwind CSS 4, and shadcn/ui
|
|
17
|
+
- TanStack Start with Vite, Nitro, React 19, Tailwind CSS 4, and shadcn/ui on Base UI
|
|
18
18
|
{{/if}}
|
|
19
19
|
{{#if (eq database 'prisma')}}
|
|
20
20
|
- PostgreSQL through Prisma 7
|
|
@@ -26,17 +26,12 @@ Instructions for AI coding agents working in {{projectName}}. Read this before m
|
|
|
26
26
|
- MongoDB through Mongoose
|
|
27
27
|
{{/if}}
|
|
28
28
|
{{#if useAuth}}
|
|
29
|
+
- Better Auth: email and password, email verification, password reset, and GitHub and Google sign-in when their keys are set. Emails go through Resend when `RESEND_API_KEY` is set and are printed to the log otherwise
|
|
29
30
|
{{#if (eq framework 'express')}}
|
|
30
|
-
-
|
|
31
|
+
- Auth routes under `/api/auth` (`POST /api/auth/sign-up/email`, `POST /api/auth/sign-in/email`, ...). Clients without cookies send `Authorization: Bearer <token>`, using the `set-auth-token` header from sign-in
|
|
31
32
|
{{/if}}
|
|
32
33
|
{{#if (eq framework 'nestjs')}}
|
|
33
|
-
-
|
|
34
|
-
{{/if}}
|
|
35
|
-
{{#if (eq framework 'nextjs')}}
|
|
36
|
-
- Better Auth with email and password
|
|
37
|
-
{{/if}}
|
|
38
|
-
{{#if (eq framework 'tanstack')}}
|
|
39
|
-
- Better Auth with email and password
|
|
34
|
+
- Auth routes under `/api/auth` (`POST /api/auth/sign-up/email`, `POST /api/auth/sign-in/email`, ...). Clients without cookies send `Authorization: Bearer <token>`, using the `set-auth-token` header from sign-in
|
|
40
35
|
{{/if}}
|
|
41
36
|
{{/if}}
|
|
42
37
|
{{#if useRateLimit}}
|
|
@@ -76,8 +71,9 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
76
71
|
- `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
|
|
77
72
|
- `src/routes/`: one router per resource, mounted in `src/index.ts`
|
|
78
73
|
{{#if useAuth}}
|
|
79
|
-
- `src/
|
|
80
|
-
- `src/
|
|
74
|
+
- `src/lib/auth.ts`: the Better Auth server, mounted on `/api/auth` in `src/index.ts`
|
|
75
|
+
- `src/lib/email.ts`: sends the verification and password reset emails
|
|
76
|
+
- `src/middleware/requireAuth.ts`: add it to a route to require a session; `getSession(res)` returns it. `src/routes/me.ts` is an example
|
|
81
77
|
{{/if}}
|
|
82
78
|
{{#if useRateLimit}}
|
|
83
79
|
- `src/middleware/rateLimit.ts`: the rate limiters
|
|
@@ -98,7 +94,8 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
98
94
|
- `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
|
|
99
95
|
- `src/health/`: `GET /health`
|
|
100
96
|
{{#if useAuth}}
|
|
101
|
-
- `src/auth/`: the auth
|
|
97
|
+
- `src/auth/`: the Better Auth server (`auth.ts`), the emails it sends (`email.ts`), and the module that mounts it on `/api/auth` (`auth.module.ts`)
|
|
98
|
+
- Every route needs a session unless it has `@AllowAnonymous()` (or `@OptionalAuth()`). Read the session with `@Session()`, as `src/me/me.controller.ts` does
|
|
102
99
|
{{/if}}
|
|
103
100
|
{{#if (eq database 'prisma')}}
|
|
104
101
|
- `src/prisma/`: `PrismaService`, injected wherever the database is needed
|
|
@@ -112,10 +109,12 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
112
109
|
{{/if}}
|
|
113
110
|
{{#if (eq framework 'nextjs')}}
|
|
114
111
|
- `app/`: routes, layouts, and route handlers. Pages are Server Components unless they need state, effects, or event handlers
|
|
115
|
-
- `components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
|
|
112
|
+
- `components/ui/`: shadcn/ui components built on Base UI (`@base-ui/react`). Add new ones with the shadcn CLI instead of writing them by hand. Use the `render` prop to change the rendered element, not Radix's `asChild`
|
|
116
113
|
- `lib/utils.ts`: the `cn()` class name helper
|
|
114
|
+
- `app/globals.css`: the shadcn theme. Style with its tokens (`bg-primary`, `text-muted-foreground`), not raw Tailwind colors, and space with `flex` + `gap-*`, not `space-y-*`
|
|
117
115
|
{{#if useAuth}}
|
|
118
|
-
- `lib/auth.ts`: the Better Auth server. `lib/auth-client.ts`: the client for components
|
|
116
|
+
- `lib/auth.ts`: the Better Auth server. `lib/auth-client.ts`: the client for components. `lib/email.ts`: sends the verification and password reset emails
|
|
117
|
+
- `lib/session.ts`: `getSession()` reads the session on the server, once per request
|
|
119
118
|
- `app/api/auth/[...all]/route.ts`: the Better Auth endpoints. Do not rename it
|
|
120
119
|
- `proxy.ts`: sends signed-out visitors from `/dashboard` to `/sign-in`. Add other protected paths to its `matcher`
|
|
121
120
|
{{/if}}
|
|
@@ -133,10 +132,11 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
133
132
|
- `src/routes/`: file-based routes. A new file becomes a route
|
|
134
133
|
- `src/routeTree.gen.ts`: generated by the dev server and the build. Never edit it
|
|
135
134
|
- `src/router.tsx`, `src/routes/__root.tsx`: the router and the root layout
|
|
136
|
-
- `src/components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
|
|
135
|
+
- `src/components/ui/`: shadcn/ui components built on Base UI (`@base-ui/react`). Add new ones with the shadcn CLI instead of writing them by hand. Use the `render` prop to change the rendered element, not Radix's `asChild`
|
|
137
136
|
- `src/lib/utils.ts`: the `cn()` class name helper
|
|
137
|
+
- `src/styles/app.css`: the shadcn theme. Style with its tokens (`bg-primary`, `text-muted-foreground`), not raw Tailwind colors, and space with `flex` + `gap-*`, not `space-y-*`
|
|
138
138
|
{{#if useAuth}}
|
|
139
|
-
- `src/lib/auth.ts`: the Better Auth server. `src/lib/auth-client.ts`: the client for components
|
|
139
|
+
- `src/lib/auth.ts`: the Better Auth server. `src/lib/auth-client.ts`: the client for components. `src/lib/email.ts`: sends the verification and password reset emails
|
|
140
140
|
- `src/routes/api/auth/$.ts`: the Better Auth endpoints. Do not rename it
|
|
141
141
|
- `src/routes/dashboard.tsx`: an example of a protected route that redirects in `beforeLoad`
|
|
142
142
|
{{/if}}
|
|
@@ -162,6 +162,12 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
162
162
|
- The Prisma client is generated into {{#if (eq framework 'express')}}`src/generated/`{{/if}}{{#if (eq framework 'nestjs')}}`src/generated/`{{/if}}{{#if (eq framework 'nextjs')}}`generated/`{{/if}}{{#if (eq framework 'tanstack')}}`generated/`{{/if}}. Never edit or commit it
|
|
163
163
|
{{/if}}
|
|
164
164
|
- Match the existing code style. ESLint settings are in `eslint.config.mjs`{{#if (eq framework 'express')}} and Prettier settings in `.prettierrc`{{/if}}{{#if (eq framework 'nestjs')}} and Prettier settings in `.prettierrc`{{/if}}
|
|
165
|
+
{{#if (eq framework 'nextjs')}}
|
|
166
|
+
- Write Tailwind classes in their canonical form. Use the theme utility when one exists (`rounded-4xl`, not `rounded-[2rem]`), and in an arbitrary value write `_` only where CSS needs a space, never after a comma: `bg-[linear-gradient(180deg,#f8fafc_0%,#eef2ff_100%)]`. The Tailwind CSS VS Code extension warns about the long forms
|
|
167
|
+
{{/if}}
|
|
168
|
+
{{#if (eq framework 'tanstack')}}
|
|
169
|
+
- Write Tailwind classes in their canonical form. Use the theme utility when one exists (`rounded-4xl`, not `rounded-[2rem]`), and in an arbitrary value write `_` only where CSS needs a space, never after a comma: `bg-[linear-gradient(180deg,#f8fafc_0%,#eef2ff_100%)]`. The Tailwind CSS VS Code extension warns about the long forms
|
|
170
|
+
{{/if}}
|
|
165
171
|
- Add a dependency only when the task needs it, and install it with {{packageManager}}
|
|
166
172
|
|
|
167
173
|
## Production rules
|
|
@@ -178,6 +184,7 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
178
184
|
- Keep the security setup in `src/main.ts` and `src/app.module.ts` in place. Set `CORS_ORIGIN` to your real origins in production
|
|
179
185
|
{{/if}}
|
|
180
186
|
{{#if (eq framework 'nextjs')}}
|
|
187
|
+
- Server first: do not `await` data at the top of a page or layout, which holds back the whole page. Move each `await` into the component that needs the data and wrap that component in `<Suspense>` with a fallback, so the rest of the page is sent at once{{#if useAuth}}. `app/dashboard/page.tsx` shows the pattern with `getSession()`{{/if}}
|
|
181
188
|
- Code that reads secrets or the database stays on the server. Only variables prefixed with `NEXT_PUBLIC_` reach the browser, so never put a secret in one
|
|
182
189
|
{{/if}}
|
|
183
190
|
{{#if (eq framework 'tanstack')}}
|
|
@@ -192,6 +199,9 @@ Run type-check and lint, and fix every error they report. Run the build too when
|
|
|
192
199
|
{{/if}}
|
|
193
200
|
{{/if}}
|
|
194
201
|
{{#if (eq framework 'express')}}
|
|
202
|
+
{{#if useAuth}}
|
|
203
|
+
- Check the session on the server in every protected route with `requireAuth`
|
|
204
|
+
{{/if}}
|
|
195
205
|
- Keep `GET /health` fast and free of auth{{#if useRateLimit}} and rate limiting{{/if}}, so hosts can check that the app is up
|
|
196
206
|
{{/if}}
|
|
197
207
|
{{#if (eq framework 'nestjs')}}
|
|
@@ -225,11 +235,9 @@ The libraries here may be newer than what you know. When you are not sure how an
|
|
|
225
235
|
- Mongoose: https://mongoosejs.com/docs
|
|
226
236
|
{{/if}}
|
|
227
237
|
{{#if useAuth}}
|
|
228
|
-
{{#if (eq framework 'nextjs')}}
|
|
229
|
-
- Better Auth: https://www.better-auth.com/docs
|
|
230
|
-
{{/if}}
|
|
231
|
-
{{#if (eq framework 'tanstack')}}
|
|
232
238
|
- Better Auth: https://www.better-auth.com/docs
|
|
239
|
+
{{#if (eq framework 'nestjs')}}
|
|
240
|
+
- NestJS Better Auth integration: https://github.com/ThallesP/nestjs-better-auth
|
|
233
241
|
{{/if}}
|
|
234
242
|
{{/if}}
|
|
235
243
|
- Zod: https://zod.dev
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "auth",
|
|
3
|
-
"name": "
|
|
4
|
-
"description": "
|
|
3
|
+
"name": "Better Auth",
|
|
4
|
+
"description": "Better Auth for Express: email and password, email verification, password reset, GitHub and Google sign-in, and bearer tokens",
|
|
5
5
|
"type": "feature",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"
|
|
8
|
-
"jsonwebtoken": "^9.0.3",
|
|
9
|
-
"zod": "^4.6.7"
|
|
10
|
-
},
|
|
11
|
-
"devDependencies": {
|
|
12
|
-
"@types/bcrypt": "^6.0.0",
|
|
13
|
-
"@types/jsonwebtoken": "^9.0.10"
|
|
7
|
+
"better-auth": "^1.7.18"
|
|
14
8
|
},
|
|
9
|
+
"devDependencies": {},
|
|
15
10
|
"scripts": {},
|
|
16
11
|
"envVars": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
12
|
+
"BETTER_AUTH_SECRET": "please-change-me-to-a-random-string",
|
|
13
|
+
"BETTER_AUTH_URL": "http://localhost:3000",
|
|
14
|
+
"GITHUB_CLIENT_ID": "",
|
|
15
|
+
"GITHUB_CLIENT_SECRET": "",
|
|
16
|
+
"GOOGLE_CLIENT_ID": "",
|
|
17
|
+
"GOOGLE_CLIENT_SECRET": "",
|
|
18
|
+
"RESEND_API_KEY": "",
|
|
19
|
+
"EMAIL_FROM": "onboarding@resend.dev"
|
|
19
20
|
}
|
|
20
21
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { betterAuth } from "better-auth";
|
|
2
|
+
{{#if (eq database 'none')}}
|
|
3
|
+
import { memoryAdapter } from "better-auth/adapters/memory";
|
|
4
|
+
{{/if}}
|
|
5
|
+
{{#if (eq database 'prisma')}}
|
|
6
|
+
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#if (eq database 'drizzle')}}
|
|
9
|
+
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
|
10
|
+
{{/if}}
|
|
11
|
+
{{#if (eq database 'mongoose')}}
|
|
12
|
+
import { mongodbAdapter } from "better-auth/adapters/mongodb";
|
|
13
|
+
import { MongoClient } from "mongodb";
|
|
14
|
+
{{/if}}
|
|
15
|
+
import { bearer } from "better-auth/plugins";
|
|
16
|
+
|
|
17
|
+
import { env } from "../env";
|
|
18
|
+
{{#if (eq database 'drizzle')}}
|
|
19
|
+
import { db } from "./db";
|
|
20
|
+
{{/if}}
|
|
21
|
+
import { sendEmail } from "./email";
|
|
22
|
+
{{#if (eq database 'prisma')}}
|
|
23
|
+
import { prisma } from "./prisma";
|
|
24
|
+
{{/if}}
|
|
25
|
+
|
|
26
|
+
{{#if (eq database 'none')}}
|
|
27
|
+
// In-memory store for trying the API out: users and sessions are lost on
|
|
28
|
+
// restart, so add a database before going to production
|
|
29
|
+
const memoryDb = { user: [], session: [], account: [], verification: [] };
|
|
30
|
+
|
|
31
|
+
{{/if}}
|
|
32
|
+
{{#if (eq database 'mongoose')}}
|
|
33
|
+
// Better Auth talks to MongoDB through the driver, next to Mongoose
|
|
34
|
+
export const mongoClient = new MongoClient(env.MONGODB_URI);
|
|
35
|
+
|
|
36
|
+
{{/if}}
|
|
37
|
+
// Reports a failed email instead of failing the request: the sign-up or reset
|
|
38
|
+
// already happened, and the user can ask for the email again
|
|
39
|
+
function deliver(email: Parameters<typeof sendEmail>[0]): void {
|
|
40
|
+
sendEmail(email).catch((error: unknown) => {
|
|
41
|
+
console.error(`Could not send "${email.subject}" to ${email.to}:`, error);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Points a verification link at the API itself when the client gave no page
|
|
46
|
+
// to return to, so opening it answers with JSON instead of a redirect to "/"
|
|
47
|
+
function withoutDefaultCallback(url: string): string {
|
|
48
|
+
const link = new URL(url);
|
|
49
|
+
if (link.searchParams.get("callbackURL") === "/") {
|
|
50
|
+
link.searchParams.delete("callbackURL");
|
|
51
|
+
}
|
|
52
|
+
return link.toString();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const auth = betterAuth({
|
|
56
|
+
{{#if (eq database 'none')}}
|
|
57
|
+
database: memoryAdapter(memoryDb),
|
|
58
|
+
{{/if}}
|
|
59
|
+
{{#if (eq database 'prisma')}}
|
|
60
|
+
database: prismaAdapter(prisma, { provider: "postgresql" }),
|
|
61
|
+
{{/if}}
|
|
62
|
+
{{#if (eq database 'drizzle')}}
|
|
63
|
+
database: drizzleAdapter(db, { provider: "pg" }),
|
|
64
|
+
{{/if}}
|
|
65
|
+
{{#if (eq database 'mongoose')}}
|
|
66
|
+
// Without the client, writes run without transactions, which a standalone
|
|
67
|
+
// MongoDB server (no replica set) does not support
|
|
68
|
+
database: mongodbAdapter(mongoClient.db()),
|
|
69
|
+
{{/if}}
|
|
70
|
+
secret: env.BETTER_AUTH_SECRET,
|
|
71
|
+
baseURL: env.BETTER_AUTH_URL,
|
|
72
|
+
// Browser apps on these origins may call the auth routes with cookies
|
|
73
|
+
trustedOrigins: typeof env.CORS_ORIGIN === "string" ? [] : env.CORS_ORIGIN,
|
|
74
|
+
emailAndPassword: {
|
|
75
|
+
enabled: true,
|
|
76
|
+
// Set to true to block sign-in until the email address is verified
|
|
77
|
+
requireEmailVerification: false,
|
|
78
|
+
// A reset signs the user out everywhere
|
|
79
|
+
revokeSessionsOnPasswordReset: true,
|
|
80
|
+
sendResetPassword: ({ user, url, token }) => {
|
|
81
|
+
deliver({
|
|
82
|
+
to: user.email,
|
|
83
|
+
subject: "Reset your password",
|
|
84
|
+
text: `Open this link to choose a new password:\n${url}\n\nOr send this token to POST /api/auth/reset-password: ${token}`,
|
|
85
|
+
});
|
|
86
|
+
return Promise.resolve();
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
emailVerification: {
|
|
90
|
+
sendOnSignUp: true,
|
|
91
|
+
autoSignInAfterVerification: true,
|
|
92
|
+
sendVerificationEmail: ({ user, url }) => {
|
|
93
|
+
deliver({
|
|
94
|
+
to: user.email,
|
|
95
|
+
subject: "Verify your email address",
|
|
96
|
+
text: `Open this link to verify your email address:\n${withoutDefaultCallback(url)}`,
|
|
97
|
+
});
|
|
98
|
+
return Promise.resolve();
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
// Each provider is turned on once its client ID and secret are set
|
|
102
|
+
socialProviders: {
|
|
103
|
+
...(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET
|
|
104
|
+
? {
|
|
105
|
+
github: {
|
|
106
|
+
clientId: env.GITHUB_CLIENT_ID,
|
|
107
|
+
clientSecret: env.GITHUB_CLIENT_SECRET,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
: {}),
|
|
111
|
+
...(env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET
|
|
112
|
+
? {
|
|
113
|
+
google: {
|
|
114
|
+
clientId: env.GOOGLE_CLIENT_ID,
|
|
115
|
+
clientSecret: env.GOOGLE_CLIENT_SECRET,
|
|
116
|
+
},
|
|
117
|
+
}
|
|
118
|
+
: {}),
|
|
119
|
+
},
|
|
120
|
+
// Clients without cookies send "Authorization: Bearer <token>"; the token
|
|
121
|
+
// comes back in the set-auth-token header of sign-in and sign-up
|
|
122
|
+
plugins: [bearer()],
|
|
123
|
+
{{#if useRateLimit}}
|
|
124
|
+
// Sign-up, sign-in, and password reset: 10 attempts per client IP in 15
|
|
125
|
+
// minutes, against password guessing
|
|
126
|
+
rateLimit: {
|
|
127
|
+
enabled: true,
|
|
128
|
+
window: 15 * 60,
|
|
129
|
+
max: 100,
|
|
130
|
+
customRules: {
|
|
131
|
+
"/sign-in/*": { window: 15 * 60, max: 10 },
|
|
132
|
+
"/sign-up/*": { window: 15 * 60, max: 10 },
|
|
133
|
+
"/request-password-reset": { window: 15 * 60, max: 10 },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
{{/if}}
|
|
137
|
+
advanced: {
|
|
138
|
+
// Set by the server from req.ip on every request (see index.ts)
|
|
139
|
+
ipAddress: { ipAddressHeaders: ["x-client-ip"] },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
export type Session = typeof auth.$Infer.Session;
|