@prmichaelsen/acp-visualizer 0.1.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/README.md +68 -0
- package/agent/commands/acp.clarification-address.md +417 -0
- package/agent/commands/acp.clarification-capture.md +386 -0
- package/agent/commands/acp.clarification-create.md +437 -0
- package/agent/commands/acp.clarifications-research.md +326 -0
- package/agent/commands/acp.command-create.md +432 -0
- package/agent/commands/acp.design-create.md +286 -0
- package/agent/commands/acp.design-reference.md +355 -0
- package/agent/commands/acp.handoff.md +270 -0
- package/agent/commands/acp.index.md +423 -0
- package/agent/commands/acp.init.md +546 -0
- package/agent/commands/acp.package-create.md +895 -0
- package/agent/commands/acp.package-info.md +212 -0
- package/agent/commands/acp.package-install.md +539 -0
- package/agent/commands/acp.package-list.md +280 -0
- package/agent/commands/acp.package-publish.md +541 -0
- package/agent/commands/acp.package-remove.md +293 -0
- package/agent/commands/acp.package-search.md +307 -0
- package/agent/commands/acp.package-update.md +361 -0
- package/agent/commands/acp.package-validate.md +540 -0
- package/agent/commands/acp.pattern-create.md +386 -0
- package/agent/commands/acp.plan.md +587 -0
- package/agent/commands/acp.proceed.md +882 -0
- package/agent/commands/acp.project-create.md +675 -0
- package/agent/commands/acp.project-info.md +312 -0
- package/agent/commands/acp.project-list.md +226 -0
- package/agent/commands/acp.project-remove.md +379 -0
- package/agent/commands/acp.project-set.md +227 -0
- package/agent/commands/acp.project-update.md +307 -0
- package/agent/commands/acp.projects-restore.md +228 -0
- package/agent/commands/acp.projects-sync.md +347 -0
- package/agent/commands/acp.report.md +407 -0
- package/agent/commands/acp.resume.md +239 -0
- package/agent/commands/acp.sessions.md +301 -0
- package/agent/commands/acp.status.md +293 -0
- package/agent/commands/acp.sync.md +364 -0
- package/agent/commands/acp.task-create.md +500 -0
- package/agent/commands/acp.update.md +302 -0
- package/agent/commands/acp.validate.md +466 -0
- package/agent/commands/acp.version-check-for-updates.md +276 -0
- package/agent/commands/acp.version-check.md +191 -0
- package/agent/commands/acp.version-update.md +289 -0
- package/agent/commands/command.template.md +339 -0
- package/agent/commands/git.commit.md +526 -0
- package/agent/commands/git.init.md +514 -0
- package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
- package/agent/commands/tanstack-cloudflare.tail.md +275 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/design.template.md +154 -0
- package/agent/design/local.dashboard-layout-routing.md +288 -0
- package/agent/design/local.data-model-yaml-parsing.md +310 -0
- package/agent/design/local.search-filtering.md +331 -0
- package/agent/design/local.server-api-auto-refresh.md +235 -0
- package/agent/design/local.table-tree-views.md +299 -0
- package/agent/design/local.visualizer-requirements.md +349 -0
- package/agent/design/requirements.template.md +387 -0
- package/agent/index/.gitkeep +0 -0
- package/agent/index/acp.core.yaml +137 -0
- package/agent/index/local.main.template.yaml +37 -0
- package/agent/manifest.template.yaml +13 -0
- package/agent/manifest.yaml +302 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
- package/agent/milestones/milestone-1-{title}.template.md +206 -0
- package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
- package/agent/package.template.yaml +86 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.template.md +1237 -0
- package/agent/patterns/pattern.template.md +382 -0
- package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
- package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
- package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
- package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
- package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
- package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
- package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
- package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
- package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
- package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
- package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
- package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
- package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
- package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
- package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
- package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
- package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
- package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
- package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
- package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
- package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
- package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
- package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
- package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
- package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
- package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
- package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
- package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
- package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
- package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
- package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
- package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
- package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
- package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
- package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
- package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
- package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
- package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
- package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
- package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
- package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
- package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
- package/agent/progress.template.yaml +161 -0
- package/agent/progress.yaml +145 -0
- package/agent/schemas/package.schema.yaml +276 -0
- package/agent/scripts/acp.common.sh +1781 -0
- package/agent/scripts/acp.install.sh +333 -0
- package/agent/scripts/acp.package-create.sh +924 -0
- package/agent/scripts/acp.package-info.sh +288 -0
- package/agent/scripts/acp.package-install.sh +893 -0
- package/agent/scripts/acp.package-list.sh +311 -0
- package/agent/scripts/acp.package-publish.sh +420 -0
- package/agent/scripts/acp.package-remove.sh +348 -0
- package/agent/scripts/acp.package-search.sh +156 -0
- package/agent/scripts/acp.package-update.sh +517 -0
- package/agent/scripts/acp.package-validate.sh +1018 -0
- package/agent/scripts/acp.uninstall.sh +85 -0
- package/agent/scripts/acp.version-check-for-updates.sh +98 -0
- package/agent/scripts/acp.version-check.sh +47 -0
- package/agent/scripts/acp.version-update.sh +176 -0
- package/agent/scripts/acp.yaml-parser.sh +985 -0
- package/agent/scripts/acp.yaml-validate.sh +205 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
- package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
- package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
- package/agent/tasks/task-1-{title}.template.md +244 -0
- package/bin/visualize.mjs +84 -0
- package/package.json +48 -0
- package/src/components/ExtraFieldsBadge.tsx +15 -0
- package/src/components/FilterBar.tsx +33 -0
- package/src/components/Header.tsx +23 -0
- package/src/components/MilestoneTable.tsx +167 -0
- package/src/components/MilestoneTree.tsx +84 -0
- package/src/components/ProgressBar.tsx +20 -0
- package/src/components/SearchInput.tsx +22 -0
- package/src/components/Sidebar.tsx +54 -0
- package/src/components/StatusBadge.tsx +23 -0
- package/src/components/StatusDot.tsx +12 -0
- package/src/components/TaskList.tsx +36 -0
- package/src/components/ViewToggle.tsx +31 -0
- package/src/lib/config.ts +8 -0
- package/src/lib/file-watcher.ts +43 -0
- package/src/lib/search.ts +48 -0
- package/src/lib/types.ts +73 -0
- package/src/lib/useAutoRefresh.ts +31 -0
- package/src/lib/useCollapse.ts +31 -0
- package/src/lib/useFilteredData.ts +55 -0
- package/src/lib/yaml-loader-real.spec.ts +47 -0
- package/src/lib/yaml-loader.spec.ts +201 -0
- package/src/lib/yaml-loader.ts +265 -0
- package/src/routeTree.gen.ts +140 -0
- package/src/router.tsx +10 -0
- package/src/routes/__root.tsx +75 -0
- package/src/routes/api/watch.ts +29 -0
- package/src/routes/index.tsx +115 -0
- package/src/routes/milestones.tsx +50 -0
- package/src/routes/search.tsx +84 -0
- package/src/routes/tasks.tsx +63 -0
- package/src/services/progress-database.service.ts +46 -0
- package/src/styles.css +25 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +16 -0
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# {Pattern Name}
|
|
2
|
+
|
|
3
|
+
**Category**: [Architecture | Design | Code | Testing | Deployment]
|
|
4
|
+
**Applicable To**: [What types of projects or components this pattern applies to]
|
|
5
|
+
**Status**: [Stable | Experimental | Deprecated]
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
[Provide a high-level description of what this pattern is and when to use it. Include the problem space it addresses and the general approach it takes.]
|
|
12
|
+
|
|
13
|
+
**Example**: "The Service Layer Pattern provides a clear separation between business logic and data access, enabling better testability, maintainability, and code reuse across different interfaces (API, CLI, etc.)."
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## When to Use This Pattern
|
|
18
|
+
|
|
19
|
+
[Describe the scenarios where this pattern is appropriate:]
|
|
20
|
+
|
|
21
|
+
✅ **Use this pattern when:**
|
|
22
|
+
- Condition 1
|
|
23
|
+
- Condition 2
|
|
24
|
+
- Condition 3
|
|
25
|
+
|
|
26
|
+
❌ **Don't use this pattern when:**
|
|
27
|
+
- Condition 1
|
|
28
|
+
- Condition 2
|
|
29
|
+
- Condition 3
|
|
30
|
+
|
|
31
|
+
**Example**:
|
|
32
|
+
|
|
33
|
+
✅ **Use this pattern when:**
|
|
34
|
+
- You have complex business logic that needs to be shared across multiple interfaces
|
|
35
|
+
- You want to isolate business logic from infrastructure concerns
|
|
36
|
+
- You need to test business logic independently of data access
|
|
37
|
+
|
|
38
|
+
❌ **Don't use this pattern when:**
|
|
39
|
+
- Your application is very simple with minimal business logic
|
|
40
|
+
- You're building a thin wrapper around a database
|
|
41
|
+
- The overhead of additional layers outweighs the benefits
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Core Principles
|
|
46
|
+
|
|
47
|
+
[List the fundamental concepts that underpin this pattern:]
|
|
48
|
+
|
|
49
|
+
1. **Principle 1**: [Description]
|
|
50
|
+
2. **Principle 2**: [Description]
|
|
51
|
+
3. **Principle 3**: [Description]
|
|
52
|
+
4. **Principle 4**: [Description]
|
|
53
|
+
|
|
54
|
+
**Example**:
|
|
55
|
+
|
|
56
|
+
1. **Separation of Concerns**: Business logic is isolated from data access and presentation
|
|
57
|
+
2. **Single Responsibility**: Each service handles one domain concept
|
|
58
|
+
3. **Dependency Injection**: Services receive their dependencies rather than creating them
|
|
59
|
+
4. **Interface-Based Design**: Services depend on abstractions, not concrete implementations
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Implementation
|
|
64
|
+
|
|
65
|
+
[Provide detailed implementation guidance with code examples:]
|
|
66
|
+
|
|
67
|
+
### Structure
|
|
68
|
+
|
|
69
|
+
[Describe the overall structure of the pattern]
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
directory-structure/
|
|
73
|
+
├── component1/
|
|
74
|
+
│ └── file1.ext
|
|
75
|
+
└── component2/
|
|
76
|
+
└── file2.ext
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Code Example
|
|
80
|
+
|
|
81
|
+
[Provide a complete, working example:]
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Example implementation
|
|
85
|
+
interface ExampleInterface {
|
|
86
|
+
method(): Promise<Result>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class ExampleImplementation implements ExampleInterface {
|
|
90
|
+
constructor(private dependency: Dependency) {}
|
|
91
|
+
|
|
92
|
+
async method(): Promise<Result> {
|
|
93
|
+
// Implementation
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Key Components
|
|
100
|
+
|
|
101
|
+
[Break down the major components:]
|
|
102
|
+
|
|
103
|
+
#### Component 1: [Name]
|
|
104
|
+
[Description and purpose]
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Code example for this component
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### Component 2: [Name]
|
|
111
|
+
[Description and purpose]
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Code example for this component
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Examples
|
|
120
|
+
|
|
121
|
+
[Provide multiple real-world examples showing different use cases:]
|
|
122
|
+
|
|
123
|
+
### Example 1: [Use Case Name]
|
|
124
|
+
|
|
125
|
+
[Description of the scenario]
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// Complete code example
|
|
129
|
+
class ConcreteExample {
|
|
130
|
+
// Implementation
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Usage
|
|
134
|
+
const example = new ConcreteExample();
|
|
135
|
+
const result = await example.doSomething();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Example 2: [Use Case Name]
|
|
139
|
+
|
|
140
|
+
[Description of the scenario]
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// Complete code example
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Benefits
|
|
149
|
+
|
|
150
|
+
[List the advantages of using this pattern:]
|
|
151
|
+
|
|
152
|
+
### 1. [Benefit Name]
|
|
153
|
+
[Detailed explanation of this benefit and why it matters]
|
|
154
|
+
|
|
155
|
+
### 2. [Benefit Name]
|
|
156
|
+
[Detailed explanation of this benefit and why it matters]
|
|
157
|
+
|
|
158
|
+
### 3. [Benefit Name]
|
|
159
|
+
[Detailed explanation of this benefit and why it matters]
|
|
160
|
+
|
|
161
|
+
**Example**:
|
|
162
|
+
|
|
163
|
+
### 1. Testability
|
|
164
|
+
Business logic can be tested in isolation without requiring database connections or external services. Mock dependencies can be easily injected for unit testing.
|
|
165
|
+
|
|
166
|
+
### 2. Reusability
|
|
167
|
+
The same business logic can be used across multiple interfaces (REST API, GraphQL, CLI, etc.) without duplication.
|
|
168
|
+
|
|
169
|
+
### 3. Maintainability
|
|
170
|
+
Changes to business logic are centralized in service classes, making the codebase easier to understand and modify.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Trade-offs
|
|
175
|
+
|
|
176
|
+
[Honestly assess the downsides and limitations:]
|
|
177
|
+
|
|
178
|
+
### 1. [Trade-off Name]
|
|
179
|
+
**Downside**: [Description]
|
|
180
|
+
**Mitigation**: [How to minimize this downside]
|
|
181
|
+
|
|
182
|
+
### 2. [Trade-off Name]
|
|
183
|
+
**Downside**: [Description]
|
|
184
|
+
**Mitigation**: [How to minimize this downside]
|
|
185
|
+
|
|
186
|
+
**Example**:
|
|
187
|
+
|
|
188
|
+
### 1. Additional Complexity
|
|
189
|
+
**Downside**: Adds extra layers and files to the codebase, which can feel like over-engineering for simple applications.
|
|
190
|
+
**Mitigation**: Only apply this pattern when complexity justifies it. Start simple and refactor to this pattern as needs grow.
|
|
191
|
+
|
|
192
|
+
### 2. Performance Overhead
|
|
193
|
+
**Downside**: Additional function calls and abstractions can add minor performance overhead.
|
|
194
|
+
**Mitigation**: In most applications, this overhead is negligible. Profile before optimizing.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Anti-Patterns
|
|
199
|
+
|
|
200
|
+
[Document what NOT to do - common mistakes and misuses:]
|
|
201
|
+
|
|
202
|
+
### ❌ Anti-Pattern 1: [Name]
|
|
203
|
+
|
|
204
|
+
**Description**: [What people do wrong]
|
|
205
|
+
|
|
206
|
+
**Why it's bad**: [Consequences]
|
|
207
|
+
|
|
208
|
+
**Instead, do this**: [Correct approach]
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
// ❌ Bad example
|
|
212
|
+
class BadExample {
|
|
213
|
+
// What not to do
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ✅ Good example
|
|
217
|
+
class GoodExample {
|
|
218
|
+
// Correct approach
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### ❌ Anti-Pattern 2: [Name]
|
|
223
|
+
|
|
224
|
+
[Similar structure as above]
|
|
225
|
+
|
|
226
|
+
**Example**:
|
|
227
|
+
|
|
228
|
+
### ❌ Anti-Pattern 1: God Service
|
|
229
|
+
|
|
230
|
+
**Description**: Creating a single service class that handles all business logic for the entire application.
|
|
231
|
+
|
|
232
|
+
**Why it's bad**: Violates single responsibility principle, becomes difficult to test and maintain, creates tight coupling.
|
|
233
|
+
|
|
234
|
+
**Instead, do this**: Create focused services, each handling a specific domain concept.
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// ❌ Bad: Everything in one service
|
|
238
|
+
class ApplicationService {
|
|
239
|
+
createUser() {}
|
|
240
|
+
deleteUser() {}
|
|
241
|
+
createProduct() {}
|
|
242
|
+
deleteProduct() {}
|
|
243
|
+
processPayment() {}
|
|
244
|
+
sendEmail() {}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ✅ Good: Focused services
|
|
248
|
+
class UserService {
|
|
249
|
+
createUser() {}
|
|
250
|
+
deleteUser() {}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
class ProductService {
|
|
254
|
+
createProduct() {}
|
|
255
|
+
deleteProduct() {}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
class PaymentService {
|
|
259
|
+
processPayment() {}
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Key Design Decisions (Optional)
|
|
266
|
+
|
|
267
|
+
<!-- This section is populated by @acp.clarification-capture when
|
|
268
|
+
create commands are invoked with --from-clar, --from-chat, or
|
|
269
|
+
--from-context. It can also be manually authored.
|
|
270
|
+
Omit this section entirely if no decisions to capture.
|
|
271
|
+
|
|
272
|
+
Group decisions by agent-inferred category using tables:
|
|
273
|
+
|
|
274
|
+
### {Category}
|
|
275
|
+
|
|
276
|
+
| Decision | Choice | Rationale |
|
|
277
|
+
|---|---|---|
|
|
278
|
+
| {decision} | {choice} | {rationale} |
|
|
279
|
+
-->
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Testing Strategy
|
|
284
|
+
|
|
285
|
+
[Describe how to test code that uses this pattern:]
|
|
286
|
+
|
|
287
|
+
### Unit Testing
|
|
288
|
+
[Approach for unit tests]
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
// Example unit test
|
|
292
|
+
describe('ExampleService', () => {
|
|
293
|
+
it('should do something', async () => {
|
|
294
|
+
// Test implementation
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Integration Testing
|
|
300
|
+
[Approach for integration tests]
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
// Example integration test
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Related Patterns
|
|
309
|
+
|
|
310
|
+
[Link to related patterns and explain relationships:]
|
|
311
|
+
|
|
312
|
+
- **[Pattern Name](./pattern-name.md)**: [How it relates]
|
|
313
|
+
- **[Pattern Name](./pattern-name.md)**: [How it relates]
|
|
314
|
+
- **[Pattern Name](./pattern-name.md)**: [How it relates]
|
|
315
|
+
|
|
316
|
+
**Example**:
|
|
317
|
+
- **[Repository Pattern](./repository-pattern.md)**: Often used together; services use repositories for data access
|
|
318
|
+
- **[Factory Pattern](./factory-pattern.md)**: Can be used to create service instances with proper dependencies
|
|
319
|
+
- **[Dependency Injection](./dependency-injection.md)**: Essential for implementing this pattern correctly
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## Migration Guide
|
|
324
|
+
|
|
325
|
+
[If adopting this pattern in an existing codebase, provide migration steps:]
|
|
326
|
+
|
|
327
|
+
### Step 1: [Action]
|
|
328
|
+
[Detailed description]
|
|
329
|
+
|
|
330
|
+
### Step 2: [Action]
|
|
331
|
+
[Detailed description]
|
|
332
|
+
|
|
333
|
+
### Step 3: [Action]
|
|
334
|
+
[Detailed description]
|
|
335
|
+
|
|
336
|
+
**Example**:
|
|
337
|
+
|
|
338
|
+
### Step 1: Identify Business Logic
|
|
339
|
+
Review existing code and identify business logic that's currently mixed with data access or presentation code.
|
|
340
|
+
|
|
341
|
+
### Step 2: Extract to Services
|
|
342
|
+
Create service classes and move business logic into them. Start with the most complex or frequently used logic.
|
|
343
|
+
|
|
344
|
+
### Step 3: Refactor Dependencies
|
|
345
|
+
Update calling code to use the new services. Inject dependencies rather than creating them directly.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## References
|
|
350
|
+
|
|
351
|
+
[Link to external resources, papers, books, or articles:]
|
|
352
|
+
|
|
353
|
+
- [Resource 1](URL): Description
|
|
354
|
+
- [Resource 2](URL): Description
|
|
355
|
+
- [Resource 3](URL): Description
|
|
356
|
+
|
|
357
|
+
**Example**:
|
|
358
|
+
- [Martin Fowler - Service Layer](https://martinfowler.com/eaaCatalog/serviceLayer.html): Original pattern description
|
|
359
|
+
- [Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html): Related architectural concepts
|
|
360
|
+
- [Domain-Driven Design](https://www.domainlanguage.com/ddd/): Context for service design
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
## Checklist for Implementation
|
|
365
|
+
|
|
366
|
+
[Provide a checklist to ensure proper implementation:]
|
|
367
|
+
|
|
368
|
+
- [ ] Services are focused on single domain concepts
|
|
369
|
+
- [ ] Dependencies are injected, not created internally
|
|
370
|
+
- [ ] Business logic is isolated from infrastructure concerns
|
|
371
|
+
- [ ] Services have clear, well-documented interfaces
|
|
372
|
+
- [ ] Unit tests cover business logic in isolation
|
|
373
|
+
- [ ] Integration tests verify end-to-end functionality
|
|
374
|
+
- [ ] Error handling is consistent and appropriate
|
|
375
|
+
- [ ] Logging provides adequate visibility
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
**Status**: [Current status of this pattern document]
|
|
380
|
+
**Recommendation**: [When and how to use this pattern]
|
|
381
|
+
**Last Updated**: [YYYY-MM-DD]
|
|
382
|
+
**Contributors**: [Names or "Community"]
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# ACL Permissions Pattern
|
|
2
|
+
|
|
3
|
+
**Category**: Architecture
|
|
4
|
+
**Applicable To**: TanStack Start + Cloudflare Workers applications with multi-user authorization
|
|
5
|
+
**Status**: Stable
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
The ACL (Access Control List) Permissions pattern provides fine-grained, flag-based authorization for multi-user features like groups, channels, and shared spaces. Instead of using coarse role-based access (owner/admin/member), each user gets a set of boolean permission flags that control exactly what actions they can perform.
|
|
12
|
+
|
|
13
|
+
Permission presets (OWNER, ADMIN, EDITOR, MEMBER) provide sensible defaults while allowing per-user customization. An `auth_level` numeric hierarchy enables authority-based checks (lower number = more authority), preventing members from elevating their own privileges or acting on users with higher authority.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## When to Use This Pattern
|
|
18
|
+
|
|
19
|
+
✅ **Use this pattern when:**
|
|
20
|
+
- Building multi-user features (groups, channels, shared spaces)
|
|
21
|
+
- Need granular control over who can do what
|
|
22
|
+
- Want to support custom permission configurations beyond simple roles
|
|
23
|
+
- Need authority hierarchy (owners can't be kicked by members)
|
|
24
|
+
- Building moderation features (kick, mute, ban)
|
|
25
|
+
|
|
26
|
+
❌ **Don't use this pattern when:**
|
|
27
|
+
- Application has only single-user data (no shared resources)
|
|
28
|
+
- Simple owner/non-owner distinction is sufficient
|
|
29
|
+
- Using an external authorization service (Auth0 FGA, etc.)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Core Principles
|
|
34
|
+
|
|
35
|
+
1. **Flag-Based Permissions**: Boolean flags for each capability, not string roles
|
|
36
|
+
2. **Authority Hierarchy**: Numeric `auth_level` (0 = owner, higher = less authority)
|
|
37
|
+
3. **Presets for Convenience**: OWNER, ADMIN, EDITOR, MEMBER presets with sensible defaults
|
|
38
|
+
4. **Stored on Entity**: Permissions stored directly on the group/channel document as `member_permissions[userId]`
|
|
39
|
+
5. **Thin ACL Service**: Validation logic in a dedicated service, not scattered across handlers
|
|
40
|
+
6. **Check Before Act**: Always validate permissions before performing any action
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Implementation
|
|
45
|
+
|
|
46
|
+
### Step 1: Define Permission Schema
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// src/schemas/group-conversation.ts
|
|
50
|
+
import { z } from 'zod'
|
|
51
|
+
|
|
52
|
+
export const MemberPermissionsSchema = z.object({
|
|
53
|
+
auth_level: z.number().int().min(0),
|
|
54
|
+
can_read: z.boolean(),
|
|
55
|
+
can_publish: z.boolean(),
|
|
56
|
+
can_revise: z.boolean(),
|
|
57
|
+
can_propose: z.boolean(),
|
|
58
|
+
can_overwrite: z.boolean(),
|
|
59
|
+
can_comment: z.boolean(),
|
|
60
|
+
can_retract_own: z.boolean(),
|
|
61
|
+
can_retract_any: z.boolean(),
|
|
62
|
+
can_manage_members: z.boolean(),
|
|
63
|
+
can_update_properties: z.boolean(),
|
|
64
|
+
can_moderate: z.boolean(),
|
|
65
|
+
can_kick: z.boolean(),
|
|
66
|
+
can_mute: z.boolean(),
|
|
67
|
+
can_ban: z.boolean(),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export type MemberPermissions = z.infer<typeof MemberPermissionsSchema>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 2: Define Permission Presets
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// src/schemas/group-conversation.ts (continued)
|
|
77
|
+
|
|
78
|
+
export const OWNER_PRESET: MemberPermissions = {
|
|
79
|
+
auth_level: 0,
|
|
80
|
+
can_read: true, can_publish: true, can_revise: true,
|
|
81
|
+
can_propose: true, can_overwrite: true, can_comment: true,
|
|
82
|
+
can_retract_own: true, can_retract_any: true, can_manage_members: true,
|
|
83
|
+
can_update_properties: true, can_moderate: true,
|
|
84
|
+
can_kick: true, can_mute: true, can_ban: true,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const ADMIN_PRESET: MemberPermissions = {
|
|
88
|
+
auth_level: 1,
|
|
89
|
+
can_read: true, can_publish: true, can_revise: true,
|
|
90
|
+
can_propose: true, can_overwrite: true, can_comment: true,
|
|
91
|
+
can_retract_own: true, can_retract_any: true, can_manage_members: true,
|
|
92
|
+
can_update_properties: true, can_moderate: true,
|
|
93
|
+
can_kick: true, can_mute: true, can_ban: true,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const EDITOR_PRESET: MemberPermissions = {
|
|
97
|
+
auth_level: 3,
|
|
98
|
+
can_read: true, can_publish: true, can_revise: true,
|
|
99
|
+
can_propose: true, can_overwrite: false, can_comment: true,
|
|
100
|
+
can_retract_own: true, can_retract_any: false, can_manage_members: false,
|
|
101
|
+
can_update_properties: false, can_moderate: false,
|
|
102
|
+
can_kick: false, can_mute: false, can_ban: false,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const MEMBER_PRESET: MemberPermissions = {
|
|
106
|
+
auth_level: 5,
|
|
107
|
+
can_read: true, can_publish: true, can_revise: false,
|
|
108
|
+
can_propose: true, can_overwrite: false, can_comment: true,
|
|
109
|
+
can_retract_own: true, can_retract_any: false, can_manage_members: false,
|
|
110
|
+
can_update_properties: false, can_moderate: false,
|
|
111
|
+
can_kick: false, can_mute: false, can_ban: false,
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Step 3: Store Permissions on Entity
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// src/schemas/group-conversation.ts (continued)
|
|
119
|
+
|
|
120
|
+
export const GroupConversationSchema = z.object({
|
|
121
|
+
id: z.string(),
|
|
122
|
+
type: z.literal('group'),
|
|
123
|
+
name: z.string(),
|
|
124
|
+
description: z.string().nullable().optional(),
|
|
125
|
+
owner_user_id: z.string(),
|
|
126
|
+
participant_user_ids: z.array(z.string()),
|
|
127
|
+
// Permissions stored per-user on the document itself
|
|
128
|
+
member_permissions: z.record(z.string(), MemberPermissionsSchema),
|
|
129
|
+
created_at: z.string().datetime(),
|
|
130
|
+
updated_at: z.string().datetime(),
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
export type GroupConversation = z.infer<typeof GroupConversationSchema>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Step 4: Create ACL Service
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// src/services/group-acl.service.ts
|
|
140
|
+
import { GroupConversationDatabaseService } from './group-conversation-database.service'
|
|
141
|
+
import type { MemberPermissions } from '@/schemas/group-conversation'
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Group ACL Service
|
|
145
|
+
*
|
|
146
|
+
* Thin access-control layer over GroupConversationDatabaseService.
|
|
147
|
+
* Provides permission-based access validation and membership queries.
|
|
148
|
+
*/
|
|
149
|
+
export class GroupAclService {
|
|
150
|
+
/**
|
|
151
|
+
* Validate a user has access to a group.
|
|
152
|
+
* Returns their permissions if granted, null otherwise.
|
|
153
|
+
*/
|
|
154
|
+
static async validateGroupAccess(
|
|
155
|
+
userId: string,
|
|
156
|
+
groupId: string
|
|
157
|
+
): Promise<MemberPermissions | null> {
|
|
158
|
+
const group = await GroupConversationDatabaseService.getGroupConversation(userId, groupId)
|
|
159
|
+
if (!group) return null
|
|
160
|
+
return group.member_permissions[userId] ?? null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all group IDs the user belongs to.
|
|
165
|
+
*/
|
|
166
|
+
static async getGroupMemberships(userId: string): Promise<string[]> {
|
|
167
|
+
const groups = await GroupConversationDatabaseService.listGroupConversations(userId)
|
|
168
|
+
return groups.map(g => g.id)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Step 5: Use in API Routes
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// src/routes/api/groups/$id/index.tsx
|
|
177
|
+
PATCH: async ({ params, request }) => {
|
|
178
|
+
const { id } = params
|
|
179
|
+
const user = await getAuthSession()
|
|
180
|
+
if (!user) return Response.json({ error: 'Unauthorized' }, { status: 401 })
|
|
181
|
+
|
|
182
|
+
// Check permission
|
|
183
|
+
const permissions = await GroupAclService.validateGroupAccess(user.uid, id)
|
|
184
|
+
if (!permissions) return Response.json({ error: 'Not found' }, { status: 404 })
|
|
185
|
+
|
|
186
|
+
if (!permissions.can_update_properties) {
|
|
187
|
+
return Response.json({ error: 'Forbidden: insufficient permissions' }, { status: 403 })
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Proceed with update...
|
|
191
|
+
const body = await request.json()
|
|
192
|
+
await GroupConversationDatabaseService.updateGroupConversation(user.uid, id, body)
|
|
193
|
+
return Response.json({ success: true })
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Step 6: Authority-Based Checks
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// src/routes/api/groups/$id/members/$userId.tsx
|
|
201
|
+
DELETE: async ({ params, request }) => {
|
|
202
|
+
const { id, userId: targetUserId } = params
|
|
203
|
+
const user = await getAuthSession()
|
|
204
|
+
|
|
205
|
+
const myPermissions = await GroupAclService.validateGroupAccess(user.uid, id)
|
|
206
|
+
if (!myPermissions?.can_kick) {
|
|
207
|
+
return Response.json({ error: 'Forbidden: cannot kick members' }, { status: 403 })
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Authority check: can't kick someone with equal or higher authority
|
|
211
|
+
const targetPermissions = await GroupAclService.validateGroupAccess(targetUserId, id)
|
|
212
|
+
if (targetPermissions && targetPermissions.auth_level <= myPermissions.auth_level) {
|
|
213
|
+
return Response.json({
|
|
214
|
+
error: 'Forbidden: cannot kick a user with equal or higher authority'
|
|
215
|
+
}, { status: 403 })
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
await GroupConversationDatabaseService.removeMember(user.uid, id, targetUserId)
|
|
219
|
+
return Response.json({ success: true })
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Permission Presets Summary
|
|
226
|
+
|
|
227
|
+
| Preset | auth_level | Key Capabilities |
|
|
228
|
+
|--------|-----------|------------------|
|
|
229
|
+
| OWNER | 0 | Everything — full control |
|
|
230
|
+
| ADMIN | 1 | Everything except cannot override owner |
|
|
231
|
+
| EDITOR | 3 | Read, publish, revise, comment, retract own |
|
|
232
|
+
| MEMBER | 5 | Read, publish, propose, comment, retract own |
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Moderation Actions
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
// src/schemas/group-conversation.ts
|
|
240
|
+
export const ModerationActionSchema = z.object({
|
|
241
|
+
action: z.enum(['kick', 'mute', 'ban', 'message_delete', 'memory_moderate']),
|
|
242
|
+
target_user_id: z.string(),
|
|
243
|
+
acted_by_user_id: z.string(),
|
|
244
|
+
acted_by_auth_level: z.number().int().min(0),
|
|
245
|
+
created_at: z.string().datetime(),
|
|
246
|
+
reversed_at: z.string().datetime().optional(),
|
|
247
|
+
reversed_by_user_id: z.string().optional(),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
// Authority rule: can only reverse actions by users at your level or below
|
|
251
|
+
// (higher auth_level number = less authority)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Benefits
|
|
257
|
+
|
|
258
|
+
### 1. Granular Control
|
|
259
|
+
15 individual flags enable precise permission configurations beyond simple roles.
|
|
260
|
+
|
|
261
|
+
### 2. Authority Hierarchy
|
|
262
|
+
Numeric `auth_level` prevents privilege escalation (members can't kick admins).
|
|
263
|
+
|
|
264
|
+
### 3. Stored on Entity
|
|
265
|
+
No separate permissions table — permissions live on the group document itself.
|
|
266
|
+
|
|
267
|
+
### 4. Preset Templates
|
|
268
|
+
OWNER/ADMIN/EDITOR/MEMBER presets provide quick setup while allowing customization.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Trade-offs
|
|
273
|
+
|
|
274
|
+
### 1. Schema Size
|
|
275
|
+
**Downside**: 15 boolean flags per member adds document size.
|
|
276
|
+
**Mitigation**: Minimal overhead per member (~200 bytes). Only relevant for very large groups.
|
|
277
|
+
|
|
278
|
+
### 2. No Inheritance
|
|
279
|
+
**Downside**: Each group manages its own permissions — no global roles.
|
|
280
|
+
**Mitigation**: Use presets for consistency. Global admin features can check a separate admin flag.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Anti-Patterns
|
|
285
|
+
|
|
286
|
+
### ❌ Anti-Pattern 1: String-Based Roles
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// ❌ BAD: Coarse roles — no way to customize
|
|
290
|
+
member_roles: { 'user1': 'admin', 'user2': 'member' }
|
|
291
|
+
|
|
292
|
+
// ✅ GOOD: Fine-grained flags
|
|
293
|
+
member_permissions: { 'user1': ADMIN_PRESET, 'user2': { ...MEMBER_PRESET, can_revise: true } }
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### ❌ Anti-Pattern 2: Checking Permissions in Components
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// ❌ BAD: Permission check on client (can be bypassed)
|
|
300
|
+
if (userRole === 'admin') showDeleteButton()
|
|
301
|
+
|
|
302
|
+
// ✅ GOOD: Server-side validation in API route
|
|
303
|
+
if (!permissions.can_retract_any) return Response.json({ error: 'Forbidden' }, { status: 403 })
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Related Patterns
|
|
309
|
+
|
|
310
|
+
- **[Zod Schema Validation](./tanstack-cloudflare.zod-schema-validation.md)**: Permission schemas defined with Zod
|
|
311
|
+
- **[API Route Handlers](./tanstack-cloudflare.api-route-handlers.md)**: ACL checks in API routes
|
|
312
|
+
- **[Library Services Pattern](./tanstack-cloudflare.library-services.md)**: ACL service as thin wrapper over database service
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Checklist for Implementation
|
|
317
|
+
|
|
318
|
+
- [ ] Permission schema defined with Zod
|
|
319
|
+
- [ ] Presets for OWNER, ADMIN, EDITOR, MEMBER
|
|
320
|
+
- [ ] `auth_level` hierarchy enforced (lower = more authority)
|
|
321
|
+
- [ ] Permissions stored on entity document as `member_permissions[userId]`
|
|
322
|
+
- [ ] ACL service validates access before any operation
|
|
323
|
+
- [ ] Authority checks prevent privilege escalation
|
|
324
|
+
- [ ] API routes return 403 for insufficient permissions
|
|
325
|
+
- [ ] Moderation actions stamped with `acted_by_auth_level`
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
**Status**: Stable - Proven permission model for multi-user features
|
|
330
|
+
**Recommendation**: Use for all shared resources requiring granular authorization
|
|
331
|
+
**Last Updated**: 2026-02-28
|
|
332
|
+
**Contributors**: Patrick Michaelsen
|