@girardmedia/bootspring 3.3.2 → 3.4.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/assets/agents/accessibility-auditor.md +39 -0
- package/assets/agents/api-designer.md +40 -0
- package/assets/agents/auth-implementer.md +64 -0
- package/assets/agents/bug-hunter.md +42 -0
- package/assets/agents/bundle-analyzer.md +40 -0
- package/assets/agents/cache-optimizer.md +55 -0
- package/assets/agents/changelog-writer.md +55 -0
- package/assets/agents/ci-cd-builder.md +40 -0
- package/assets/agents/code-explainer.md +39 -0
- package/assets/agents/code-reviewer.md +39 -0
- package/assets/agents/cost-optimizer.md +57 -0
- package/assets/agents/cron-scheduler.md +51 -0
- package/assets/agents/data-seeder.md +56 -0
- package/assets/agents/database-architect.md +40 -0
- package/assets/agents/dependency-updater.md +40 -0
- package/assets/agents/deploy-checker.md +40 -0
- package/assets/agents/docker-optimizer.md +40 -0
- package/assets/agents/documentation-writer.md +40 -0
- package/assets/agents/email-builder.md +55 -0
- package/assets/agents/env-setup.md +40 -0
- package/assets/agents/error-handler.md +40 -0
- package/assets/agents/eslint-fixer.md +46 -0
- package/assets/agents/feature-flagger.md +69 -0
- package/assets/agents/git-detective.md +39 -0
- package/assets/agents/graphql-builder.md +60 -0
- package/assets/agents/incident-responder.md +59 -0
- package/assets/agents/log-analyzer.md +39 -0
- package/assets/agents/migration-planner.md +41 -0
- package/assets/agents/monorepo-navigator.md +39 -0
- package/assets/agents/nextjs-expert.md +57 -0
- package/assets/agents/notification-builder.md +56 -0
- package/assets/agents/onboarding-guide.md +39 -0
- package/assets/agents/performance-profiler.md +40 -0
- package/assets/agents/prisma-expert.md +57 -0
- package/assets/agents/rate-limiter.md +58 -0
- package/assets/agents/react-expert.md +58 -0
- package/assets/agents/refactorer.md +42 -0
- package/assets/agents/regex-builder.md +46 -0
- package/assets/agents/release-manager.md +40 -0
- package/assets/agents/s3-manager.md +58 -0
- package/assets/agents/schema-validator.md +40 -0
- package/assets/agents/search-builder.md +62 -0
- package/assets/agents/security-auditor.md +39 -0
- package/assets/agents/sitemap-generator.md +53 -0
- package/assets/agents/stripe-integrator.md +59 -0
- package/assets/agents/tailwind-expert.md +55 -0
- package/assets/agents/tech-debt-tracker.md +39 -0
- package/assets/agents/test-writer.md +42 -0
- package/assets/agents/type-fixer.md +45 -0
- package/assets/agents/webhook-builder.md +54 -0
- package/assets/rules/cpp.md +53 -0
- package/assets/rules/css.md +52 -0
- package/assets/rules/go.md +50 -0
- package/assets/rules/html.md +52 -0
- package/assets/rules/java.md +51 -0
- package/assets/rules/kotlin.md +50 -0
- package/assets/rules/php.md +51 -0
- package/assets/rules/python.md +51 -0
- package/assets/rules/ruby.md +51 -0
- package/assets/rules/rust.md +49 -0
- package/assets/rules/shell.md +52 -0
- package/assets/rules/sql.md +49 -0
- package/assets/rules/swift.md +50 -0
- package/assets/rules/typescript.md +52 -0
- package/assets/rules/yaml-json.md +51 -0
- package/assets/skills/accessibility.md +210 -0
- package/assets/skills/agent-patterns.md +387 -0
- package/assets/skills/ai-integration.md +263 -0
- package/assets/skills/animation-patterns.md +224 -0
- package/assets/skills/api-design.md +218 -0
- package/assets/skills/api-gateway.md +341 -0
- package/assets/skills/api-versioning.md +226 -0
- package/assets/skills/astro-patterns.md +233 -0
- package/assets/skills/auth-patterns.md +248 -0
- package/assets/skills/aws-patterns.md +171 -0
- package/assets/skills/background-jobs.md +162 -0
- package/assets/skills/browser-extensions.md +309 -0
- package/assets/skills/caching-patterns.md +253 -0
- package/assets/skills/ci-cd.md +251 -0
- package/assets/skills/cli-development.md +296 -0
- package/assets/skills/code-review.md +185 -0
- package/assets/skills/cron-patterns.md +327 -0
- package/assets/skills/data-fetching.md +231 -0
- package/assets/skills/database-migrations.md +346 -0
- package/assets/skills/database-patterns.md +219 -0
- package/assets/skills/debugging.md +281 -0
- package/assets/skills/design-system.md +289 -0
- package/assets/skills/django-patterns.md +182 -0
- package/assets/skills/docker-patterns.md +235 -0
- package/assets/skills/e2e-testing.md +287 -0
- package/assets/skills/edge-computing.md +268 -0
- package/assets/skills/electron-patterns.md +266 -0
- package/assets/skills/email-templates.md +206 -0
- package/assets/skills/error-handling.md +265 -0
- package/assets/skills/event-driven.md +232 -0
- package/assets/skills/express-patterns.md +239 -0
- package/assets/skills/fastapi-patterns.md +198 -0
- package/assets/skills/feature-flags.md +212 -0
- package/assets/skills/figma-to-code.md +298 -0
- package/assets/skills/file-upload.md +228 -0
- package/assets/skills/forms-patterns.md +264 -0
- package/assets/skills/gcp-patterns.md +189 -0
- package/assets/skills/git-workflow.md +187 -0
- package/assets/skills/golang-patterns.md +185 -0
- package/assets/skills/graphql-patterns.md +244 -0
- package/assets/skills/i18n-patterns.md +172 -0
- package/assets/skills/image-processing.md +350 -0
- package/assets/skills/java-springboot.md +226 -0
- package/assets/skills/kotlin-patterns.md +207 -0
- package/assets/skills/kubernetes-patterns.md +326 -0
- package/assets/skills/laravel-patterns.md +261 -0
- package/assets/skills/llm-fine-tuning.md +335 -0
- package/assets/skills/load-testing.md +303 -0
- package/assets/skills/logging-observability.md +228 -0
- package/assets/skills/markdown-processing.md +318 -0
- package/assets/skills/mcp-server-patterns.md +292 -0
- package/assets/skills/microservices.md +272 -0
- package/assets/skills/migration-patterns.md +239 -0
- package/assets/skills/mongodb-patterns.md +189 -0
- package/assets/skills/monorepo-patterns.md +287 -0
- package/assets/skills/nextjs-app-router.md +237 -0
- package/assets/skills/notification-patterns.md +348 -0
- package/assets/skills/oauth-patterns.md +246 -0
- package/assets/skills/payment-integration.md +222 -0
- package/assets/skills/pdf-generation.md +307 -0
- package/assets/skills/performance-optimization.md +277 -0
- package/assets/skills/php-patterns.md +210 -0
- package/assets/skills/prisma-patterns.md +241 -0
- package/assets/skills/prompt-engineering.md +193 -0
- package/assets/skills/pwa-patterns.md +247 -0
- package/assets/skills/python-patterns.md +158 -0
- package/assets/skills/python-testing.md +172 -0
- package/assets/skills/queue-patterns.md +295 -0
- package/assets/skills/rag-patterns.md +159 -0
- package/assets/skills/rate-limiting.md +319 -0
- package/assets/skills/react-components.md +201 -0
- package/assets/skills/react-native-patterns.md +299 -0
- package/assets/skills/real-time-patterns.md +181 -0
- package/assets/skills/redis-patterns.md +188 -0
- package/assets/skills/refactoring.md +218 -0
- package/assets/skills/regex-patterns.md +191 -0
- package/assets/skills/remix-patterns.md +262 -0
- package/assets/skills/responsive-design.md +199 -0
- package/assets/skills/ruby-rails-patterns.md +178 -0
- package/assets/skills/rust-patterns.md +211 -0
- package/assets/skills/search-patterns.md +227 -0
- package/assets/skills/security-hardening.md +237 -0
- package/assets/skills/seo-patterns.md +179 -0
- package/assets/skills/serverless-patterns.md +223 -0
- package/assets/skills/sql-optimization.md +154 -0
- package/assets/skills/state-management.md +254 -0
- package/assets/skills/storybook-patterns.md +330 -0
- package/assets/skills/svelte-patterns.md +258 -0
- package/assets/skills/swift-patterns.md +227 -0
- package/assets/skills/tailwind-patterns.md +272 -0
- package/assets/skills/tdd-workflow.md +199 -0
- package/assets/skills/terraform-patterns.md +270 -0
- package/assets/skills/testing-react.md +240 -0
- package/assets/skills/testing-vitest.md +232 -0
- package/assets/skills/typescript-strict.md +159 -0
- package/assets/skills/video-processing.md +340 -0
- package/assets/skills/vue-patterns.md +247 -0
- package/assets/skills/web-workers.md +327 -0
- package/assets/skills/webhooks-patterns.md +283 -0
- package/assets/skills/websocket-patterns.md +306 -0
- package/dist/cli/index.js +941 -958
- package/dist/core/index.d.ts +341 -11
- package/dist/core.js +58 -95
- package/dist/mcp/index.d.ts +33 -1
- package/dist/mcp-server.js +177 -255
- package/package.json +4 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prisma-patterns
|
|
3
|
+
description: Use Prisma ORM effectively — relations, transactions, raw queries, migrations, and soft deletes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prisma ORM Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Apply these patterns when using Prisma as your database layer. Prisma's type
|
|
11
|
+
safety is powerful but has sharp edges around performance, transactions, and
|
|
12
|
+
advanced queries. These patterns keep you out of trouble as your schema grows.
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
### 1. Relations — Explicit Over Implicit
|
|
17
|
+
|
|
18
|
+
Define both sides of every relation. Use `@relation` with named references when
|
|
19
|
+
a model has multiple relations to the same table.
|
|
20
|
+
|
|
21
|
+
```prisma
|
|
22
|
+
model User {
|
|
23
|
+
id String @id @default(cuid())
|
|
24
|
+
email String @unique
|
|
25
|
+
posts Post[] @relation("author")
|
|
26
|
+
reviews Post[] @relation("reviewer")
|
|
27
|
+
profile Profile?
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
model Post {
|
|
31
|
+
id String @id @default(cuid())
|
|
32
|
+
authorId String
|
|
33
|
+
author User @relation("author", fields: [authorId], references: [id])
|
|
34
|
+
reviewerId String?
|
|
35
|
+
reviewer User? @relation("reviewer", fields: [reviewerId], references: [id])
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
model Profile {
|
|
39
|
+
id String @id @default(cuid())
|
|
40
|
+
userId String @unique
|
|
41
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
42
|
+
bio String @default("")
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Always set `onDelete` explicitly. The default is `SetNull` for optional relations
|
|
47
|
+
and `Restrict` for required ones — both can surprise you.
|
|
48
|
+
|
|
49
|
+
### 2. Select Only What You Need
|
|
50
|
+
|
|
51
|
+
Avoid fetching entire objects when you need two fields.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Bad — fetches every column
|
|
55
|
+
const users = await prisma.user.findMany({ include: { posts: true } });
|
|
56
|
+
|
|
57
|
+
// Good — fetches only what the API response needs
|
|
58
|
+
const users = await prisma.user.findMany({
|
|
59
|
+
select: {
|
|
60
|
+
id: true,
|
|
61
|
+
email: true,
|
|
62
|
+
posts: {
|
|
63
|
+
select: { id: true, title: true },
|
|
64
|
+
where: { published: true },
|
|
65
|
+
take: 5,
|
|
66
|
+
orderBy: { createdAt: 'desc' },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 3. Transactions
|
|
73
|
+
|
|
74
|
+
Use interactive transactions for multi-step operations that must be atomic.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const [order, payment] = await prisma.$transaction(async (tx) => {
|
|
78
|
+
const order = await tx.order.create({
|
|
79
|
+
data: { userId, items: { create: lineItems } },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const payment = await tx.payment.create({
|
|
83
|
+
data: {
|
|
84
|
+
orderId: order.id,
|
|
85
|
+
amount: calculateTotal(lineItems),
|
|
86
|
+
status: 'pending',
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// If this throws, both order and payment are rolled back
|
|
91
|
+
await chargePaymentProvider(payment);
|
|
92
|
+
|
|
93
|
+
return [order, payment];
|
|
94
|
+
}, {
|
|
95
|
+
maxWait: 5000, // max time to acquire a connection
|
|
96
|
+
timeout: 10000, // max transaction duration
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For simple batch writes, use the array form:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
await prisma.$transaction([
|
|
104
|
+
prisma.user.update({ where: { id }, data: { credits: { decrement: 10 } } }),
|
|
105
|
+
prisma.auditLog.create({ data: { action: 'credit_deduction', userId: id } }),
|
|
106
|
+
]);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### 4. Raw Queries for Complex Operations
|
|
110
|
+
|
|
111
|
+
When Prisma's query builder can't express what you need.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Upsert with conflict handling
|
|
115
|
+
await prisma.$executeRaw`
|
|
116
|
+
INSERT INTO usage (user_id, month, api_calls)
|
|
117
|
+
VALUES (${userId}, ${month}, 1)
|
|
118
|
+
ON CONFLICT (user_id, month)
|
|
119
|
+
DO UPDATE SET api_calls = usage.api_calls + 1
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
// Complex aggregate
|
|
123
|
+
const stats = await prisma.$queryRaw<{ day: Date; count: bigint }[]>`
|
|
124
|
+
SELECT date_trunc('day', created_at) AS day, COUNT(*) AS count
|
|
125
|
+
FROM orders
|
|
126
|
+
WHERE created_at > ${startDate}
|
|
127
|
+
GROUP BY day
|
|
128
|
+
ORDER BY day
|
|
129
|
+
`;
|
|
130
|
+
|
|
131
|
+
// Note: $queryRaw returns bigint for COUNT — convert to Number if safe
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Always use tagged template literals (not string concatenation) for SQL injection
|
|
135
|
+
protection.
|
|
136
|
+
|
|
137
|
+
### 5. Migrations Workflow
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Development: create and apply migration
|
|
141
|
+
npx prisma migrate dev --name add_user_profile
|
|
142
|
+
|
|
143
|
+
# Production: apply pending migrations (no interactive prompts)
|
|
144
|
+
npx prisma migrate deploy
|
|
145
|
+
|
|
146
|
+
# Reset dev database (drops all data)
|
|
147
|
+
npx prisma migrate reset
|
|
148
|
+
|
|
149
|
+
# Generate client after schema changes
|
|
150
|
+
npx prisma generate
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Name migrations descriptively: `add_user_profile`, `make_email_unique`,
|
|
154
|
+
`add_order_status_index`.
|
|
155
|
+
|
|
156
|
+
### 6. Seeding
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// prisma/seed.ts
|
|
160
|
+
import { PrismaClient } from '@prisma/client';
|
|
161
|
+
const prisma = new PrismaClient();
|
|
162
|
+
|
|
163
|
+
async function main() {
|
|
164
|
+
await prisma.user.upsert({
|
|
165
|
+
where: { email: 'admin@example.com' },
|
|
166
|
+
update: {},
|
|
167
|
+
create: {
|
|
168
|
+
email: 'admin@example.com',
|
|
169
|
+
name: 'Admin',
|
|
170
|
+
role: 'ADMIN',
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
main()
|
|
176
|
+
.catch(console.error)
|
|
177
|
+
.finally(() => prisma.$disconnect());
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Use `upsert` in seeds so they're idempotent — safe to run multiple times.
|
|
181
|
+
|
|
182
|
+
### 7. Soft Deletes with Middleware
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// Middleware approach
|
|
186
|
+
prisma.$use(async (params, next) => {
|
|
187
|
+
if (params.model === 'Post') {
|
|
188
|
+
if (params.action === 'delete') {
|
|
189
|
+
params.action = 'update';
|
|
190
|
+
params.args.data = { deletedAt: new Date() };
|
|
191
|
+
}
|
|
192
|
+
if (params.action === 'findMany') {
|
|
193
|
+
params.args.where = { ...params.args.where, deletedAt: null };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return next(params);
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
For new projects, prefer Prisma Client Extensions over middleware:
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
const xprisma = prisma.$extends({
|
|
204
|
+
query: {
|
|
205
|
+
post: {
|
|
206
|
+
async delete({ args, query }) {
|
|
207
|
+
return prisma.post.update({
|
|
208
|
+
...args,
|
|
209
|
+
data: { deletedAt: new Date() },
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 8. Indexes for Performance
|
|
218
|
+
|
|
219
|
+
```prisma
|
|
220
|
+
model Order {
|
|
221
|
+
id String @id @default(cuid())
|
|
222
|
+
userId String
|
|
223
|
+
status String
|
|
224
|
+
createdAt DateTime @default(now())
|
|
225
|
+
|
|
226
|
+
@@index([userId, status]) // composite for filtered user queries
|
|
227
|
+
@@index([createdAt(sort: Desc)]) // sorted listing
|
|
228
|
+
}
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Checklist
|
|
232
|
+
|
|
233
|
+
- [ ] Every relation has explicit `onDelete` behavior
|
|
234
|
+
- [ ] List queries use `select` instead of `include` when possible
|
|
235
|
+
- [ ] Multi-step mutations use `$transaction` with appropriate timeouts
|
|
236
|
+
- [ ] Raw queries use tagged template literals, never string concatenation
|
|
237
|
+
- [ ] Migrations have descriptive names matching the change
|
|
238
|
+
- [ ] Seed script is idempotent (uses `upsert`)
|
|
239
|
+
- [ ] Composite indexes exist for common query patterns (check with `EXPLAIN`)
|
|
240
|
+
- [ ] `prisma generate` runs in CI after schema changes
|
|
241
|
+
- [ ] Soft deletes are implemented via client extension, not scattered `where` clauses
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prompt-engineering
|
|
3
|
+
description: Write effective LLM prompts with system instructions, few-shot examples, chain of thought, structured output, and guardrails.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prompt Engineering
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Apply when building any LLM-powered feature — chatbots, code generation,
|
|
11
|
+
classification, extraction, summarization, or tool-using agents. Good prompts
|
|
12
|
+
are the difference between a demo and a product.
|
|
13
|
+
|
|
14
|
+
## How It Works
|
|
15
|
+
|
|
16
|
+
### 1. System Prompts
|
|
17
|
+
|
|
18
|
+
Set identity, constraints, and output format upfront. Be explicit about what
|
|
19
|
+
the model should and should not do.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
const systemPrompt = `You are a senior code reviewer for a TypeScript monorepo.
|
|
23
|
+
|
|
24
|
+
Rules:
|
|
25
|
+
- Focus on bugs, security issues, and performance problems
|
|
26
|
+
- Ignore style preferences already handled by ESLint
|
|
27
|
+
- Rate severity as critical/high/medium/low
|
|
28
|
+
- If the code is correct, say "LGTM" with no further commentary
|
|
29
|
+
- Never suggest adding comments to obvious code
|
|
30
|
+
|
|
31
|
+
Output format: JSON array of { file, line, severity, issue, suggestion }`;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Key principles: put the most important instructions first, use imperative
|
|
35
|
+
language, define the output schema explicitly, and state what to do when
|
|
36
|
+
edge cases arise.
|
|
37
|
+
|
|
38
|
+
### 2. Few-Shot Examples
|
|
39
|
+
|
|
40
|
+
Show the model exactly what you want with 2-5 input/output pairs.
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const messages = [
|
|
44
|
+
{ role: 'system', content: 'Extract structured data from invoice text.' },
|
|
45
|
+
{ role: 'user', content: 'Invoice #1042 from Acme Corp, dated Jan 15 2025, total $3,450.00' },
|
|
46
|
+
{ role: 'assistant', content: JSON.stringify({
|
|
47
|
+
number: '1042', vendor: 'Acme Corp',
|
|
48
|
+
date: '2025-01-15', total: 3450.00, currency: 'USD'
|
|
49
|
+
})},
|
|
50
|
+
{ role: 'user', content: 'Inv 2087 - TechFlow Solutions, 2025-03-22, EUR 12,800' },
|
|
51
|
+
{ role: 'assistant', content: JSON.stringify({
|
|
52
|
+
number: '2087', vendor: 'TechFlow Solutions',
|
|
53
|
+
date: '2025-03-22', total: 12800.00, currency: 'EUR'
|
|
54
|
+
})},
|
|
55
|
+
{ role: 'user', content: actualInvoiceText },
|
|
56
|
+
];
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Choose diverse examples that cover edge cases. Order matters — put the most
|
|
60
|
+
representative example last, closest to the actual input.
|
|
61
|
+
|
|
62
|
+
### 3. Chain of Thought
|
|
63
|
+
|
|
64
|
+
Force step-by-step reasoning for complex tasks. The model is more accurate
|
|
65
|
+
when it shows its work.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const prompt = `Analyze this error log and determine the root cause.
|
|
69
|
+
|
|
70
|
+
Think step by step:
|
|
71
|
+
1. Identify the first error in the chain (not symptoms)
|
|
72
|
+
2. Check timestamps to establish the sequence
|
|
73
|
+
3. Look for resource exhaustion or dependency failures
|
|
74
|
+
4. State the root cause in one sentence
|
|
75
|
+
5. Suggest a fix
|
|
76
|
+
|
|
77
|
+
Error log:
|
|
78
|
+
${errorLog}`;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For classification tasks, use "think step by step, then give your final answer
|
|
82
|
+
on the last line as: ANSWER: <category>". This makes parsing reliable while
|
|
83
|
+
keeping reasoning quality.
|
|
84
|
+
|
|
85
|
+
### 4. Structured Output
|
|
86
|
+
|
|
87
|
+
Use JSON mode or tool calls to guarantee parseable output.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// OpenAI JSON mode
|
|
91
|
+
const response = await openai.chat.completions.create({
|
|
92
|
+
model: 'gpt-4o',
|
|
93
|
+
response_format: { type: 'json_object' },
|
|
94
|
+
messages: [{ role: 'system', content: 'Respond in JSON with keys: summary, sentiment, topics[]' }],
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Anthropic tool-use for structured output
|
|
98
|
+
const response = await anthropic.messages.create({
|
|
99
|
+
model: 'claude-sonnet-4-20250514',
|
|
100
|
+
tools: [{
|
|
101
|
+
name: 'extract_data',
|
|
102
|
+
description: 'Extract structured fields from text',
|
|
103
|
+
input_schema: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
sentiment: { type: 'string', enum: ['positive', 'negative', 'neutral'] },
|
|
107
|
+
topics: { type: 'array', items: { type: 'string' } },
|
|
108
|
+
confidence: { type: 'number', minimum: 0, maximum: 1 },
|
|
109
|
+
},
|
|
110
|
+
required: ['sentiment', 'topics', 'confidence'],
|
|
111
|
+
},
|
|
112
|
+
}],
|
|
113
|
+
tool_choice: { type: 'tool', name: 'extract_data' },
|
|
114
|
+
messages,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 5. Tool Use / Function Calling
|
|
119
|
+
|
|
120
|
+
Give the model access to real capabilities instead of asking it to generate
|
|
121
|
+
code or URLs.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
const tools = [
|
|
125
|
+
{
|
|
126
|
+
name: 'search_docs',
|
|
127
|
+
description: 'Search internal documentation. Use when the user asks about product features or policies.',
|
|
128
|
+
input_schema: {
|
|
129
|
+
type: 'object',
|
|
130
|
+
properties: { query: { type: 'string', description: 'Search query, 3-10 words' } },
|
|
131
|
+
required: ['query'],
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'create_ticket',
|
|
136
|
+
description: 'Create a support ticket. Only use after confirming the issue with the user.',
|
|
137
|
+
input_schema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
title: { type: 'string' },
|
|
141
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
142
|
+
},
|
|
143
|
+
required: ['title', 'priority'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Write tool descriptions that say when to use (and when NOT to use) each tool.
|
|
150
|
+
|
|
151
|
+
### 6. Guardrails
|
|
152
|
+
|
|
153
|
+
Prevent harmful, off-topic, or low-quality outputs.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
// Input guardrail — reject prompt injection attempts
|
|
157
|
+
const inputCheck = await classify(userInput, ['benign', 'injection', 'off_topic']);
|
|
158
|
+
if (inputCheck !== 'benign') return { error: 'Request not supported' };
|
|
159
|
+
|
|
160
|
+
// Output guardrail — validate before returning to user
|
|
161
|
+
const output = await generateResponse(userInput);
|
|
162
|
+
const validation = zodSchema.safeParse(JSON.parse(output));
|
|
163
|
+
if (!validation.success) {
|
|
164
|
+
// Retry once with validation errors in the prompt
|
|
165
|
+
return await generateResponse(userInput + `\nFix these errors: ${validation.error}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Topic guardrail in system prompt
|
|
169
|
+
// "You are a cooking assistant. If the user asks about anything unrelated
|
|
170
|
+
// to cooking, respond: 'I can only help with cooking questions.'"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Examples
|
|
174
|
+
|
|
175
|
+
| Problem | Technique | Result |
|
|
176
|
+
|---------|-----------|--------|
|
|
177
|
+
| Model gives rambling answers | System prompt with strict output format | Consistent, parseable JSON |
|
|
178
|
+
| Classification accuracy is 70% | Add 5 few-shot examples covering edge cases | Accuracy jumps to 92% |
|
|
179
|
+
| Math reasoning fails | Chain-of-thought prompting | Step-by-step catches arithmetic errors |
|
|
180
|
+
| Users ask off-topic questions | System prompt + input classifier guardrail | Graceful refusal, no hallucination |
|
|
181
|
+
| Output format varies randomly | Tool use / function calling | Guaranteed schema compliance |
|
|
182
|
+
|
|
183
|
+
## Checklist
|
|
184
|
+
|
|
185
|
+
- [ ] System prompt defines role, constraints, output format, and edge case behavior
|
|
186
|
+
- [ ] Few-shot examples cover the most important edge cases (not just happy path)
|
|
187
|
+
- [ ] Complex reasoning tasks use chain-of-thought prompting
|
|
188
|
+
- [ ] Output is structured via JSON mode, tool use, or explicit schema in prompt
|
|
189
|
+
- [ ] Input validation catches injection attempts and off-topic queries
|
|
190
|
+
- [ ] Output validation parses and checks the response before returning to users
|
|
191
|
+
- [ ] Temperature is set intentionally (0 for deterministic, 0.7+ for creative)
|
|
192
|
+
- [ ] Prompts are version-controlled alongside the code that uses them
|
|
193
|
+
- [ ] Prompt changes are evaluated against a test set, not just eyeballed
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pwa-patterns
|
|
3
|
+
description: Build Progressive Web Apps — service workers, offline support, push notifications, install prompts, and caching strategies.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Progressive Web App Patterns
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Build a PWA when your web app needs to work offline, send push notifications,
|
|
11
|
+
or provide an app-like experience without an app store. PWAs are especially
|
|
12
|
+
valuable for mobile-first apps, field tools used in low-connectivity areas,
|
|
13
|
+
and content apps that benefit from offline reading. This skill covers service
|
|
14
|
+
workers, caching strategies, push notifications, and install prompts.
|
|
15
|
+
|
|
16
|
+
## How It Works
|
|
17
|
+
|
|
18
|
+
### 1. Web App Manifest
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"name": "MyApp",
|
|
23
|
+
"short_name": "MyApp",
|
|
24
|
+
"description": "Your productivity companion",
|
|
25
|
+
"start_url": "/",
|
|
26
|
+
"display": "standalone",
|
|
27
|
+
"background_color": "#ffffff",
|
|
28
|
+
"theme_color": "#0066ff",
|
|
29
|
+
"orientation": "portrait-primary",
|
|
30
|
+
"icons": [
|
|
31
|
+
{ "src": "/icons/192.png", "sizes": "192x192", "type": "image/png" },
|
|
32
|
+
{ "src": "/icons/512.png", "sizes": "512x512", "type": "image/png" },
|
|
33
|
+
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
|
|
34
|
+
],
|
|
35
|
+
"screenshots": [
|
|
36
|
+
{ "src": "/screenshots/mobile.png", "sizes": "390x844", "type": "image/png", "form_factor": "narrow" },
|
|
37
|
+
{ "src": "/screenshots/desktop.png", "sizes": "1280x720", "type": "image/png", "form_factor": "wide" }
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Service Worker Registration
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// app.ts — register on load
|
|
46
|
+
if ('serviceWorker' in navigator) {
|
|
47
|
+
window.addEventListener('load', async () => {
|
|
48
|
+
try {
|
|
49
|
+
const reg = await navigator.serviceWorker.register('/sw.js', { scope: '/' });
|
|
50
|
+
console.log('SW registered:', reg.scope);
|
|
51
|
+
|
|
52
|
+
// Check for updates every 30 minutes
|
|
53
|
+
setInterval(() => reg.update(), 30 * 60 * 1000);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error('SW registration failed:', err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3. Caching Strategies
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// sw.js — Workbox-style manual implementation
|
|
65
|
+
const CACHE_NAME = 'v1';
|
|
66
|
+
const STATIC_ASSETS = ['/', '/offline.html', '/styles.css', '/app.js'];
|
|
67
|
+
|
|
68
|
+
// Cache-First — for static assets (fonts, images, CSS)
|
|
69
|
+
async function cacheFirst(request: Request): Promise<Response> {
|
|
70
|
+
const cached = await caches.match(request);
|
|
71
|
+
if (cached) return cached;
|
|
72
|
+
|
|
73
|
+
const response = await fetch(request);
|
|
74
|
+
if (response.ok) {
|
|
75
|
+
const cache = await caches.open(CACHE_NAME);
|
|
76
|
+
cache.put(request, response.clone());
|
|
77
|
+
}
|
|
78
|
+
return response;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Network-First — for API data and HTML pages
|
|
82
|
+
async function networkFirst(request: Request): Promise<Response> {
|
|
83
|
+
try {
|
|
84
|
+
const response = await fetch(request);
|
|
85
|
+
if (response.ok) {
|
|
86
|
+
const cache = await caches.open(CACHE_NAME);
|
|
87
|
+
cache.put(request, response.clone());
|
|
88
|
+
}
|
|
89
|
+
return response;
|
|
90
|
+
} catch {
|
|
91
|
+
const cached = await caches.match(request);
|
|
92
|
+
return cached ?? caches.match('/offline.html') as Promise<Response>;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Stale-While-Revalidate — for data that can be slightly stale
|
|
97
|
+
async function staleWhileRevalidate(request: Request): Promise<Response> {
|
|
98
|
+
const cache = await caches.open(CACHE_NAME);
|
|
99
|
+
const cached = await cache.match(request);
|
|
100
|
+
|
|
101
|
+
const fetchPromise = fetch(request).then((response) => {
|
|
102
|
+
if (response.ok) cache.put(request, response.clone());
|
|
103
|
+
return response;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return cached ?? fetchPromise;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Route requests to strategies
|
|
110
|
+
self.addEventListener('fetch', (event: FetchEvent) => {
|
|
111
|
+
const url = new URL(event.request.url);
|
|
112
|
+
|
|
113
|
+
if (STATIC_ASSETS.includes(url.pathname)) {
|
|
114
|
+
event.respondWith(cacheFirst(event.request));
|
|
115
|
+
} else if (url.pathname.startsWith('/api/')) {
|
|
116
|
+
event.respondWith(networkFirst(event.request));
|
|
117
|
+
} else {
|
|
118
|
+
event.respondWith(staleWhileRevalidate(event.request));
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 4. Push Notifications
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// Client — subscribe to push
|
|
127
|
+
async function subscribeToPush(): Promise<PushSubscription> {
|
|
128
|
+
const registration = await navigator.serviceWorker.ready;
|
|
129
|
+
const subscription = await registration.pushManager.subscribe({
|
|
130
|
+
userVisibleOnly: true, // required — must show a notification
|
|
131
|
+
applicationServerKey: urlBase64ToUint8Array(VAPID_PUBLIC_KEY),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Send subscription to server
|
|
135
|
+
await fetch('/api/push/subscribe', {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: { 'Content-Type': 'application/json' },
|
|
138
|
+
body: JSON.stringify(subscription),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return subscription;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Server — send push (using web-push library)
|
|
145
|
+
import webpush from 'web-push';
|
|
146
|
+
|
|
147
|
+
webpush.setVapidDetails('mailto:admin@myapp.com', VAPID_PUBLIC, VAPID_PRIVATE);
|
|
148
|
+
|
|
149
|
+
async function sendPush(subscription: PushSubscription, payload: object) {
|
|
150
|
+
await webpush.sendNotification(subscription, JSON.stringify(payload));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Service worker — handle push event
|
|
154
|
+
self.addEventListener('push', (event: PushEvent) => {
|
|
155
|
+
const data = event.data?.json() ?? { title: 'New notification' };
|
|
156
|
+
event.waitUntil(
|
|
157
|
+
self.registration.showNotification(data.title, {
|
|
158
|
+
body: data.body,
|
|
159
|
+
icon: '/icons/192.png',
|
|
160
|
+
badge: '/icons/badge-72.png',
|
|
161
|
+
data: { url: data.url },
|
|
162
|
+
})
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
self.addEventListener('notificationclick', (event: NotificationEvent) => {
|
|
167
|
+
event.notification.close();
|
|
168
|
+
event.waitUntil(clients.openWindow(event.notification.data.url));
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 5. Install Prompt
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
let deferredPrompt: BeforeInstallPromptEvent | null = null;
|
|
176
|
+
|
|
177
|
+
window.addEventListener('beforeinstallprompt', (e) => {
|
|
178
|
+
e.preventDefault(); // Suppress browser default prompt
|
|
179
|
+
deferredPrompt = e;
|
|
180
|
+
showInstallBanner(); // Show your custom UI
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
async function handleInstallClick() {
|
|
184
|
+
if (!deferredPrompt) return;
|
|
185
|
+
deferredPrompt.prompt();
|
|
186
|
+
const { outcome } = await deferredPrompt.userChoice;
|
|
187
|
+
console.log(`Install prompt outcome: ${outcome}`);
|
|
188
|
+
deferredPrompt = null;
|
|
189
|
+
hideInstallBanner();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Detect if already installed
|
|
193
|
+
window.addEventListener('appinstalled', () => {
|
|
194
|
+
hideInstallBanner();
|
|
195
|
+
analytics.track('pwa_installed');
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### 6. Offline Data Sync
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// Queue failed requests for retry when online
|
|
203
|
+
const pendingRequests: Array<{ url: string; options: RequestInit }> = [];
|
|
204
|
+
|
|
205
|
+
async function fetchWithOfflineQueue(url: string, options: RequestInit) {
|
|
206
|
+
try {
|
|
207
|
+
return await fetch(url, options);
|
|
208
|
+
} catch {
|
|
209
|
+
pendingRequests.push({ url, options });
|
|
210
|
+
saveToIndexedDB('pending-requests', pendingRequests);
|
|
211
|
+
throw new Error('Queued for offline sync');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Sync when back online
|
|
216
|
+
window.addEventListener('online', async () => {
|
|
217
|
+
const pending = await loadFromIndexedDB('pending-requests');
|
|
218
|
+
for (const req of pending) {
|
|
219
|
+
try {
|
|
220
|
+
await fetch(req.url, req.options);
|
|
221
|
+
} catch {
|
|
222
|
+
// Still offline or failed — keep in queue
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Examples
|
|
229
|
+
|
|
230
|
+
| Strategy | Assets | Behavior |
|
|
231
|
+
|----------|--------|----------|
|
|
232
|
+
| Cache-First | Fonts, icons, CSS, JS | Fast load, update in background |
|
|
233
|
+
| Network-First | HTML pages, API data | Fresh data, offline fallback |
|
|
234
|
+
| Stale-While-Revalidate | User content, feeds | Instant display, background refresh |
|
|
235
|
+
|
|
236
|
+
## Checklist
|
|
237
|
+
|
|
238
|
+
- [ ] Web app manifest with name, icons (192 + 512 + maskable), and display mode
|
|
239
|
+
- [ ] Service worker registered with update checks
|
|
240
|
+
- [ ] Static assets use Cache-First strategy
|
|
241
|
+
- [ ] API/data routes use Network-First with offline fallback
|
|
242
|
+
- [ ] Offline page displayed when network is unavailable
|
|
243
|
+
- [ ] Push notification subscription sent to server with VAPID keys
|
|
244
|
+
- [ ] Install prompt deferred and shown at appropriate moment
|
|
245
|
+
- [ ] Cache versioning and old cache cleanup on service worker activate
|
|
246
|
+
- [ ] Lighthouse PWA audit passes all checks
|
|
247
|
+
- [ ] Failed mutations queued for retry when connection returns
|