everything-gemini 1.2.0 → 1.8.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/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- package/.github/ISSUE_TEMPLATE/subagent_proposal.md +29 -0
- package/.github/workflows/ci.yml +72 -1
- package/.github/workflows/release.yml +17 -2
- package/CHANGELOG.md +155 -0
- package/CONTRIBUTING.md +85 -4
- package/README.md +134 -19
- package/action.yml +35 -0
- package/agents/browser-qa.md +1 -1
- package/agents/database-migrator.md +26 -0
- package/agents/documentation-writer.md +27 -0
- package/agents/incident-responder.md +34 -0
- package/agents/performance-tuner.md +27 -0
- package/agents/release-engineer.md +34 -0
- package/bin/everything-gemini +1517 -122
- package/docs/COMMUNITY_SUBMISSIONS.md +88 -0
- package/docs/LAUNCH_KIT.md +138 -13
- package/docs/architecture/progressive-disclosure.md +3 -3
- package/docs/index.html +401 -40
- package/docs/launch-kit.md +163 -0
- package/examples/cli-workflows/demo_workflow.sh +15 -7
- package/examples/python-sdk/README.md +28 -5
- package/examples/python-sdk/documentation_audit_pipeline.py +83 -0
- package/examples/python-sdk/incident_triage_pipeline.py +170 -0
- package/examples/python-sdk/multi_agent_pipeline.py +95 -0
- package/examples/python-sdk/release_readiness_audit.py +144 -0
- package/hooks/secret-scrubber/hooks.json +16 -0
- package/hooks/secret-scrubber/scripts/scrub-secrets.sh +62 -0
- package/mcp/README.md +20 -0
- package/mcp/mcp_config.example.json +35 -0
- package/package.json +3 -1
- package/plugins/core/rules/core-practices.md +63 -4
- package/plugins/python-ai/rules/ai-agent-patterns.md +67 -4
- package/plugins/security-shield/rules/zero-trust.md +60 -4
- package/plugins/web-fullstack/rules/frontend-standards.md +74 -4
- package/rules/paradigms/security-hardened.md +53 -10
- package/rules/paradigms/tdd.md +65 -8
- package/scripts/install.ps1 +14 -0
- package/scripts/install.sh +15 -1
- package/scripts/validate.sh +103 -12
- package/skills/apache-kafka-streams/SKILL.md +152 -0
- package/skills/argo-workflows/SKILL.md +151 -0
- package/skills/axum-web-service/SKILL.md +159 -0
- package/skills/cilium-ebpf-networking/SKILL.md +145 -0
- package/skills/clickhouse-analytics/SKILL.md +128 -0
- package/skills/dbt-transformation/SKILL.md +179 -0
- package/skills/debezium-cdc/SKILL.md +166 -0
- package/skills/duckdb-local-olap/SKILL.md +134 -0
- package/skills/fastapi-production/SKILL.md +157 -0
- package/skills/grpc-connect-rpc/SKILL.md +176 -0
- package/skills/langgraph-orchestrator/SKILL.md +144 -0
- package/skills/litellm-proxy-gateway/SKILL.md +154 -0
- package/skills/nats-jetstream/SKILL.md +122 -0
- package/skills/open-telemetry-tracing/SKILL.md +151 -0
- package/skills/playwright-visual-regression/SKILL.md +124 -0
- package/skills/redis-cache-patterns/SKILL.md +159 -0
- package/skills/svelte-kit-architect/SKILL.md +190 -0
- package/skills/temporal-workflows/SKILL.md +158 -0
- package/skills/trpc-react-query/SKILL.md +169 -0
- package/skills/turborepo-monorepo/SKILL.md +199 -0
- package/skills/vllm-inference/SKILL.md +144 -0
- package/skills/wasm-rust-optimizer/SKILL.md +130 -0
- package/templates/full-power/hooks.json +14 -0
- package/templates/full-power/mcp_config.json +43 -0
- package/templates/recommended/hooks.json +14 -0
- package/tests/cli.test.js +383 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: redis-cache-patterns
|
|
3
|
+
description: >-
|
|
4
|
+
Implements enterprise Redis caching strategies (cache-aside, write-through, stale-while-revalidate), distributed locking (Redlock), sliding-window rate limiting, and pub/sub streams.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Redis Cache Patterns & Distributed Primitives
|
|
8
|
+
|
|
9
|
+
This skill provides production-tested patterns for high-throughput Redis caching, concurrency coordination, and resilient distributed systems.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 🚀 Key Patterns
|
|
14
|
+
|
|
15
|
+
### 1. Cache-Aside with Jitter (Preventing Cache Stampedes)
|
|
16
|
+
|
|
17
|
+
When calculating TTLs, always inject jitter (random variance) to prevent simultaneous mass-expiration of keys:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { Redis } from 'ioredis';
|
|
21
|
+
|
|
22
|
+
const redis = new Redis(process.env.REDIS_URL!);
|
|
23
|
+
|
|
24
|
+
export async function getCachedEntity<T>(
|
|
25
|
+
key: string,
|
|
26
|
+
fetchFn: () => Promise<T>,
|
|
27
|
+
baseTtlSeconds: number = 3600
|
|
28
|
+
): Promise<T> {
|
|
29
|
+
const cached = await redis.get(key);
|
|
30
|
+
if (cached) {
|
|
31
|
+
return JSON.parse(cached) as T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const freshData = await fetchFn();
|
|
35
|
+
|
|
36
|
+
// Inject 10-15% random jitter to distribute TTL expiration spikes
|
|
37
|
+
const jitter = Math.floor(Math.random() * (baseTtlSeconds * 0.15));
|
|
38
|
+
const ttl = baseTtlSeconds + jitter;
|
|
39
|
+
|
|
40
|
+
await redis.set(key, JSON.stringify(freshData), 'EX', ttl);
|
|
41
|
+
return freshData;
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### 2. Distributed Locking with Safe Lua Release
|
|
48
|
+
|
|
49
|
+
Never release a Redis lock with a plain `DEL` command, as doing so can accidentally delete a lock held by another process if the operation took longer than the TTL. Use atomic Lua verification:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { randomUUID } from 'crypto';
|
|
53
|
+
|
|
54
|
+
const RELEASE_LOCK_SCRIPT = `
|
|
55
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
56
|
+
return redis.call("del", KEYS[1])
|
|
57
|
+
else
|
|
58
|
+
return 0
|
|
59
|
+
end
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
export async function withDistributedLock<T>(
|
|
63
|
+
lockKey: string,
|
|
64
|
+
ttlMs: number,
|
|
65
|
+
task: () => Promise<T>
|
|
66
|
+
): Promise<T> {
|
|
67
|
+
const lockToken = randomUUID();
|
|
68
|
+
const acquired = await redis.set(lockKey, lockToken, 'PX', ttlMs, 'NX');
|
|
69
|
+
|
|
70
|
+
if (!acquired) {
|
|
71
|
+
throw new Error(`Failed to acquire distributed lock for key: ${lockKey}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
return await task();
|
|
76
|
+
} finally {
|
|
77
|
+
// Atomically release only if the token matches
|
|
78
|
+
await redis.eval(RELEASE_LOCK_SCRIPT, 1, lockKey, lockToken);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 3. Sliding-Window Rate Limiting (Sorted Sets)
|
|
86
|
+
|
|
87
|
+
Sliding-window rate limiters prevent burst exploitation around window boundaries:
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
export async function isRateLimited(
|
|
91
|
+
identifier: string,
|
|
92
|
+
limit: number,
|
|
93
|
+
windowSeconds: number
|
|
94
|
+
): Promise<{ allowed: boolean; remaining: number }> {
|
|
95
|
+
const now = Date.now();
|
|
96
|
+
const clearBefore = now - windowSeconds * 1000;
|
|
97
|
+
const key = `ratelimit:${identifier}`;
|
|
98
|
+
|
|
99
|
+
const pipeline = redis.pipeline();
|
|
100
|
+
pipeline.zremrangebyscore(key, 0, clearBefore);
|
|
101
|
+
pipeline.zadd(key, now, `${now}-${Math.random()}`);
|
|
102
|
+
pipeline.zcard(key);
|
|
103
|
+
pipeline.expire(key, windowSeconds);
|
|
104
|
+
|
|
105
|
+
const results = await pipeline.exec();
|
|
106
|
+
const count = (results?.[2]?.[1] as number) || 0;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
allowed: count <= limit,
|
|
110
|
+
remaining: Math.max(0, limit - count),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### 4. Redis Streams with Consumer Groups
|
|
118
|
+
|
|
119
|
+
For reliable, decoupled background tasks without message loss:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Producer: Append event to stream
|
|
123
|
+
export async function publishEvent(streamName: string, event: Record<string, string>) {
|
|
124
|
+
return await redis.xadd(streamName, '*', ...Object.entries(event).flat());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Consumer: Read events with acknowledgments
|
|
128
|
+
export async function consumeEvents(
|
|
129
|
+
streamName: string,
|
|
130
|
+
groupName: string,
|
|
131
|
+
consumerName: string,
|
|
132
|
+
handler: (id: string, fields: string[]) => Promise<void>
|
|
133
|
+
) {
|
|
134
|
+
const response = await redis.xreadgroup(
|
|
135
|
+
'GROUP', groupName, consumerName,
|
|
136
|
+
'COUNT', 10,
|
|
137
|
+
'BLOCK', 2000,
|
|
138
|
+
'STREAMS', streamName, '>'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!response) return;
|
|
142
|
+
|
|
143
|
+
for (const [, messages] of response as any[]) {
|
|
144
|
+
for (const [id, fields] of messages) {
|
|
145
|
+
await handler(id, fields);
|
|
146
|
+
// Acknowledge processed message
|
|
147
|
+
await redis.xack(streamName, groupName, id);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### 5. Memory & Performance Best Practices
|
|
156
|
+
|
|
157
|
+
- **Structured Namespaces**: Use colons for domain hierarchy (e.g., `user:1042:profile`, `session:tok_9918`).
|
|
158
|
+
- **Use Hashes for Objects**: Storing small objects in Redis `HSET` saves ~40% memory compared to JSON strings due to memory-efficient `ziplist`/`listpack` encodings.
|
|
159
|
+
- **Always Configure Eviction Policy**: In `redis.conf`, set `maxmemory-policy volatile-lru` or `allkeys-lru` to avoid hard out-of-memory errors.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: svelte-kit-architect
|
|
3
|
+
description: >-
|
|
4
|
+
Architects, builds, and optimizes modern SvelteKit web applications using Svelte 5 runes ($state, $derived, $effect), SSR/prerendering, form actions, and type-safe server load functions.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SvelteKit Architect & Svelte 5 Mastery
|
|
8
|
+
|
|
9
|
+
This skill guides the design, structure, and implementation of production SvelteKit applications utilizing modern Svelte 5 Runes, progressive enhancement, and robust server-side routing.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⚡ Svelte 5 Runes Modernization
|
|
14
|
+
|
|
15
|
+
Svelte 5 replaces legacy `export let` and `$:` syntax with explicit, fine-grained reactivity runes:
|
|
16
|
+
|
|
17
|
+
### 1. State, Derived & Props
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script lang="ts">
|
|
21
|
+
interface Props {
|
|
22
|
+
title: string;
|
|
23
|
+
initialCount?: number;
|
|
24
|
+
onUpdate?: (val: number) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Props declaration with defaults
|
|
28
|
+
let { title, initialCount = 0, onUpdate }: Props = $props();
|
|
29
|
+
|
|
30
|
+
// Reactive state
|
|
31
|
+
let count = $state(initialCount);
|
|
32
|
+
|
|
33
|
+
// Derived computed values
|
|
34
|
+
let doubled = $derived(count * 2);
|
|
35
|
+
|
|
36
|
+
function increment() {
|
|
37
|
+
count += 1;
|
|
38
|
+
onUpdate?.(count);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Side-effect cleanup
|
|
42
|
+
$effect(() => {
|
|
43
|
+
console.log(`Current count: ${count}`);
|
|
44
|
+
return () => console.log('Cleaning up effect');
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<button onclick={increment}>
|
|
49
|
+
{title}: {count} (Doubled: {doubled})
|
|
50
|
+
</button>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 📁 SvelteKit Routing & Data Loading
|
|
56
|
+
|
|
57
|
+
### 1. Type-Safe Server Load Function (`+page.server.ts`)
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import type { PageServerLoad } from './$types';
|
|
61
|
+
import { error } from '@sveltejs/kit';
|
|
62
|
+
import { db } from '$lib/server/db';
|
|
63
|
+
|
|
64
|
+
export const load: PageServerLoad = async ({ params, locals }) => {
|
|
65
|
+
const item = await db.item.findUnique({
|
|
66
|
+
where: { id: params.id },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (!item) {
|
|
70
|
+
throw error(404, { message: 'Item not found' });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
item,
|
|
75
|
+
user: locals.user,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Rendering Data in Component (`+page.svelte`)
|
|
81
|
+
|
|
82
|
+
```svelte
|
|
83
|
+
<script lang="ts">
|
|
84
|
+
import type { PageData } from './$types';
|
|
85
|
+
|
|
86
|
+
let { data }: { data: PageData } = $props();
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<h1>{data.item.title}</h1>
|
|
90
|
+
<p>Author: {data.user?.name ?? 'Anonymous'}</p>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 📝 Form Actions & Progressive Enhancement
|
|
96
|
+
|
|
97
|
+
Handle mutations without client-side JavaScript dependency while maintaining smooth client transitions using `use:enhance`:
|
|
98
|
+
|
|
99
|
+
### Server Action (`+page.server.ts`)
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import type { Actions } from './$types';
|
|
103
|
+
import { fail } from '@sveltejs/kit';
|
|
104
|
+
import { z } from 'zod';
|
|
105
|
+
|
|
106
|
+
const schema = z.object({
|
|
107
|
+
email: z.string().email(),
|
|
108
|
+
comment: z.string().min(3),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const actions: Actions = {
|
|
112
|
+
createComment: async ({ request }) => {
|
|
113
|
+
const formData = await request.formData();
|
|
114
|
+
const parsed = schema.safeParse(Object.fromEntries(formData));
|
|
115
|
+
|
|
116
|
+
if (!parsed.success) {
|
|
117
|
+
return fail(400, {
|
|
118
|
+
errors: parsed.error.flatten().fieldErrors,
|
|
119
|
+
values: Object.fromEntries(formData),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Process mutation in database
|
|
124
|
+
return { success: true };
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Form in Svelte Component (`+page.svelte`)
|
|
130
|
+
|
|
131
|
+
```svelte
|
|
132
|
+
<script lang="ts">
|
|
133
|
+
import { enhance } from '$app/forms';
|
|
134
|
+
import type { ActionData } from './$types';
|
|
135
|
+
|
|
136
|
+
let { form }: { form: ActionData } = $props();
|
|
137
|
+
let isSubmitting = $state(false);
|
|
138
|
+
</script>
|
|
139
|
+
|
|
140
|
+
<form
|
|
141
|
+
method="POST"
|
|
142
|
+
action="?/createComment"
|
|
143
|
+
use:enhance={() => {
|
|
144
|
+
isSubmitting = true;
|
|
145
|
+
return async ({ update }) => {
|
|
146
|
+
isSubmitting = false;
|
|
147
|
+
await update();
|
|
148
|
+
};
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
<input name="email" type="email" value={form?.values?.email ?? ''} required />
|
|
152
|
+
{#if form?.errors?.email}
|
|
153
|
+
<span class="error">{form.errors.email[0]}</span>
|
|
154
|
+
{/if}
|
|
155
|
+
|
|
156
|
+
<textarea name="comment" required></textarea>
|
|
157
|
+
|
|
158
|
+
<button type="submit" disabled={isSubmitting}>
|
|
159
|
+
{isSubmitting ? 'Submitting...' : 'Post Comment'}
|
|
160
|
+
</button>
|
|
161
|
+
</form>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 🌐 Global Shared State with Rune Classes
|
|
167
|
+
|
|
168
|
+
Use universal classes with `$state` for application-wide stores without legacy Svelte `writable`:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// src/lib/stores/cart.svelte.ts
|
|
172
|
+
export class CartStore {
|
|
173
|
+
items = $state<Array<{ id: string; qty: number }>>([]);
|
|
174
|
+
|
|
175
|
+
get totalItems() {
|
|
176
|
+
return this.items.reduce((sum, item) => sum + item.qty, 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
addItem(id: string) {
|
|
180
|
+
const existing = this.items.find((i) => i.id === id);
|
|
181
|
+
if (existing) {
|
|
182
|
+
existing.qty += 1;
|
|
183
|
+
} else {
|
|
184
|
+
this.items.push({ id, qty: 1 });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const cart = new CartStore();
|
|
190
|
+
```
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: temporal-workflows
|
|
3
|
+
description: >-
|
|
4
|
+
Architects and implements fault-tolerant distributed workflows, retry policies, async activities, saga compensations, and child workflows using the Temporal TypeScript/Python SDK.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Temporal Workflows & Resilient Distributed Orchestration
|
|
8
|
+
|
|
9
|
+
This skill guides the design and implementation of bulletproof distributed workflows with Temporal, ensuring zero state loss during worker crashes, network partitions, or transient external API failures.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⚡ The Cardinal Rule: Workflow Determinism
|
|
14
|
+
|
|
15
|
+
Workflows in Temporal are replayed from event history. Therefore, **workflow functions must be 100% deterministic**:
|
|
16
|
+
|
|
17
|
+
| ❌ NEVER in Workflow Functions | ✅ ALWAYS in Activities or SDK Wrappers |
|
|
18
|
+
| :--- | :--- |
|
|
19
|
+
| `Date.now()`, `new Date()` | `workflow.currentTime()` |
|
|
20
|
+
| `Math.random()`, `crypto.randomUUID()` | Pass random values as input or generate in Activities |
|
|
21
|
+
| `fetch()`, database queries, file I/O | Isolate all network, disk, and DB calls inside Activities |
|
|
22
|
+
| Mutable global variables, timers (`setTimeout`) | Use `workflow.sleep()` or `workflow.condition()` |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🏗️ Architecture: Workflows & Activities (TypeScript)
|
|
27
|
+
|
|
28
|
+
### 1. Activities Definition (`activities.ts`)
|
|
29
|
+
Activities handle all non-deterministic side effects and I/O:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { Context } from '@temporalio/activity';
|
|
33
|
+
|
|
34
|
+
export async function chargePayment(customerId: string, amountCents: number): Promise<string> {
|
|
35
|
+
const { heartbeat } = Context.current();
|
|
36
|
+
heartbeat('Connecting to payment gateway');
|
|
37
|
+
|
|
38
|
+
// Call external Stripe or payment API
|
|
39
|
+
const chargeId = await stripeClient.charges.create({ customer: customerId, amount: amountCents });
|
|
40
|
+
return chargeId;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function refundPayment(chargeId: string): Promise<void> {
|
|
44
|
+
await stripeClient.refunds.create({ charge: chargeId });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function provisionResource(tenantId: string): Promise<string> {
|
|
48
|
+
return await cloudProvider.provision(tenantId);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### 2. Workflow Orchestration with Saga Compensations (`workflows.ts`)
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { proxyActivities, sleep, defineSignal, setHandler, condition } from '@temporalio/workflow';
|
|
58
|
+
import type * as activities from './activities';
|
|
59
|
+
|
|
60
|
+
// Configure activity timeouts and retry policies
|
|
61
|
+
const { chargePayment, refundPayment, provisionResource } = proxyActivities<typeof activities>({
|
|
62
|
+
startToCloseTimeout: '1 minute',
|
|
63
|
+
retry: {
|
|
64
|
+
initialInterval: '2 seconds',
|
|
65
|
+
backoffCoefficient: 2,
|
|
66
|
+
maximumInterval: '1 minute',
|
|
67
|
+
maximumAttempts: 5,
|
|
68
|
+
nonRetryableErrorTypes: ['InvalidCardError', 'CustomerNotFoundError'],
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export const cancelOrderSignal = defineSignal('cancelOrder');
|
|
73
|
+
|
|
74
|
+
export async function orderFulfillmentWorkflow(order: { customerId: string; amount: number; tenantId: string }): Promise<string> {
|
|
75
|
+
const compensations: Array<() => Promise<void>> = [];
|
|
76
|
+
let isCancelled = false;
|
|
77
|
+
|
|
78
|
+
// Listen for external user cancellation signal
|
|
79
|
+
setHandler(cancelOrderSignal, () => {
|
|
80
|
+
isCancelled = true;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Step 1: Charge payment
|
|
85
|
+
const chargeId = await chargePayment(order.customerId, order.amount);
|
|
86
|
+
compensations.push(async () => await refundPayment(chargeId));
|
|
87
|
+
|
|
88
|
+
if (isCancelled) {
|
|
89
|
+
throw new Error('Order cancelled by user before provisioning');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Step 2: Provision cloud resources
|
|
93
|
+
const resourceId = await provisionResource(order.tenantId);
|
|
94
|
+
return resourceId;
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
// SAGA Pattern: Execute compensations in reverse order
|
|
98
|
+
for (const compensate of compensations.reverse()) {
|
|
99
|
+
try {
|
|
100
|
+
await compensate();
|
|
101
|
+
} catch (compErr) {
|
|
102
|
+
// Log critical compensation failure for manual intervention
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 🧪 Testing Workflows (`workflow.test.ts`)
|
|
113
|
+
|
|
114
|
+
Test workflows without connecting to a remote Temporal server using `TestWorkflowEnvironment`:
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { TestWorkflowEnvironment } from '@temporalio/testing';
|
|
118
|
+
import { Worker } from '@temporalio/worker';
|
|
119
|
+
import { orderFulfillmentWorkflow } from './workflows';
|
|
120
|
+
|
|
121
|
+
describe('orderFulfillmentWorkflow', () => {
|
|
122
|
+
let testEnv: TestWorkflowEnvironment;
|
|
123
|
+
|
|
124
|
+
beforeAll(async () => {
|
|
125
|
+
testEnv = await TestWorkflowEnvironment.createTimeSkipping();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
afterAll(async () => {
|
|
129
|
+
await testEnv?.teardown();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('successfully completes workflow with mocked activities', async () => {
|
|
133
|
+
const { client, nativeConnection } = testEnv;
|
|
134
|
+
const taskQueue = 'test-queue';
|
|
135
|
+
|
|
136
|
+
const worker = await Worker.create({
|
|
137
|
+
connection: nativeConnection,
|
|
138
|
+
taskQueue,
|
|
139
|
+
workflowsPath: require.resolve('./workflows'),
|
|
140
|
+
activities: {
|
|
141
|
+
chargePayment: async () => 'ch_test_123',
|
|
142
|
+
refundPayment: async () => {},
|
|
143
|
+
provisionResource: async () => 'res_prod_999',
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await worker.runUntil(async () => {
|
|
148
|
+
const handle = await client.workflow.start(orderFulfillmentWorkflow, {
|
|
149
|
+
args: [{ customerId: 'cust_1', amount: 5000, tenantId: 'ten_abc' }],
|
|
150
|
+
taskQueue,
|
|
151
|
+
workflowId: 'test-order-1',
|
|
152
|
+
});
|
|
153
|
+
const result = await handle.result();
|
|
154
|
+
expect(result).toBe('res_prod_999');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
```
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trpc-react-query
|
|
3
|
+
description: >-
|
|
4
|
+
Architects end-to-end type-safe APIs with tRPC v11 and TanStack React Query v5, implementing routers, context middlewares, mutations, and optimistic cache updates.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# tRPC v11 & TanStack React Query v5 Architecture
|
|
8
|
+
|
|
9
|
+
This skill guides building end-to-end type-safe fullstack applications where frontend components get compile-time autocomplete and validation for backend routes without generating client SDKs.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ⚡ Server-Side Router Architecture
|
|
14
|
+
|
|
15
|
+
### 1. Initialization & Context (`trpc.ts`)
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { initTRPC, TRPCError } from '@trpc/server';
|
|
19
|
+
import superjson from 'superjson';
|
|
20
|
+
import { type CreateNextContextOptions } from '@trpc/server/adapters/next';
|
|
21
|
+
import { getAuthSession } from '$lib/auth';
|
|
22
|
+
|
|
23
|
+
export async function createContext(opts?: CreateNextContextOptions) {
|
|
24
|
+
const session = await getAuthSession(opts?.req, opts?.res);
|
|
25
|
+
return { session, db };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type Context = Awaited<ReturnType<typeof createContext>>;
|
|
29
|
+
|
|
30
|
+
const t = initTRPC.context<Context>().create({
|
|
31
|
+
transformer: superjson,
|
|
32
|
+
errorFormatter({ shape, error }) {
|
|
33
|
+
return {
|
|
34
|
+
...shape,
|
|
35
|
+
data: {
|
|
36
|
+
...shape.data,
|
|
37
|
+
zodError: error.cause instanceof ZodError ? error.cause.flatten() : null,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export const router = t.router;
|
|
44
|
+
export const publicProcedure = t.procedure;
|
|
45
|
+
|
|
46
|
+
// Protected Procedure Middleware
|
|
47
|
+
export const protectedProcedure = t.procedure.use(async ({ ctx, next }) => {
|
|
48
|
+
if (!ctx.session?.user) {
|
|
49
|
+
throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Authentication required' });
|
|
50
|
+
}
|
|
51
|
+
return next({
|
|
52
|
+
ctx: {
|
|
53
|
+
...ctx,
|
|
54
|
+
user: ctx.session.user,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
### 2. Feature Subrouter with Input Validation (`routers/tasks.ts`)
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
import { z } from 'zod';
|
|
66
|
+
import { router, protectedProcedure } from '../trpc';
|
|
67
|
+
|
|
68
|
+
export const taskRouter = router({
|
|
69
|
+
list: protectedProcedure
|
|
70
|
+
.input(
|
|
71
|
+
z.object({
|
|
72
|
+
status: z.enum(['pending', 'completed']).optional(),
|
|
73
|
+
limit: z.number().min(1).max(100).default(20),
|
|
74
|
+
})
|
|
75
|
+
)
|
|
76
|
+
.query(async ({ ctx, input }) => {
|
|
77
|
+
return await ctx.db.task.findMany({
|
|
78
|
+
where: { userId: ctx.user.id, ...(input.status && { status: input.status }) },
|
|
79
|
+
take: input.limit,
|
|
80
|
+
orderBy: { createdAt: 'desc' },
|
|
81
|
+
});
|
|
82
|
+
}),
|
|
83
|
+
|
|
84
|
+
toggleStatus: protectedProcedure
|
|
85
|
+
.input(z.object({ id: z.string().uuid(), completed: z.boolean() }))
|
|
86
|
+
.mutation(async ({ ctx, input }) => {
|
|
87
|
+
return await ctx.db.task.update({
|
|
88
|
+
where: { id: input.id, userId: ctx.user.id },
|
|
89
|
+
data: { completed: input.completed },
|
|
90
|
+
});
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## 🌐 Client-Side React Query Integration
|
|
98
|
+
|
|
99
|
+
### 1. Optimistic UI Updates (`TaskList.tsx`)
|
|
100
|
+
|
|
101
|
+
Render instant UI updates while the network request is inflight, reverting cleanly if an error occurs:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import { trpc } from '@/utils/trpc';
|
|
105
|
+
|
|
106
|
+
export function TaskItem({ task }: { task: Task }) {
|
|
107
|
+
const utils = trpc.useUtils();
|
|
108
|
+
|
|
109
|
+
const toggleMutation = trpc.task.toggleStatus.useMutation({
|
|
110
|
+
onMutate: async (variables) => {
|
|
111
|
+
// 1. Cancel ongoing queries for list
|
|
112
|
+
await utils.task.list.cancel();
|
|
113
|
+
|
|
114
|
+
// 2. Snapshot previous cache value
|
|
115
|
+
const previousTasks = utils.task.list.getData();
|
|
116
|
+
|
|
117
|
+
// 3. Optimistically update client cache
|
|
118
|
+
utils.task.list.setData(undefined, (old) =>
|
|
119
|
+
old?.map((t) => (t.id === variables.id ? { ...t, completed: variables.completed } : t))
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
return { previousTasks };
|
|
123
|
+
},
|
|
124
|
+
onError: (err, variables, context) => {
|
|
125
|
+
// 4. Rollback cache upon mutation failure
|
|
126
|
+
if (context?.previousTasks) {
|
|
127
|
+
utils.task.list.setData(undefined, context.previousTasks);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
onSettled: () => {
|
|
131
|
+
// 5. Invalidate and synchronize with source of truth
|
|
132
|
+
utils.task.list.invalidate();
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<div>
|
|
138
|
+
<input
|
|
139
|
+
type="checkbox"
|
|
140
|
+
checked={task.completed}
|
|
141
|
+
onChange={(e) => toggleMutation.mutate({ id: task.id, completed: e.target.checked })}
|
|
142
|
+
/>
|
|
143
|
+
<span>{task.title}</span>
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 🚀 Server-Side Prefetching (Next.js App Router / SSR)
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { createServerSideHelpers } from '@trpc/react-query/server';
|
|
155
|
+
import { appRouter } from '@/server/routers/_app';
|
|
156
|
+
import { createContext } from '@/server/trpc';
|
|
157
|
+
import SuperJSON from 'superjson';
|
|
158
|
+
|
|
159
|
+
export async function prefetchTasks() {
|
|
160
|
+
const helpers = createServerSideHelpers({
|
|
161
|
+
router: appRouter,
|
|
162
|
+
ctx: await createContext(),
|
|
163
|
+
transformer: SuperJSON,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await helpers.task.list.prefetch({ limit: 20 });
|
|
167
|
+
return helpers.dehydrate();
|
|
168
|
+
}
|
|
169
|
+
```
|