@dv.nghiem/flowdeck 0.2.4 → 0.3.1
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/README.md +24 -41
- package/dist/hooks/approval-hook.d.ts +6 -0
- package/dist/hooks/approval-hook.d.ts.map +1 -1
- package/dist/hooks/guard-rails.d.ts +0 -8
- package/dist/hooks/guard-rails.d.ts.map +1 -1
- package/dist/hooks/memory-hook.d.ts +21 -0
- package/dist/hooks/memory-hook.d.ts.map +1 -0
- package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
- package/dist/hooks/patch-trust.d.ts.map +1 -1
- package/dist/hooks/todo-hook.d.ts +1 -7
- package/dist/hooks/todo-hook.d.ts.map +1 -1
- package/dist/hooks/tool-guard.d.ts +1 -0
- package/dist/hooks/tool-guard.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +728 -428
- package/dist/services/memory-store.d.ts +40 -0
- package/dist/services/memory-store.d.ts.map +1 -0
- package/dist/services/policy-compiler.d.ts.map +1 -1
- package/dist/tools/memory-search.d.ts +3 -0
- package/dist/tools/memory-search.d.ts.map +1 -0
- package/docs/commands/fd-doctor.md +21 -0
- package/docs/commands/fd-quick.md +33 -0
- package/docs/commands/fd-reflect.md +23 -0
- package/docs/commands/fd-status.md +31 -0
- package/docs/commands/fd-translate-intent.md +17 -0
- package/docs/commands.md +209 -271
- package/docs/configuration.md +5 -2
- package/docs/index.md +22 -28
- package/docs/memory.md +69 -0
- package/docs/quick-start.md +1 -1
- package/package.json +1 -1
- package/src/commands/fd-deploy-check.md +131 -11
- package/src/commands/fd-new-project.md +14 -1
- package/src/commands/fd-quick.md +60 -0
- package/src/commands/fd-reflect.md +41 -2
- package/src/commands/fd-status.md +84 -0
- package/src/rules/README.md +8 -7
- package/src/skills/agent-harness-construction/SKILL.md +227 -0
- package/src/skills/api-design/SKILL.md +5 -0
- package/src/skills/backend-patterns/SKILL.md +105 -0
- package/src/skills/clean-architecture/SKILL.md +85 -0
- package/src/skills/cqrs/SKILL.md +230 -0
- package/src/skills/ddd-architecture/SKILL.md +104 -0
- package/src/skills/django-patterns/SKILL.md +304 -0
- package/src/skills/django-tdd/SKILL.md +297 -0
- package/src/skills/event-driven-architecture/SKILL.md +152 -0
- package/src/skills/frontend-pattern/SKILL.md +159 -0
- package/src/skills/hexagonal-architecture/SKILL.md +80 -0
- package/src/skills/layered-architecture/SKILL.md +64 -0
- package/src/skills/postgres-patterns/SKILL.md +74 -0
- package/src/skills/python-patterns/SKILL.md +5 -0
- package/src/skills/saga-architecture/SKILL.md +113 -0
- package/dist/tools/run-parallel.d.ts +0 -4
- package/dist/tools/run-parallel.d.ts.map +0 -1
- package/docs/command-migration.md +0 -175
- package/docs/commands/fd-analyze-change.md +0 -107
- package/docs/commands/fd-dashboard.md +0 -11
- package/docs/commands/fd-evaluate-risk.md +0 -134
- package/docs/commands/fd-guarded-edit.md +0 -105
- package/docs/commands/fd-progress.md +0 -11
- package/docs/commands/fd-review-code.md +0 -29
- package/docs/commands/fd-roadmap.md +0 -10
- package/docs/commands/fd-settings.md +0 -10
- package/docs/parallel-execution.md +0 -255
- package/src/commands/fd-analyze-change.md +0 -57
- package/src/commands/fd-approve.md +0 -64
- package/src/commands/fd-blast-radius.md +0 -49
- package/src/commands/fd-dashboard.md +0 -57
- package/src/commands/fd-evaluate-risk.md +0 -62
- package/src/commands/fd-guarded-edit.md +0 -69
- package/src/commands/fd-impact-radar.md +0 -51
- package/src/commands/fd-learn.md +0 -36
- package/src/commands/fd-progress.md +0 -50
- package/src/commands/fd-regression-predict.md +0 -57
- package/src/commands/fd-review-code.md +0 -96
- package/src/commands/fd-review-route.md +0 -54
- package/src/commands/fd-roadmap.md +0 -46
- package/src/commands/fd-settings.md +0 -57
- package/src/commands/fd-test-gap.md +0 -54
- package/src/commands/fd-volatility-map.md +0 -64
- package/src/commands/fd-workspace-status.md +0 -34
- package/src/skills/parallel-execute/SKILL.md +0 -92
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Event-Driven Architecture
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
|
|
5
|
+
Activate when:
|
|
6
|
+
- Designing or implementing message-based communication between services
|
|
7
|
+
- Building systems that require asynchronous processing
|
|
8
|
+
- Decoupling producers from consumers in distributed systems
|
|
9
|
+
- Implementing event sourcing or audit trails
|
|
10
|
+
- Setting up webhooks, message queues, or pub/sub patterns
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Identify Event Boundaries
|
|
15
|
+
|
|
16
|
+
Define what constitutes an "event" in your system:
|
|
17
|
+
- Events are **facts** about something that happened (past tense: `OrderPlaced`, `PaymentProcessed`)
|
|
18
|
+
- Commands are **requests** for an action (present tense: `PlaceOrder`, `ProcessPayment`)
|
|
19
|
+
- Events should be **immutable** once emitted
|
|
20
|
+
|
|
21
|
+
### 2. Choose the Right Messaging Pattern
|
|
22
|
+
|
|
23
|
+
| Pattern | Use Case | Examples |
|
|
24
|
+
|---------|----------|----------|
|
|
25
|
+
| Pub/Sub | One-to-many notification | Notifications, audit logs |
|
|
26
|
+
| Message Queue | Point-to-point processing | Order processing, email sending |
|
|
27
|
+
| Event Streaming | Durable, replayable event log | Event sourcing, analytics |
|
|
28
|
+
| Webhooks | External system integration | HTTP callbacks |
|
|
29
|
+
|
|
30
|
+
### 3. Design Event Schema
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
interface Event<T> {
|
|
34
|
+
id: string; // Unique event identifier (UUID)
|
|
35
|
+
type: string; // Event type (e.g., "ORDER_PLACED")
|
|
36
|
+
version: string; // Schema version for evolution
|
|
37
|
+
timestamp: string; // ISO 8601 timestamp
|
|
38
|
+
source: string; // Origin service name
|
|
39
|
+
data: T; // Event payload
|
|
40
|
+
metadata?: Record<string, unknown>; // Optional tracing/correlation
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 4. Handle Eventual Consistency
|
|
45
|
+
|
|
46
|
+
- Design consumers to be **idempotent** (safe to process twice)
|
|
47
|
+
- Use **correlation IDs** to track event chains
|
|
48
|
+
- Implement **dead letter queues** for failed processing
|
|
49
|
+
- Set **retry policies** with exponential backoff
|
|
50
|
+
|
|
51
|
+
### 5. Ensure Durability
|
|
52
|
+
|
|
53
|
+
- Use persistent message storage (not in-memory)
|
|
54
|
+
- Acknowledge messages only after successful processing
|
|
55
|
+
- Implement **at-least-once** delivery semantics
|
|
56
|
+
|
|
57
|
+
## Examples
|
|
58
|
+
|
|
59
|
+
### TypeScript Event Emitter
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
interface OrderEvent {
|
|
63
|
+
orderId: string;
|
|
64
|
+
customerId: string;
|
|
65
|
+
total: number;
|
|
66
|
+
items: OrderItem[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class OrderEventPublisher {
|
|
70
|
+
private emitter: EventEmitter;
|
|
71
|
+
|
|
72
|
+
async publishOrderPlaced(event: OrderEvent): Promise<void> {
|
|
73
|
+
const message: Event<OrderEvent> = {
|
|
74
|
+
id: crypto.randomUUID(),
|
|
75
|
+
type: 'ORDER_PLACED',
|
|
76
|
+
version: '1.0',
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
source: 'order-service',
|
|
79
|
+
data: event,
|
|
80
|
+
metadata: {
|
|
81
|
+
correlationId: event.orderId,
|
|
82
|
+
partitionKey: event.customerId
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
await this.messageBroker.publish('orders.placed', message);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Message Consumer with Retry
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
class OrderEventConsumer {
|
|
95
|
+
async handleOrderPlaced(event: Event<OrderEvent>): Promise<void> {
|
|
96
|
+
try {
|
|
97
|
+
// Idempotent processing
|
|
98
|
+
const existingOrder = await this.orderRepo.findById(event.data.orderId);
|
|
99
|
+
if (existingOrder) {
|
|
100
|
+
logger.info('Order already processed, skipping', { orderId: event.data.orderId });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await this.orderService.processOrder(event.data);
|
|
105
|
+
await this.messageBroker.ack(event.id);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
if (error instanceof TransientError) {
|
|
108
|
+
// Requeue with delay for retry
|
|
109
|
+
await this.messageBroker.requeue(event.id, { delay: 5000 });
|
|
110
|
+
} else {
|
|
111
|
+
// Send to dead letter queue
|
|
112
|
+
await this.messageBroker.sendToDlq(event, error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Event Schema Registry
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// contracts/order-events.ts
|
|
123
|
+
export const OrderPlacedEventSchema = {
|
|
124
|
+
type: 'object',
|
|
125
|
+
required: ['orderId', 'customerId', 'total', 'items'],
|
|
126
|
+
properties: {
|
|
127
|
+
orderId: { type: 'string', format: 'uuid' },
|
|
128
|
+
customerId: { type: 'string', format: 'uuid' },
|
|
129
|
+
total: { type: 'number', minimum: 0 },
|
|
130
|
+
items: {
|
|
131
|
+
type: 'array',
|
|
132
|
+
items: {
|
|
133
|
+
type: 'object',
|
|
134
|
+
required: ['productId', 'quantity', 'price'],
|
|
135
|
+
properties: {
|
|
136
|
+
productId: { type: 'string' },
|
|
137
|
+
quantity: { type: 'number', minimum: 1 },
|
|
138
|
+
price: { type: 'number', minimum: 0 }
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Related Skills
|
|
147
|
+
|
|
148
|
+
- api-design
|
|
149
|
+
- backend-patterns
|
|
150
|
+
- cqrs
|
|
151
|
+
- event-sourcing
|
|
152
|
+
- message-queues
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: frontend-pattern
|
|
3
|
+
description: Frontend development patterns — component composition, state management, URL-as-state, data fetching, and animation best practices for React/TypeScript web applications
|
|
4
|
+
origin: FlowDeck
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Frontend Pattern Skill
|
|
8
|
+
|
|
9
|
+
Implements maintainable, performant frontend patterns using React and TypeScript.
|
|
10
|
+
|
|
11
|
+
## When to Activate
|
|
12
|
+
|
|
13
|
+
Activate when:
|
|
14
|
+
- Building new UI components
|
|
15
|
+
- Setting up state management
|
|
16
|
+
- Implementing data fetching
|
|
17
|
+
- Adding animations or transitions
|
|
18
|
+
- Structuring a new feature module
|
|
19
|
+
|
|
20
|
+
## Component Patterns
|
|
21
|
+
|
|
22
|
+
### Compound Components
|
|
23
|
+
|
|
24
|
+
Use compound components when related UI shares state and interaction semantics:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<Tabs defaultValue="overview">
|
|
28
|
+
<Tabs.List>
|
|
29
|
+
<Tabs.Trigger value="overview">Overview</Tabs.Trigger>
|
|
30
|
+
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
|
31
|
+
</Tabs.List>
|
|
32
|
+
<Tabs.Content value="overview">...</Tabs.Content>
|
|
33
|
+
<Tabs.Content value="settings">...</Tabs.Content>
|
|
34
|
+
</Tabs>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Parent owns state via `useState` or context
|
|
38
|
+
- Children consume via context — no prop drilling
|
|
39
|
+
- Keeps keyboard handling, ARIA, and focus logic in the headless layer
|
|
40
|
+
|
|
41
|
+
### Container / Presentational Split
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Container — owns data loading and side effects
|
|
45
|
+
function UserProfileContainer({ userId }: { userId: string }) {
|
|
46
|
+
const { data, isLoading } = useUser(userId);
|
|
47
|
+
if (isLoading) return <Skeleton />;
|
|
48
|
+
return <UserProfileView user={data} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Presentational — receives props, renders UI
|
|
52
|
+
function UserProfileView({ user }: { user: User }) {
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
<Avatar src={user.avatar} />
|
|
56
|
+
<h1>{user.name}</h1>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## State Management
|
|
63
|
+
|
|
64
|
+
| Concern | Tooling |
|
|
65
|
+
|---------|---------|
|
|
66
|
+
| Server state | TanStack Query, SWR, tRPC |
|
|
67
|
+
| Client state | Zustand, Jotai, signals |
|
|
68
|
+
| URL state | search params, route segments |
|
|
69
|
+
| Form state | React Hook Form or equivalent |
|
|
70
|
+
|
|
71
|
+
**Do not duplicate server state into client stores.** Derive values instead of storing redundant computed state.
|
|
72
|
+
|
|
73
|
+
## URL As State
|
|
74
|
+
|
|
75
|
+
Persist shareable, bookmarkable state in the URL:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// Good: filters, sort, pagination in URL
|
|
79
|
+
const [searchParams, setSearchParams] = useSearchParams();
|
|
80
|
+
const filter = searchParams.get('filter') ?? 'all';
|
|
81
|
+
|
|
82
|
+
// Usage
|
|
83
|
+
<button onClick={() => setSearchParams({ filter: 'active' })}>
|
|
84
|
+
Active
|
|
85
|
+
</button>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Data Fetching Patterns
|
|
89
|
+
|
|
90
|
+
### Stale-While-Revalidate
|
|
91
|
+
|
|
92
|
+
Return cached data immediately, revalidate in background:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const { data } = useQuery({
|
|
96
|
+
queryKey: ['users', userId],
|
|
97
|
+
queryFn: () => fetchUser(userId),
|
|
98
|
+
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Optimistic Updates
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const mutation = useMutation({
|
|
106
|
+
mutationFn: updateUser,
|
|
107
|
+
onMutate: async (newData) => {
|
|
108
|
+
await queryClient.cancelQueries({ queryKey: ['user', newData.id] });
|
|
109
|
+
const previous = queryClient.getQueryData(['user', newData.id]);
|
|
110
|
+
queryClient.setQueryData(['user', newData.id], newData);
|
|
111
|
+
return { previous };
|
|
112
|
+
},
|
|
113
|
+
onError: (err, newData, context) => {
|
|
114
|
+
queryClient.setQueryData(['user', newData.id], context.previous);
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## CSS Custom Properties
|
|
120
|
+
|
|
121
|
+
Define design tokens as CSS variables — do not hardcode values:
|
|
122
|
+
|
|
123
|
+
```css
|
|
124
|
+
:root {
|
|
125
|
+
--color-surface: oklch(98% 0 0);
|
|
126
|
+
--color-text: oklch(18% 0 0);
|
|
127
|
+
--color-accent: oklch(68% 0.21 250);
|
|
128
|
+
|
|
129
|
+
--text-base: clamp(1rem, 0.92rem + 0.4vw, 1.125rem);
|
|
130
|
+
--space-section: clamp(4rem, 3rem + 5vw, 10rem);
|
|
131
|
+
|
|
132
|
+
--duration-fast: 150ms;
|
|
133
|
+
--duration-normal: 300ms;
|
|
134
|
+
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Animation Guidelines
|
|
139
|
+
|
|
140
|
+
Use compositor-friendly properties only:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
✅ transform, opacity, clip-path, filter
|
|
144
|
+
❌ width, height, top, left, margin, padding, border, font-size
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
// Good
|
|
149
|
+
const style = { opacity: isVisible ? 1 : 0, transform: `translateY(${isVisible ? 0 : 20}px)` };
|
|
150
|
+
|
|
151
|
+
// Bad
|
|
152
|
+
const style = { height: isVisible ? 'auto' : 0 };
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Related Skills
|
|
156
|
+
|
|
157
|
+
- [code-review](code-review) — Review frontend code for quality
|
|
158
|
+
- [security-scan](security-scan) — Check for XSS and injection vulnerabilities
|
|
159
|
+
- [test-coverage](test-coverage) — Ensure UI component tests exist
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# hexagonal-architecture
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
When building applications that must remain flexible to changing external systems (databases, APIs, UI frameworks) and need to support multiple entry points (ports) for the same business logic.
|
|
5
|
+
|
|
6
|
+
## Steps
|
|
7
|
+
1. **Identify the core domain** - Isolate the pure business logic that makes no references to infrastructure.
|
|
8
|
+
2. **Define inbound ports** - Create interfaces (ports) for primary/ driving actors (UI, API controllers) that trigger application logic.
|
|
9
|
+
3. **Define outbound ports** - Create interfaces (ports) for secondary/ driven actors (databases, external services) that the domain calls.
|
|
10
|
+
4. **Implement primary adapters** - Create adapters for inbound traffic (REST controllers, GraphQL resolvers, CLI commands).
|
|
11
|
+
5. **Implement secondary adapters** - Create adapters for outbound traffic (Postgres repositories, Redis caches, email gateways).
|
|
12
|
+
6. **Ensure domain has no external dependencies** - The domain layer should compile and run with no imports from adapters.
|
|
13
|
+
7. **Wire via dependency injection** - Connect adapters to ports at application startup.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
```typescript
|
|
17
|
+
// Domain Core - Pure business logic, no infrastructure dependencies
|
|
18
|
+
class Transfer {
|
|
19
|
+
constructor(
|
|
20
|
+
public readonly fromAccountId: string,
|
|
21
|
+
public readonly toAccountId: string,
|
|
22
|
+
public readonly amount: number
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
execute(accounts: Map<string, Account>): TransferResult {
|
|
26
|
+
const from = accounts.get(this.fromAccountId)
|
|
27
|
+
const to = accounts.get(this.toAccountId)
|
|
28
|
+
|
|
29
|
+
if (!from || !to) {
|
|
30
|
+
return TransferResult.failed('Account not found')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!from.canDebit(this.amount)) {
|
|
34
|
+
return TransferResult.failed('Insufficient funds')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
from.debit(this.amount)
|
|
38
|
+
to.credit(this.amount)
|
|
39
|
+
|
|
40
|
+
return TransferResult.success()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Inbound Port (Primary Port) - Interface for driving operations
|
|
45
|
+
interface TransferUseCase {
|
|
46
|
+
execute(transfer: Transfer): TransferResult
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Outbound Port (Secondary Port) - Interface for driven operations
|
|
50
|
+
interface AccountRepository {
|
|
51
|
+
findById(id: string): Promise<Account | null>
|
|
52
|
+
save(account: Account): Promise<void>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface EventBus {
|
|
56
|
+
publish(event: DomainEvent): Promise<void>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Primary Adapter - REST API
|
|
60
|
+
class TransferController implements TransferUseCase {
|
|
61
|
+
constructor(private readonly accounts: AccountRepository) {}
|
|
62
|
+
|
|
63
|
+
async execute(transfer: Transfer): Promise<TransferResult> {
|
|
64
|
+
const allAccounts = await this.accounts.findById(transfer.fromAccountId)
|
|
65
|
+
// ... handle via injected port
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Secondary Adapter - PostgreSQL implementation
|
|
70
|
+
class PostgresAccountRepository implements AccountRepository {
|
|
71
|
+
constructor(private readonly db: Database) {}
|
|
72
|
+
// ... implementation
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Related Skills
|
|
77
|
+
- clean-architecture
|
|
78
|
+
- layered-architecture
|
|
79
|
+
- ddd-architecture
|
|
80
|
+
- backend-patterns
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# layered-architecture
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
When building traditional monolithic or client-server applications where clear vertical separation of concerns improves maintainability (e.g., MVC applications, REST APIs, data-driven apps).
|
|
5
|
+
|
|
6
|
+
## Steps
|
|
7
|
+
1. **Identify natural layers** - Determine the distinct vertical tiers based on responsibility (e.g., presentation, business logic, data access).
|
|
8
|
+
2. **Define layer responsibilities** - Establish clear contracts for what each layer can and cannot depend on.
|
|
9
|
+
3. **Implement top-down dependencies** - Higher layers (presentation) depend on lower layers (data), but never vice versa.
|
|
10
|
+
4. **Create layer interfaces** - Use interfaces or abstract classes to define how adjacent layers communicate.
|
|
11
|
+
5. **Enforce layer access rules** - Use module visibility, package private, or architectural linting tools to prevent cross-layer pollution.
|
|
12
|
+
6. **Keep thin layers** - Avoid bloating any single layer; if the business logic layer grows large, consider extracting domain objects.
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
```typescript
|
|
16
|
+
// Presentation Layer - Controllers/Handlers
|
|
17
|
+
class OrderController {
|
|
18
|
+
constructor(private readonly orderService: OrderService) {}
|
|
19
|
+
|
|
20
|
+
async createOrder(req: Request, res: Response): Promise<void> {
|
|
21
|
+
const order = await this.orderService.createOrder(req.body)
|
|
22
|
+
res.status(201).json(order)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Business Logic Layer - Services
|
|
27
|
+
class OrderService {
|
|
28
|
+
constructor(
|
|
29
|
+
private readonly orderRepository: OrderRepository,
|
|
30
|
+
private readonly paymentGateway: PaymentGateway
|
|
31
|
+
) {}
|
|
32
|
+
|
|
33
|
+
async createOrder(data: CreateOrderDto): Promise<Order> {
|
|
34
|
+
const order = new Order(data.items)
|
|
35
|
+
|
|
36
|
+
if (data.paymentMethod === 'prepaid') {
|
|
37
|
+
await this.paymentGateway.charge(order.total, data.paymentToken)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this.orderRepository.save(order)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Data Access Layer - Repositories
|
|
45
|
+
interface OrderRepository {
|
|
46
|
+
save(order: Order): Promise<void>
|
|
47
|
+
findById(id: string): Promise<Order | null>
|
|
48
|
+
findByCustomer(customerId: string): Promise<Order[]>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class PostgresOrderRepository implements OrderRepository {
|
|
52
|
+
constructor(private readonly db: Database) {}
|
|
53
|
+
|
|
54
|
+
async save(order: Order): Promise<void> {
|
|
55
|
+
await this.db.query('INSERT INTO orders (...) VALUES (...)', order.toDbFormat())
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Related Skills
|
|
61
|
+
- clean-architecture
|
|
62
|
+
- hexagonal-architecture
|
|
63
|
+
- ddd-architecture
|
|
64
|
+
- backend-patterns
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# postgres-patterns
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
When designing database schemas, writing complex queries, or optimizing database performance. Use before creating migrations or writing SQL queries.
|
|
5
|
+
|
|
6
|
+
## Steps
|
|
7
|
+
1. **Design indexes strategically** - Create individual btree indexes on each column for multi-column searches (allows BitmapAnd)
|
|
8
|
+
2. **Use EXPLAIN ANALYZE** - Always verify query plans before and after optimization
|
|
9
|
+
3. **Choose correct index type** - B-tree for equality/range, Bloom for multi-column filters with high selectivity
|
|
10
|
+
4. **Avoid multi-column btree on non-leading columns** - Queries on non-first columns of multi-column btree indexes will do sequential scans
|
|
11
|
+
5. **Use parameterized queries** - Let the planner cache and reuse query plans
|
|
12
|
+
6. **Run ANALYZE regularly** - Keep statistics fresh for optimal planner decisions
|
|
13
|
+
|
|
14
|
+
## Examples
|
|
15
|
+
|
|
16
|
+
```sql
|
|
17
|
+
-- AVOID: Multi-column btree for non-leading column queries
|
|
18
|
+
CREATE INDEX btreeidx ON tbloom (i1, i2, i3, i4, i5, i6);
|
|
19
|
+
-- Query on i2 and i5 will do sequential scan, not use the index
|
|
20
|
+
|
|
21
|
+
-- PREFER: Individual btree indexes for multi-column searches
|
|
22
|
+
CREATE INDEX btreeidx1 ON tbloom (i1);
|
|
23
|
+
CREATE INDEX btreeidx2 ON tbloom (i2);
|
|
24
|
+
CREATE INDEX btreeidx3 ON tbloom (i3);
|
|
25
|
+
CREATE INDEX btreeidx4 ON tbloom (i4);
|
|
26
|
+
CREATE INDEX btreeidx5 ON tbloom (i5);
|
|
27
|
+
CREATE INDEX btreeidx6 ON tbloom (i6);
|
|
28
|
+
-- Bitmap Index Scan with BitmapAnd is used for multi-column queries
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
-- Bloom Index for multi-column filtering (good for low selectivity columns)
|
|
33
|
+
CREATE INDEX bloomidx ON tbloom USING bloom (i1, i2, i3, i4, i5, i6);
|
|
34
|
+
-- More efficient than btree for queries filtering on many columns
|
|
35
|
+
-- Smaller index size, faster Bitmap Index Scans
|
|
36
|
+
|
|
37
|
+
-- Always verify with EXPLAIN ANALYZE
|
|
38
|
+
EXPLAIN ANALYZE SELECT * FROM tbloom WHERE i2 = 898732 AND i5 = 123451;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
-- Query Planner Configuration (temporary fix only)
|
|
43
|
+
SET enable_hashjoin = off; -- Force nested-loop or merge join
|
|
44
|
+
SET enable_seqscan = off; -- Prefer index scans
|
|
45
|
+
SET random_page_cost = 1.1; -- Make index scans cheaper (SSD)
|
|
46
|
+
SET effective_cache_size = '8GB'; -- Help planner estimate
|
|
47
|
+
|
|
48
|
+
-- Better approaches:
|
|
49
|
+
-- 1. Run ANALYZE to update statistics
|
|
50
|
+
ANALYZE;
|
|
51
|
+
-- 2. Increase statistics for specific columns
|
|
52
|
+
ALTER TABLE orders SET STATISTICS = 500;
|
|
53
|
+
ANALYZE orders;
|
|
54
|
+
-- 3. Adjust planner cost constants (postgresql.conf)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- Repository Pattern in SQL
|
|
59
|
+
-- Define standard operations
|
|
60
|
+
interface OrderRepository {
|
|
61
|
+
findAll(filter: OrderFilter, pagination: Pagination): Promise<Order[]>;
|
|
62
|
+
findById(id: string): Promise<Order | null>;
|
|
63
|
+
create(order: CreateOrderDTO): Promise<Order>;
|
|
64
|
+
update(id: string, attributes: UpdateOrderDTO): Promise<Order>;
|
|
65
|
+
delete(id: string): Promise<void>;
|
|
66
|
+
count(filter?: OrderFilter): Promise<number>;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Related Skills
|
|
71
|
+
- api-design
|
|
72
|
+
- backend-patterns
|
|
73
|
+
- database-migrations
|
|
74
|
+
- postgres-performance
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# saga-architecture
|
|
2
|
+
|
|
3
|
+
## When to Activate
|
|
4
|
+
When coordinating distributed operations across multiple services or data stores where ACID transactions are not available and compensating actions are needed to maintain eventual consistency.
|
|
5
|
+
|
|
6
|
+
## Steps
|
|
7
|
+
1. **Identify the saga participants** - Determine which services or components participate in the distributed operation.
|
|
8
|
+
2. **Define the saga choreography or orchestration** - Choose whether sagas will be choreographed (event-driven) or orchestrated (central coordinator).
|
|
9
|
+
3. **Define each step with corresponding compensation** - For every forward action, define what compensating action undoes it.
|
|
10
|
+
4. **Implement idempotent operations** - Ensure each step can be safely retried and compensation can be safely reapplied.
|
|
11
|
+
5. **Handle saga failures with compensation** - On failure, execute compensations in reverse order (for orchestrating sagas) or react to failure events (for choreographing sagas).
|
|
12
|
+
6. **Persist saga state** - Store saga state to survive process crashes and enable recovery.
|
|
13
|
+
7. **Add timeout and retry logic** - Detect stuck sagas and advance or compensate accordingly.
|
|
14
|
+
|
|
15
|
+
## Examples
|
|
16
|
+
```typescript
|
|
17
|
+
// Saga State
|
|
18
|
+
interface SagaState<T> {
|
|
19
|
+
id: string
|
|
20
|
+
currentStep: number
|
|
21
|
+
data: T
|
|
22
|
+
status: 'pending' | 'in_progress' | 'completed' | 'compensating' | 'failed'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Orchestrating Saga - Central coordinator manages steps
|
|
26
|
+
class OrderProcessingSaga {
|
|
27
|
+
private readonly steps: SagaStep[]
|
|
28
|
+
|
|
29
|
+
constructor(
|
|
30
|
+
private readonly sagaOrchestrator: SagaOrchestrator,
|
|
31
|
+
private readonly inventoryService: InventoryService,
|
|
32
|
+
private readonly paymentService: PaymentService,
|
|
33
|
+
private readonly shippingService: ShippingService
|
|
34
|
+
) {
|
|
35
|
+
this.steps = [
|
|
36
|
+
{
|
|
37
|
+
name: 'reserve_inventory',
|
|
38
|
+
execute: (state) => this.inventoryService.reserve(state.orderId, state.items),
|
|
39
|
+
compensate: (state) => this.inventoryService.release(state.orderId, state.items)
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'process_payment',
|
|
43
|
+
execute: (state) => this.paymentService.charge(state.orderId, state.total),
|
|
44
|
+
compensate: (state) => this.paymentService.refund(state.orderId, state.total)
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'initiate_shipping',
|
|
48
|
+
execute: (state) => this.shippingService.createShipment(state.orderId),
|
|
49
|
+
compensate: (state) => this.shippingService.cancelShipment(state.shipmentId)
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async execute(orderId: string): Promise<void> {
|
|
55
|
+
const state: SagaState<OrderSagaData> = {
|
|
56
|
+
id: generateId(),
|
|
57
|
+
currentStep: 0,
|
|
58
|
+
data: { orderId, items: [], total: 0 },
|
|
59
|
+
status: 'in_progress'
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await this.sagaOrchestrator.start(state, this.steps)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Choreography-based Saga - Events trigger reactions
|
|
67
|
+
class OrderCreatedHandler {
|
|
68
|
+
constructor(private readonly eventBus: EventBus) {}
|
|
69
|
+
|
|
70
|
+
async handle(event: OrderCreatedEvent): Promise<void> {
|
|
71
|
+
// Step 1: Reserve inventory
|
|
72
|
+
try {
|
|
73
|
+
await this.inventoryService.reserve(event.orderId, event.items)
|
|
74
|
+
this.eventBus.publish(new InventoryReservedEvent(event.orderId))
|
|
75
|
+
} catch (error) {
|
|
76
|
+
this.eventBus.publish(new InventoryReservationFailedEvent(event.orderId, error.message))
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
class InventoryReservedHandler {
|
|
82
|
+
async handle(event: InventoryReservedEvent): Promise<void> {
|
|
83
|
+
// Step 2: Process payment
|
|
84
|
+
try {
|
|
85
|
+
await this.paymentService.charge(event.orderId, event.total)
|
|
86
|
+
this.eventBus.publish(new PaymentProcessedEvent(event.orderId))
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Compensate by releasing inventory
|
|
89
|
+
this.eventBus.publish(new InventoryReleaseRequestedEvent(event.orderId))
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Idempotent Step Implementation
|
|
95
|
+
class PaymentService {
|
|
96
|
+
async charge(orderId: string, amount: Money): Promise<TransactionId> {
|
|
97
|
+
const existingTx = await this.transactionRepo.findByOrderId(orderId)
|
|
98
|
+
if (existingTx) {
|
|
99
|
+
return existingTx.id // Idempotent: return existing instead of charging again
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const transaction = await this.paymentGateway.charge(amount)
|
|
103
|
+
await this.transactionRepo.save({ orderId, transaction })
|
|
104
|
+
return transaction.id
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Related Skills
|
|
110
|
+
- clean-architecture
|
|
111
|
+
- hexagonal-architecture
|
|
112
|
+
- ddd-architecture
|
|
113
|
+
- backend-patterns
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"run-parallel.d.ts","sourceRoot":"","sources":["../../src/tools/run-parallel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAsBtD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CAiK5E"}
|