claude-toolkit 0.1.9
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/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/bin/cli.ts +112 -0
- package/core/agents/ct-code-reviewer.md +123 -0
- package/core/agents/ct-github-workflow.md +137 -0
- package/core/commands/ct/code-quality.md +59 -0
- package/core/commands/ct/onboard.md +84 -0
- package/core/commands/ct/pr-review.md +104 -0
- package/core/commands/ct/pr-summary.md +59 -0
- package/core/commands/ct/proto-check.md +74 -0
- package/core/commands/ct/ticket.md +71 -0
- package/core/hooks/skill-eval.js +381 -0
- package/core/hooks/skill-eval.sh +35 -0
- package/core/hooks/skill-rules.schema.json +112 -0
- package/core/skills/systematic-debugging/SKILL.md +44 -0
- package/core/skills/testing-patterns/SKILL.md +52 -0
- package/core/skills/typescript-conventions/SKILL.md +57 -0
- package/core/skills/verification-before-completion/SKILL.md +42 -0
- package/docs/README.md +49 -0
- package/docs/agents/code-reviewer.md +76 -0
- package/docs/agents/github-workflow.md +98 -0
- package/docs/best-practices/solidjs/README.md +43 -0
- package/docs/best-practices/solidjs/anti-patterns.md +166 -0
- package/docs/best-practices/solidjs/component-patterns.md +131 -0
- package/docs/best-practices/solidjs/context-and-global-state.md +131 -0
- package/docs/best-practices/solidjs/control-flow.md +124 -0
- package/docs/best-practices/solidjs/data-fetching.md +205 -0
- package/docs/best-practices/solidjs/effects-and-lifecycle.md +113 -0
- package/docs/best-practices/solidjs/performance.md +100 -0
- package/docs/best-practices/solidjs/props-patterns.md +100 -0
- package/docs/best-practices/solidjs/reactivity-model.md +104 -0
- package/docs/best-practices/solidjs/signals-and-state.md +78 -0
- package/docs/best-practices/solidjs/stores-and-nested-state.md +111 -0
- package/docs/best-practices/solidjs/typescript-integration.md +186 -0
- package/docs/best-practices/typescript/README.md +45 -0
- package/docs/best-practices/typescript/any-and-unknown.md +73 -0
- package/docs/best-practices/typescript/deriving-vs-decoupling.md +83 -0
- package/docs/best-practices/typescript/discriminated-unions.md +75 -0
- package/docs/best-practices/typescript/enums-alternatives.md +72 -0
- package/docs/best-practices/typescript/essential-patterns.md +119 -0
- package/docs/best-practices/typescript/generics-patterns.md +105 -0
- package/docs/best-practices/typescript/micro-opinions.md +87 -0
- package/docs/best-practices/typescript/runtime-validation.md +62 -0
- package/docs/best-practices/typescript/satisfies-operator.md +100 -0
- package/docs/best-practices/typescript/tsconfig-cheat-sheet.md +129 -0
- package/docs/best-practices/typescript/type-organization.md +64 -0
- package/docs/best-practices/typescript/type-vs-interface.md +80 -0
- package/docs/commands/code-quality.md +42 -0
- package/docs/commands/onboard.md +72 -0
- package/docs/commands/pr-review.md +102 -0
- package/docs/commands/pr-summary.md +50 -0
- package/docs/commands/proto-check.md +59 -0
- package/docs/commands/ticket.md +56 -0
- package/docs/skills/systematic-debugging.md +70 -0
- package/docs/skills/testing-patterns.md +89 -0
- package/docs/skills/typescript-conventions.md +137 -0
- package/docs/skills/verification-before-completion.md +91 -0
- package/docs/stacks/cloudflare-d1-kv.md +110 -0
- package/docs/stacks/i18n-typesafe.md +141 -0
- package/docs/stacks/protobuf-contracts.md +85 -0
- package/docs/stacks/rust-wasm-patterns.md +106 -0
- package/docs/stacks/solidjs-patterns.md +110 -0
- package/docs/stacks/vanilla-extract-patterns.md +115 -0
- package/package.json +58 -0
- package/src/generator.ts +317 -0
- package/src/index.ts +30 -0
- package/src/types.ts +85 -0
- package/src/utils.ts +53 -0
- package/stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md +84 -0
- package/stacks/cloudflare/stack.json +26 -0
- package/stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md +64 -0
- package/stacks/i18n-typesafe/stack.json +25 -0
- package/stacks/protobuf/skills/protobuf-contracts/SKILL.md +78 -0
- package/stacks/protobuf/stack.json +25 -0
- package/stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md +76 -0
- package/stacks/rust-wasm/stack.json +26 -0
- package/stacks/solidjs/skills/solidjs-patterns/SKILL.md +66 -0
- package/stacks/solidjs/stack.json +52 -0
- package/stacks/vanilla-extract/skills/vanilla-extract-patterns/SKILL.md +76 -0
- package/stacks/vanilla-extract/stack.json +40 -0
- package/templates/claude-toolkit.config.ts +34 -0
- package/templates/configs/biome.base.json +35 -0
- package/templates/configs/tsconfig.base.json +16 -0
- package/templates/skill-rules.base.json +98 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# TypeScript Conventions
|
|
2
|
+
|
|
3
|
+
> Strict TypeScript patterns for type safety, readability, and maintainable codebases.
|
|
4
|
+
|
|
5
|
+
**Type:** Core Skill (always included)
|
|
6
|
+
**Source:** [`core/skills/typescript-conventions/SKILL.md`](../core/skills/typescript-conventions/SKILL.md)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Rules for writing TypeScript that leverages the type system fully. These assume strict mode is enabled and prioritize catching errors at compile time over runtime.
|
|
11
|
+
|
|
12
|
+
## Key Patterns
|
|
13
|
+
|
|
14
|
+
### Strict Mode
|
|
15
|
+
|
|
16
|
+
Enable all strict checks in `tsconfig.json`:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"compilerOptions": {
|
|
21
|
+
"strict": true,
|
|
22
|
+
"noUncheckedIndexedAccess": true,
|
|
23
|
+
"noUnusedLocals": true,
|
|
24
|
+
"noUnusedParameters": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Never disable strict checks for convenience.
|
|
30
|
+
|
|
31
|
+
### No `any`
|
|
32
|
+
|
|
33
|
+
Never use `any`. It disables type checking entirely.
|
|
34
|
+
|
|
35
|
+
- Use `unknown` when the type is genuinely not known, then narrow before use
|
|
36
|
+
- Use generics when the type varies but has a consistent shape
|
|
37
|
+
- Use specific types when you know the data shape
|
|
38
|
+
|
|
39
|
+
### Interface vs Type
|
|
40
|
+
|
|
41
|
+
- **Prefer `interface`** for object shapes -- extendable, clearer error messages
|
|
42
|
+
- **Use `type`** for unions, intersections, mapped types, and utility types
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// Object shape: use interface
|
|
46
|
+
interface User {
|
|
47
|
+
id: string;
|
|
48
|
+
email: string;
|
|
49
|
+
role: UserRole;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Union: use type
|
|
53
|
+
type UserRole = "admin" | "member" | "guest";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `as const` for Literal Types
|
|
57
|
+
|
|
58
|
+
Use `as const` to narrow literal values and create readonly tuples:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const ROLES = ["admin", "member", "guest"] as const;
|
|
62
|
+
type Role = (typeof ROLES)[number]; // "admin" | "member" | "guest"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Exhaustive Switch with `never`
|
|
66
|
+
|
|
67
|
+
Handle every case in a switch and use `never` to catch missing branches at compile time:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
function getPermissions(role: UserRole): string[] {
|
|
71
|
+
switch (role) {
|
|
72
|
+
case "admin":
|
|
73
|
+
return ["read", "write", "delete"];
|
|
74
|
+
case "member":
|
|
75
|
+
return ["read", "write"];
|
|
76
|
+
case "guest":
|
|
77
|
+
return ["read"];
|
|
78
|
+
default: {
|
|
79
|
+
const _exhaustive: never = role;
|
|
80
|
+
throw new Error(`Unhandled role: ${_exhaustive}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Discriminated Unions for State Modeling
|
|
87
|
+
|
|
88
|
+
Model states with different associated data as discriminated unions to make illegal states unrepresentable:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
type AsyncState<T> =
|
|
92
|
+
| { status: "loading" }
|
|
93
|
+
| { status: "success"; data: T }
|
|
94
|
+
| { status: "error"; error: Error };
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Generic Constraints
|
|
98
|
+
|
|
99
|
+
Use `extends` to constrain generics, documenting expectations and catching misuse:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
function findById<T extends { id: string }>(items: T[], id: string): T | undefined {
|
|
103
|
+
return items.find((item) => item.id === id);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Anti-patterns
|
|
108
|
+
|
|
109
|
+
| Anti-pattern | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| **Type assertions (`as`)** | Tells the compiler "trust me" -- use type guards and narrowing instead. |
|
|
112
|
+
| **Non-null assertion (`!`)** | Suppresses null checks. Handle the null case explicitly. |
|
|
113
|
+
| **Enum overuse** | Prefer string literal unions. Enums generate runtime code and have surprising behavior. |
|
|
114
|
+
| **`Object`, `Function`, `{}`** | Almost never what you want. Use specific interfaces or function signatures. |
|
|
115
|
+
| **`@ts-ignore`** | Suppressing errors without tracking means the error will be forgotten. |
|
|
116
|
+
|
|
117
|
+
## Best Practices Reference
|
|
118
|
+
|
|
119
|
+
For deeper guidance on the patterns referenced above (sourced from Matt Pocock / Total TypeScript):
|
|
120
|
+
|
|
121
|
+
| Topic | Guide |
|
|
122
|
+
|---|---|
|
|
123
|
+
| Default to `type`, use `interface` for `extends` | [Type vs Interface](../best-practices/typescript/type-vs-interface.md) |
|
|
124
|
+
| Why enums are problematic, `as const` alternative | [Enums & Alternatives](../best-practices/typescript/enums-alternatives.md) |
|
|
125
|
+
| When `any` is acceptable (two exceptions) | [any & unknown](../best-practices/typescript/any-and-unknown.md) |
|
|
126
|
+
| State modeling with discriminated unions | [Discriminated Unions](../best-practices/typescript/discriminated-unions.md) |
|
|
127
|
+
| Three patterns for generics | [Generics Patterns](../best-practices/typescript/generics-patterns.md) |
|
|
128
|
+
| Recommended `tsconfig.json` settings | [TSConfig Cheat Sheet](../best-practices/typescript/tsconfig-cheat-sheet.md) |
|
|
129
|
+
| Branded types, assertion functions, type predicates | [Essential Patterns](../best-practices/typescript/essential-patterns.md) |
|
|
130
|
+
|
|
131
|
+
See the full collection: [TypeScript Best Practices](../best-practices/typescript/README.md)
|
|
132
|
+
|
|
133
|
+
## Trigger Conditions
|
|
134
|
+
|
|
135
|
+
- **Keywords:** `typescript`, `type`, `interface`, `generic`
|
|
136
|
+
- **File patterns:** `**/*.d.ts`, `**/types/**`
|
|
137
|
+
- **Intent patterns:** "define/create type/interface"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Verification Before Completion
|
|
2
|
+
|
|
3
|
+
> Evidence-based completion claims -- never say "done" without proof that the work is correct.
|
|
4
|
+
|
|
5
|
+
**Type:** Core Skill (always included)
|
|
6
|
+
**Source:** [`core/skills/verification-before-completion/SKILL.md`](../core/skills/verification-before-completion/SKILL.md)
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
The most common source of rework is claiming completion without verification. Every "done" claim must be backed by evidence that the code works correctly. "I wrote the code" is not evidence. "The tests pass, the types check, and here is the output" is evidence.
|
|
11
|
+
|
|
12
|
+
## Verification Checklist
|
|
13
|
+
|
|
14
|
+
### 1. Tests Pass
|
|
15
|
+
|
|
16
|
+
Run the full test suite (or related tests) and show the output. If any test fails, fix it before claiming complete.
|
|
17
|
+
|
|
18
|
+
### 2. Type Checks Pass
|
|
19
|
+
|
|
20
|
+
Run the type checker with no errors (`tsc --noEmit` or equivalent). Zero errors, zero warnings on changed files.
|
|
21
|
+
|
|
22
|
+
### 3. Linting Passes
|
|
23
|
+
|
|
24
|
+
Run the project's linter on changed files. Fix any violations introduced by your changes.
|
|
25
|
+
|
|
26
|
+
### 4. The Feature Actually Works
|
|
27
|
+
|
|
28
|
+
For user-facing changes, demonstrate the feature works with actual output -- API responses, rendered UI, CLI output:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
$ curl -s localhost:3000/api/users/1 | jq .
|
|
32
|
+
{
|
|
33
|
+
"id": "1",
|
|
34
|
+
"name": "Alice",
|
|
35
|
+
"email": "alice@example.com"
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 5. Edge Cases Verified
|
|
40
|
+
|
|
41
|
+
Test boundaries and unusual inputs explicitly:
|
|
42
|
+
|
|
43
|
+
- **Empty inputs** -- empty strings, empty arrays, null, undefined
|
|
44
|
+
- **Boundary values** -- zero, negative numbers, maximum values, off-by-one
|
|
45
|
+
- **Invalid inputs** -- wrong types, malformed data, missing required fields
|
|
46
|
+
- **Concurrent access** -- simultaneous requests or operations
|
|
47
|
+
- **Error paths** -- network failures, database errors, permission denied
|
|
48
|
+
|
|
49
|
+
### 6. Regression Check
|
|
50
|
+
|
|
51
|
+
- Run related tests, not just the tests you wrote
|
|
52
|
+
- Check integration points if you changed a shared utility, API contract, or database schema
|
|
53
|
+
- Smoke test the application if it has a running instance
|
|
54
|
+
|
|
55
|
+
## Evidence Format
|
|
56
|
+
|
|
57
|
+
When reporting completion, include concrete evidence:
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
## Completed: User authentication endpoint
|
|
61
|
+
|
|
62
|
+
### Tests
|
|
63
|
+
- 12 tests passing (3 new, 9 existing)
|
|
64
|
+
- `npm test -- --grep "auth"` output: all green
|
|
65
|
+
|
|
66
|
+
### Type check
|
|
67
|
+
- `tsc --noEmit`: 0 errors
|
|
68
|
+
|
|
69
|
+
### Manual verification
|
|
70
|
+
- POST /api/auth/login with valid credentials: 200 + JWT token
|
|
71
|
+
- POST /api/auth/login with wrong password: 401 + error message
|
|
72
|
+
- POST /api/auth/login with locked account: 423 + lockout message
|
|
73
|
+
|
|
74
|
+
### Edge cases verified
|
|
75
|
+
- Empty email: 400 validation error
|
|
76
|
+
- SQL injection attempt in email: properly escaped, no error
|
|
77
|
+
- Expired token refresh: 401 with clear message
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## What This Prevents
|
|
81
|
+
|
|
82
|
+
- **"It works on my machine" failures** -- evidence shows it works in a verifiable way
|
|
83
|
+
- **Phantom completions** -- tasks marked done that are actually broken
|
|
84
|
+
- **Regression blindness** -- catching breakage before it reaches review or production
|
|
85
|
+
- **Incomplete implementations** -- edge cases and error handling are verified, not assumed
|
|
86
|
+
|
|
87
|
+
## Trigger Conditions
|
|
88
|
+
|
|
89
|
+
- **Keywords:** `verify`, `check`, `confirm`, `done`, `complete`
|
|
90
|
+
- **Intent patterns:** "is it done", "verify it works"
|
|
91
|
+
- **Context patterns:** "before claiming", "make sure"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Cloudflare D1 and KV Patterns
|
|
2
|
+
|
|
3
|
+
> Cloudflare D1 SQL database and KV cache patterns.
|
|
4
|
+
|
|
5
|
+
**Type:** Stack Skill (requires `cloudflare` stack)
|
|
6
|
+
**Source:** [`stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md`](../stacks/cloudflare/skills/cloudflare-d1-kv/SKILL.md)
|
|
7
|
+
**Directory Mappings:** `src/db/`, `migrations/`
|
|
8
|
+
**File Extensions:** `.sql`
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
D1 is Cloudflare's serverless SQLite database, and KV is an eventually-consistent key-value store. Together they provide primary data storage and read-heavy caching for Workers.
|
|
13
|
+
|
|
14
|
+
## D1 Database
|
|
15
|
+
|
|
16
|
+
### Prepared Statements
|
|
17
|
+
|
|
18
|
+
Always use prepared statements with `.bind()` -- never concatenate SQL strings.
|
|
19
|
+
|
|
20
|
+
```rust
|
|
21
|
+
let stmt = db.prepare("SELECT * FROM users WHERE id = ?1")
|
|
22
|
+
.bind(&[id.into()])?;
|
|
23
|
+
let user = stmt.first::<User>(None).await?;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Batch Operations
|
|
27
|
+
|
|
28
|
+
Execute multiple statements in a single round trip. Batch operations are transactional -- all succeed or all fail.
|
|
29
|
+
|
|
30
|
+
```rust
|
|
31
|
+
let results = db.batch(vec![
|
|
32
|
+
db.prepare("INSERT INTO events ...").bind(&[...])?,
|
|
33
|
+
db.prepare("INSERT INTO event_members ...").bind(&[...])?,
|
|
34
|
+
]).await?;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Migration Workflow
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
wrangler d1 migrations create DB "add_users_table" # Create migration
|
|
41
|
+
wrangler d1 migrations apply DB --local # Apply locally
|
|
42
|
+
wrangler d1 migrations apply DB --remote # Apply to remote
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Schema Conventions
|
|
46
|
+
|
|
47
|
+
- Use `TEXT` for IDs (UUIDs/nanoids, not auto-increment integers)
|
|
48
|
+
- Use `TEXT` for timestamps in ISO 8601 format
|
|
49
|
+
- Always add `created_at` and `updated_at` columns
|
|
50
|
+
- Create indexes for columns used in WHERE clauses and JOINs
|
|
51
|
+
- Use `IF NOT EXISTS` / `IF EXISTS` for idempotent migrations
|
|
52
|
+
- D1 has a **5MB response size limit** -- always use LIMIT
|
|
53
|
+
|
|
54
|
+
## KV Namespace
|
|
55
|
+
|
|
56
|
+
### Read-Through Caching Pattern
|
|
57
|
+
|
|
58
|
+
```rust
|
|
59
|
+
async fn get_user_cached(kv: &KvStore, db: &D1Database, user_id: &str) -> Result<User> {
|
|
60
|
+
let cache_key = format!("user:{}", user_id);
|
|
61
|
+
|
|
62
|
+
// Try cache first
|
|
63
|
+
if let Some(cached) = kv.get(&cache_key).text().await? {
|
|
64
|
+
if let Ok(user) = serde_json::from_str::<User>(&cached) {
|
|
65
|
+
return Ok(user);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Cache miss -- query DB, then populate cache with TTL
|
|
70
|
+
let user = /* query D1 */;
|
|
71
|
+
kv.put(&cache_key, &json)?.expiration_ttl(3600).execute().await?;
|
|
72
|
+
Ok(user)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### TTL Strategies
|
|
77
|
+
|
|
78
|
+
| Data Type | TTL | Rationale |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| User profile | 1 hour | Changes infrequently |
|
|
81
|
+
| Session data | 24 hours | Matches session lifetime |
|
|
82
|
+
| Config/settings | 5 minutes | Needs faster propagation |
|
|
83
|
+
| Public listings | 15 minutes | Balances freshness and load |
|
|
84
|
+
|
|
85
|
+
### Key Naming Conventions
|
|
86
|
+
|
|
87
|
+
Use colon-separated hierarchical keys:
|
|
88
|
+
```
|
|
89
|
+
user:{userId}
|
|
90
|
+
user:{userId}:profile
|
|
91
|
+
event:{eventId}
|
|
92
|
+
event:{eventId}:members
|
|
93
|
+
session:{sessionToken}
|
|
94
|
+
cache:feed:{userId}:page:{pageNum}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Cache Invalidation
|
|
98
|
+
|
|
99
|
+
Invalidate on write operations by deleting the corresponding cache key after updating D1.
|
|
100
|
+
|
|
101
|
+
## Anti-patterns
|
|
102
|
+
|
|
103
|
+
| Anti-pattern | Why it's wrong |
|
|
104
|
+
|---|---|
|
|
105
|
+
| **Unbounded queries** | Always use LIMIT. D1 has a 5MB response limit. |
|
|
106
|
+
| **Missing indexes** | D1 is SQLite; without indexes, every query is a full table scan. |
|
|
107
|
+
| **KV as primary data store** | KV is eventually consistent with no query capability. Use D1 for primary data. |
|
|
108
|
+
| **Ignoring batch operations** | Each D1 call is a network round trip. Batch for performance. |
|
|
109
|
+
| **KV cache without TTL** | Stale data without TTL persists indefinitely. |
|
|
110
|
+
| **Not testing migrations locally** | Always apply with `--local` before `--remote`. |
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Typesafe-i18n Patterns
|
|
2
|
+
|
|
3
|
+
> Typesafe-i18n internationalization patterns for SolidJS.
|
|
4
|
+
|
|
5
|
+
**Type:** Stack Skill (requires `i18n-typesafe` stack)
|
|
6
|
+
**Source:** [`stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md`](../stacks/i18n-typesafe/skills/i18n-typesafe/SKILL.md)
|
|
7
|
+
**Directory Mappings:** `src/locales/`, `src/i18n/`
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
typesafe-i18n provides fully type-safe translations with autocompletion. Translation keys are checked at compile time -- missing or misspelled keys cause TypeScript errors.
|
|
12
|
+
|
|
13
|
+
## Setup
|
|
14
|
+
|
|
15
|
+
### Directory Structure
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
src/i18n/
|
|
19
|
+
i18n-types.ts # Auto-generated types
|
|
20
|
+
i18n-util.ts # Auto-generated utilities
|
|
21
|
+
i18n-solid.tsx # SolidJS adapter
|
|
22
|
+
en/index.ts # Base locale (source of truth)
|
|
23
|
+
fr/index.ts # French translations
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Configuration (`.typesafe-i18n.json`)
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"baseLocale": "en",
|
|
31
|
+
"adapter": "solid",
|
|
32
|
+
"outputPath": "src/i18n/{locale}/"
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Run the Generator
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx typesafe-i18n # Watches base locale and regenerates types
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Base Locale
|
|
43
|
+
|
|
44
|
+
The base locale is the source of truth. All types are derived from it.
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// src/i18n/en/index.ts
|
|
48
|
+
const en = {
|
|
49
|
+
common: {
|
|
50
|
+
welcome: 'Welcome, {name:string}!',
|
|
51
|
+
itemCount: '{count:number} {{item|items}}',
|
|
52
|
+
save: 'Save',
|
|
53
|
+
},
|
|
54
|
+
auth: {
|
|
55
|
+
signIn: 'Sign in',
|
|
56
|
+
signOut: 'Sign out',
|
|
57
|
+
},
|
|
58
|
+
} satisfies BaseTranslation;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Adding Translations
|
|
62
|
+
|
|
63
|
+
Other locales use the `Translation` type derived from the base locale. Missing keys or wrong parameter types cause compile errors.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
// src/i18n/fr/index.ts
|
|
67
|
+
const fr = {
|
|
68
|
+
common: {
|
|
69
|
+
welcome: 'Bienvenue, {name} !',
|
|
70
|
+
itemCount: '{count} {{article|articles}}',
|
|
71
|
+
save: 'Enregistrer',
|
|
72
|
+
},
|
|
73
|
+
auth: {
|
|
74
|
+
signIn: 'Se connecter',
|
|
75
|
+
signOut: 'Se deconnecter',
|
|
76
|
+
},
|
|
77
|
+
} satisfies Translation;
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Usage in Components
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
const { LL } = useI18nContext();
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
<h1>{LL().events.title()}</h1>
|
|
88
|
+
<span>{LL().events.attendees({ count: event.memberCount })}</span>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Provider Setup
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<I18nProvider locale="en">
|
|
97
|
+
<Router />
|
|
98
|
+
</I18nProvider>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Parameterized Translations
|
|
102
|
+
|
|
103
|
+
| Syntax | Example | Description |
|
|
104
|
+
| ------------------------ | ---------------------------- | ------------------------ |
|
|
105
|
+
| `{name:string}` | `'Hello {name:string}'` | Named string parameter |
|
|
106
|
+
| `{count:number}` | `'{count:number} items'` | Named number parameter |
|
|
107
|
+
| `{{singular\|plural}}` | `'{count} {{item\|items}}'` | Plural forms |
|
|
108
|
+
| `{0:string}` | `'Click {0:string}'` | Positional parameter |
|
|
109
|
+
|
|
110
|
+
## Namespace Organization
|
|
111
|
+
|
|
112
|
+
Organize translations by feature/domain. Keep namespaces flat (one level deep):
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const en = {
|
|
116
|
+
common: { ... }, // Global
|
|
117
|
+
auth: { ... }, // Authentication
|
|
118
|
+
events: { ... }, // Events feature
|
|
119
|
+
profile: { ... }, // User profile
|
|
120
|
+
settings: { ... }, // Settings
|
|
121
|
+
admin: { ... }, // Admin panel
|
|
122
|
+
};
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Anti-patterns
|
|
126
|
+
|
|
127
|
+
| Anti-pattern | Why it's wrong |
|
|
128
|
+
| ------------------------------ | ------------------------------------------------------------------------------- |
|
|
129
|
+
| **Hardcoded strings** | Every user-visible string must go through `LL()`. |
|
|
130
|
+
| **Missing translations** | `Translation` type catches these at compile time -- run `tsc --noEmit` in CI. |
|
|
131
|
+
| **Interpolating HTML** | Do not embed HTML in translation strings. Use component composition. |
|
|
132
|
+
| **Dynamic key access** | `LL()[dynamicKey]()` bypasses type safety. Use conditional rendering. |
|
|
133
|
+
| **Forgetting to load locale** | Call `loadLocale()` before rendering. |
|
|
134
|
+
| **Over-splitting namespaces** | Too many small namespaces add overhead. Group by feature, not component. |
|
|
135
|
+
|
|
136
|
+
## Best Practices Reference
|
|
137
|
+
|
|
138
|
+
| Topic | Guide |
|
|
139
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------- |
|
|
140
|
+
| `satisfies` pattern used in translations | [The satisfies Operator](../best-practices/typescript/satisfies-operator.md) |
|
|
141
|
+
| i18n provider and context pattern | [Context & Global State](../best-practices/solidjs/context-and-global-state.md) |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Protobuf Contracts
|
|
2
|
+
|
|
3
|
+
> Protocol Buffer definitions and code generation for frontend/backend contracts.
|
|
4
|
+
|
|
5
|
+
**Type:** Stack Skill (requires `protobuf` stack)
|
|
6
|
+
**Source:** [`stacks/protobuf/skills/protobuf-contracts/SKILL.md`](../stacks/protobuf/skills/protobuf-contracts/SKILL.md)
|
|
7
|
+
**Directory Mappings:** `proto/`
|
|
8
|
+
**File Extensions:** `.proto`
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Protocol Buffers (proto3) define the API contract between frontend and backend. Protobuf provides compact binary serialization, type safety across languages, and backward-compatible schema evolution.
|
|
13
|
+
|
|
14
|
+
## Proto3 Conventions
|
|
15
|
+
|
|
16
|
+
- Use **PascalCase** for message names, **snake_case** for field names
|
|
17
|
+
- Fields 1-15 use 1 byte (reserve for frequent fields), 16-2047 use 2 bytes
|
|
18
|
+
- **Never reuse** a field number after removing a field
|
|
19
|
+
- When removing fields, reserve the number AND name
|
|
20
|
+
- Enums must always start with `UNSPECIFIED = 0`
|
|
21
|
+
|
|
22
|
+
## Message Patterns
|
|
23
|
+
|
|
24
|
+
### Request/Response Pairs
|
|
25
|
+
```protobuf
|
|
26
|
+
message GetUserRequest { string user_id = 1; }
|
|
27
|
+
message GetUserResponse { UserProfile user = 1; }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Lists with Pagination
|
|
31
|
+
```protobuf
|
|
32
|
+
message ListEventsRequest {
|
|
33
|
+
int32 page_size = 1;
|
|
34
|
+
string page_token = 2;
|
|
35
|
+
}
|
|
36
|
+
message ListEventsResponse {
|
|
37
|
+
repeated Event events = 1;
|
|
38
|
+
string next_page_token = 2;
|
|
39
|
+
int32 total_count = 3;
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Service Definitions
|
|
44
|
+
```protobuf
|
|
45
|
+
service UserService {
|
|
46
|
+
rpc GetUser(GetUserRequest) returns (GetUserResponse);
|
|
47
|
+
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
|
|
48
|
+
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Code Generation
|
|
53
|
+
|
|
54
|
+
| Target | Tool | Command |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| Frontend (TypeScript) | protoc-gen-ts | `buf generate --template buf.gen.ts.yaml` |
|
|
57
|
+
| Backend (Rust) | prost-build | Via `build.rs` with `prost_build::Config` |
|
|
58
|
+
|
|
59
|
+
## Buf CLI Commands
|
|
60
|
+
|
|
61
|
+
| Command | Purpose |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `buf lint` | Lint proto files |
|
|
64
|
+
| `buf format -w` | Format in place |
|
|
65
|
+
| `buf breaking --against .git#branch=main` | Check for breaking changes |
|
|
66
|
+
| `buf generate` | Run code generation |
|
|
67
|
+
|
|
68
|
+
## Versioning Rules
|
|
69
|
+
|
|
70
|
+
1. **Never remove or renumber fields** -- use `reserved` instead
|
|
71
|
+
2. **Never change a field type** -- add a new field with the correct type
|
|
72
|
+
3. **Enum values are forever** -- 0 must always be UNSPECIFIED
|
|
73
|
+
4. **Adding fields is safe** -- new fields get default values in old clients
|
|
74
|
+
5. **Run `buf breaking` before merging** -- catch breaking changes in CI
|
|
75
|
+
|
|
76
|
+
## Anti-patterns
|
|
77
|
+
|
|
78
|
+
| Anti-pattern | Why it's wrong |
|
|
79
|
+
|---|---|
|
|
80
|
+
| **Reusing field numbers** | Causes data corruption. Always reserve removed field numbers. |
|
|
81
|
+
| **Overly nested messages** | Deep nesting hurts readability and evolution. Flatten where practical. |
|
|
82
|
+
| **Missing UNSPECIFIED enum** | Proto3 requires 0 as default. Without UNSPECIFIED, 0 maps to a real value. |
|
|
83
|
+
| **Using proto2 syntax** | Always use proto3 for new projects. |
|
|
84
|
+
| **Skipping buf lint** | Inconsistent naming compounds over time. |
|
|
85
|
+
| **Large messages** | Protobuf is not designed for multi-MB payloads. Use streaming or chunking. |
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Rust WASM Patterns
|
|
2
|
+
|
|
3
|
+
> Rust WASM patterns for Cloudflare Workers with worker-rs.
|
|
4
|
+
|
|
5
|
+
**Type:** Stack Skill (requires `rust-wasm` stack)
|
|
6
|
+
**Source:** [`stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md`](../stacks/rust-wasm/skills/rust-wasm-patterns/SKILL.md)
|
|
7
|
+
**Directory Mappings:** `worker/`, `worker/src/`
|
|
8
|
+
**File Extensions:** `.rs`, `.toml`
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
Cloudflare Workers can run Rust compiled to WebAssembly via the `worker` crate (worker-rs). The target is `wasm32-unknown-unknown`. Workers handle HTTP requests at the edge with low latency.
|
|
13
|
+
|
|
14
|
+
## Project Structure
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
worker/
|
|
18
|
+
Cargo.toml
|
|
19
|
+
src/
|
|
20
|
+
lib.rs # Entry point with main router
|
|
21
|
+
routes/
|
|
22
|
+
models/
|
|
23
|
+
error.rs # Custom error types
|
|
24
|
+
wrangler.toml # Cloudflare config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Key Patterns
|
|
28
|
+
|
|
29
|
+
### Request Routing
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
#[event(fetch)]
|
|
33
|
+
async fn fetch(req: Request, env: Env, _ctx: Context) -> Result<Response> {
|
|
34
|
+
Router::new()
|
|
35
|
+
.get_async("/api/users/:id", get_user)
|
|
36
|
+
.post_async("/api/users", create_user)
|
|
37
|
+
.run(req, env)
|
|
38
|
+
.await
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Handler Pattern
|
|
43
|
+
|
|
44
|
+
```rust
|
|
45
|
+
async fn get_user(req: Request, ctx: RouteContext<()>) -> Result<Response> {
|
|
46
|
+
let id = ctx.param("id")
|
|
47
|
+
.ok_or_else(|| Error::RustError("Missing id parameter".into()))?;
|
|
48
|
+
let db = ctx.env.d1("DB")?;
|
|
49
|
+
// ...
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Error Handling
|
|
54
|
+
|
|
55
|
+
Define a custom `AppError` enum that converts to `worker::Error`:
|
|
56
|
+
|
|
57
|
+
```rust
|
|
58
|
+
pub enum AppError {
|
|
59
|
+
NotFound(String),
|
|
60
|
+
BadRequest(String),
|
|
61
|
+
Internal(String),
|
|
62
|
+
Unauthorized,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl From<AppError> for WorkerError { ... }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Env Bindings
|
|
69
|
+
|
|
70
|
+
Access Cloudflare services through the `Env` object:
|
|
71
|
+
|
|
72
|
+
| Binding | Access |
|
|
73
|
+
|---|---|
|
|
74
|
+
| D1 Database | `env.d1("DB")?` |
|
|
75
|
+
| KV Namespace | `env.kv("MY_KV")?` |
|
|
76
|
+
| R2 Bucket | `env.bucket("MY_BUCKET")?` |
|
|
77
|
+
| Secrets | `env.secret("API_KEY")?.to_string()` |
|
|
78
|
+
| Variables | `env.var("CONFIG_VAR")?.to_string()` |
|
|
79
|
+
|
|
80
|
+
### Serde for JSON
|
|
81
|
+
|
|
82
|
+
Use `#[derive(Serialize, Deserialize)]` for request/response types. Parse request bodies with `req.json().await?` and respond with `Response::from_json(&data)`.
|
|
83
|
+
|
|
84
|
+
## Cargo.toml Essentials
|
|
85
|
+
|
|
86
|
+
```toml
|
|
87
|
+
[lib]
|
|
88
|
+
crate-type = ["cdylib"]
|
|
89
|
+
|
|
90
|
+
[dependencies]
|
|
91
|
+
worker = "0.4"
|
|
92
|
+
serde = { version = "1", features = ["derive"] }
|
|
93
|
+
serde_json = "1"
|
|
94
|
+
console_error_panic_hook = "0.1"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Anti-patterns
|
|
98
|
+
|
|
99
|
+
| Anti-pattern | Why it's wrong |
|
|
100
|
+
|---|---|
|
|
101
|
+
| **Panics in production** | Always use `Result`. `unwrap()` and `expect()` crash the worker. |
|
|
102
|
+
| **Blocking operations** | WASM is single-threaded. All I/O must be `async`. |
|
|
103
|
+
| **Large binary sizes** | Keep dependencies minimal. Target under 1MB compressed. |
|
|
104
|
+
| **Ignoring memory limits** | Workers have 128MB memory. Stream large files, don't buffer. |
|
|
105
|
+
| **Raw SQL string concatenation** | Always use prepared statements with `bind()`. |
|
|
106
|
+
| **Missing CORS headers** | Workers serving APIs must handle OPTIONS preflight requests. |
|