Skip to content

Commit 731f422

Browse files
committed
Perf, deduplication, tsc passing, discord
1 parent 5b9bbcb commit 731f422

100 files changed

Lines changed: 1487 additions & 814 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ dist
2828
# Content Collections generated files
2929
.content-collections
3030

31+
test-results

AGENTS.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,38 @@ const value = getValue() // Type inferred from function implementation
4040

4141
If types need to be fixed, fix them at the source (schema, API definition, function signature) rather than casting at the point of use.
4242

43+
### Generic Type Parameter Naming
44+
45+
**All generic type parameters must be prefixed with `T`.**
46+
47+
This convention makes it immediately clear that a name refers to a type parameter rather than a concrete type or value.
48+
49+
**Bad:**
50+
51+
```typescript
52+
function withCapability<Args extends unknown[], R>(
53+
handler: (user: AuthUser, ...args: Args) => R,
54+
) { ... }
55+
```
56+
57+
**Good:**
58+
59+
```typescript
60+
function withCapability<TArgs extends unknown[], TReturn>(
61+
handler: (user: AuthUser, ...args: TArgs) => TReturn,
62+
) { ... }
63+
```
64+
65+
Common examples:
66+
67+
- `T` for a single generic type
68+
- `TArgs` for argument types
69+
- `TReturn` for return types
70+
- `TData` for data types
71+
- `TError` for error types
72+
- `TKey` for key types
73+
- `TValue` for value types
74+
4375
## Route Loaders
4476

4577
### loaderDeps Must Be Specific

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
"db:generate": "drizzle-kit generate",
1818
"db:migrate": "drizzle-kit migrate",
1919
"db:push": "drizzle-kit push",
20-
"db:studio": "drizzle-kit studio"
20+
"db:studio": "drizzle-kit studio",
21+
"test": "pnpm run test:smoke",
22+
"test:smoke": "playwright test"
2123
},
2224
"dependencies": {
2325
"@auth/core": "0.37.0",
@@ -53,6 +55,7 @@
5355
"cmdk": "^1.1.1",
5456
"d3": "^7.9.0",
5557
"date-fns": "^2.30.0",
58+
"discord-interactions": "^4.4.0",
5659
"drizzle-orm": "^0.44.7",
5760
"eslint-plugin-jsx-a11y": "^6.10.2",
5861
"gray-matter": "^4.0.3",
@@ -93,6 +96,7 @@
9396
"@content-collections/core": "^0.8.2",
9497
"@content-collections/vite": "^0.2.4",
9598
"@eslint/js": "^9.39.1",
99+
"@playwright/test": "^1.57.0",
96100
"@shikijs/transformers": "^1.10.3",
97101
"@types/node": "^24.3.0",
98102
"@types/pg": "^8.15.6",

playwright.config.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { defineConfig } from '@playwright/test'
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
fullyParallel: true,
6+
forbidOnly: !!process.env.CI,
7+
retries: 0,
8+
workers: 1,
9+
reporter: 'list',
10+
timeout: 30000,
11+
use: {
12+
baseURL: 'http://localhost:3000',
13+
trace: 'off',
14+
video: 'off',
15+
screenshot: 'off',
16+
},
17+
webServer: {
18+
command: 'pnpm dev',
19+
url: 'http://localhost:3000',
20+
reuseExistingServer: !process.env.CI,
21+
timeout: 120000,
22+
},
23+
projects: [
24+
{
25+
name: 'chromium',
26+
use: { browserName: 'chromium', headless: true },
27+
},
28+
],
29+
})

pnpm-lock.yaml

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
199 KB
Loading
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* Register Discord slash commands with the Discord API.
3+
*
4+
* Run this script after adding new commands or modifying existing ones:
5+
* pnpm tsx scripts/register-discord-commands.ts
6+
*
7+
* Required environment variables:
8+
* DISCORD_APPLICATION_ID - Your Discord application ID
9+
* DISCORD_BOT_TOKEN - Your Discord bot token
10+
*/
11+
12+
const DISCORD_APPLICATION_ID = process.env.DISCORD_APPLICATION_ID
13+
const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN
14+
15+
if (!DISCORD_APPLICATION_ID || !DISCORD_BOT_TOKEN) {
16+
console.error('Missing required environment variables:')
17+
if (!DISCORD_APPLICATION_ID) console.error(' - DISCORD_APPLICATION_ID')
18+
if (!DISCORD_BOT_TOKEN) console.error(' - DISCORD_BOT_TOKEN')
19+
process.exit(1)
20+
}
21+
22+
const commands = [
23+
{
24+
name: 'tanstack',
25+
description: 'Check TanStack Bot status',
26+
type: 1, // CHAT_INPUT
27+
},
28+
]
29+
30+
async function registerCommands() {
31+
const url = `https://discord.com/api/v10/applications/${DISCORD_APPLICATION_ID}/commands`
32+
33+
console.log('Registering commands with Discord API...')
34+
console.log(`Application ID: ${DISCORD_APPLICATION_ID}`)
35+
console.log(`Commands to register: ${commands.map((c) => c.name).join(', ')}`)
36+
37+
const response = await fetch(url, {
38+
method: 'PUT',
39+
headers: {
40+
'Content-Type': 'application/json',
41+
Authorization: `Bot ${DISCORD_BOT_TOKEN}`,
42+
},
43+
body: JSON.stringify(commands),
44+
})
45+
46+
if (!response.ok) {
47+
const error = await response.text()
48+
console.error(`Failed to register commands: ${response.status}`)
49+
console.error(error)
50+
process.exit(1)
51+
}
52+
53+
const registered = await response.json()
54+
console.log('\nSuccessfully registered commands:')
55+
for (const cmd of registered) {
56+
console.log(` - /${cmd.name} (ID: ${cmd.id})`)
57+
}
58+
}
59+
60+
registerCommands().catch((error) => {
61+
console.error('Failed to register commands:', error)
62+
process.exit(1)
63+
})

src/auth/guards.server.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -117,30 +117,30 @@ export type AuthGuards = ReturnType<typeof createAuthGuards>
117117
/**
118118
* Create a guard that wraps a handler with capability check
119119
*/
120-
export function withCapability<T extends (...args: unknown[]) => unknown>(
120+
export function withCapability<TArgs extends unknown[], TReturn>(
121121
guards: AuthGuards,
122122
capability: Capability,
123123
getRequest: () => Request,
124-
handler: (user: AuthUser, ...args: Parameters<T>) => ReturnType<T>,
125-
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
126-
return async (...args: Parameters<T>) => {
124+
handler: (user: AuthUser, ...args: TArgs) => TReturn,
125+
) {
126+
return async (...args: TArgs): Promise<Awaited<TReturn>> => {
127127
const request = getRequest()
128128
const user = await guards.requireCapability(request, capability)
129-
return handler(user, ...args) as Awaited<ReturnType<T>>
129+
return (await handler(user, ...args)) as Awaited<TReturn>
130130
}
131131
}
132132

133133
/**
134134
* Create a guard that wraps a handler with auth check
135135
*/
136-
export function withAuth<T extends (...args: unknown[]) => unknown>(
136+
export function withAuth<TArgs extends unknown[], TReturn>(
137137
guards: AuthGuards,
138138
getRequest: () => Request,
139-
handler: (user: AuthUser, ...args: Parameters<T>) => ReturnType<T>,
140-
): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
141-
return async (...args: Parameters<T>) => {
139+
handler: (user: AuthUser, ...args: TArgs) => TReturn,
140+
) {
141+
return async (...args: TArgs): Promise<Awaited<TReturn>> => {
142142
const request = getRequest()
143143
const user = await guards.requireAuth(request)
144-
return handler(user, ...args) as Awaited<ReturnType<T>>
144+
return (await handler(user, ...args)) as Awaited<TReturn>
145145
}
146146
}

src/auth/types.ts

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,11 @@
66
* server and client code.
77
*/
88

9-
// ============================================================================
10-
// Capability Types
11-
// ============================================================================
12-
13-
export type Capability =
14-
| 'admin'
15-
| 'disableAds'
16-
| 'builder'
17-
| 'feed'
18-
| 'moderate-feedback'
19-
| 'moderate-showcases'
20-
21-
export const VALID_CAPABILITIES: readonly Capability[] = [
22-
'admin',
23-
'disableAds',
24-
'builder',
25-
'feed',
26-
'moderate-feedback',
27-
'moderate-showcases',
28-
] as const
29-
30-
// ============================================================================
31-
// OAuth Types
32-
// ============================================================================
9+
// Re-export shared types from db/types.ts (single source of truth)
10+
export type { Capability, OAuthProvider } from '~/db/types'
11+
export { CAPABILITIES as VALID_CAPABILITIES } from '~/db/types'
3312

34-
export type OAuthProvider = 'github' | 'google'
13+
import type { Capability, OAuthProvider } from '~/db/types'
3514

3615
export interface OAuthProfile {
3716
id: string

src/blog/tanstack-ai-why-we-split-the-adapters.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
---
2-
title: 'Why We Split the Adapters'
2+
title: 'TanStack AI: Why We Split the Adapters'
33
published: 2026-01-02
44
authors:
55
- Alem Tuzlak
66
---
77

8+
![TanStack AI: Why We Split the Adapters](/blog-assets/tanstack-ai-why-we-split-the-adapters/header.jpeg)
9+
810
With the latest release we brought a major architectural change to how we do adapters. Instead of one monolithic adapter that does everything, we split into smaller adapters. Each in charge of a single functionality.
911

1012
Here's why.

0 commit comments

Comments
 (0)