@percepta/create 3.0.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.
Files changed (138) hide show
  1. package/README.md +93 -0
  2. package/dist/chunk-GEVZERMP.js +108 -0
  3. package/dist/chunk-R4FWPE4A.js +49 -0
  4. package/dist/chunk-WMJT7CB5.js +57 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +974 -0
  7. package/dist/init-Z4VGBHAK.js +96 -0
  8. package/dist/status-MITGDLTT.js +76 -0
  9. package/dist/sync-J4SFZHDX.js +136 -0
  10. package/dist/upstream-AQI7P4EU.js +144 -0
  11. package/package.json +58 -0
  12. package/template-versions.json +4 -0
  13. package/templates/library/README.md +30 -0
  14. package/templates/library/eslint.config.js +10 -0
  15. package/templates/library/gitignore.template +18 -0
  16. package/templates/library/package.json.template +29 -0
  17. package/templates/library/src/index.ts +9 -0
  18. package/templates/library/tsconfig.json +19 -0
  19. package/templates/monorepo/README.md +41 -0
  20. package/templates/monorepo/eslint.config.js +10 -0
  21. package/templates/monorepo/gitignore.template +31 -0
  22. package/templates/monorepo/npmrc.template +4 -0
  23. package/templates/monorepo/package.json.template +25 -0
  24. package/templates/monorepo/packages/.gitkeep +0 -0
  25. package/templates/monorepo/pnpm-workspace.yaml +2 -0
  26. package/templates/monorepo/tsconfig.json +16 -0
  27. package/templates/webapp/.claude/commands/sync.md +19 -0
  28. package/templates/webapp/.claude/commands/upstream.md +17 -0
  29. package/templates/webapp/.dockerignore +59 -0
  30. package/templates/webapp/.gitattributes +1 -0
  31. package/templates/webapp/.github/workflows/__APP_NAME__-ryvn-release.yaml +114 -0
  32. package/templates/webapp/.github/workflows/__APP_NAME__-terraform.yml +28 -0
  33. package/templates/webapp/.github/workflows/ci.yml +149 -0
  34. package/templates/webapp/.node-version +2 -0
  35. package/templates/webapp/.prettierrc.mjs +5 -0
  36. package/templates/webapp/AGENTS.md +240 -0
  37. package/templates/webapp/Dockerfile +64 -0
  38. package/templates/webapp/README.md +200 -0
  39. package/templates/webapp/agent-skills/database.md +140 -0
  40. package/templates/webapp/agent-skills/deploy.md +94 -0
  41. package/templates/webapp/agent-skills/inngest.md +147 -0
  42. package/templates/webapp/agent-skills/langfuse.md +117 -0
  43. package/templates/webapp/agent-skills/oneshot.md +216 -0
  44. package/templates/webapp/agent-skills/ryvn.md +25 -0
  45. package/templates/webapp/deploy/README.md +39 -0
  46. package/templates/webapp/deploy/ryvn/__APP_NAME__.service.yaml +11 -0
  47. package/templates/webapp/deploy/ryvn/environments/percepta-test/installations/__APP_NAME__.env.percepta-test.serviceinstallation.yaml +121 -0
  48. package/templates/webapp/docker-compose.yml +19 -0
  49. package/templates/webapp/drizzle.config.ts +30 -0
  50. package/templates/webapp/env.example.template +44 -0
  51. package/templates/webapp/eslint.config.mjs +52 -0
  52. package/templates/webapp/gitignore.template +53 -0
  53. package/templates/webapp/next.config.ts +8 -0
  54. package/templates/webapp/npmrc.template +4 -0
  55. package/templates/webapp/package.json.template +122 -0
  56. package/templates/webapp/postcss.config.mjs +5 -0
  57. package/templates/webapp/scripts/create-user.ts +47 -0
  58. package/templates/webapp/scripts/migrate.ts +18 -0
  59. package/templates/webapp/scripts/seed.ts +62 -0
  60. package/templates/webapp/scripts/setup-database.ts +57 -0
  61. package/templates/webapp/scripts/setup-readonly-user.ts +193 -0
  62. package/templates/webapp/scripts/start.sh +52 -0
  63. package/templates/webapp/src/app/(app)/layout.tsx +21 -0
  64. package/templates/webapp/src/app/(app)/page.tsx +30 -0
  65. package/templates/webapp/src/app/(auth)/auth/signin/CredentialsSignInForm.tsx +103 -0
  66. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +30 -0
  67. package/templates/webapp/src/app/(auth)/layout.tsx +15 -0
  68. package/templates/webapp/src/app/api/auth/[...all]/route.ts +4 -0
  69. package/templates/webapp/src/app/api/healthz/route.ts +10 -0
  70. package/templates/webapp/src/app/api/inngest/route.ts +31 -0
  71. package/templates/webapp/src/app/api/readyz/route.ts +31 -0
  72. package/templates/webapp/src/app/api/trpc/[trpc]/route.ts +21 -0
  73. package/templates/webapp/src/app/favicon.ico +0 -0
  74. package/templates/webapp/src/app/global-error.tsx +27 -0
  75. package/templates/webapp/src/app/layout.tsx +18 -0
  76. package/templates/webapp/src/components/FaroProvider.tsx +37 -0
  77. package/templates/webapp/src/components/Header.tsx +70 -0
  78. package/templates/webapp/src/components/Providers.tsx +45 -0
  79. package/templates/webapp/src/components/form/FormItem.tsx +82 -0
  80. package/templates/webapp/src/config/clientEnvConfig.ts +11 -0
  81. package/templates/webapp/src/config/getEnvConfig.ts +62 -0
  82. package/templates/webapp/src/config/isDev.ts +7 -0
  83. package/templates/webapp/src/drizzle/db.ts +28 -0
  84. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +57 -0
  85. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +376 -0
  86. package/templates/webapp/src/drizzle/migrations/meta/_journal.json +13 -0
  87. package/templates/webapp/src/drizzle/schema/auth/accounts.ts +33 -0
  88. package/templates/webapp/src/drizzle/schema/auth/sessions.ts +25 -0
  89. package/templates/webapp/src/drizzle/schema/auth/users.ts +38 -0
  90. package/templates/webapp/src/drizzle/schema/auth/verifications.ts +19 -0
  91. package/templates/webapp/src/drizzle/schema/index.ts +4 -0
  92. package/templates/webapp/src/drizzle/schema/utils/jsonbFromZod.ts +25 -0
  93. package/templates/webapp/src/instrumentation.ts +35 -0
  94. package/templates/webapp/src/lib/auth/index.ts +85 -0
  95. package/templates/webapp/src/lib/auth-client.ts +6 -0
  96. package/templates/webapp/src/lib/trpc.ts +15 -0
  97. package/templates/webapp/src/server/api/root.ts +5 -0
  98. package/templates/webapp/src/server/trpc.ts +61 -0
  99. package/templates/webapp/src/services/AuthContextService.ts +63 -0
  100. package/templates/webapp/src/services/DatabaseService.ts +54 -0
  101. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +5 -0
  102. package/templates/webapp/src/services/inngest/InngestService.ts +71 -0
  103. package/templates/webapp/src/services/inngest/events/AppEvents.ts +34 -0
  104. package/templates/webapp/src/services/inngest/events/payloads/ExampleEventPayload.ts +14 -0
  105. package/templates/webapp/src/services/langfuse/LangfuseService.ts +80 -0
  106. package/templates/webapp/src/services/logger/AppLogger.ts +61 -0
  107. package/templates/webapp/src/services/logger/withRequestContext.ts +27 -0
  108. package/templates/webapp/src/services/observability/initFaro.ts +22 -0
  109. package/templates/webapp/src/startup-checks.ts +32 -0
  110. package/templates/webapp/src/styles/globals.css +27 -0
  111. package/templates/webapp/src/utils/__tests__/cn.test.ts +20 -0
  112. package/templates/webapp/src/utils/cn.ts +6 -0
  113. package/templates/webapp/src/utils/syncInngestApp.ts +62 -0
  114. package/templates/webapp/terraform/README.md +147 -0
  115. package/templates/webapp/terraform/deploy.sh +97 -0
  116. package/templates/webapp/terraform/main.tf +101 -0
  117. package/templates/webapp/terraform/modules/cloudtrail/main.tf +27 -0
  118. package/templates/webapp/terraform/modules/cloudtrail/outputs.tf +10 -0
  119. package/templates/webapp/terraform/modules/cloudtrail/variables.tf +15 -0
  120. package/templates/webapp/terraform/modules/networking/main.tf +118 -0
  121. package/templates/webapp/terraform/modules/networking/outputs.tf +38 -0
  122. package/templates/webapp/terraform/modules/networking/variables.tf +24 -0
  123. package/templates/webapp/terraform/modules/rds/main.tf +227 -0
  124. package/templates/webapp/terraform/modules/rds/outputs.tf +73 -0
  125. package/templates/webapp/terraform/modules/rds/variables.tf +61 -0
  126. package/templates/webapp/terraform/modules/s3-logging/main.tf +148 -0
  127. package/templates/webapp/terraform/modules/s3-logging/outputs.tf +10 -0
  128. package/templates/webapp/terraform/modules/s3-logging/variables.tf +16 -0
  129. package/templates/webapp/terraform/modules/secrets/main.tf +39 -0
  130. package/templates/webapp/terraform/modules/secrets/outputs.tf +9 -0
  131. package/templates/webapp/terraform/modules/secrets/variables.tf +51 -0
  132. package/templates/webapp/terraform/outputs.tf +102 -0
  133. package/templates/webapp/terraform/providers.tf +32 -0
  134. package/templates/webapp/terraform/terraform.tfvars.example +65 -0
  135. package/templates/webapp/terraform/variables.tf +129 -0
  136. package/templates/webapp/tsconfig.json +14 -0
  137. package/templates/webapp/vitest.config.ts +9 -0
  138. package/templates/webapp/vitest.setup.ts +5 -0
@@ -0,0 +1,240 @@
1
+ # Webapp Template
2
+
3
+ Next.js 15 full-stack application scaffolded from the Mosaic webapp template via `@percepta/create`. Uses React 19, TypeScript, Tailwind CSS v4, tRPC, Drizzle ORM, Better Auth, and Inngest.
4
+
5
+ ## Build & Dev Commands
6
+
7
+ - `pnpm dev` — start dev server with Turbopack
8
+ - `pnpm build` — production build
9
+ - `pnpm lint` — run ESLint
10
+ - `pnpm test` — run Vitest tests
11
+ - `pnpm docker:up` / `pnpm docker:down` — start/stop PostgreSQL
12
+ - `pnpm db:generate` — generate Drizzle migrations
13
+ - `pnpm db:migrate` — apply migrations
14
+ - `pnpm db:setup-and-migrate` — create DB + migrate
15
+ - `pnpm db:studio` — open Drizzle Studio
16
+ - `pnpm db:seed` — seed default dev user (admin@example.com / password)
17
+
18
+ **Package manager**: Always use `pnpm`, never `npm` or `yarn`.
19
+
20
+ ## Code Style
21
+
22
+ - Double quotes for strings
23
+ - `no-console` is enforced — use `@percepta/logger` instead (see below)
24
+ - Logger messages must be plain string literals, not variables or templates
25
+ - `no-process-env` is enforced — use `getEnvConfig()` from `src/config/`
26
+ - Use `@percepta/design` components before writing custom UI
27
+ - Use `AsyncContent` from `@percepta/components` for loading/error states
28
+ - Tailwind CSS for all styling; icons from `lucide-react`
29
+
30
+ ## Project Structure
31
+
32
+ ```
33
+ src/ # Application source
34
+ ├── app/ # Next.js App Router pages, layouts, and API route handlers
35
+ ├── components/ # React components
36
+ ├── config/ # Env config (clientEnvConfig, getEnvConfig)
37
+ ├── drizzle/ # Schema definitions & SQL migrations
38
+ │ ├── schema/
39
+ │ └── migrations/
40
+ ├── lib/ # Auth config, tRPC client, API middleware
41
+ ├── server/ # tRPC router definitions
42
+ │ ├── trpc.ts # Context & procedure builders
43
+ │ └── api/root.ts # Root appRouter
44
+ ├── services/
45
+ │ ├── inngest/ # Background job definitions
46
+ │ ├── langfuse/ # LLM observability
47
+ │ ├── logger/ # App logger setup (wraps @percepta/logger)
48
+ │ └── observability/ # OpenTelemetry setup
49
+ └── utils/ # Helpers (cn, pathEncryption, etc.)
50
+
51
+ deploy/ # Infrastructure-as-code for Ryvn deployments
52
+ └── ryvn/
53
+ ├── __APP_NAME__.service.yaml # Ryvn Service definition
54
+ └── environments/<env>/installations/__APP_NAME__.env.<env>.serviceinstallation.yaml
55
+ ```
56
+
57
+ ## @percepta Packages
58
+
59
+ ### @percepta/design — UI Component Library
60
+
61
+ 47+ accessible components built on Radix UI. Import styles and theme in your app:
62
+
63
+ ```tsx
64
+ // src/app/layout.tsx
65
+ import "@percepta/design/styles";
66
+
67
+ // src/styles/globals.css
68
+ @import "@percepta/design/theme";
69
+ ```
70
+
71
+ **Tailwind v4 + globals.css:** This project uses Tailwind CSS v4, which is CSS-based — there is no `tailwind.config.js`. Do not create one.
72
+
73
+ - Import order in globals.css matters: `@import "tailwindcss"` must come before `@import "@percepta/design/theme"` so design tokens layer correctly
74
+ - Custom theme values go in the `@theme inline` block in CSS, not a JS config file
75
+ - Use `@import "tailwindcss"`, not the old v3 directives (`@tailwind base`, `@tailwind components`, `@tailwind utilities`)
76
+
77
+ Import components directly:
78
+
79
+ ```tsx
80
+ import {
81
+ Button, Dialog, DropdownMenu, Select, Tabs,
82
+ Input, Checkbox, Switch, Card, Badge, Table,
83
+ Tooltip, Popover, Accordion, Avatar, Calendar,
84
+ // ... 47+ components available
85
+ } from "@percepta/design";
86
+ ```
87
+
88
+ Also includes composite components: `ButtonWithDropdown`, `Combobox`, `IconButton`, `InputGroup`, `MosaicDialog`, `MarkdownEditor`.
89
+
90
+ **Discovering components and props:** These are shadcn-style components wrapping Radix UI, shipped as an npm package. To explore what's available:
91
+
92
+ - Full export list: read `node_modules/@percepta/design/dist/src/index.d.ts`
93
+ - Component props and variants: read `node_modules/@percepta/design/dist/src/components/ui/<name>.d.ts` (e.g., `button.d.ts`)
94
+ - Composite components: `node_modules/@percepta/design/dist/src/components/composite/<Name>.d.ts`
95
+ - Props follow standard Radix UI patterns — if you know Radix, you know these components
96
+
97
+ ### @percepta/build — Shared Build Configs
98
+
99
+ Provides centralized ESLint, Prettier, TypeScript, and Vitest configuration.
100
+
101
+ ```js
102
+ // eslint.config.mjs
103
+ import createEslintConfig from "@percepta/build/eslint";
104
+ export default [
105
+ ...createEslintConfig({ type: "react", dirname: import.meta.dirname }),
106
+ // app-specific overrides...
107
+ ];
108
+ ```
109
+
110
+ ```json
111
+ // tsconfig.json
112
+ { "extends": "@percepta/build/tsconfig/web" }
113
+ ```
114
+
115
+ Vitest config is also available via `@percepta/build/vitest`.
116
+
117
+ ### @percepta/components — React Utilities
118
+
119
+ Async data handling and hooks for React Query:
120
+
121
+ - **`AsyncContent<T>`** — renders loading spinner, error state, or children based on a React Query result. Use this for all data-fetching UI.
122
+ - **`AsyncArrayContent<T[]>`** — same but for multiple parallel queries
123
+ - **`ErrorContainer`** — consistent error display
124
+ - **`useBoolean`** — boolean state with `setTrue`, `setFalse`, `toggle`
125
+ - **`useDebouncedUpdate`** — debounced value updates with callback
126
+ - **`useSafeParseQueryParams`** — Zod-based URL query param validation
127
+
128
+ ### @percepta/logger — Structured Logging
129
+
130
+ PII-safe logging with `safe`/`unsafe` field separation. Unsafe data is automatically redacted.
131
+
132
+ ```tsx
133
+ import { getLogger } from "@/services/logger/AppLogger";
134
+ const logger = getLogger();
135
+
136
+ // safe data appears as-is; unsafe data (PII) is redacted
137
+ logger.info({ safe: { requestId }, unsafe: { email } }, "User action completed");
138
+
139
+ // errors go as the third argument
140
+ logger.error({ safe: { documentId } }, "Processing failed", error);
141
+ ```
142
+
143
+ The app's logger is initialized in `src/services/logger/AppLogger.ts` using `createLogFactory()` and `createTracerFactory()` from this package. It uses `AsyncLocalStorage` for automatic request context propagation.
144
+
145
+ ### @percepta/utils — Shared Utilities
146
+
147
+ - **`assertNever(value)`** — TypeScript exhaustiveness check (throws on impossible cases)
148
+ - **`batchAsync(items, fn, concurrency)`** — process items in batched parallel
149
+ - **`getEnvConfig()`** — typed environment variable loading
150
+ - **`zodKeyedTypeUnion()`** — Zod schemas for discriminated unions
151
+
152
+ ### @percepta/next-utils — Next.js Middleware
153
+
154
+ Request context management and observability for Next.js:
155
+
156
+ - **`createRequestContextMiddleware()`** — middleware that adds `X-Request-ID` headers
157
+ - **`withRequestContext(handler)`** — wraps Pages Router API handlers
158
+ - **`withAppRouterRequestContext(handler)`** — wraps App Router handlers
159
+ - **`getRequestId()`** — extracts or generates request IDs
160
+ - **Faro integration** — `@percepta/next-utils/faro` for Grafana frontend observability
161
+
162
+ ### @percepta/chat-components — Agent Chat UI
163
+
164
+ Pre-built components for conversational AI interfaces:
165
+
166
+ - **`ChatContainer`** — main chat wrapper (manages agent slug, threads, messages)
167
+ - **`MessageList`** — renders message history
168
+ - **`MessageSender`** — input UI for sending messages
169
+ - **`MosaicContextProvider`** — context provider for chat state
170
+ - Hooks: `useCreateThread`, `useMessages`, `useSendMessage`
171
+
172
+ Requires `@percepta/mosaic-typescript-sdk` as a peer dependency.
173
+
174
+ ## Skill Guides
175
+
176
+ Detailed how-to guides for each major stack component. Read the relevant guide when working with that technology.
177
+
178
+ | Guide | File | When to read |
179
+ |-------|------|-------------|
180
+ | Background Jobs (Inngest) | [agent-skills/inngest.md](agent-skills/inngest.md) | Adding async tasks, scheduled jobs, or agent workflows |
181
+ | LLM Observability (Langfuse) | [agent-skills/langfuse.md](agent-skills/langfuse.md) | App uses LLMs and needs trace/eval monitoring |
182
+ | Database (Drizzle) | [agent-skills/database.md](agent-skills/database.md) | Adding tables, writing migrations, querying data |
183
+ | Deployment (Ryvn) | [agent-skills/ryvn.md](agent-skills/ryvn.md) | Ryvn overview and Percepta environment context |
184
+ | Deploy to percepta-test | [agent-skills/deploy.md](agent-skills/deploy.md) | Step-by-step deploy using the pre-scaffolded `deploy/ryvn/` IaC files |
185
+ | Build App (Oneshot) | [agent-skills/oneshot.md](agent-skills/oneshot.md) | Building a complete app from requirements end-to-end |
186
+
187
+ ## Key Patterns
188
+
189
+ ### tRPC
190
+
191
+ Type-safe API layer. Define routers in `src/server/api/`, compose in `root.ts`:
192
+
193
+ ```tsx
194
+ // src/server/api/root.ts
195
+ import { router } from "../trpc";
196
+ export const appRouter = router({ /* add routers here */ });
197
+ export type AppRouter = typeof appRouter;
198
+ ```
199
+
200
+ Client-side usage via `src/lib/trpc.ts`.
201
+
202
+ ### Authentication
203
+
204
+ Better Auth configured in `src/lib/auth/`. Email/password credentials enabled by default.
205
+
206
+ - **Server-side**: `auth.api.getSession({ headers: await headers() })` — get session in server components or tRPC context
207
+ - **Client-side**: `authClient.useSession()` — React hook from `src/lib/auth-client.ts`
208
+ - **Sign in**: `authClient.signIn.email({ email, password })` — client-side
209
+ - **Sign out**: `authClient.signOut()` — client-side
210
+ - **API route**: `src/app/api/auth/[...all]/route.ts` — Better Auth handler
211
+ - **Env vars**: `BETTER_AUTH_SECRET` (required), `BETTER_AUTH_URL` (defaults to `http://localhost:3000`)
212
+
213
+ ### Background Jobs
214
+
215
+ Inngest for async task processing. Define functions in `src/services/inngest/`. Configure via `INNGEST_*` env vars.
216
+
217
+ ### Database
218
+
219
+ PostgreSQL with Drizzle ORM. Schema in `src/drizzle/schema/`, migrations in `src/drizzle/migrations/`. Connection managed by `src/services/DatabaseService.ts`.
220
+
221
+ ### Observability
222
+
223
+ OpenTelemetry initialized in `src/instrumentation.ts`. Langfuse for LLM tracking in `src/services/langfuse/`. Faro for frontend monitoring via `@percepta/next-utils/faro`.
224
+
225
+ ## Deployment
226
+
227
+ To deploy this app to percepta-test, follow [agent-skills/deploy.md](agent-skills/deploy.md). The Ryvn service definition and percepta-test installation YAML are already scaffolded at `deploy/ryvn/` with all values filled in — deploy is mostly "copy these two files into the infra repo, open a PR, set three secrets in the Ryvn UI."
228
+
229
+ The release CI/CD workflow is already included at `.github/workflows/ryvn-release.yaml`.
230
+
231
+ For Ryvn CLI operations, use the `/use-ryvn` skill.
232
+
233
+ ## Template Sync
234
+
235
+ This app tracks its template origin in `.mosaic-template.json`. Two Claude commands are available:
236
+
237
+ - **`/sync`** — pull downstream changes from the mosaic template into this app
238
+ - **`/upstream`** — propose app improvements back to the mosaic template
239
+
240
+ Both commands use `@percepta/create` CLI under the hood. Check `mosaic-template-notes.md` for documented intentional divergences from the template.
@@ -0,0 +1,64 @@
1
+ # Base image with PNPM:
2
+ ARG NODE_VERSION=24.4.1
3
+ FROM node:${NODE_VERSION}-slim AS base
4
+ WORKDIR /app
5
+ RUN npm install -g pnpm
6
+
7
+ # Build stage:
8
+ FROM base AS builder
9
+
10
+ COPY . .
11
+
12
+ # Create .npmrc with build-time token for private packages:
13
+ ARG NPM_TOKEN
14
+ RUN echo "@percepta:registry=https://registry.npmjs.org/" > .npmrc && \
15
+ echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
16
+
17
+ # Add BASE_PATH as a build argument
18
+ ARG BASE_PATH
19
+ ENV BASE_PATH=${BASE_PATH}
20
+
21
+ RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
22
+
23
+ RUN NODE_ENV=production NODE_OPTIONS="--max-old-space-size=4096" pnpm build
24
+
25
+ # Remove .npmrc for security (contains auth token):
26
+ RUN rm -f .npmrc
27
+
28
+ # Production stage - create the final image:
29
+ FROM base AS production
30
+
31
+ # Install PostgreSQL client:
32
+ USER root
33
+ RUN apt-get update \
34
+ && apt-get install -y --no-install-recommends \
35
+ postgresql-client \
36
+ && rm -rf /var/lib/apt/lists/*
37
+
38
+ # Set production environment:
39
+ ENV NODE_ENV=production
40
+ ENV NEXT_TELEMETRY_DISABLED=1
41
+
42
+ # Copy BASE_PATH from the builder stage
43
+ ENV BASE_PATH=${BASE_PATH}
44
+
45
+ # Copy built app from builder stage
46
+ COPY --from=builder /app/.next/standalone ./
47
+ COPY --from=builder /app/.next/static ./.next/static
48
+ COPY --from=builder /app/public ./public
49
+
50
+ # Copy scripts and source files needed for start.sh and runtime
51
+ COPY --from=builder /app/scripts ./scripts
52
+ COPY --from=builder /app/src ./src
53
+ COPY --from=builder /app/node_modules ./node_modules
54
+
55
+
56
+ # Expose the port:
57
+ EXPOSE 3000
58
+
59
+ # Set correct permissions and user:
60
+ RUN chown -R 1000:1000 /app && chmod +x /app/scripts/start.sh
61
+ USER 1000
62
+
63
+ # Start the application:
64
+ CMD ["./scripts/start.sh"]
@@ -0,0 +1,200 @@
1
+ # __APP_TITLE__
2
+
3
+ A production-ready Next.js application with authentication, database, logging, background jobs, and infrastructure as code.
4
+
5
+ ## Features
6
+
7
+ - **Next.js 15** with App Router
8
+ - **Authentication** via Better Auth with email/password credentials
9
+ - **Database** with PostgreSQL, Drizzle ORM, and migrations
10
+ - **Logging** with Pino and structured safe/unsafe data separation
11
+ - **Background Jobs** with Inngest
12
+ - **Observability** with OpenTelemetry and Langfuse integration
13
+ - **Infrastructure** with Terraform modules for AWS (RDS, S3, IAM)
14
+ - **Type Safety** with TypeScript and Zod schemas
15
+
16
+ ## Quick Start
17
+
18
+ ### 1. Start the Database
19
+
20
+ ```bash
21
+ pnpm docker:up
22
+ ```
23
+
24
+ ### 2. Initialize the Database
25
+
26
+ ```bash
27
+ pnpm db:setup-and-migrate
28
+ ```
29
+
30
+ ### 3. Configure Environment
31
+
32
+ Copy `.env.example` to `.env.local` and configure your environment variables.
33
+
34
+ ### 4. Start Development Server
35
+
36
+ ```bash
37
+ pnpm dev
38
+ ```
39
+
40
+ Open [http://localhost:3000](http://localhost:3000) to see your app.
41
+
42
+ ## Project Structure
43
+
44
+ ```
45
+ src/
46
+ ├── app/ # Next.js App Router pages, layouts, and API route handlers
47
+ ├── components/ # React components
48
+ ├── config/ # Environment configuration
49
+ ├── drizzle/ # Database schema and migrations
50
+ ├── hooks/ # Custom React hooks
51
+ ├── lib/ # Authentication and utilities
52
+ ├── server/ # tRPC routers
53
+ ├── services/ # Business logic services
54
+ │ ├── inngest/ # Background job definitions
55
+ │ ├── langfuse/ # LLM observability
56
+ │ └── logger/ # Structured logging
57
+ └── utils/ # Utility functions
58
+ ```
59
+
60
+ ## Available Scripts
61
+
62
+ | Script | Description |
63
+ |--------|-------------|
64
+ | `pnpm dev` | Start development server with Turbopack |
65
+ | `pnpm build` | Build for production |
66
+ | `pnpm start` | Start production server |
67
+ | `pnpm lint` | Run ESLint |
68
+ | `pnpm docker:up` | Start PostgreSQL container |
69
+ | `pnpm docker:down` | Stop PostgreSQL container |
70
+ | `pnpm db:generate` | Generate Drizzle migrations |
71
+ | `pnpm db:migrate` | Run database migrations |
72
+ | `pnpm db:setup` | Create database and user |
73
+ | `pnpm db:setup-and-migrate` | Setup and migrate database |
74
+ | `pnpm db:studio` | Open Drizzle Studio |
75
+ | `pnpm db:create-user` | Create a user account |
76
+ | `pnpm db:seed` | Seed default dev user |
77
+
78
+ ## Logging
79
+
80
+ This template uses structured logging with safe/unsafe data separation to protect PII:
81
+
82
+ ```typescript
83
+ import { getLogger } from "@/services/logger/AppLogger";
84
+
85
+ const logger = getLogger();
86
+
87
+ // Safe data appears as-is in logs
88
+ // Unsafe data (PII) is redacted
89
+ logger.info(
90
+ { safe: { requestId }, unsafe: { email } },
91
+ "User action completed"
92
+ );
93
+
94
+ // Errors should be passed as the third parameter
95
+ logger.error(
96
+ { safe: { documentId } },
97
+ "Processing failed",
98
+ error
99
+ );
100
+ ```
101
+
102
+ ## Authentication
103
+
104
+ This app uses [Better Auth](https://better-auth.com) for authentication with email/password credentials enabled by default.
105
+
106
+ Required environment variables:
107
+
108
+ ```bash
109
+ BETTER_AUTH_SECRET=generate-with-openssl-rand-base64-32
110
+ BETTER_AUTH_URL=http://localhost:3000
111
+ ```
112
+
113
+ To create a dev user:
114
+ ```bash
115
+ pnpm db:seed
116
+ # Creates admin@example.com / password
117
+ ```
118
+
119
+ ## Environment Variables
120
+
121
+ ### Application
122
+
123
+ | Variable | Description | Default |
124
+ |----------|-------------|---------|
125
+ | `NODE_ENV` | Environment mode | `development` |
126
+ | `APP_BASE_URL` | Base URL for the app | - |
127
+
128
+ ### Database
129
+
130
+ | Variable | Description | Default |
131
+ |----------|-------------|---------|
132
+ | `DATABASE_HOST` | PostgreSQL host | `localhost` |
133
+ | `DATABASE_PORT` | PostgreSQL port | `5434` |
134
+ | `DATABASE_USERNAME` | Database user | `postgres` |
135
+ | `DATABASE_PASSWORD` | Database password | `postgres` |
136
+ | `DATABASE_NAME` | Database name | `__DB_NAME__` |
137
+ | `DATABASE_USE_SSL` | Enable SSL | `false` |
138
+
139
+ ### Security
140
+
141
+ | Variable | Description |
142
+ |----------|-------------|
143
+ | `ENCRYPTION_SECRET_KEY` | 32-character key for URL encryption |
144
+
145
+ Generate a secret key:
146
+ ```bash
147
+ node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
148
+ ```
149
+
150
+ ### Inngest (Background Jobs)
151
+
152
+ | Variable | Description |
153
+ |----------|-------------|
154
+ | `INNGEST_BASE_URL` | Inngest server URL |
155
+ | `INNGEST_SIGNING_KEY` | Inngest signing key |
156
+ | `INNGEST_EVENT_KEY` | Inngest event key |
157
+
158
+ ### Langfuse (LLM Observability)
159
+
160
+ | Variable | Description |
161
+ |----------|-------------|
162
+ | `LANGFUSE_BASE_URL` | Langfuse server URL |
163
+ | `LANGFUSE_PUBLIC_KEY` | Langfuse public key |
164
+ | `LANGFUSE_SECRET_KEY` | Langfuse secret key |
165
+
166
+ ## Local AWS Development
167
+
168
+ This application uses the default AWS SDK credential provider chain:
169
+
170
+ 1. **Environment Variables**:
171
+ - `AWS_ACCESS_KEY_ID`
172
+ - `AWS_SECRET_ACCESS_KEY`
173
+ - `AWS_SESSION_TOKEN` (optional)
174
+ - `AWS_REGION` (defaults to `us-east-1`)
175
+
176
+ 2. **AWS Credentials File**: Run `aws configure` or `aws sso login`
177
+
178
+ ## Infrastructure (Terraform)
179
+
180
+ The `terraform/` directory contains modules for AWS infrastructure:
181
+
182
+ - **RDS**: PostgreSQL database
183
+ - **S3**: File storage with logging
184
+ - **Networking**: VPC endpoints
185
+ - **Secrets**: AWS Secrets Manager
186
+ - **IAM**: Roles and policies
187
+
188
+ See `terraform/README.md` for deployment instructions.
189
+
190
+ ## Learn More
191
+
192
+ - [Next.js Documentation](https://nextjs.org/docs)
193
+ - [Drizzle ORM](https://orm.drizzle.team)
194
+ - [Better Auth](https://better-auth.com/docs)
195
+ - [Inngest](https://www.inngest.com/docs)
196
+ - [Langfuse](https://langfuse.com/docs)
197
+
198
+ ## License
199
+
200
+ MIT
@@ -0,0 +1,140 @@
1
+ # Database — PostgreSQL + Drizzle ORM
2
+
3
+ This template uses PostgreSQL as the database and Drizzle ORM as the query builder and migration tool. Drizzle is a TypeScript-first ORM — schemas are defined in TypeScript, queries are type-safe, and migrations are generated as plain SQL files.
4
+
5
+ ## Adding a New Table
6
+
7
+ ### 1. Define the schema
8
+
9
+ Create a new schema file alongside the existing ones:
10
+
11
+ ```typescript
12
+ import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
13
+ import { users } from "./auth/users";
14
+
15
+ export const documents = pgTable("documents", {
16
+ id: uuid("id").defaultRandom().primaryKey(),
17
+ title: text("title").notNull(),
18
+ content: text("content"),
19
+ userId: uuid("user_id")
20
+ .notNull()
21
+ .references(() => users.id),
22
+ createdAt: timestamp("created_at").defaultNow().notNull(),
23
+ updatedAt: timestamp("updated_at").defaultNow().notNull(),
24
+ });
25
+ ```
26
+
27
+ ### 2. Export from the schema index
28
+
29
+ Add a re-export for your new table in the schema index file:
30
+
31
+ ```typescript
32
+ export * from "./documents";
33
+ ```
34
+
35
+ ### 3. Generate the migration
36
+
37
+ ```bash
38
+ pnpm db:generate
39
+ ```
40
+
41
+ This creates a new SQL migration file. **Review the generated SQL** — Drizzle generates it automatically but you should verify it's correct.
42
+
43
+ ### 4. Apply the migration
44
+
45
+ ```bash
46
+ pnpm db:migrate
47
+ ```
48
+
49
+ ## Querying Data
50
+
51
+ ### Basic queries with DatabaseService
52
+
53
+ ```typescript
54
+ import { DatabaseService } from "@/services/DatabaseService";
55
+ import { documents } from "@/drizzle/schema";
56
+ import { eq } from "drizzle-orm";
57
+
58
+ const db = DatabaseService.create().getDatabase();
59
+
60
+ // Select
61
+ const docs = await db.select().from(documents).where(eq(documents.userId, userId));
62
+
63
+ // Insert
64
+ await db.insert(documents).values({ title: "New Doc", userId });
65
+
66
+ // Update
67
+ await db.update(documents).set({ title: "Updated" }).where(eq(documents.id, docId));
68
+
69
+ // Delete
70
+ await db.delete(documents).where(eq(documents.id, docId));
71
+ ```
72
+
73
+ ### Transactions
74
+
75
+ `DatabaseService` propagates transactions via `AsyncLocalStorage` — any code running inside `createTransaction` automatically uses the same transaction, even across nested function calls:
76
+
77
+ ```typescript
78
+ const dbService = DatabaseService.create();
79
+
80
+ await dbService.createTransaction(async (txn) => {
81
+ // These both run in the same transaction
82
+ await txn.insert(documents).values({ title: "Doc 1", userId });
83
+ await txn.insert(documents).values({ title: "Doc 2", userId });
84
+
85
+ // If any nested function calls dbService.getDatabase(),
86
+ // it gets the same transaction — not a new connection
87
+ });
88
+ ```
89
+
90
+ If `createTransaction` is called while already inside a transaction, it reuses the existing one (no nested transactions).
91
+
92
+ ## Running PostgreSQL Locally
93
+
94
+ ### Start with Docker Compose
95
+
96
+ ```bash
97
+ pnpm docker:up
98
+ ```
99
+
100
+ This starts PostgreSQL 16 on port **5434** (not the default 5432, to avoid conflicts).
101
+
102
+ ### Create the database and run migrations
103
+
104
+ ```bash
105
+ pnpm db:setup-and-migrate
106
+ ```
107
+
108
+ This creates the database if it doesn't exist and applies all pending migrations.
109
+
110
+ ### Inspect the database
111
+
112
+ ```bash
113
+ pnpm db:studio
114
+ ```
115
+
116
+ Opens Drizzle Studio in the browser — a visual database explorer.
117
+
118
+ ### Stop PostgreSQL
119
+
120
+ ```bash
121
+ pnpm docker:down
122
+ ```
123
+
124
+ ## Environment Variables
125
+
126
+ | Variable | Default | Description |
127
+ |----------|---------|-------------|
128
+ | `DATABASE_HOST` | `localhost` | PostgreSQL host |
129
+ | `DATABASE_PORT` | `5434` | PostgreSQL port |
130
+ | `DATABASE_USERNAME` | `postgres` | Database user |
131
+ | `DATABASE_PASSWORD` | `postgres` | Database password |
132
+ | `DATABASE_NAME` | `__DB_NAME__` | Database name |
133
+ | `DATABASE_USE_SSL` | `false` | Enable SSL connections |
134
+
135
+ ## Key Concepts
136
+
137
+ - **Schemas are TypeScript, migrations are SQL.** You define tables in TS, then `pnpm db:generate` creates the SQL diff. Never hand-write migration SQL.
138
+ - **DatabaseService is a singleton.** Call `DatabaseService.create()` anywhere — it always returns the same instance with the same connection pool.
139
+ - **Transaction propagation is automatic.** Code inside `createTransaction` gets a transaction; code outside gets the raw connection. `getDatabase()` returns whichever is active.
140
+ - **Port 5434.** The local Docker Compose uses port 5434 to avoid conflicting with any system PostgreSQL on 5432.