@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.
Files changed (95) hide show
  1. package/.env.example +65 -0
  2. package/AGENT.md +840 -0
  3. package/README.md +72 -0
  4. package/agent/design/.gitkeep +0 -0
  5. package/agent/design/access-control-result-pattern.md +458 -0
  6. package/agent/design/action-audit-memory-types.md +637 -0
  7. package/agent/design/common-template-fields.md +282 -0
  8. package/agent/design/complete-tool-set.md +407 -0
  9. package/agent/design/content-types-expansion.md +521 -0
  10. package/agent/design/cross-database-id-strategy.md +358 -0
  11. package/agent/design/default-template-library.md +423 -0
  12. package/agent/design/firestore-wrapper-analysis.md +606 -0
  13. package/agent/design/llm-provider-abstraction.md +691 -0
  14. package/agent/design/location-handling-architecture.md +523 -0
  15. package/agent/design/memory-templates-design.md +364 -0
  16. package/agent/design/permissions-storage-architecture.md +680 -0
  17. package/agent/design/relationship-storage-strategy.md +361 -0
  18. package/agent/design/remember-mcp-implementation-tasks.md +417 -0
  19. package/agent/design/remember-mcp-progress.yaml +141 -0
  20. package/agent/design/requirements-enhancements.md +468 -0
  21. package/agent/design/requirements.md +56 -0
  22. package/agent/design/template-storage-strategy.md +412 -0
  23. package/agent/design/template-suggestion-system.md +853 -0
  24. package/agent/design/trust-escalation-prevention.md +343 -0
  25. package/agent/design/trust-system-implementation.md +592 -0
  26. package/agent/design/user-preferences.md +683 -0
  27. package/agent/design/weaviate-collection-strategy.md +461 -0
  28. package/agent/milestones/.gitkeep +0 -0
  29. package/agent/milestones/milestone-1-project-foundation.md +121 -0
  30. package/agent/milestones/milestone-2-core-memory-system.md +150 -0
  31. package/agent/milestones/milestone-3-relationships-graph.md +116 -0
  32. package/agent/milestones/milestone-4-user-preferences.md +103 -0
  33. package/agent/milestones/milestone-5-template-system.md +126 -0
  34. package/agent/milestones/milestone-6-auth-multi-tenancy.md +124 -0
  35. package/agent/milestones/milestone-7-trust-permissions.md +133 -0
  36. package/agent/milestones/milestone-8-testing-quality.md +137 -0
  37. package/agent/milestones/milestone-9-deployment-documentation.md +147 -0
  38. package/agent/patterns/.gitkeep +0 -0
  39. package/agent/patterns/bootstrap.md +1271 -0
  40. package/agent/patterns/firebase-admin-sdk-v8-usage.md +950 -0
  41. package/agent/patterns/firestore-users-pattern-best-practices.md +347 -0
  42. package/agent/patterns/library-services.md +454 -0
  43. package/agent/patterns/testing-colocated.md +316 -0
  44. package/agent/progress.yaml +395 -0
  45. package/agent/tasks/.gitkeep +0 -0
  46. package/agent/tasks/task-1-initialize-project-structure.md +266 -0
  47. package/agent/tasks/task-2-install-dependencies.md +199 -0
  48. package/agent/tasks/task-3-setup-weaviate-client.md +330 -0
  49. package/agent/tasks/task-4-setup-firestore-client.md +362 -0
  50. package/agent/tasks/task-5-create-basic-mcp-server.md +114 -0
  51. package/agent/tasks/task-6-create-integration-tests.md +195 -0
  52. package/agent/tasks/task-7-finalize-milestone-1.md +363 -0
  53. package/agent/tasks/task-8-setup-utility-scripts.md +382 -0
  54. package/agent/tasks/task-9-create-server-factory.md +404 -0
  55. package/dist/config.d.ts +26 -0
  56. package/dist/constants/content-types.d.ts +60 -0
  57. package/dist/firestore/init.d.ts +14 -0
  58. package/dist/firestore/paths.d.ts +53 -0
  59. package/dist/firestore/paths.spec.d.ts +2 -0
  60. package/dist/server-factory.d.ts +40 -0
  61. package/dist/server-factory.js +1741 -0
  62. package/dist/server-factory.spec.d.ts +2 -0
  63. package/dist/server.d.ts +3 -0
  64. package/dist/server.js +1690 -0
  65. package/dist/tools/create-memory.d.ts +94 -0
  66. package/dist/tools/delete-memory.d.ts +47 -0
  67. package/dist/tools/search-memory.d.ts +88 -0
  68. package/dist/types/memory.d.ts +183 -0
  69. package/dist/utils/logger.d.ts +7 -0
  70. package/dist/weaviate/client.d.ts +39 -0
  71. package/dist/weaviate/client.spec.d.ts +2 -0
  72. package/dist/weaviate/schema.d.ts +29 -0
  73. package/esbuild.build.js +60 -0
  74. package/esbuild.watch.js +25 -0
  75. package/jest.config.js +31 -0
  76. package/jest.e2e.config.js +17 -0
  77. package/package.json +68 -0
  78. package/src/.gitkeep +0 -0
  79. package/src/config.ts +56 -0
  80. package/src/constants/content-types.ts +454 -0
  81. package/src/firestore/init.ts +68 -0
  82. package/src/firestore/paths.spec.ts +75 -0
  83. package/src/firestore/paths.ts +124 -0
  84. package/src/server-factory.spec.ts +60 -0
  85. package/src/server-factory.ts +215 -0
  86. package/src/server.ts +243 -0
  87. package/src/tools/create-memory.ts +198 -0
  88. package/src/tools/delete-memory.ts +126 -0
  89. package/src/tools/search-memory.ts +216 -0
  90. package/src/types/memory.ts +276 -0
  91. package/src/utils/logger.ts +42 -0
  92. package/src/weaviate/client.spec.ts +58 -0
  93. package/src/weaviate/client.ts +114 -0
  94. package/src/weaviate/schema.ts +288 -0
  95. package/tsconfig.json +26 -0
@@ -0,0 +1,606 @@
1
+ # Firestore Client Wrapper - Do We Need It?
2
+
3
+ **Question**: Should we create a Firestore client wrapper, or use firebase-admin-sdk-v8 directly?
4
+ **Created**: 2026-02-11
5
+ **Status**: Design Analysis
6
+
7
+ ---
8
+
9
+ ## The Question
10
+
11
+ The `@prmichaelsen/firebase-admin-sdk-v8` library already provides:
12
+ - `getDocument()`, `setDocument()`, `addDocument()`, `updateDocument()`, `deleteDocument()`
13
+ - `queryDocuments()` with filters, ordering, pagination
14
+ - `batchWrite()` for atomic operations
15
+ - `FieldValue` operations (increment, arrayUnion, etc.)
16
+ - `verifyIdToken()` for authentication
17
+
18
+ **Do we need a wrapper?** Or should we use it directly?
19
+
20
+ ---
21
+
22
+ ## Analysis
23
+
24
+ ### Option 1: Use firebase-admin-sdk-v8 Directly (No Wrapper)
25
+
26
+ ```typescript
27
+ // In tools or services
28
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
29
+
30
+ export async function getUserPreferences(userId: string) {
31
+ return await getDocument('user_preferences', userId);
32
+ }
33
+
34
+ export async function updatePreferences(userId: string, updates: any) {
35
+ await setDocument('user_preferences', userId, updates, { merge: true });
36
+ }
37
+ ```
38
+
39
+ **Pros**:
40
+ - ✅ **Simpler** - No extra abstraction layer
41
+ - ✅ **Less code** - Fewer files to maintain
42
+ - ✅ **Direct access** - Use library features directly
43
+ - ✅ **Clear API** - Library API is already clean
44
+ - ✅ **No duplication** - Don't reimplement what library provides
45
+
46
+ **Cons**:
47
+ - ❌ **Scattered initialization** - `initializeApp()` called in multiple places
48
+ - ❌ **No centralized error handling** - Each call handles errors separately
49
+ - ❌ **No testing helpers** - No mock-friendly interface
50
+ - ❌ **No connection state** - Can't check if initialized
51
+
52
+ ---
53
+
54
+ ### Option 2: Minimal Wrapper (Initialization Only)
55
+
56
+ ```typescript
57
+ // src/firestore/client.ts
58
+ import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';
59
+ import { config } from '../config.js';
60
+
61
+ let initialized = false;
62
+
63
+ export async function initFirestore(): Promise<void> {
64
+ if (initialized) return;
65
+
66
+ initializeApp({
67
+ serviceAccount: JSON.parse(config.firebase.serviceAccount),
68
+ projectId: config.firebase.projectId
69
+ });
70
+
71
+ initialized = true;
72
+ console.log('[Firestore] Initialized');
73
+ }
74
+
75
+ export function isFirestoreInitialized(): boolean {
76
+ return initialized;
77
+ }
78
+
79
+ // Re-export library functions for convenience
80
+ export {
81
+ getDocument,
82
+ setDocument,
83
+ addDocument,
84
+ updateDocument,
85
+ deleteDocument,
86
+ queryDocuments,
87
+ batchWrite,
88
+ FieldValue,
89
+ verifyIdToken
90
+ } from '@prmichaelsen/firebase-admin-sdk-v8';
91
+ ```
92
+
93
+ **Pros**:
94
+ - ✅ **Centralized initialization** - Single place to initialize
95
+ - ✅ **Connection state tracking** - Can check if initialized
96
+ - ✅ **Simple** - Just initialization + re-exports
97
+ - ✅ **Direct access** - Still use library functions directly
98
+ - ✅ **Easy testing** - Can mock initialization
99
+
100
+ **Cons**:
101
+ - ❌ **Minimal value** - Just wraps initialization
102
+ - ❌ **Extra import** - Need to import from wrapper instead of library
103
+
104
+ ---
105
+
106
+ ### Option 3: Service Layer (No Client Wrapper)
107
+
108
+ ```typescript
109
+ // src/services/user-preferences.service.ts
110
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
111
+
112
+ export class UserPreferencesService {
113
+ static async get(userId: string): Promise<UserPreferences> {
114
+ const doc = await getDocument('user_preferences', userId);
115
+ return doc || DEFAULT_PREFERENCES;
116
+ }
117
+
118
+ static async update(userId: string, updates: Partial<UserPreferences>): Promise<void> {
119
+ await setDocument('user_preferences', userId, {
120
+ ...updates,
121
+ updated_at: new Date().toISOString()
122
+ }, { merge: true });
123
+ }
124
+ }
125
+
126
+ // src/services/permissions.service.ts
127
+ import { getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
128
+
129
+ export class PermissionsService {
130
+ static async grant(ownerUserId: string, accessorUserId: string, trustLevel: number): Promise<void> {
131
+ const path = `user_permissions/${ownerUserId}/allowed_accessors`;
132
+ await setDocument(path, accessorUserId, {
133
+ trust_level: trustLevel,
134
+ granted_at: new Date().toISOString()
135
+ });
136
+ }
137
+
138
+ static async check(ownerUserId: string, accessorUserId: string): Promise<UserPermission | null> {
139
+ const path = `user_permissions/${ownerUserId}/allowed_accessors`;
140
+ return await getDocument(path, accessorUserId);
141
+ }
142
+ }
143
+ ```
144
+
145
+ **Pros**:
146
+ - ✅ **Domain-focused** - Services organized by business logic
147
+ - ✅ **Type-safe** - Each service has typed methods
148
+ - ✅ **Testable** - Can mock services easily
149
+ - ✅ **Clear responsibilities** - Each service handles one domain
150
+ - ✅ **No wrapper needed** - Use library directly in services
151
+ - ✅ **Follows agentbase.me pattern** - Proven approach
152
+
153
+ **Cons**:
154
+ - ❌ **More files** - One service per domain
155
+ - ❌ **Initialization scattered** - Each service might call initializeApp
156
+
157
+ ---
158
+
159
+ ## Recommendation: Service Layer Pattern (Option 3)
160
+
161
+ ### Why Service Layer is Better
162
+
163
+ **1. Firebase Admin SDK is Already a "Client"**
164
+ - The library provides clean functions: `getDocument()`, `setDocument()`, etc.
165
+ - No need to wrap what's already well-designed
166
+ - Adding a wrapper just adds indirection
167
+
168
+ **2. Security Rules Don't Apply to Admin SDK**
169
+ - Admin SDK **bypasses** Firestore security rules
170
+ - Security rules only apply to client SDKs (web, mobile)
171
+ - Admin SDK has full access to all data
172
+ - Security must be enforced in **application logic**, not database rules
173
+
174
+ **3. Service Layer Provides Better Organization**
175
+ - Groups related operations by domain
176
+ - Type-safe interfaces per service
177
+ - Clear business logic separation
178
+ - Easier to test and maintain
179
+
180
+ **4. Proven Pattern from agentbase.me**
181
+ - agentbase.me uses service classes successfully
182
+ - No Firestore client wrapper needed
183
+ - Services use firebase-admin-sdk-v8 directly
184
+ - Clean, maintainable code
185
+
186
+ ---
187
+
188
+ ## Proposed Architecture
189
+
190
+ ### Initialization (Minimal Wrapper)
191
+
192
+ ```typescript
193
+ // src/firestore/init.ts
194
+ import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';
195
+ import { config } from '../config.js';
196
+
197
+ let initialized = false;
198
+
199
+ export function initFirestore(): void {
200
+ if (initialized) return;
201
+
202
+ try {
203
+ initializeApp({
204
+ serviceAccount: JSON.parse(config.firebase.serviceAccount),
205
+ projectId: config.firebase.projectId
206
+ });
207
+
208
+ initialized = true;
209
+ console.log('[Firestore] Initialized');
210
+ } catch (error) {
211
+ console.error('[Firestore] Initialization failed:', error);
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ export function isFirestoreInitialized(): boolean {
217
+ return initialized;
218
+ }
219
+ ```
220
+
221
+ ### Service Classes
222
+
223
+ ```typescript
224
+ // src/services/user-preferences.service.ts
225
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
226
+ import type { UserPreferences } from '../types/preferences.js';
227
+
228
+ export class UserPreferencesService {
229
+ static async get(userId: string): Promise<UserPreferences> {
230
+ const doc = await getDocument('user_preferences', userId);
231
+ return doc || DEFAULT_PREFERENCES;
232
+ }
233
+
234
+ static async update(userId: string, updates: Partial<UserPreferences>): Promise<void> {
235
+ await setDocument('user_preferences', userId, {
236
+ ...updates,
237
+ updated_at: new Date().toISOString()
238
+ }, { merge: true });
239
+ }
240
+ }
241
+
242
+ // src/services/permissions.service.ts
243
+ import { getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
244
+ import type { UserPermission } from '../types/permissions.js';
245
+
246
+ export class PermissionsService {
247
+ static async grant(ownerUserId: string, accessorUserId: string, permission: UserPermission): Promise<void> {
248
+ const path = `user_permissions/${ownerUserId}/allowed_accessors`;
249
+ await setDocument(path, accessorUserId, permission);
250
+ }
251
+
252
+ static async check(ownerUserId: string, accessorUserId: string): Promise<UserPermission | null> {
253
+ const path = `user_permissions/${ownerUserId}/allowed_accessors`;
254
+ return await getDocument(path, accessorUserId);
255
+ }
256
+
257
+ static async list(ownerUserId: string): Promise<UserPermission[]> {
258
+ const path = `user_permissions/${ownerUserId}/allowed_accessors`;
259
+ const results = await queryDocuments(path, {
260
+ where: [{ field: 'revoked', op: '!=', value: true }],
261
+ orderBy: [{ field: 'trust_level', direction: 'DESCENDING' }]
262
+ });
263
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
264
+ }
265
+ }
266
+
267
+ // src/services/templates.service.ts
268
+ import { getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
269
+ import type { Template } from '../types/template.js';
270
+
271
+ export class TemplatesService {
272
+ static async getDefault(): Promise<Template[]> {
273
+ const results = await queryDocuments('templates/default', {
274
+ where: [{ field: 'is_default', op: '==', value: true }]
275
+ });
276
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
277
+ }
278
+
279
+ static async getUserTemplates(userId: string): Promise<Template[]> {
280
+ const path = `users/${userId}/templates`;
281
+ const results = await queryDocuments(path, {
282
+ orderBy: [{ field: 'created_at', direction: 'DESCENDING' }]
283
+ });
284
+ return results.map(doc => ({ id: doc.id, ...doc.data }));
285
+ }
286
+ }
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Security Considerations
292
+
293
+ ### Admin SDK Bypasses Security Rules
294
+
295
+ **Important**: Firebase Admin SDK has **full access** to all data, regardless of security rules.
296
+
297
+ ```javascript
298
+ // Firestore security rules (these DON'T apply to Admin SDK)
299
+ rules_version = '2';
300
+ service cloud.firestore {
301
+ match /databases/{database}/documents {
302
+ match /user_preferences/{userId} {
303
+ allow read, write: if request.auth.uid == userId; // ❌ Admin SDK bypasses this
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ **Security must be enforced in application code**:
310
+
311
+ ```typescript
312
+ export class UserPreferencesService {
313
+ static async get(userId: string, requestingUserId: string): Promise<UserPreferences> {
314
+ // ✅ Enforce security in code
315
+ if (userId !== requestingUserId) {
316
+ throw new Error('Unauthorized: Cannot access another user\'s preferences');
317
+ }
318
+
319
+ return await getDocument('user_preferences', userId);
320
+ }
321
+ }
322
+ ```
323
+
324
+ ### When Security Rules Matter
325
+
326
+ Security rules **do matter** for:
327
+ - ✅ Firebase Client SDK (web, mobile apps)
328
+ - ✅ Direct Firestore REST API calls from clients
329
+ - ✅ Protection against compromised client apps
330
+
331
+ Security rules **don't matter** for:
332
+ - ❌ Firebase Admin SDK (full access)
333
+ - ❌ Server-side code with service account
334
+ - ❌ Our MCP server (uses Admin SDK)
335
+
336
+ **For remember-mcp**:
337
+ - We use Admin SDK on server → Security rules don't apply
338
+ - We must enforce security in **application logic**
339
+ - Trust system enforced in **code**, not database rules
340
+
341
+ ---
342
+
343
+ ## Comparison with Weaviate
344
+
345
+ ### Why We Have Weaviate Wrapper
346
+
347
+ ```typescript
348
+ // Weaviate needs wrapper because:
349
+ export function getMemoryCollectionName(userId: string): string {
350
+ return `Memory_${sanitizeUserId(userId)}`; // ✅ Multi-tenant logic
351
+ }
352
+
353
+ export function sanitizeUserId(userId: string): string {
354
+ // ✅ Weaviate-specific naming rules
355
+ return userId.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase();
356
+ }
357
+ ```
358
+
359
+ **Weaviate wrapper provides**:
360
+ - ✅ Multi-tenant collection naming
361
+ - ✅ User ID sanitization (Weaviate naming rules)
362
+ - ✅ Collection existence checking
363
+ - ✅ Connection state management
364
+
365
+ ### Why Firestore Doesn't Need Wrapper
366
+
367
+ ```typescript
368
+ // Firestore paths are simple strings
369
+ const path = `user_preferences/${userId}`; // ✅ No sanitization needed
370
+ await getDocument(path, documentId); // ✅ Library API is clean
371
+ ```
372
+
373
+ **Firestore doesn't need wrapper because**:
374
+ - ❌ No special naming rules (paths are just strings)
375
+ - ❌ No sanitization needed (Firebase handles all characters)
376
+ - ❌ Library API is already clean and simple
377
+ - ❌ Service layer provides better organization
378
+
379
+ ---
380
+
381
+ ## Final Recommendation
382
+
383
+ ### ✅ DO: Use Service Layer Pattern
384
+
385
+ **Create domain-specific services**:
386
+ - `src/services/user-preferences.service.ts`
387
+ - `src/services/permissions.service.ts`
388
+ - `src/services/templates.service.ts`
389
+ - `src/services/trust-history.service.ts`
390
+
391
+ **Each service**:
392
+ - Uses firebase-admin-sdk-v8 functions directly
393
+ - Provides type-safe methods
394
+ - Handles domain-specific logic
395
+ - Enforces security in code
396
+
397
+ ### ✅ DO: Minimal Initialization Helper
398
+
399
+ **Create simple init helper**:
400
+ - `src/firestore/init.ts` - Just initialization + state tracking
401
+ - Re-exports library functions for convenience
402
+ - No complex wrapper logic
403
+
404
+ ### ❌ DON'T: Create Full Client Wrapper
405
+
406
+ **Don't create**:
407
+ - `FirestoreClientWrapper` class
408
+ - Methods that just proxy to library functions
409
+ - Unnecessary abstraction layer
410
+
411
+ ---
412
+
413
+ ## Updated Task 4 Approach
414
+
415
+ ### What to Create
416
+
417
+ 1. **src/firestore/init.ts** (minimal)
418
+ - `initFirestore()` - Initialize once
419
+ - `isFirestoreInitialized()` - Check state
420
+ - Re-export library functions
421
+
422
+ 2. **src/services/** (domain services)
423
+ - `user-preferences.service.ts`
424
+ - `permissions.service.ts`
425
+ - `templates.service.ts`
426
+
427
+ 3. **src/types/** (type definitions)
428
+ - `preferences.ts`
429
+ - `permissions.ts`
430
+ - `template.ts`
431
+
432
+ 4. **tests/unit/** (service tests)
433
+ - `user-preferences.service.test.ts`
434
+ - `permissions.service.test.ts`
435
+
436
+ ### What NOT to Create
437
+
438
+ - ❌ `src/firestore/client.ts` with full wrapper class
439
+ - ❌ Methods that just proxy library functions
440
+ - ❌ Complex abstraction layer
441
+
442
+ ---
443
+
444
+ ## Code Examples
445
+
446
+ ### Minimal Init (Recommended)
447
+
448
+ ```typescript
449
+ // src/firestore/init.ts
450
+ import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';
451
+ import { config } from '../config.js';
452
+
453
+ let initialized = false;
454
+
455
+ export function initFirestore(): void {
456
+ if (initialized) return;
457
+
458
+ initializeApp({
459
+ serviceAccount: JSON.parse(config.firebase.serviceAccount),
460
+ projectId: config.firebase.projectId
461
+ });
462
+
463
+ initialized = true;
464
+ console.log('[Firestore] Initialized');
465
+ }
466
+
467
+ export function isFirestoreInitialized(): boolean {
468
+ return initialized;
469
+ }
470
+
471
+ // Re-export for convenience
472
+ export {
473
+ getDocument,
474
+ setDocument,
475
+ addDocument,
476
+ updateDocument,
477
+ deleteDocument,
478
+ queryDocuments,
479
+ batchWrite,
480
+ FieldValue,
481
+ verifyIdToken,
482
+ type QueryOptions
483
+ } from '@prmichaelsen/firebase-admin-sdk-v8';
484
+ ```
485
+
486
+ ### Service Layer (Recommended)
487
+
488
+ ```typescript
489
+ // src/services/user-preferences.service.ts
490
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
491
+ import type { UserPreferences } from '../types/preferences.js';
492
+ import { DEFAULT_PREFERENCES } from '../constants/defaults.js';
493
+
494
+ export class UserPreferencesService {
495
+ /**
496
+ * Get user preferences (returns defaults if not found)
497
+ */
498
+ static async get(userId: string): Promise<UserPreferences> {
499
+ const doc = await getDocument('user_preferences', userId);
500
+
501
+ if (!doc) {
502
+ // Create with defaults
503
+ await this.create(userId);
504
+ return DEFAULT_PREFERENCES;
505
+ }
506
+
507
+ return doc as UserPreferences;
508
+ }
509
+
510
+ /**
511
+ * Update user preferences
512
+ */
513
+ static async update(userId: string, updates: Partial<UserPreferences>): Promise<void> {
514
+ await setDocument('user_preferences', userId, {
515
+ ...updates,
516
+ updated_at: new Date().toISOString()
517
+ }, { merge: true });
518
+ }
519
+
520
+ /**
521
+ * Create user preferences with defaults
522
+ */
523
+ static async create(userId: string): Promise<void> {
524
+ await setDocument('user_preferences', userId, {
525
+ ...DEFAULT_PREFERENCES,
526
+ user_id: userId,
527
+ created_at: new Date().toISOString(),
528
+ updated_at: new Date().toISOString()
529
+ });
530
+ }
531
+ }
532
+ ```
533
+
534
+ ---
535
+
536
+ ## Comparison with agentbase.me
537
+
538
+ ### agentbase.me Pattern (What They Do)
539
+
540
+ ```typescript
541
+ // They use services directly, no wrapper
542
+ import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
543
+
544
+ export class ConversationDatabaseService {
545
+ static async createConversation(userId: string, title: string) {
546
+ const path = getUserConversations(userId);
547
+ const docRef = await addDocument(path, { title, created_at: now });
548
+ return { id: docRef.id, ...conversation };
549
+ }
550
+ }
551
+ ```
552
+
553
+ **They DON'T have**:
554
+ - ❌ Firestore client wrapper
555
+ - ❌ Abstraction layer over firebase-admin-sdk-v8
556
+
557
+ **They DO have**:
558
+ - ✅ Service classes for each domain
559
+ - ✅ Collection path helpers
560
+ - ✅ Direct use of library functions
561
+
562
+ ---
563
+
564
+ ## Conclusion
565
+
566
+ ### ✅ Recommended Approach
567
+
568
+ **Use firebase-admin-sdk-v8 directly through service layer**:
569
+
570
+ 1. **Minimal init helper** (`src/firestore/init.ts`)
571
+ - Just initialization + state tracking
572
+ - Re-export library functions
573
+
574
+ 2. **Service classes** (`src/services/*.service.ts`)
575
+ - Domain-specific logic
576
+ - Type-safe methods
577
+ - Direct use of library functions
578
+
579
+ 3. **No client wrapper**
580
+ - Library API is already clean
581
+ - Wrapper adds no value
582
+ - Service layer provides better organization
583
+
584
+ ### Why This is Better
585
+
586
+ - ✅ **Simpler** - Less code, less complexity
587
+ - ✅ **Proven** - agentbase.me uses this pattern successfully
588
+ - ✅ **Maintainable** - Clear separation of concerns
589
+ - ✅ **Flexible** - Easy to add new services
590
+ - ✅ **Testable** - Mock services, not wrappers
591
+ - ✅ **Direct** - Use library features without indirection
592
+
593
+ ### Security Note
594
+
595
+ **Admin SDK bypasses security rules** - we must enforce security in code:
596
+ - Check user permissions in service methods
597
+ - Validate user_id matches requesting user
598
+ - Enforce trust levels in application logic
599
+ - Use PermissionsService to check access rights
600
+
601
+ ---
602
+
603
+ **Status**: Design Recommendation
604
+ **Decision**: Use service layer pattern, no client wrapper
605
+ **Rationale**: Library API is clean, service layer provides better organization
606
+ **Next**: Update Task 4 to create services instead of wrapper