@prmichaelsen/remember-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +65 -0
- package/AGENT.md +840 -0
- package/README.md +72 -0
- package/agent/design/.gitkeep +0 -0
- package/agent/design/access-control-result-pattern.md +458 -0
- package/agent/design/action-audit-memory-types.md +637 -0
- package/agent/design/common-template-fields.md +282 -0
- package/agent/design/complete-tool-set.md +407 -0
- package/agent/design/content-types-expansion.md +521 -0
- package/agent/design/cross-database-id-strategy.md +358 -0
- package/agent/design/default-template-library.md +423 -0
- package/agent/design/firestore-wrapper-analysis.md +606 -0
- package/agent/design/llm-provider-abstraction.md +691 -0
- package/agent/design/location-handling-architecture.md +523 -0
- package/agent/design/memory-templates-design.md +364 -0
- package/agent/design/permissions-storage-architecture.md +680 -0
- package/agent/design/relationship-storage-strategy.md +361 -0
- package/agent/design/remember-mcp-implementation-tasks.md +417 -0
- package/agent/design/remember-mcp-progress.yaml +141 -0
- package/agent/design/requirements-enhancements.md +468 -0
- package/agent/design/requirements.md +56 -0
- package/agent/design/template-storage-strategy.md +412 -0
- package/agent/design/template-suggestion-system.md +853 -0
- package/agent/design/trust-escalation-prevention.md +343 -0
- package/agent/design/trust-system-implementation.md +592 -0
- package/agent/design/user-preferences.md +683 -0
- package/agent/design/weaviate-collection-strategy.md +461 -0
- package/agent/milestones/.gitkeep +0 -0
- package/agent/milestones/milestone-1-project-foundation.md +121 -0
- package/agent/milestones/milestone-2-core-memory-system.md +150 -0
- package/agent/milestones/milestone-3-relationships-graph.md +116 -0
- package/agent/milestones/milestone-4-user-preferences.md +103 -0
- package/agent/milestones/milestone-5-template-system.md +126 -0
- package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
- package/agent/milestones/milestone-7-trust-permissions.md +133 -0
- package/agent/milestones/milestone-8-testing-quality.md +137 -0
- package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
- package/agent/patterns/.gitkeep +0 -0
- package/agent/patterns/bootstrap.md +1271 -0
- package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
- package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
- package/agent/patterns/library-services.md +454 -0
- package/agent/patterns/testing-colocated.md +316 -0
- package/agent/progress.yaml +395 -0
- package/agent/tasks/.gitkeep +0 -0
- package/agent/tasks/task-1-initialize-project-structure.md +266 -0
- package/agent/tasks/task-2-install-dependencies.md +199 -0
- package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
- package/agent/tasks/task-4-setup-firestore-client.md +362 -0
- package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
- package/agent/tasks/task-6-create-integration-tests.md +195 -0
- package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
- package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
- package/agent/tasks/task-9-create-server-factory.md +404 -0
- package/dist/config.d.ts +26 -0
- package/dist/constants/content-types.d.ts +60 -0
- package/dist/firestore/init.d.ts +14 -0
- package/dist/firestore/paths.d.ts +53 -0
- package/dist/firestore/paths.spec.d.ts +2 -0
- package/dist/server-factory.d.ts +40 -0
- package/dist/server-factory.js +1741 -0
- package/dist/server-factory.spec.d.ts +2 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +1690 -0
- package/dist/tools/create-memory.d.ts +94 -0
- package/dist/tools/delete-memory.d.ts +47 -0
- package/dist/tools/search-memory.d.ts +88 -0
- package/dist/types/memory.d.ts +183 -0
- package/dist/utils/logger.d.ts +7 -0
- package/dist/weaviate/client.d.ts +39 -0
- package/dist/weaviate/client.spec.d.ts +2 -0
- package/dist/weaviate/schema.d.ts +29 -0
- package/esbuild.build.js +60 -0
- package/esbuild.watch.js +25 -0
- package/jest.config.js +31 -0
- package/jest.e2e.config.js +17 -0
- package/package.json +68 -0
- package/src/.gitkeep +0 -0
- package/src/config.ts +56 -0
- package/src/constants/content-types.ts +454 -0
- package/src/firestore/init.ts +68 -0
- package/src/firestore/paths.spec.ts +75 -0
- package/src/firestore/paths.ts +124 -0
- package/src/server-factory.spec.ts +60 -0
- package/src/server-factory.ts +215 -0
- package/src/server.ts +243 -0
- package/src/tools/create-memory.ts +198 -0
- package/src/tools/delete-memory.ts +126 -0
- package/src/tools/search-memory.ts +216 -0
- package/src/types/memory.ts +276 -0
- package/src/utils/logger.ts +42 -0
- package/src/weaviate/client.spec.ts +58 -0
- package/src/weaviate/client.ts +114 -0
- package/src/weaviate/schema.ts +288 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
# Library Services Pattern
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
All data access operations (API calls, Firestore operations, external services) must go through dedicated service layer libraries. Direct calls from components, routes, or other non-service code are anti-patterns.
|
|
6
|
+
|
|
7
|
+
## Service Types
|
|
8
|
+
|
|
9
|
+
### 1. Database Services
|
|
10
|
+
**Purpose**: Direct Firestore/database operations
|
|
11
|
+
**Naming**: `{Domain}DatabaseService`
|
|
12
|
+
**File**: `{domain}-database.service.ts`
|
|
13
|
+
**Used By**: API routes, beforeLoad, server functions, cron jobs
|
|
14
|
+
|
|
15
|
+
**Characteristics**:
|
|
16
|
+
- Directly calls `getDocument`, `setDocument`, `queryDocuments`
|
|
17
|
+
- Server-side only (uses firebase-admin-sdk)
|
|
18
|
+
- Handles Zod validation
|
|
19
|
+
- Manages timestamps (created_at, updated_at)
|
|
20
|
+
- Returns typed data models
|
|
21
|
+
|
|
22
|
+
**Example**: `OAuthIntegrationDatabaseService`, `ConversationDatabaseService`
|
|
23
|
+
|
|
24
|
+
### 2. API Services (Client Wrappers)
|
|
25
|
+
**Purpose**: Wrap API endpoint calls for client-side use
|
|
26
|
+
**Naming**: `{Domain}Service`
|
|
27
|
+
**File**: `{domain}.service.ts`
|
|
28
|
+
**Used By**: Components, client-side hooks
|
|
29
|
+
|
|
30
|
+
**Characteristics**:
|
|
31
|
+
- Calls `fetch('/api/...')`
|
|
32
|
+
- Client-side safe
|
|
33
|
+
- Handles HTTP errors
|
|
34
|
+
- Returns typed data models
|
|
35
|
+
|
|
36
|
+
**Example**: `IntegrationsService`, `UserService`
|
|
37
|
+
|
|
38
|
+
## Naming Convention
|
|
39
|
+
|
|
40
|
+
**Key Insight**: Service class names indicate scope - no method suffixes needed!
|
|
41
|
+
|
|
42
|
+
### Database Services
|
|
43
|
+
- **File**: `oauth-integration-database.service.ts`
|
|
44
|
+
- **Class**: `OAuthIntegrationDatabaseService`
|
|
45
|
+
- **Methods**: `getIntegration()`, `saveIntegration()`, `getUserIntegrations()`
|
|
46
|
+
|
|
47
|
+
### API Services
|
|
48
|
+
- **File**: `integrations.service.ts`
|
|
49
|
+
- **Class**: `IntegrationsService`
|
|
50
|
+
- **Methods**: `getUserIntegrations()` (same name as database service!)
|
|
51
|
+
|
|
52
|
+
### Benefits
|
|
53
|
+
|
|
54
|
+
✅ **No method suffixes** - class name indicates scope
|
|
55
|
+
✅ **Same method names** across services - consistent API
|
|
56
|
+
✅ **Clear separation** - `DatabaseService` vs `Service`
|
|
57
|
+
✅ **Import errors prevent misuse** - can't accidentally use database service in component
|
|
58
|
+
✅ **Self-documenting** - name tells you everything
|
|
59
|
+
|
|
60
|
+
## Architecture Diagram
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
┌─────────────────────────────────────────────────────────┐
|
|
64
|
+
│ Components │
|
|
65
|
+
│ (Client-Side Only) │
|
|
66
|
+
└────────────────────┬────────────────────────────────────┘
|
|
67
|
+
│
|
|
68
|
+
│ calls
|
|
69
|
+
▼
|
|
70
|
+
┌─────────────────────────────────────────────────────────┐
|
|
71
|
+
│ IntegrationsService │
|
|
72
|
+
│ (API Service - Client Layer) │
|
|
73
|
+
│ - getUserIntegrations() │
|
|
74
|
+
│ - Calls fetch('/api/integrations') │
|
|
75
|
+
└────────────────────┬────────────────────────────────────┘
|
|
76
|
+
│
|
|
77
|
+
│ HTTP
|
|
78
|
+
▼
|
|
79
|
+
┌─────────────────────────────────────────────────────────┐
|
|
80
|
+
│ API Routes │
|
|
81
|
+
│ (Server-Side Handlers) │
|
|
82
|
+
│ - /api/integrations/ │
|
|
83
|
+
│ - Validates session │
|
|
84
|
+
└────────────────────┬────────────────────────────────────┘
|
|
85
|
+
│
|
|
86
|
+
│ calls
|
|
87
|
+
▼
|
|
88
|
+
┌─────────────────────────────────────────────────────────┐
|
|
89
|
+
│ OAuthIntegrationDatabaseService │
|
|
90
|
+
│ (Database Service - Data Layer) │
|
|
91
|
+
│ - getUserIntegrations() │
|
|
92
|
+
│ - Calls getDocument(), setDocument() │
|
|
93
|
+
└────────────────────┬────────────────────────────────────┘
|
|
94
|
+
│
|
|
95
|
+
│ queries
|
|
96
|
+
▼
|
|
97
|
+
┌─────────────────────────────────────────────────────────┐
|
|
98
|
+
│ Firestore │
|
|
99
|
+
│ (Database Layer) │
|
|
100
|
+
└─────────────────────────────────────────────────────────┘
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## When to Use Each Type
|
|
104
|
+
|
|
105
|
+
### Use Database Services When:
|
|
106
|
+
- ✅ In API route handlers (server-side)
|
|
107
|
+
- ✅ In beforeLoad (server-side rendering)
|
|
108
|
+
- ✅ In server functions
|
|
109
|
+
- ✅ In other services (service can call service)
|
|
110
|
+
- ✅ In cron jobs or background tasks
|
|
111
|
+
|
|
112
|
+
### Use API Services When:
|
|
113
|
+
- ✅ In React components (client-side)
|
|
114
|
+
- ✅ In `useEffect` hooks
|
|
115
|
+
- ✅ In event handlers (onClick, onSubmit)
|
|
116
|
+
- ✅ In custom hooks
|
|
117
|
+
|
|
118
|
+
## Example Implementation
|
|
119
|
+
|
|
120
|
+
### Database Service
|
|
121
|
+
|
|
122
|
+
**File**: `src/services/oauth-integration-database.service.ts`
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8'
|
|
126
|
+
import { getUserOAuthIntegration } from '@/constant/collections'
|
|
127
|
+
import { OAuthIntegrationSchema, type OAuthIntegration } from '@/schemas/oauth-integration'
|
|
128
|
+
|
|
129
|
+
export class OAuthIntegrationDatabaseService {
|
|
130
|
+
static async getIntegration(userId: string, provider: string): Promise<OAuthIntegration | null> {
|
|
131
|
+
try {
|
|
132
|
+
const path = getUserOAuthIntegration(userId, provider)
|
|
133
|
+
const doc = await getDocument(path, 'current')
|
|
134
|
+
|
|
135
|
+
if (!doc) return null
|
|
136
|
+
|
|
137
|
+
const result = OAuthIntegrationSchema.safeParse(doc)
|
|
138
|
+
if (!result.success) {
|
|
139
|
+
console.error(`Invalid OAuth integration data for ${provider}:`, result.error)
|
|
140
|
+
return null
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result.data
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(`Failed to get OAuth integration for ${provider}:`, error)
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
static async saveIntegration(userId: string, provider: string, data: OAuthIntegrationInput): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const path = getUserOAuthIntegration(userId, provider)
|
|
153
|
+
const now = new Date().toISOString()
|
|
154
|
+
|
|
155
|
+
const integration: OAuthIntegration = {
|
|
156
|
+
...data,
|
|
157
|
+
connected_at: now,
|
|
158
|
+
created_at: now,
|
|
159
|
+
updated_at: now,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await setDocument(path, 'current', integration)
|
|
163
|
+
console.log(`[OAuthIntegrationDatabaseService] Saved ${provider} integration for user ${userId}`)
|
|
164
|
+
} catch (error) {
|
|
165
|
+
console.error(`[OAuthIntegrationDatabaseService] Failed to save ${provider} integration:`, error)
|
|
166
|
+
throw error
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static async getUserIntegrations(userId: string, providers: string[]): Promise<Record<string, OAuthIntegration>> {
|
|
171
|
+
const integrations: Record<string, OAuthIntegration> = {}
|
|
172
|
+
|
|
173
|
+
await Promise.all(
|
|
174
|
+
providers.map(async (provider) => {
|
|
175
|
+
const integration = await this.getIntegration(userId, provider)
|
|
176
|
+
if (integration && integration.connected) {
|
|
177
|
+
integrations[provider] = integration
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
return integrations
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### API Service
|
|
188
|
+
|
|
189
|
+
**File**: `src/services/integrations.service.ts`
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { OAuthIntegrationDatabaseService } from './oauth-integration-database.service'
|
|
193
|
+
import type { OAuthIntegration } from '@/schemas/oauth-integration'
|
|
194
|
+
|
|
195
|
+
export class IntegrationsService {
|
|
196
|
+
/**
|
|
197
|
+
* Get user's OAuth integrations (client-side)
|
|
198
|
+
* Calls the API endpoint which validates session server-side
|
|
199
|
+
*/
|
|
200
|
+
static async getUserIntegrations(): Promise<Record<string, OAuthIntegration>> {
|
|
201
|
+
try {
|
|
202
|
+
const response = await fetch('/api/integrations/')
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
throw new Error(`Failed to fetch integrations: ${response.statusText}`)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const data: any = await response.json()
|
|
209
|
+
return data.integrations || {}
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('[IntegrationsService] Failed to fetch integrations:', error)
|
|
212
|
+
return {}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Usage Examples
|
|
219
|
+
|
|
220
|
+
### In Component (Client-Side)
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { IntegrationsService } from '@/services/integrations.service'
|
|
224
|
+
|
|
225
|
+
function MyComponent() {
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
IntegrationsService.getUserIntegrations() // Calls API
|
|
228
|
+
.then(integrations => setUserIntegrations(integrations))
|
|
229
|
+
}, [user])
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### In API Route (Server-Side)
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { OAuthIntegrationDatabaseService } from '@/services/oauth-integration-database.service'
|
|
237
|
+
|
|
238
|
+
export const APIRoute = createAPIFileRoute('/api/integrations')({
|
|
239
|
+
GET: async ({ request }) => {
|
|
240
|
+
const session = await getServerSession(request)
|
|
241
|
+
|
|
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
|
+
### In beforeLoad (Server-Side)
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { OAuthIntegrationDatabaseService } from '@/services/oauth-integration-database.service'
|
|
256
|
+
|
|
257
|
+
export const Route = createFileRoute('/integrations')({
|
|
258
|
+
beforeLoad: async () => {
|
|
259
|
+
const user = await getAuthSession()
|
|
260
|
+
if (!user) return { initialIntegrations: {} }
|
|
261
|
+
|
|
262
|
+
const initialIntegrations = await OAuthIntegrationDatabaseService.getUserIntegrations(
|
|
263
|
+
user.uid,
|
|
264
|
+
['instagram', 'eventbrite']
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
return { initialIntegrations }
|
|
268
|
+
},
|
|
269
|
+
})
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Core Principles
|
|
273
|
+
|
|
274
|
+
### 1. Service Layer Abstraction
|
|
275
|
+
- **All data operations** must be encapsulated in service classes
|
|
276
|
+
- Services provide a clean API for data access
|
|
277
|
+
- Services handle error logging and validation
|
|
278
|
+
- Services can be easily mocked for testing
|
|
279
|
+
|
|
280
|
+
### 2. No Direct Database Calls
|
|
281
|
+
```typescript
|
|
282
|
+
// ❌ ANTI-PATTERN: Direct Firestore call in component
|
|
283
|
+
import { setDocument } from '@prmichaelsen/firebase-admin-sdk-v8'
|
|
284
|
+
|
|
285
|
+
function MyComponent() {
|
|
286
|
+
const handleSave = async () => {
|
|
287
|
+
await setDocument('users', userId, data) // BAD!
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ✅ CORRECT: Use service layer
|
|
292
|
+
import { UserDatabaseService } from '@/services/user-database.service'
|
|
293
|
+
|
|
294
|
+
function MyComponent() {
|
|
295
|
+
const handleSave = async () => {
|
|
296
|
+
await UserDatabaseService.updateUser(userId, data) // GOOD!
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### 3. No Direct API Calls
|
|
302
|
+
```typescript
|
|
303
|
+
// ❌ ANTI-PATTERN: Direct fetch in component
|
|
304
|
+
function MyComponent() {
|
|
305
|
+
useEffect(() => {
|
|
306
|
+
fetch('/api/integrations') // BAD!
|
|
307
|
+
.then(res => res.json())
|
|
308
|
+
.then(data => setData(data))
|
|
309
|
+
}, [])
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ✅ CORRECT: Use service layer
|
|
313
|
+
import { IntegrationsService } from '@/services/integrations.service'
|
|
314
|
+
|
|
315
|
+
function MyComponent() {
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
IntegrationsService.getUserIntegrations() // GOOD!
|
|
318
|
+
.then(integrations => setData(integrations))
|
|
319
|
+
}, [])
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Benefits
|
|
324
|
+
|
|
325
|
+
### 1. Testability
|
|
326
|
+
```typescript
|
|
327
|
+
// Easy to mock services in tests
|
|
328
|
+
jest.mock('@/services/integrations.service')
|
|
329
|
+
|
|
330
|
+
test('component loads integrations', async () => {
|
|
331
|
+
IntegrationsService.getUserIntegrations.mockResolvedValue({ instagram: {...} })
|
|
332
|
+
// Test component behavior
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 2. Consistency
|
|
337
|
+
- All Firestore operations follow same pattern
|
|
338
|
+
- Consistent error handling and logging
|
|
339
|
+
- Consistent Zod validation
|
|
340
|
+
|
|
341
|
+
### 3. Maintainability
|
|
342
|
+
- Change database structure in one place
|
|
343
|
+
- Update API endpoints in one place
|
|
344
|
+
- Easy to add caching, retry logic, etc.
|
|
345
|
+
|
|
346
|
+
### 4. Type Safety
|
|
347
|
+
- Services provide typed interfaces
|
|
348
|
+
- No `any` types leaking into components
|
|
349
|
+
- Zod validation at service boundary
|
|
350
|
+
|
|
351
|
+
## Anti-Patterns to Avoid
|
|
352
|
+
|
|
353
|
+
### ❌ Direct Firestore in Components
|
|
354
|
+
```typescript
|
|
355
|
+
// BAD
|
|
356
|
+
function MyComponent() {
|
|
357
|
+
const handleSave = async () => {
|
|
358
|
+
await setDocument('users', userId, data)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### ❌ Direct Firestore in Routes
|
|
364
|
+
```typescript
|
|
365
|
+
// BAD
|
|
366
|
+
export const Route = createFileRoute('/users')({
|
|
367
|
+
beforeLoad: async () => {
|
|
368
|
+
const doc = await getDocument('users', userId)
|
|
369
|
+
return { user: doc }
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### ❌ Direct fetch in Components
|
|
375
|
+
```typescript
|
|
376
|
+
// BAD
|
|
377
|
+
function MyComponent() {
|
|
378
|
+
useEffect(() => {
|
|
379
|
+
fetch('/api/data').then(...)
|
|
380
|
+
}, [])
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
### ❌ Mixing Concerns
|
|
385
|
+
```typescript
|
|
386
|
+
// BAD: Service doing UI logic
|
|
387
|
+
static async saveUser(user: User): Promise<void> {
|
|
388
|
+
await setDocument(...)
|
|
389
|
+
toast.success('User saved!') // UI logic in service!
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## Migration Guide
|
|
394
|
+
|
|
395
|
+
### Step 1: Identify Direct Calls
|
|
396
|
+
Search codebase for:
|
|
397
|
+
- `setDocument(`
|
|
398
|
+
- `getDocument(`
|
|
399
|
+
- `queryDocuments(`
|
|
400
|
+
- `fetch('/api/`
|
|
401
|
+
|
|
402
|
+
### Step 2: Create Services
|
|
403
|
+
```typescript
|
|
404
|
+
// src/services/domain-database.service.ts
|
|
405
|
+
export class DomainDatabaseService {
|
|
406
|
+
static async operation(): Promise<Result> {
|
|
407
|
+
// Move database logic here
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/services/domain.service.ts
|
|
412
|
+
export class DomainService {
|
|
413
|
+
static async operation(): Promise<Result> {
|
|
414
|
+
// Move API logic here
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Step 3: Update Callers
|
|
420
|
+
```typescript
|
|
421
|
+
// Before
|
|
422
|
+
await setDocument(path, id, data)
|
|
423
|
+
|
|
424
|
+
// After (in API route)
|
|
425
|
+
await DomainDatabaseService.saveEntity(id, data)
|
|
426
|
+
|
|
427
|
+
// After (in component)
|
|
428
|
+
await DomainService.saveEntity(id, data)
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### Step 4: Test
|
|
432
|
+
- Verify functionality unchanged
|
|
433
|
+
- Add unit tests for services
|
|
434
|
+
- Mock services in component tests
|
|
435
|
+
|
|
436
|
+
## Summary
|
|
437
|
+
|
|
438
|
+
✅ **DO**:
|
|
439
|
+
- Use `{Domain}DatabaseService` for database operations
|
|
440
|
+
- Use `{Domain}Service` for API wrappers
|
|
441
|
+
- Same method names across both service types
|
|
442
|
+
- Handle errors and validation in services
|
|
443
|
+
- Log operations in services
|
|
444
|
+
- Use Zod schemas for validation
|
|
445
|
+
|
|
446
|
+
❌ **DON'T**:
|
|
447
|
+
- Call `setDocument`, `getDocument`, etc. directly
|
|
448
|
+
- Call `fetch('/api/...` directly
|
|
449
|
+
- Mix UI logic with data logic
|
|
450
|
+
- Skip error handling
|
|
451
|
+
- Use `any` types
|
|
452
|
+
- Add method suffixes - let class name indicate scope
|
|
453
|
+
|
|
454
|
+
**Every data operation should go through a service layer.**
|