@prmichaelsen/acp-visualizer 0.1.0 → 0.1.1

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