@prmichaelsen/acp-visualizer 0.1.0 → 0.1.2

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.
Files changed (159) hide show
  1. package/package.json +8 -10
  2. package/src/components/ExtraFieldsBadge.tsx +1 -1
  3. package/src/components/FilterBar.tsx +1 -1
  4. package/src/components/Header.tsx +1 -1
  5. package/src/components/MilestoneTable.tsx +1 -1
  6. package/src/components/MilestoneTree.tsx +2 -2
  7. package/src/components/StatusBadge.tsx +1 -1
  8. package/src/components/StatusDot.tsx +1 -1
  9. package/src/components/TaskList.tsx +1 -1
  10. package/src/routes/__root.tsx +5 -5
  11. package/src/routes/api/watch.ts +1 -1
  12. package/src/routes/index.tsx +2 -2
  13. package/src/routes/milestones.tsx +7 -7
  14. package/src/routes/search.tsx +4 -4
  15. package/src/routes/tasks.tsx +3 -3
  16. package/src/services/progress-database.service.ts +3 -3
  17. package/agent/commands/acp.clarification-address.md +0 -417
  18. package/agent/commands/acp.clarification-capture.md +0 -386
  19. package/agent/commands/acp.clarification-create.md +0 -437
  20. package/agent/commands/acp.clarifications-research.md +0 -326
  21. package/agent/commands/acp.command-create.md +0 -432
  22. package/agent/commands/acp.design-create.md +0 -286
  23. package/agent/commands/acp.design-reference.md +0 -355
  24. package/agent/commands/acp.handoff.md +0 -270
  25. package/agent/commands/acp.index.md +0 -423
  26. package/agent/commands/acp.init.md +0 -546
  27. package/agent/commands/acp.package-create.md +0 -895
  28. package/agent/commands/acp.package-info.md +0 -212
  29. package/agent/commands/acp.package-install.md +0 -539
  30. package/agent/commands/acp.package-list.md +0 -280
  31. package/agent/commands/acp.package-publish.md +0 -541
  32. package/agent/commands/acp.package-remove.md +0 -293
  33. package/agent/commands/acp.package-search.md +0 -307
  34. package/agent/commands/acp.package-update.md +0 -361
  35. package/agent/commands/acp.package-validate.md +0 -540
  36. package/agent/commands/acp.pattern-create.md +0 -386
  37. package/agent/commands/acp.plan.md +0 -587
  38. package/agent/commands/acp.proceed.md +0 -882
  39. package/agent/commands/acp.project-create.md +0 -675
  40. package/agent/commands/acp.project-info.md +0 -312
  41. package/agent/commands/acp.project-list.md +0 -226
  42. package/agent/commands/acp.project-remove.md +0 -379
  43. package/agent/commands/acp.project-set.md +0 -227
  44. package/agent/commands/acp.project-update.md +0 -307
  45. package/agent/commands/acp.projects-restore.md +0 -228
  46. package/agent/commands/acp.projects-sync.md +0 -347
  47. package/agent/commands/acp.report.md +0 -407
  48. package/agent/commands/acp.resume.md +0 -239
  49. package/agent/commands/acp.sessions.md +0 -301
  50. package/agent/commands/acp.status.md +0 -293
  51. package/agent/commands/acp.sync.md +0 -364
  52. package/agent/commands/acp.task-create.md +0 -500
  53. package/agent/commands/acp.update.md +0 -302
  54. package/agent/commands/acp.validate.md +0 -466
  55. package/agent/commands/acp.version-check-for-updates.md +0 -276
  56. package/agent/commands/acp.version-check.md +0 -191
  57. package/agent/commands/acp.version-update.md +0 -289
  58. package/agent/commands/command.template.md +0 -339
  59. package/agent/commands/git.commit.md +0 -526
  60. package/agent/commands/git.init.md +0 -514
  61. package/agent/commands/tanstack-cloudflare.deploy.md +0 -272
  62. package/agent/commands/tanstack-cloudflare.tail.md +0 -275
  63. package/agent/design/.gitkeep +0 -0
  64. package/agent/design/design.template.md +0 -154
  65. package/agent/design/local.dashboard-layout-routing.md +0 -288
  66. package/agent/design/local.data-model-yaml-parsing.md +0 -310
  67. package/agent/design/local.search-filtering.md +0 -331
  68. package/agent/design/local.server-api-auto-refresh.md +0 -235
  69. package/agent/design/local.table-tree-views.md +0 -299
  70. package/agent/design/local.visualizer-requirements.md +0 -349
  71. package/agent/design/requirements.template.md +0 -387
  72. package/agent/index/.gitkeep +0 -0
  73. package/agent/index/acp.core.yaml +0 -137
  74. package/agent/index/local.main.template.yaml +0 -37
  75. package/agent/manifest.template.yaml +0 -13
  76. package/agent/manifest.yaml +0 -302
  77. package/agent/milestones/.gitkeep +0 -0
  78. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +0 -67
  79. package/agent/milestones/milestone-1-{title}.template.md +0 -206
  80. package/agent/milestones/milestone-2-dashboard-views-interaction.md +0 -79
  81. package/agent/package.template.yaml +0 -86
  82. package/agent/patterns/.gitkeep +0 -0
  83. package/agent/patterns/bootstrap.template.md +0 -1237
  84. package/agent/patterns/pattern.template.md +0 -382
  85. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +0 -332
  86. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +0 -416
  87. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +0 -401
  88. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +0 -387
  89. package/agent/patterns/tanstack-cloudflare.card-and-list.md +0 -271
  90. package/agent/patterns/tanstack-cloudflare.chat-engine.md +0 -353
  91. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +0 -346
  92. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +0 -516
  93. package/agent/patterns/tanstack-cloudflare.email-service.md +0 -431
  94. package/agent/patterns/tanstack-cloudflare.expander.md +0 -98
  95. package/agent/patterns/tanstack-cloudflare.fcm-push.md +0 -115
  96. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +0 -441
  97. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +0 -348
  98. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +0 -550
  99. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +0 -369
  100. package/agent/patterns/tanstack-cloudflare.form-controls.md +0 -145
  101. package/agent/patterns/tanstack-cloudflare.global-search-context.md +0 -93
  102. package/agent/patterns/tanstack-cloudflare.image-carousel.md +0 -126
  103. package/agent/patterns/tanstack-cloudflare.library-services.md +0 -553
  104. package/agent/patterns/tanstack-cloudflare.lightbox.md +0 -169
  105. package/agent/patterns/tanstack-cloudflare.markdown-content.md +0 -115
  106. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +0 -98
  107. package/agent/patterns/tanstack-cloudflare.modal.md +0 -156
  108. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +0 -461
  109. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +0 -151
  110. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +0 -90
  111. package/agent/patterns/tanstack-cloudflare.og-metadata.md +0 -296
  112. package/agent/patterns/tanstack-cloudflare.pagination.md +0 -442
  113. package/agent/patterns/tanstack-cloudflare.pill-input.md +0 -220
  114. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +0 -401
  115. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +0 -323
  116. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +0 -338
  117. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +0 -375
  118. package/agent/patterns/tanstack-cloudflare.slide-over.md +0 -129
  119. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +0 -571
  120. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +0 -508
  121. package/agent/patterns/tanstack-cloudflare.toast-system.md +0 -142
  122. package/agent/patterns/tanstack-cloudflare.unified-header.md +0 -280
  123. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +0 -628
  124. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +0 -237
  125. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +0 -358
  126. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +0 -336
  127. package/agent/progress.template.yaml +0 -161
  128. package/agent/progress.yaml +0 -145
  129. package/agent/schemas/package.schema.yaml +0 -276
  130. package/agent/scripts/acp.common.sh +0 -1781
  131. package/agent/scripts/acp.install.sh +0 -333
  132. package/agent/scripts/acp.package-create.sh +0 -924
  133. package/agent/scripts/acp.package-info.sh +0 -288
  134. package/agent/scripts/acp.package-install.sh +0 -893
  135. package/agent/scripts/acp.package-list.sh +0 -311
  136. package/agent/scripts/acp.package-publish.sh +0 -420
  137. package/agent/scripts/acp.package-remove.sh +0 -348
  138. package/agent/scripts/acp.package-search.sh +0 -156
  139. package/agent/scripts/acp.package-update.sh +0 -517
  140. package/agent/scripts/acp.package-validate.sh +0 -1018
  141. package/agent/scripts/acp.uninstall.sh +0 -85
  142. package/agent/scripts/acp.version-check-for-updates.sh +0 -98
  143. package/agent/scripts/acp.version-check.sh +0 -47
  144. package/agent/scripts/acp.version-update.sh +0 -176
  145. package/agent/scripts/acp.yaml-parser.sh +0 -985
  146. package/agent/scripts/acp.yaml-validate.sh +0 -205
  147. package/agent/tasks/.gitkeep +0 -0
  148. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +0 -210
  149. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +0 -294
  150. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +0 -193
  151. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +0 -262
  152. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +0 -156
  153. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +0 -178
  154. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +0 -141
  155. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +0 -153
  156. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +0 -174
  157. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +0 -233
  158. package/agent/tasks/task-1-{title}.template.md +0 -244
  159. package/vitest.config.ts +0 -27
@@ -1,126 +0,0 @@
1
- # Image Carousel
2
-
3
- **Category**: Design
4
- **Applicable To**: Horizontal image galleries in chat messages, profiles, and any scrollable media strip
5
- **Status**: Stable
6
-
7
- ---
8
-
9
- ## Overview
10
-
11
- A CSS scroll-snap carousel with IntersectionObserver-based visibility tracking, lazy loading, scale/opacity interpolation per slide, keyboard navigation, and responsive dot/counter navigation. Used for chat message image galleries, memory card carousels, and profile media.
12
-
13
- ---
14
-
15
- ## Implementation
16
-
17
- ### ImageCarousel
18
-
19
- **File**: `src/components/ImageCarousel.tsx`
20
-
21
- ```typescript
22
- interface ImageCarouselProps {
23
- images: Array<{ src: string; alt?: string; crop?: CropData | null }>
24
- onImageClick?: (index: number) => void // Opens lightbox
25
- }
26
- ```
27
-
28
- **Core Mechanics**:
29
-
30
- 1. **CSS Scroll-Snap**:
31
- ```typescript
32
- <div className="flex overflow-x-auto snap-x snap-mandatory"
33
- style={{ scrollBehavior: 'smooth', WebkitOverflowScrolling: 'touch' }}>
34
- {images.map((img, i) => (
35
- <div key={i} className="flex-shrink-0 w-full snap-center">
36
- <img src={img.src} />
37
- </div>
38
- ))}
39
- </div>
40
- ```
41
-
42
- 2. **IntersectionObserver** (21-step threshold: 0/20 to 20/20):
43
- - Tracks per-slide visibility ratio (0.0 to 1.0)
44
- - Updates `slideVisibility` array on each intersection change
45
- - Most-visible slide becomes `currentIndex`
46
-
47
- 3. **Scale/Opacity Interpolation**:
48
- ```typescript
49
- const ratio = slideVisibility[i] ?? 0
50
- const scale = 0.92 + 0.08 * ratio // 92% → 100%
51
- const opacity = 0.4 + 0.6 * ratio // 40% → 100%
52
- ```
53
-
54
- 4. **Lazy Loading**:
55
- - `loadedIndices` Set tracks rendered slides
56
- - Preloads current ± 1 adjacent slides
57
- - Unloaded slides render placeholder (prevents layout shift)
58
-
59
- 5. **Navigation UI**:
60
- - **Dots**: For ≤7 slides, dot indicators at bottom (active = white, inactive = gray)
61
- - **Counter**: For >7 slides, text "3 / 10"
62
- - **Chevrons**: Desktop only (`hidden md:flex`), hidden at boundaries
63
-
64
- 6. **Keyboard**: ArrowLeft/ArrowRight scrolls to adjacent slide
65
-
66
- **Touch Direction Detection** (MemoryCardCarousel variant):
67
- ```typescript
68
- const handleTouchMove = (e: ReactTouchEvent) => {
69
- const dx = Math.abs(e.touches[0].clientX - touchStart.x)
70
- const dy = Math.abs(e.touches[0].clientY - touchStart.y)
71
- // Lock horizontal scroll-snap when horizontal swipe detected
72
- if (dx > dy * 2) {
73
- scrollRef.current.style.scrollSnapType = 'x mandatory'
74
- } else {
75
- // Disable snap to allow vertical page scroll
76
- scrollRef.current.style.scrollSnapType = 'none'
77
- }
78
- }
79
- ```
80
-
81
- ---
82
-
83
- ## Anti-Patterns
84
-
85
- ### Using State for Slide Index Instead of IntersectionObserver
86
-
87
- ```typescript
88
- // Bad: Scroll events are noisy and imprecise
89
- onScroll={(e) => setIndex(Math.round(e.target.scrollLeft / slideWidth))}
90
-
91
- // Good: IntersectionObserver provides precise visibility ratios
92
- const observer = new IntersectionObserver(entries => {
93
- entries.forEach(entry => {
94
- slideVisibility[index] = entry.intersectionRatio
95
- })
96
- }, { threshold: Array.from({ length: 21 }, (_, i) => i / 20) })
97
- ```
98
-
99
- ### Rendering All Slides Eagerly
100
-
101
- ```typescript
102
- // Bad: All images load at once
103
- {images.map((img) => <img src={img.src} />)}
104
-
105
- // Good: Lazy load with preload ±1
106
- {loadedIndices.has(i) ? <img src={img.src} /> : <div className="bg-gray-800" />}
107
- ```
108
-
109
- ---
110
-
111
- ## Checklist
112
-
113
- - [ ] Use `snap-x snap-mandatory` on scroll container
114
- - [ ] Use `snap-center` on each slide
115
- - [ ] Track visibility with IntersectionObserver (21-step threshold)
116
- - [ ] Lazy-load slides with ±1 preloading
117
- - [ ] Apply scale/opacity interpolation for smooth transitions
118
- - [ ] Show dots for ≤7 slides, counter for >7
119
- - [ ] Hide chevrons on mobile, show only at non-boundary positions
120
- - [ ] Handle touch direction to avoid blocking vertical page scroll
121
-
122
- ---
123
-
124
- **Status**: Stable
125
- **Last Updated**: 2026-03-14
126
- **Contributors**: Community
@@ -1,553 +0,0 @@
1
- # Library Services Pattern
2
-
3
- **Category**: Architecture
4
- **Applicable To**: TanStack Start + Cloudflare Workers applications with Firestore/database operations
5
- **Status**: Stable
6
-
7
- ---
8
-
9
- ## Overview
10
-
11
- All data access operations (API calls, Firestore operations, external services) must go through dedicated service layer libraries. This pattern provides a clear separation between client-side API wrappers and server-side database services, ensuring proper isolation, testability, and maintainability.
12
-
13
- The pattern enforces that direct calls from components, routes, or other non-service code are anti-patterns. Instead, all data operations flow through well-defined service classes that handle validation, error logging, and type safety.
14
-
15
- ---
16
-
17
- ## When to Use This Pattern
18
-
19
- ✅ **Use this pattern when:**
20
- - Building TanStack Start applications with server-side data access
21
- - Working with Firestore or other database operations
22
- - Need to separate client-side and server-side data access logic
23
- - Want to ensure type safety and validation at service boundaries
24
- - Building applications that require testable, mockable data layers
25
- - Need consistent error handling and logging across data operations
26
-
27
- ❌ **Don't use this pattern when:**
28
- - Building purely static sites with no data access
29
- - Working on trivial prototypes or demos
30
- - The overhead of service layers outweighs benefits (very simple CRUD apps)
31
- - You have no server-side data access requirements
32
-
33
- ---
34
-
35
- ## Core Principles
36
-
37
- 1. **Service Layer Abstraction**: All data operations must be encapsulated in service classes that provide clean APIs for data access
38
- 2. **No Direct Database Calls**: Components and routes never call `getDocument`, `setDocument`, or `queryDocuments` directly
39
- 3. **No Direct API Calls**: Components never call `fetch('/api/...')` directly - they use API service wrappers
40
- 4. **Clear Naming Convention**: Service class names indicate scope - `DatabaseService` for server-side, `Service` for client-side
41
- 5. **Same Method Names**: Database and API services use identical method names for consistency
42
- 6. **Type Safety**: Services enforce Zod validation and return typed data models
43
- 7. **Error Handling**: Services centralize error logging and handling
44
-
45
- ---
46
-
47
- ## Implementation
48
-
49
- ### Structure
50
-
51
- ```
52
- src/
53
- ├── services/
54
- │ ├── {domain}-database.service.ts # Server-side database operations
55
- │ ├── {domain}.service.ts # Client-side API wrappers
56
- │ └── ...
57
- ├── routes/
58
- │ └── api/
59
- │ └── {domain}/
60
- │ └── index.ts # API routes use DatabaseService
61
- └── components/
62
- └── {domain}/
63
- └── Component.tsx # Components use Service (API wrapper)
64
- ```
65
-
66
- ### Service Types
67
-
68
- #### 1. Database Services (Server-Side)
69
-
70
- **Purpose**: Direct Firestore/database operations
71
- **Naming**: `{Domain}DatabaseService`
72
- **File**: `{domain}-database.service.ts`
73
- **Used By**: API routes, beforeLoad, server functions, cron jobs
74
-
75
- **Characteristics**:
76
- - Directly calls `getDocument`, `setDocument`, `queryDocuments`
77
- - Server-side only (uses firebase-admin-sdk)
78
- - Handles Zod validation
79
- - Manages timestamps (created_at, updated_at)
80
- - Returns typed data models
81
-
82
- #### 2. API Services (Client Wrappers)
83
-
84
- **Purpose**: Wrap API endpoint calls for client-side use
85
- **Naming**: `{Domain}Service`
86
- **File**: `{domain}.service.ts`
87
- **Used By**: Components, client-side hooks
88
-
89
- **Characteristics**:
90
- - Calls `fetch('/api/...')`
91
- - Client-side safe
92
- - Handles HTTP errors
93
- - Returns typed data models
94
-
95
- ### Code Example
96
-
97
- #### Database Service (Server-Side)
98
-
99
- ```typescript
100
- // src/services/oauth-integration-database.service.ts
101
- import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8'
102
- import { getUserOAuthIntegration } from '@/constant/collections'
103
- import { OAuthIntegrationSchema, type OAuthIntegration } from '@/schemas/oauth-integration'
104
-
105
- export class OAuthIntegrationDatabaseService {
106
- static async getIntegration(userId: string, provider: string): Promise<OAuthIntegration | null> {
107
- try {
108
- const path = getUserOAuthIntegration(userId, provider)
109
- const doc = await getDocument(path, 'current')
110
-
111
- if (!doc) return null
112
-
113
- const result = OAuthIntegrationSchema.safeParse(doc)
114
- if (!result.success) {
115
- console.error(`Invalid OAuth integration data for ${provider}:`, result.error)
116
- return null
117
- }
118
-
119
- return result.data
120
- } catch (error) {
121
- console.error(`Failed to get OAuth integration for ${provider}:`, error)
122
- return null
123
- }
124
- }
125
-
126
- static async saveIntegration(userId: string, provider: string, data: OAuthIntegrationInput): Promise<void> {
127
- try {
128
- const path = getUserOAuthIntegration(userId, provider)
129
- const now = new Date().toISOString()
130
-
131
- const integration: OAuthIntegration = {
132
- ...data,
133
- connected_at: now,
134
- created_at: now,
135
- updated_at: now,
136
- }
137
-
138
- await setDocument(path, 'current', integration)
139
- console.log(`[OAuthIntegrationDatabaseService] Saved ${provider} integration for user ${userId}`)
140
- } catch (error) {
141
- console.error(`[OAuthIntegrationDatabaseService] Failed to save ${provider} integration:`, error)
142
- throw error
143
- }
144
- }
145
-
146
- static async getUserIntegrations(userId: string, providers: string[]): Promise<Record<string, OAuthIntegration>> {
147
- const integrations: Record<string, OAuthIntegration> = {}
148
-
149
- await Promise.all(
150
- providers.map(async (provider) => {
151
- const integration = await this.getIntegration(userId, provider)
152
- if (integration && integration.connected) {
153
- integrations[provider] = integration
154
- }
155
- })
156
- )
157
-
158
- return integrations
159
- }
160
- }
161
- ```
162
-
163
- #### API Service (Client-Side)
164
-
165
- ```typescript
166
- // src/services/integrations.service.ts
167
- import type { OAuthIntegration } from '@/schemas/oauth-integration'
168
-
169
- export class IntegrationsService {
170
- /**
171
- * Get user's OAuth integrations (client-side)
172
- * Calls the API endpoint which validates session server-side
173
- */
174
- static async getUserIntegrations(): Promise<Record<string, OAuthIntegration>> {
175
- try {
176
- const response = await fetch('/api/integrations/')
177
-
178
- if (!response.ok) {
179
- throw new Error(`Failed to fetch integrations: ${response.statusText}`)
180
- }
181
-
182
- const data: any = await response.json()
183
- return data.integrations || {}
184
- } catch (error) {
185
- console.error('[IntegrationsService] Failed to fetch integrations:', error)
186
- return {}
187
- }
188
- }
189
- }
190
- ```
191
-
192
- ---
193
-
194
- ## Examples
195
-
196
- ### Example 1: Using API Service in Component (Client-Side)
197
-
198
- ```typescript
199
- // src/components/integrations/IntegrationsPanel.tsx
200
- import { useEffect, useState } from 'react'
201
- import { IntegrationsService } from '@/services/integrations.service'
202
- import type { OAuthIntegration } from '@/schemas/oauth-integration'
203
-
204
- function IntegrationsPanel() {
205
- const [integrations, setIntegrations] = useState<Record<string, OAuthIntegration>>({})
206
-
207
- useEffect(() => {
208
- // ✅ CORRECT: Use API service wrapper
209
- IntegrationsService.getUserIntegrations()
210
- .then(data => setIntegrations(data))
211
- }, [])
212
-
213
- return (
214
- <div>
215
- {Object.entries(integrations).map(([provider, integration]) => (
216
- <div key={provider}>
217
- {provider}: {integration.connected ? 'Connected' : 'Disconnected'}
218
- </div>
219
- ))}
220
- </div>
221
- )
222
- }
223
- ```
224
-
225
- ### Example 2: Using Database Service in API Route (Server-Side)
226
-
227
- ```typescript
228
- // src/routes/api/integrations/index.ts
229
- import { createAPIFileRoute } from '@tanstack/start/api'
230
- import { getServerSession } from '@/lib/auth/session'
231
- import { OAuthIntegrationDatabaseService } from '@/services/oauth-integration-database.service'
232
-
233
- export const APIRoute = createAPIFileRoute('/api/integrations')({
234
- GET: async ({ request }) => {
235
- const session = await getServerSession(request)
236
-
237
- if (!session?.user) {
238
- return Response.json({ error: 'Unauthorized' }, { status: 401 })
239
- }
240
-
241
- // ✅ CORRECT: Use database service
242
- const integrations = await OAuthIntegrationDatabaseService.getUserIntegrations(
243
- session.user.uid,
244
- ['instagram', 'eventbrite']
245
- )
246
-
247
- return Response.json({ integrations })
248
- }
249
- })
250
- ```
251
-
252
- ### Example 3: Using Database Service in beforeLoad (Server-Side)
253
-
254
- ```typescript
255
- // src/routes/integrations.tsx
256
- import { createFileRoute } from '@tanstack/react-router'
257
- import { getAuthSession } from '@/lib/auth/server-fn'
258
- import { OAuthIntegrationDatabaseService } from '@/services/oauth-integration-database.service'
259
-
260
- export const Route = createFileRoute('/integrations')({
261
- beforeLoad: async () => {
262
- const user = await getAuthSession()
263
- if (!user) return { initialIntegrations: {} }
264
-
265
- // ✅ CORRECT: Use database service in server-side context
266
- const initialIntegrations = await OAuthIntegrationDatabaseService.getUserIntegrations(
267
- user.uid,
268
- ['instagram', 'eventbrite']
269
- )
270
-
271
- return { initialIntegrations }
272
- },
273
- })
274
- ```
275
-
276
- ---
277
-
278
- ## Benefits
279
-
280
- ### 1. Testability
281
-
282
- Services can be easily mocked for testing without requiring database connections or external services:
283
-
284
- ```typescript
285
- // Easy to mock services in tests
286
- jest.mock('@/services/integrations.service')
287
-
288
- test('component loads integrations', async () => {
289
- IntegrationsService.getUserIntegrations.mockResolvedValue({ instagram: {...} })
290
- // Test component behavior
291
- })
292
- ```
293
-
294
- ### 2. Consistency
295
-
296
- All Firestore operations follow the same pattern with consistent error handling, logging, and Zod validation. Changes to database structure or API endpoints only need to be updated in one place.
297
-
298
- ### 3. Type Safety
299
-
300
- Services provide typed interfaces with no `any` types leaking into components. Zod validation at service boundaries ensures data integrity.
301
-
302
- ### 4. Maintainability
303
-
304
- Change database structure in one place, update API endpoints in one place, and easily add caching, retry logic, or other cross-cutting concerns.
305
-
306
- ### 5. Import Errors Prevent Misuse
307
-
308
- Can't accidentally use database service in component - TypeScript import errors will prevent it since database services use server-only imports.
309
-
310
- ---
311
-
312
- ## Trade-offs
313
-
314
- ### 1. Additional Complexity
315
-
316
- **Downside**: Adds extra layers and files to the codebase, which can feel like over-engineering for simple applications.
317
-
318
- **Mitigation**: Only apply this pattern when complexity justifies it. For very simple CRUD apps, direct database access might be acceptable. Start simple and refactor to this pattern as needs grow.
319
-
320
- ### 2. Boilerplate Code
321
-
322
- **Downside**: Requires creating multiple service files and maintaining parallel API/Database service structures.
323
-
324
- **Mitigation**: Use code generation or templates to quickly scaffold new services. The consistency benefits outweigh the initial setup cost.
325
-
326
- ---
327
-
328
- ## Anti-Patterns
329
-
330
- ### ❌ Anti-Pattern 1: Direct Firestore Calls in Components
331
-
332
- **Description**: Calling `setDocument`, `getDocument`, or `queryDocuments` directly from React components.
333
-
334
- **Why it's bad**: Violates separation of concerns, makes testing difficult, couples components to database implementation, can't be used client-side safely.
335
-
336
- **Instead, do this**: Use API service wrappers that call API endpoints.
337
-
338
- ```typescript
339
- // ❌ BAD: Direct Firestore call in component
340
- import { setDocument } from '@prmichaelsen/firebase-admin-sdk-v8'
341
-
342
- function MyComponent() {
343
- const handleSave = async () => {
344
- await setDocument('users', userId, data) // BAD!
345
- }
346
- }
347
-
348
- // ✅ GOOD: Use service layer
349
- import { UserService } from '@/services/user.service'
350
-
351
- function MyComponent() {
352
- const handleSave = async () => {
353
- await UserService.updateUser(userId, data) // GOOD!
354
- }
355
- }
356
- ```
357
-
358
- ### ❌ Anti-Pattern 2: Direct fetch Calls in Components
359
-
360
- **Description**: Calling `fetch('/api/...')` directly from components instead of using service wrappers.
361
-
362
- **Why it's bad**: Scatters API endpoint knowledge throughout codebase, makes refactoring difficult, no centralized error handling.
363
-
364
- **Instead, do this**: Create API service wrappers.
365
-
366
- ```typescript
367
- // ❌ BAD: Direct fetch in component
368
- function MyComponent() {
369
- useEffect(() => {
370
- fetch('/api/integrations') // BAD!
371
- .then(res => res.json())
372
- .then(data => setData(data))
373
- }, [])
374
- }
375
-
376
- // ✅ GOOD: Use service layer
377
- import { IntegrationsService } from '@/services/integrations.service'
378
-
379
- function MyComponent() {
380
- useEffect(() => {
381
- IntegrationsService.getUserIntegrations() // GOOD!
382
- .then(integrations => setData(integrations))
383
- }, [])
384
- }
385
- ```
386
-
387
- ### ❌ Anti-Pattern 3: Mixing UI Logic in Services
388
-
389
- **Description**: Putting UI concerns (toasts, navigation, etc.) inside service methods.
390
-
391
- **Why it's bad**: Services should be pure data operations. UI logic belongs in components.
392
-
393
- **Instead, do this**: Return data from services and handle UI in components.
394
-
395
- ```typescript
396
- // ❌ BAD: Service doing UI logic
397
- export class UserDatabaseService {
398
- static async saveUser(user: User): Promise<void> {
399
- await setDocument(...)
400
- toast.success('User saved!') // UI logic in service!
401
- }
402
- }
403
-
404
- // ✅ GOOD: Service returns data, component handles UI
405
- export class UserDatabaseService {
406
- static async saveUser(user: User): Promise<void> {
407
- await setDocument(...)
408
- // No UI logic
409
- }
410
- }
411
-
412
- // Component handles UI
413
- function MyComponent() {
414
- const handleSave = async () => {
415
- await UserDatabaseService.saveUser(user)
416
- toast.success('User saved!') // UI logic in component
417
- }
418
- }
419
- ```
420
-
421
- ---
422
-
423
- ## Testing Strategy
424
-
425
- ### Unit Testing Services
426
-
427
- ```typescript
428
- // Test database service with mocked Firestore
429
- jest.mock('@prmichaelsen/firebase-admin-sdk-v8')
430
-
431
- describe('OAuthIntegrationDatabaseService', () => {
432
- it('should get integration', async () => {
433
- const mockDoc = { connected: true, provider: 'instagram' }
434
- getDocument.mockResolvedValue(mockDoc)
435
-
436
- const result = await OAuthIntegrationDatabaseService.getIntegration('user1', 'instagram')
437
-
438
- expect(result).toEqual(mockDoc)
439
- expect(getDocument).toHaveBeenCalledWith(
440
- expect.stringContaining('user1'),
441
- 'current'
442
- )
443
- })
444
- })
445
- ```
446
-
447
- ### Integration Testing Components
448
-
449
- ```typescript
450
- // Test component with mocked API service
451
- jest.mock('@/services/integrations.service')
452
-
453
- describe('IntegrationsPanel', () => {
454
- it('should load integrations', async () => {
455
- IntegrationsService.getUserIntegrations.mockResolvedValue({
456
- instagram: { connected: true }
457
- })
458
-
459
- render(<IntegrationsPanel />)
460
-
461
- await waitFor(() => {
462
- expect(screen.getByText(/instagram/i)).toBeInTheDocument()
463
- })
464
- })
465
- })
466
- ```
467
-
468
- ---
469
-
470
- ## Related Patterns
471
-
472
- - **[User-Scoped Collections](./tanstack-cloudflare.user-scoped-collections.md)**: Database services use user-scoped collection paths for data isolation
473
- - **[SSR Preload Pattern](./tanstack-cloudflare.ssr-preload.md)**: Database services are used in `beforeLoad` for server-side data preloading
474
-
475
- ---
476
-
477
- ## Migration Guide
478
-
479
- ### Step 1: Identify Direct Calls
480
-
481
- Search codebase for:
482
- - `setDocument(`
483
- - `getDocument(`
484
- - `queryDocuments(`
485
- - `fetch('/api/`
486
-
487
- ### Step 2: Create Services
488
-
489
- ```typescript
490
- // src/services/domain-database.service.ts
491
- export class DomainDatabaseService {
492
- static async operation(): Promise<Result> {
493
- // Move database logic here
494
- }
495
- }
496
-
497
- // src/services/domain.service.ts
498
- export class DomainService {
499
- static async operation(): Promise<Result> {
500
- // Move API logic here
501
- }
502
- }
503
- ```
504
-
505
- ### Step 3: Update Callers
506
-
507
- ```typescript
508
- // Before
509
- await setDocument(path, id, data)
510
-
511
- // After (in API route)
512
- await DomainDatabaseService.saveEntity(id, data)
513
-
514
- // After (in component)
515
- await DomainService.saveEntity(id, data)
516
- ```
517
-
518
- ### Step 4: Test
519
-
520
- - Verify functionality unchanged
521
- - Add unit tests for services
522
- - Mock services in component tests
523
-
524
- ---
525
-
526
- ## References
527
-
528
- - [TanStack Start Documentation](https://tanstack.com/start/latest)
529
- - [Cloudflare Workers](https://developers.cloudflare.com/workers/)
530
- - [firebase-admin-sdk-v8](https://github.com/prmichaelsen/firebase-admin-sdk-v8)
531
- - [Martin Fowler - Service Layer](https://martinfowler.com/eaaCatalog/serviceLayer.html)
532
-
533
- ---
534
-
535
- ## Checklist for Implementation
536
-
537
- - [ ] Database services use `{Domain}DatabaseService` naming
538
- - [ ] API services use `{Domain}Service` naming
539
- - [ ] Same method names across both service types
540
- - [ ] No direct `getDocument`/`setDocument` calls outside services
541
- - [ ] No direct `fetch` calls outside services
542
- - [ ] Services handle Zod validation
543
- - [ ] Services log errors appropriately
544
- - [ ] Services return typed data models
545
- - [ ] Unit tests cover service logic
546
- - [ ] Components mock services in tests
547
-
548
- ---
549
-
550
- **Status**: Stable - Proven pattern for TanStack Start + Cloudflare applications
551
- **Recommendation**: Use for all TanStack Start applications with server-side data access
552
- **Last Updated**: 2026-02-21
553
- **Contributors**: Patrick Michaelsen