@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.
Files changed (180) hide show
  1. package/README.md +68 -0
  2. package/agent/commands/acp.clarification-address.md +417 -0
  3. package/agent/commands/acp.clarification-capture.md +386 -0
  4. package/agent/commands/acp.clarification-create.md +437 -0
  5. package/agent/commands/acp.clarifications-research.md +326 -0
  6. package/agent/commands/acp.command-create.md +432 -0
  7. package/agent/commands/acp.design-create.md +286 -0
  8. package/agent/commands/acp.design-reference.md +355 -0
  9. package/agent/commands/acp.handoff.md +270 -0
  10. package/agent/commands/acp.index.md +423 -0
  11. package/agent/commands/acp.init.md +546 -0
  12. package/agent/commands/acp.package-create.md +895 -0
  13. package/agent/commands/acp.package-info.md +212 -0
  14. package/agent/commands/acp.package-install.md +539 -0
  15. package/agent/commands/acp.package-list.md +280 -0
  16. package/agent/commands/acp.package-publish.md +541 -0
  17. package/agent/commands/acp.package-remove.md +293 -0
  18. package/agent/commands/acp.package-search.md +307 -0
  19. package/agent/commands/acp.package-update.md +361 -0
  20. package/agent/commands/acp.package-validate.md +540 -0
  21. package/agent/commands/acp.pattern-create.md +386 -0
  22. package/agent/commands/acp.plan.md +587 -0
  23. package/agent/commands/acp.proceed.md +882 -0
  24. package/agent/commands/acp.project-create.md +675 -0
  25. package/agent/commands/acp.project-info.md +312 -0
  26. package/agent/commands/acp.project-list.md +226 -0
  27. package/agent/commands/acp.project-remove.md +379 -0
  28. package/agent/commands/acp.project-set.md +227 -0
  29. package/agent/commands/acp.project-update.md +307 -0
  30. package/agent/commands/acp.projects-restore.md +228 -0
  31. package/agent/commands/acp.projects-sync.md +347 -0
  32. package/agent/commands/acp.report.md +407 -0
  33. package/agent/commands/acp.resume.md +239 -0
  34. package/agent/commands/acp.sessions.md +301 -0
  35. package/agent/commands/acp.status.md +293 -0
  36. package/agent/commands/acp.sync.md +364 -0
  37. package/agent/commands/acp.task-create.md +500 -0
  38. package/agent/commands/acp.update.md +302 -0
  39. package/agent/commands/acp.validate.md +466 -0
  40. package/agent/commands/acp.version-check-for-updates.md +276 -0
  41. package/agent/commands/acp.version-check.md +191 -0
  42. package/agent/commands/acp.version-update.md +289 -0
  43. package/agent/commands/command.template.md +339 -0
  44. package/agent/commands/git.commit.md +526 -0
  45. package/agent/commands/git.init.md +514 -0
  46. package/agent/commands/tanstack-cloudflare.deploy.md +272 -0
  47. package/agent/commands/tanstack-cloudflare.tail.md +275 -0
  48. package/agent/design/.gitkeep +0 -0
  49. package/agent/design/design.template.md +154 -0
  50. package/agent/design/local.dashboard-layout-routing.md +288 -0
  51. package/agent/design/local.data-model-yaml-parsing.md +310 -0
  52. package/agent/design/local.search-filtering.md +331 -0
  53. package/agent/design/local.server-api-auto-refresh.md +235 -0
  54. package/agent/design/local.table-tree-views.md +299 -0
  55. package/agent/design/local.visualizer-requirements.md +349 -0
  56. package/agent/design/requirements.template.md +387 -0
  57. package/agent/index/.gitkeep +0 -0
  58. package/agent/index/acp.core.yaml +137 -0
  59. package/agent/index/local.main.template.yaml +37 -0
  60. package/agent/manifest.template.yaml +13 -0
  61. package/agent/manifest.yaml +302 -0
  62. package/agent/milestones/.gitkeep +0 -0
  63. package/agent/milestones/milestone-1-project-scaffold-data-pipeline.md +67 -0
  64. package/agent/milestones/milestone-1-{title}.template.md +206 -0
  65. package/agent/milestones/milestone-2-dashboard-views-interaction.md +79 -0
  66. package/agent/package.template.yaml +86 -0
  67. package/agent/patterns/.gitkeep +0 -0
  68. package/agent/patterns/bootstrap.template.md +1237 -0
  69. package/agent/patterns/pattern.template.md +382 -0
  70. package/agent/patterns/tanstack-cloudflare.acl-permissions.md +332 -0
  71. package/agent/patterns/tanstack-cloudflare.action-bar-item.md +416 -0
  72. package/agent/patterns/tanstack-cloudflare.api-route-handlers.md +401 -0
  73. package/agent/patterns/tanstack-cloudflare.auth-session-management.md +387 -0
  74. package/agent/patterns/tanstack-cloudflare.card-and-list.md +271 -0
  75. package/agent/patterns/tanstack-cloudflare.chat-engine.md +353 -0
  76. package/agent/patterns/tanstack-cloudflare.confirmation-tokens.md +346 -0
  77. package/agent/patterns/tanstack-cloudflare.durable-objects-websocket.md +516 -0
  78. package/agent/patterns/tanstack-cloudflare.email-service.md +431 -0
  79. package/agent/patterns/tanstack-cloudflare.expander.md +98 -0
  80. package/agent/patterns/tanstack-cloudflare.fcm-push.md +115 -0
  81. package/agent/patterns/tanstack-cloudflare.firebase-anonymous-sessions.md +441 -0
  82. package/agent/patterns/tanstack-cloudflare.firebase-auth.md +348 -0
  83. package/agent/patterns/tanstack-cloudflare.firebase-firestore.md +550 -0
  84. package/agent/patterns/tanstack-cloudflare.firebase-storage.md +369 -0
  85. package/agent/patterns/tanstack-cloudflare.form-controls.md +145 -0
  86. package/agent/patterns/tanstack-cloudflare.global-search-context.md +93 -0
  87. package/agent/patterns/tanstack-cloudflare.image-carousel.md +126 -0
  88. package/agent/patterns/tanstack-cloudflare.library-services.md +553 -0
  89. package/agent/patterns/tanstack-cloudflare.lightbox.md +169 -0
  90. package/agent/patterns/tanstack-cloudflare.markdown-content.md +115 -0
  91. package/agent/patterns/tanstack-cloudflare.mention-suggestions.md +98 -0
  92. package/agent/patterns/tanstack-cloudflare.modal.md +156 -0
  93. package/agent/patterns/tanstack-cloudflare.nextjs-to-tanstack-routing.md +461 -0
  94. package/agent/patterns/tanstack-cloudflare.notifications-engine.md +151 -0
  95. package/agent/patterns/tanstack-cloudflare.oauth-token-refresh.md +90 -0
  96. package/agent/patterns/tanstack-cloudflare.og-metadata.md +296 -0
  97. package/agent/patterns/tanstack-cloudflare.pagination.md +442 -0
  98. package/agent/patterns/tanstack-cloudflare.pill-input.md +220 -0
  99. package/agent/patterns/tanstack-cloudflare.provider-adapter.md +401 -0
  100. package/agent/patterns/tanstack-cloudflare.rate-limiting.md +323 -0
  101. package/agent/patterns/tanstack-cloudflare.scheduled-tasks.md +338 -0
  102. package/agent/patterns/tanstack-cloudflare.searchable-settings.md +375 -0
  103. package/agent/patterns/tanstack-cloudflare.slide-over.md +129 -0
  104. package/agent/patterns/tanstack-cloudflare.ssr-preload.md +571 -0
  105. package/agent/patterns/tanstack-cloudflare.third-party-api-integration.md +508 -0
  106. package/agent/patterns/tanstack-cloudflare.toast-system.md +142 -0
  107. package/agent/patterns/tanstack-cloudflare.unified-header.md +280 -0
  108. package/agent/patterns/tanstack-cloudflare.user-scoped-collections.md +628 -0
  109. package/agent/patterns/tanstack-cloudflare.websocket-manager.md +237 -0
  110. package/agent/patterns/tanstack-cloudflare.wrangler-configuration.md +358 -0
  111. package/agent/patterns/tanstack-cloudflare.zod-schema-validation.md +336 -0
  112. package/agent/progress.template.yaml +161 -0
  113. package/agent/progress.yaml +145 -0
  114. package/agent/schemas/package.schema.yaml +276 -0
  115. package/agent/scripts/acp.common.sh +1781 -0
  116. package/agent/scripts/acp.install.sh +333 -0
  117. package/agent/scripts/acp.package-create.sh +924 -0
  118. package/agent/scripts/acp.package-info.sh +288 -0
  119. package/agent/scripts/acp.package-install.sh +893 -0
  120. package/agent/scripts/acp.package-list.sh +311 -0
  121. package/agent/scripts/acp.package-publish.sh +420 -0
  122. package/agent/scripts/acp.package-remove.sh +348 -0
  123. package/agent/scripts/acp.package-search.sh +156 -0
  124. package/agent/scripts/acp.package-update.sh +517 -0
  125. package/agent/scripts/acp.package-validate.sh +1018 -0
  126. package/agent/scripts/acp.uninstall.sh +85 -0
  127. package/agent/scripts/acp.version-check-for-updates.sh +98 -0
  128. package/agent/scripts/acp.version-check.sh +47 -0
  129. package/agent/scripts/acp.version-update.sh +176 -0
  130. package/agent/scripts/acp.yaml-parser.sh +985 -0
  131. package/agent/scripts/acp.yaml-validate.sh +205 -0
  132. package/agent/tasks/.gitkeep +0 -0
  133. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-1-initialize-tanstack-start-project.md +210 -0
  134. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-2-implement-data-model-yaml-parser.md +294 -0
  135. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-3-build-server-api-data-loading.md +193 -0
  136. package/agent/tasks/milestone-1-project-scaffold-data-pipeline/task-4-add-auto-refresh-sse.md +262 -0
  137. package/agent/tasks/milestone-2-dashboard-views-interaction/task-10-polish-integration-testing.md +156 -0
  138. package/agent/tasks/milestone-2-dashboard-views-interaction/task-5-build-dashboard-layout-routing.md +178 -0
  139. package/agent/tasks/milestone-2-dashboard-views-interaction/task-6-build-overview-page.md +141 -0
  140. package/agent/tasks/milestone-2-dashboard-views-interaction/task-7-implement-milestone-table-view.md +153 -0
  141. package/agent/tasks/milestone-2-dashboard-views-interaction/task-8-implement-milestone-tree-view.md +174 -0
  142. package/agent/tasks/milestone-2-dashboard-views-interaction/task-9-implement-search-filtering.md +233 -0
  143. package/agent/tasks/task-1-{title}.template.md +244 -0
  144. package/bin/visualize.mjs +84 -0
  145. package/package.json +48 -0
  146. package/src/components/ExtraFieldsBadge.tsx +15 -0
  147. package/src/components/FilterBar.tsx +33 -0
  148. package/src/components/Header.tsx +23 -0
  149. package/src/components/MilestoneTable.tsx +167 -0
  150. package/src/components/MilestoneTree.tsx +84 -0
  151. package/src/components/ProgressBar.tsx +20 -0
  152. package/src/components/SearchInput.tsx +22 -0
  153. package/src/components/Sidebar.tsx +54 -0
  154. package/src/components/StatusBadge.tsx +23 -0
  155. package/src/components/StatusDot.tsx +12 -0
  156. package/src/components/TaskList.tsx +36 -0
  157. package/src/components/ViewToggle.tsx +31 -0
  158. package/src/lib/config.ts +8 -0
  159. package/src/lib/file-watcher.ts +43 -0
  160. package/src/lib/search.ts +48 -0
  161. package/src/lib/types.ts +73 -0
  162. package/src/lib/useAutoRefresh.ts +31 -0
  163. package/src/lib/useCollapse.ts +31 -0
  164. package/src/lib/useFilteredData.ts +55 -0
  165. package/src/lib/yaml-loader-real.spec.ts +47 -0
  166. package/src/lib/yaml-loader.spec.ts +201 -0
  167. package/src/lib/yaml-loader.ts +265 -0
  168. package/src/routeTree.gen.ts +140 -0
  169. package/src/router.tsx +10 -0
  170. package/src/routes/__root.tsx +75 -0
  171. package/src/routes/api/watch.ts +29 -0
  172. package/src/routes/index.tsx +115 -0
  173. package/src/routes/milestones.tsx +50 -0
  174. package/src/routes/search.tsx +84 -0
  175. package/src/routes/tasks.tsx +63 -0
  176. package/src/services/progress-database.service.ts +46 -0
  177. package/src/styles.css +25 -0
  178. package/tsconfig.json +24 -0
  179. package/vite.config.ts +16 -0
  180. package/vitest.config.ts +27 -0
@@ -0,0 +1,126 @@
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
@@ -0,0 +1,553 @@
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