Skip to content

Commit 9c6973b

Browse files
authored
chore(deps): update AI SDK v6, React 19.2.3, tRPC 11.8 (#2144)
1 parent 19de9bd commit 9c6973b

35 files changed

+1079
-1063
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
name: "Build"
3535
runs-on: ubuntu-latest
3636
steps:
37-
- uses: actions/checkout@v4
37+
- uses: actions/checkout@v6
3838
# Configure Bun and install dependencies
3939
- uses: oven-sh/setup-bun@v2
4040
with:
@@ -74,7 +74,7 @@ jobs:
7474
- run: docker save api:${{ github.sha }} | gzip > api-image.tar.gz
7575

7676
# Upload build artifacts
77-
- uses: actions/upload-artifact@v4
77+
- uses: actions/upload-artifact@v6
7878
with:
7979
name: "build"
8080
path: "apps/web/dist\napps/app/dist\napps/api/dist\napi-image.tar.gz\n"

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,13 @@
101101
"signup",
102102
"sourcemap",
103103
"swapi",
104+
"tanstack",
104105
"tarkus",
105106
"trpc",
106107
"tslib",
107108
"typechecking",
108109
"vite",
110+
"vitepress",
109111
"vitest",
110112
"webflow"
111113
]

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ bun wrangler secret put RESEND_API_KEY
172172
bun wrangler secret put OPENAI_API_KEY
173173
```
174174

175+
Note: run these commands from the target app directory or pass `--config apps/<app>/wrangler.jsonc`.
176+
175177
**Note:** The `RESEND_EMAIL_FROM` is configured in `wrangler.jsonc` as it's not sensitive.
176178

177179
### 2. Build and Deploy

apps/api/dev.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
*/
66

77
import { Hono } from "hono";
8+
import { logger } from "hono/logger";
9+
import { requestId } from "hono/request-id";
10+
import { secureHeaders } from "hono/secure-headers";
811
import { parseArgs } from "node:util";
912
import { getPlatformProxy } from "wrangler";
1013
import api from "./index.js";
1114
import { createAuth } from "./lib/auth.js";
1215
import type { AppContext } from "./lib/context.js";
1316
import { createDb } from "./lib/db.js";
1417
import type { Env } from "./lib/env.js";
18+
import { errorHandler, notFoundHandler } from "./lib/middleware.js";
1519

1620
const { values: args } = parseArgs({
1721
args: Bun.argv.slice(2),
@@ -27,6 +31,15 @@ type CloudflareEnv = {
2731

2832
const app = new Hono<AppContext>();
2933

34+
// Error and 404 handlers (must be on top-level app)
35+
app.onError(errorHandler);
36+
app.notFound(notFoundHandler);
37+
38+
// Standard middleware
39+
app.use(secureHeaders());
40+
app.use(requestId());
41+
app.use(logger());
42+
3043
// persist:true maintains state across restarts in .wrangler directory
3144
const cf = await getPlatformProxy<CloudflareEnv>({
3245
configPath: "./wrangler.jsonc",
@@ -37,7 +50,7 @@ const cf = await getPlatformProxy<CloudflareEnv>({
3750
// Inject context with two database connections:
3851
// - db: Hyperdrive caching for read-heavy queries
3952
// - dbDirect: No cache for writes and transactions
40-
app.use("*", async (c, next) => {
53+
app.use(async (c, next) => {
4154
const db = createDb(cf.env.HYPERDRIVE_CACHED);
4255
const dbDirect = createDb(cf.env.HYPERDRIVE_DIRECT);
4356

apps/api/lib/ai.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
import type { OpenAIProvider } from "@ai-sdk/openai";
22
import { createOpenAI } from "@ai-sdk/openai";
3-
import type { Env } from "./env";
3+
import type { TRPCContext } from "./context";
44

5-
// Request-scoped cache key
6-
const OPENAI_CACHE_KEY = Symbol("openai");
5+
type OpenAIContext = Pick<TRPCContext, "env" | "cache">;
6+
7+
// Request-scoped cache key for the provider instance.
8+
const OPENAI_PROVIDER = Symbol("openaiProvider");
79

810
/**
9-
* Returns an OpenAI provider instance with request-scoped caching.
10-
* Uses the tRPC context cache to avoid recreating the provider multiple times
11-
* within the same request while ensuring environment isolation.
11+
* Returns a request-scoped OpenAI provider instance.
12+
* Pass the tRPC context to reuse the provider within a single request.
1213
*/
13-
export function getOpenAI(env: Env, cache?: Map<string | symbol, unknown>) {
14-
// Use request-scoped cache if available (from tRPC context)
15-
if (cache?.has(OPENAI_CACHE_KEY)) {
16-
return cache.get(OPENAI_CACHE_KEY) as OpenAIProvider;
14+
export function getOpenAI(ctx: OpenAIContext): OpenAIProvider {
15+
if (ctx.cache.has(OPENAI_PROVIDER)) {
16+
return ctx.cache.get(OPENAI_PROVIDER) as OpenAIProvider;
1717
}
1818

1919
const provider = createOpenAI({
20-
apiKey: env.OPENAI_API_KEY,
20+
apiKey: ctx.env.OPENAI_API_KEY,
2121
});
2222

23-
// Cache for this request only
24-
cache?.set(OPENAI_CACHE_KEY, provider);
23+
ctx.cache.set(OPENAI_PROVIDER, provider);
2524

2625
return provider;
2726
}

apps/api/lib/auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,8 @@ export function createAuth(
181181
}
182182

183183
export type Auth = ReturnType<typeof betterAuth>;
184+
185+
// Base session types from Better Auth - plugin-specific fields added at runtime
186+
type SessionResponse = Auth["$Infer"]["Session"];
187+
export type AuthUser = SessionResponse["user"];
188+
export type AuthSession = SessionResponse["session"];

apps/api/lib/context.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import type { DatabaseSchema } from "@repo/db";
22
import type { CreateHTTPContextOptions } from "@trpc/server/adapters/standalone";
3-
import type { Session, User } from "better-auth/types";
43
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
54
import type { Resend } from "resend";
6-
import type { Auth } from "./auth.js";
5+
import type { Auth, AuthSession, AuthUser } from "./auth.js";
76
import type { Env } from "./env.js";
87

98
/**
@@ -42,10 +41,10 @@ export type TRPCContext = {
4241
dbDirect: PostgresJsDatabase<DatabaseSchema>;
4342

4443
/** Authenticated user session (null if not authenticated) */
45-
session: Session | null;
44+
session: AuthSession | null;
4645

4746
/** Authenticated user data (null if not authenticated) */
48-
user: User | null;
47+
user: AuthUser | null;
4948

5049
/** Request-scoped cache for storing computed values during request lifecycle */
5150
cache: Map<string | symbol, unknown>;
@@ -79,7 +78,7 @@ export type AppContext = {
7978
dbDirect: PostgresJsDatabase<DatabaseSchema>;
8079
auth: Auth;
8180
resend?: Resend;
82-
session: Session | null;
83-
user: User | null;
81+
session: AuthSession | null;
82+
user: AuthUser | null;
8483
};
8584
};

apps/api/lib/middleware.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @file Shared Hono middleware for both production and development entrypoints.
3+
*/
4+
5+
import type { Context, ErrorHandler, NotFoundHandler } from "hono";
6+
import { HTTPException } from "hono/http-exception";
7+
8+
/**
9+
* Global error handler for top-level Hono apps.
10+
*
11+
* Handles HTTPException specially (returns its response),
12+
* logs unexpected errors and returns a generic 500.
13+
*/
14+
export const errorHandler: ErrorHandler = (err, c) => {
15+
if (err instanceof HTTPException) {
16+
// getResponse() is not context-aware; merge headers from middleware
17+
const res = err.getResponse();
18+
const headers = new Headers(res.headers);
19+
c.res.headers.forEach((v, k) => headers.set(k, v));
20+
return new Response(res.body, {
21+
status: res.status,
22+
statusText: res.statusText,
23+
headers,
24+
});
25+
}
26+
console.error(`[${c.req.method}] ${c.req.path}:`, err);
27+
return c.json({ error: "Internal Server Error" }, 500);
28+
};
29+
30+
/**
31+
* 404 handler for unmatched routes.
32+
*
33+
* Must be registered on top-level app (notFound on mounted sub-apps is ignored).
34+
*/
35+
export const notFoundHandler: NotFoundHandler = (c) => {
36+
return c.json({ error: "Not Found", path: c.req.path }, 404);
37+
};
38+
39+
/**
40+
* Request ID generator using Cloudflare Ray ID when available.
41+
*/
42+
export function requestIdGenerator(c: Context): string {
43+
return c.req.header("cf-ray") ?? crypto.randomUUID();
44+
}

apps/api/lib/trpc.ts

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { initTRPC, TRPCError } from "@trpc/server";
1+
import { initTRPC, TRPCError, type TRPCProcedureBuilder } from "@trpc/server";
22
import { flattenError, ZodError } from "zod";
33
import type { TRPCContext } from "./context.js";
44

@@ -18,18 +18,49 @@ const t = initTRPC.context<TRPCContext>().create({
1818
export const router = t.router;
1919
export const publicProcedure = t.procedure;
2020

21-
export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
22-
if (!ctx.session || !ctx.user) {
23-
throw new TRPCError({
24-
code: "UNAUTHORIZED",
25-
message: "Authentication required",
21+
// Derive type from publicProcedure to stay in sync with initTRPC config.
22+
// Explicit annotation required to avoid TS2742 (non-portable inferred type).
23+
type ProtectedProcedure =
24+
typeof publicProcedure extends TRPCProcedureBuilder<
25+
infer TContext,
26+
infer TMeta,
27+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
28+
infer TContextOverrides,
29+
infer TInputIn,
30+
infer TInputOut,
31+
infer TOutputIn,
32+
infer TOutputOut,
33+
infer TCaller
34+
>
35+
? TRPCProcedureBuilder<
36+
TContext,
37+
TMeta,
38+
{
39+
session: NonNullable<TRPCContext["session"]>;
40+
user: NonNullable<TRPCContext["user"]>;
41+
},
42+
TInputIn,
43+
TInputOut,
44+
TOutputIn,
45+
TOutputOut,
46+
TCaller
47+
>
48+
: never;
49+
50+
export const protectedProcedure: ProtectedProcedure = t.procedure.use(
51+
({ ctx, next }) => {
52+
if (!ctx.session || !ctx.user) {
53+
throw new TRPCError({
54+
code: "UNAUTHORIZED",
55+
message: "Authentication required",
56+
});
57+
}
58+
return next({
59+
ctx: {
60+
...ctx,
61+
session: ctx.session,
62+
user: ctx.user,
63+
},
2664
});
27-
}
28-
return next({
29-
ctx: {
30-
...ctx,
31-
session: ctx.session,
32-
user: ctx.user,
33-
},
34-
});
35-
});
65+
},
66+
);

apps/api/lib/utils.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)