@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,950 @@
|
|
|
1
|
+
# Firebase Admin SDK v8 Usage Pattern
|
|
2
|
+
|
|
3
|
+
**Library**: @prmichaelsen/firebase-admin-sdk-v8
|
|
4
|
+
**Version**: 2.2.0+
|
|
5
|
+
**Created**: 2026-02-11
|
|
6
|
+
**Status**: Reference Pattern
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The `@prmichaelsen/firebase-admin-sdk-v8` library provides Firebase Admin SDK functionality for Cloudflare Workers and edge runtimes using REST APIs instead of Node.js Admin SDK. This makes it compatible with environments that don't support Node.js.
|
|
13
|
+
|
|
14
|
+
**Key Features**:
|
|
15
|
+
- ✅ Zero dependencies (uses Web APIs: crypto.subtle, fetch)
|
|
16
|
+
- ✅ JWT token generation for service account authentication
|
|
17
|
+
- ✅ ID token verification (supports Firebase v9 and v10 formats)
|
|
18
|
+
- ✅ Firestore REST API with full CRUD operations
|
|
19
|
+
- ✅ Advanced queries (where, orderBy, limit, pagination)
|
|
20
|
+
- ✅ Field value operations (increment, arrayUnion, serverTimestamp, delete)
|
|
21
|
+
- ✅ Batch operations (atomic multi-document writes)
|
|
22
|
+
- ✅ TypeScript support with full type definitions
|
|
23
|
+
|
|
24
|
+
**Limitations**:
|
|
25
|
+
- ❌ No realtime listeners (REST API is stateless)
|
|
26
|
+
- ❌ No custom token creation yet
|
|
27
|
+
- ❌ No user management yet
|
|
28
|
+
- ❌ No transactions yet
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @prmichaelsen/firebase-admin-sdk-v8
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Initialization
|
|
41
|
+
|
|
42
|
+
### Option 1: Explicit Initialization (Recommended for remember-mcp)
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import { initializeApp } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
46
|
+
|
|
47
|
+
// Initialize once at startup
|
|
48
|
+
initializeApp({
|
|
49
|
+
serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
|
|
50
|
+
projectId: process.env.FIREBASE_PROJECT_ID
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Option 2: Auto-Detection from process.env
|
|
55
|
+
|
|
56
|
+
If you don't call `initializeApp()`, the SDK automatically uses:
|
|
57
|
+
- `process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY` (JSON string)
|
|
58
|
+
- `process.env.FIREBASE_PROJECT_ID`
|
|
59
|
+
|
|
60
|
+
### Environment Variables
|
|
61
|
+
|
|
62
|
+
```env
|
|
63
|
+
FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{"type":"service_account","project_id":"...","private_key":"...","client_email":"..."}'
|
|
64
|
+
FIREBASE_PROJECT_ID=your-project-id
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Authentication
|
|
70
|
+
|
|
71
|
+
### Verify ID Tokens
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { verifyIdToken, getUserFromToken } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
75
|
+
|
|
76
|
+
// Verify token and get decoded claims
|
|
77
|
+
const decodedToken = await verifyIdToken(idToken);
|
|
78
|
+
console.log('User ID:', decodedToken.uid);
|
|
79
|
+
console.log('Email:', decodedToken.email);
|
|
80
|
+
|
|
81
|
+
// Or get user object directly
|
|
82
|
+
const user = await getUserFromToken(idToken);
|
|
83
|
+
console.log('User:', user.email, user.displayName);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Token Structure**:
|
|
87
|
+
```typescript
|
|
88
|
+
interface DecodedIdToken {
|
|
89
|
+
uid: string; // User ID
|
|
90
|
+
email?: string;
|
|
91
|
+
email_verified?: boolean;
|
|
92
|
+
name?: string;
|
|
93
|
+
picture?: string;
|
|
94
|
+
iss: string; // Issuer
|
|
95
|
+
aud: string; // Audience (project ID)
|
|
96
|
+
auth_time: number; // Authentication time
|
|
97
|
+
iat: number; // Issued at
|
|
98
|
+
exp: number; // Expires at
|
|
99
|
+
sub: string; // Subject (same as uid)
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Firestore Operations
|
|
106
|
+
|
|
107
|
+
### Basic CRUD
|
|
108
|
+
|
|
109
|
+
#### Get Document
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { getDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
113
|
+
|
|
114
|
+
// Get a document
|
|
115
|
+
const user = await getDocument('users', 'user123');
|
|
116
|
+
|
|
117
|
+
// Get from subcollection
|
|
118
|
+
const message = await getDocument('users/user123/messages', 'msg456');
|
|
119
|
+
|
|
120
|
+
// Returns null if document doesn't exist
|
|
121
|
+
if (!user) {
|
|
122
|
+
console.log('User not found');
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
#### Set Document (Create or Overwrite)
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
130
|
+
|
|
131
|
+
// Create or overwrite document
|
|
132
|
+
await setDocument('users', 'user123', {
|
|
133
|
+
name: 'John Doe',
|
|
134
|
+
email: 'john@example.com',
|
|
135
|
+
createdAt: new Date().toISOString()
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Merge with existing data (don't overwrite)
|
|
139
|
+
await setDocument('users', 'user123', {
|
|
140
|
+
lastLogin: new Date().toISOString()
|
|
141
|
+
}, { merge: true });
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### Add Document (Auto-Generated ID)
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { addDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
148
|
+
|
|
149
|
+
// Add document with auto-generated ID
|
|
150
|
+
const docRef = await addDocument('users/user123/messages', {
|
|
151
|
+
content: 'Hello world',
|
|
152
|
+
timestamp: new Date().toISOString()
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
console.log('Created document with ID:', docRef.id);
|
|
156
|
+
|
|
157
|
+
// Add with custom ID
|
|
158
|
+
const docRef2 = await addDocument('users/user123/messages', {
|
|
159
|
+
content: 'Custom ID message'
|
|
160
|
+
}, 'custom-message-id');
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### Update Document
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { updateDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
167
|
+
|
|
168
|
+
// Update specific fields
|
|
169
|
+
await updateDocument('users', 'user123', {
|
|
170
|
+
name: 'Jane Doe',
|
|
171
|
+
updatedAt: new Date().toISOString()
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Note: updateDocument fails if document doesn't exist
|
|
175
|
+
// Use setDocument with merge: true if you want upsert behavior
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Delete Document
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { deleteDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
182
|
+
|
|
183
|
+
// Delete a document
|
|
184
|
+
await deleteDocument('users', 'user123');
|
|
185
|
+
|
|
186
|
+
// Soft delete (recommended)
|
|
187
|
+
await setDocument('users', 'user123', {
|
|
188
|
+
deleted: true,
|
|
189
|
+
deletedAt: new Date().toISOString()
|
|
190
|
+
}, { merge: true });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Field Value Operations
|
|
196
|
+
|
|
197
|
+
### Special Field Values
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { FieldValue, setDocument, updateDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
201
|
+
|
|
202
|
+
// Server timestamp
|
|
203
|
+
await setDocument('users', 'user123', {
|
|
204
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
205
|
+
updatedAt: FieldValue.serverTimestamp()
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Increment/Decrement
|
|
209
|
+
await updateDocument('users', 'user123', {
|
|
210
|
+
loginCount: FieldValue.increment(1),
|
|
211
|
+
credits: FieldValue.increment(-10) // Decrement
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Array operations
|
|
215
|
+
await updateDocument('users', 'user123', {
|
|
216
|
+
tags: FieldValue.arrayUnion('premium', 'verified'),
|
|
217
|
+
blockedUsers: FieldValue.arrayRemove('user456')
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Delete field
|
|
221
|
+
await updateDocument('users', 'user123', {
|
|
222
|
+
temporaryField: FieldValue.delete()
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Queries
|
|
229
|
+
|
|
230
|
+
### Basic Query
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
234
|
+
import type { QueryOptions } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
235
|
+
|
|
236
|
+
const options: QueryOptions = {
|
|
237
|
+
where: [
|
|
238
|
+
{ field: 'active', op: '==', value: true }
|
|
239
|
+
],
|
|
240
|
+
orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
|
|
241
|
+
limit: 10
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const results = await queryDocuments('users', options);
|
|
245
|
+
|
|
246
|
+
// Results format
|
|
247
|
+
results.forEach(doc => {
|
|
248
|
+
console.log('ID:', doc.id);
|
|
249
|
+
console.log('Data:', doc.data);
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Query Operators
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
// Comparison operators
|
|
257
|
+
{ field: 'age', op: '==', value: 25 }
|
|
258
|
+
{ field: 'age', op: '!=', value: 25 }
|
|
259
|
+
{ field: 'age', op: '<', value: 25 }
|
|
260
|
+
{ field: 'age', op: '<=', value: 25 }
|
|
261
|
+
{ field: 'age', op: '>', value: 25 }
|
|
262
|
+
{ field: 'age', op: '>=', value: 25 }
|
|
263
|
+
|
|
264
|
+
// Array operators
|
|
265
|
+
{ field: 'tags', op: 'array-contains', value: 'premium' }
|
|
266
|
+
{ field: 'tags', op: 'array-contains-any', value: ['premium', 'verified'] }
|
|
267
|
+
{ field: 'roles', op: 'in', value: ['admin', 'moderator'] }
|
|
268
|
+
{ field: 'status', op: 'not-in', value: ['banned', 'suspended'] }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Complex Queries
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
// Multiple conditions (AND)
|
|
275
|
+
const activeAdults = await queryDocuments('users', {
|
|
276
|
+
where: [
|
|
277
|
+
{ field: 'active', op: '==', value: true },
|
|
278
|
+
{ field: 'age', op: '>=', value: 18 },
|
|
279
|
+
{ field: 'verified', op: '==', value: true }
|
|
280
|
+
],
|
|
281
|
+
orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
|
|
282
|
+
limit: 50
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Pagination with cursor
|
|
286
|
+
const firstPage = await queryDocuments('users', {
|
|
287
|
+
orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
|
|
288
|
+
limit: 10
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Get next page
|
|
292
|
+
const secondPage = await queryDocuments('users', {
|
|
293
|
+
orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
|
|
294
|
+
limit: 10,
|
|
295
|
+
startAfter: [firstPage[firstPage.length - 1].data.createdAt]
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Batch Operations
|
|
302
|
+
|
|
303
|
+
### Atomic Writes
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { batchWrite } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
307
|
+
|
|
308
|
+
await batchWrite([
|
|
309
|
+
{
|
|
310
|
+
type: 'set',
|
|
311
|
+
collection: 'users',
|
|
312
|
+
documentId: 'user123',
|
|
313
|
+
data: { name: 'John' }
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
type: 'update',
|
|
317
|
+
collection: 'users',
|
|
318
|
+
documentId: 'user456',
|
|
319
|
+
data: { loginCount: FieldValue.increment(1) }
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: 'delete',
|
|
323
|
+
collection: 'users',
|
|
324
|
+
documentId: 'user789'
|
|
325
|
+
}
|
|
326
|
+
]);
|
|
327
|
+
|
|
328
|
+
// All operations succeed or all fail (atomic)
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Collection Path Patterns
|
|
334
|
+
|
|
335
|
+
### User-Scoped Collections
|
|
336
|
+
|
|
337
|
+
Following agentbase.me pattern:
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
// Helper functions for collection paths
|
|
341
|
+
export function getUserCollection(userId: string): string {
|
|
342
|
+
return `users/${userId}`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function getUserSubcollection(userId: string, subcollection: string): string {
|
|
346
|
+
return `users/${userId}/${subcollection}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Usage
|
|
350
|
+
const conversationsPath = getUserSubcollection(userId, 'conversations');
|
|
351
|
+
const messagesPath = `users/${userId}/conversations/${conversationId}/messages`;
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### remember-mcp Collection Patterns
|
|
355
|
+
|
|
356
|
+
Based on design documents:
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// User preferences
|
|
360
|
+
export function getUserPreferencesPath(userId: string): string {
|
|
361
|
+
return `user_preferences/${userId}`;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// User permissions (who can access user's memories)
|
|
365
|
+
export function getUserPermissionsPath(userId: string): string {
|
|
366
|
+
return `user_permissions/${userId}/allowed_accessors`;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Trust history
|
|
370
|
+
export function getTrustHistoryPath(userId: string): string {
|
|
371
|
+
return `trust_history/${userId}/history`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Templates
|
|
375
|
+
export function getDefaultTemplatesPath(): string {
|
|
376
|
+
return 'templates/default';
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export function getUserTemplatesPath(userId: string): string {
|
|
380
|
+
return `users/${userId}/templates`;
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Service Class Pattern
|
|
387
|
+
|
|
388
|
+
### Recommended Pattern (from agentbase.me)
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { getDocument, setDocument, queryDocuments, addDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
392
|
+
import type { QueryOptions } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
393
|
+
|
|
394
|
+
export class UserPreferencesService {
|
|
395
|
+
/**
|
|
396
|
+
* Get user preferences
|
|
397
|
+
*/
|
|
398
|
+
static async getPreferences(userId: string): Promise<UserPreferences | null> {
|
|
399
|
+
const doc = await getDocument('user_preferences', userId);
|
|
400
|
+
|
|
401
|
+
if (!doc) {
|
|
402
|
+
// Return defaults if not found
|
|
403
|
+
return DEFAULT_PREFERENCES;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return doc as UserPreferences;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Update user preferences
|
|
411
|
+
*/
|
|
412
|
+
static async updatePreferences(
|
|
413
|
+
userId: string,
|
|
414
|
+
updates: Partial<UserPreferences>
|
|
415
|
+
): Promise<void> {
|
|
416
|
+
await setDocument('user_preferences', userId, {
|
|
417
|
+
...updates,
|
|
418
|
+
updated_at: new Date().toISOString()
|
|
419
|
+
}, { merge: true });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Create user preferences with defaults
|
|
424
|
+
*/
|
|
425
|
+
static async createPreferences(userId: string): Promise<UserPreferences> {
|
|
426
|
+
const preferences: UserPreferences = {
|
|
427
|
+
...DEFAULT_PREFERENCES,
|
|
428
|
+
user_id: userId,
|
|
429
|
+
created_at: new Date().toISOString(),
|
|
430
|
+
updated_at: new Date().toISOString()
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
await setDocument('user_preferences', userId, preferences);
|
|
434
|
+
return preferences;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## Multi-Tenant Patterns
|
|
442
|
+
|
|
443
|
+
### Per-User Collections
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
// Pattern 1: User as document ID
|
|
447
|
+
// Collection: user_preferences/{userId}
|
|
448
|
+
await setDocument('user_preferences', userId, preferences);
|
|
449
|
+
|
|
450
|
+
// Pattern 2: User as subcollection
|
|
451
|
+
// Collection: users/{userId}/preferences
|
|
452
|
+
await setDocument(`users/${userId}/preferences`, 'settings', preferences);
|
|
453
|
+
|
|
454
|
+
// Pattern 3: User-scoped subcollections
|
|
455
|
+
// Collection: users/{userId}/conversations/{conversationId}
|
|
456
|
+
const conversationPath = `users/${userId}/conversations`;
|
|
457
|
+
await addDocument(conversationPath, conversation);
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Query User-Scoped Data
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
// Get all conversations for a user
|
|
464
|
+
const conversations = await queryDocuments(`users/${userId}/conversations`, {
|
|
465
|
+
orderBy: [{ field: 'updated_at', direction: 'DESCENDING' }],
|
|
466
|
+
limit: 50
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Get user's memories with filters
|
|
470
|
+
const memories = await queryDocuments(`users/${userId}/memories`, {
|
|
471
|
+
where: [
|
|
472
|
+
{ field: 'type', op: '==', value: 'note' },
|
|
473
|
+
{ field: 'weight', op: '>=', value: 0.5 }
|
|
474
|
+
],
|
|
475
|
+
orderBy: [{ field: 'created_at', direction: 'DESCENDING' }],
|
|
476
|
+
limit: 20
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
---
|
|
481
|
+
|
|
482
|
+
## Common Patterns from agentbase.me
|
|
483
|
+
|
|
484
|
+
### 1. Service Class Pattern
|
|
485
|
+
|
|
486
|
+
```typescript
|
|
487
|
+
export class ConversationDatabaseService {
|
|
488
|
+
static async createConversation(userId: string, title: string): Promise<Conversation> {
|
|
489
|
+
const now = new Date().toISOString();
|
|
490
|
+
|
|
491
|
+
const conversation = {
|
|
492
|
+
title,
|
|
493
|
+
created_at: now,
|
|
494
|
+
updated_at: now
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
const conversationsPath = getUserConversations(userId);
|
|
498
|
+
const docRef = await addDocument(conversationsPath, conversation);
|
|
499
|
+
|
|
500
|
+
return {
|
|
501
|
+
id: docRef.id,
|
|
502
|
+
user_id: userId,
|
|
503
|
+
...conversation
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
static async getConversation(userId: string, conversationId: string): Promise<Conversation | null> {
|
|
508
|
+
const conversationsPath = getUserConversations(userId);
|
|
509
|
+
return await getDocument(conversationsPath, conversationId);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
static async getUserConversations(userId: string, limit = 50): Promise<Conversation[]> {
|
|
513
|
+
const conversationsPath = getUserConversations(userId);
|
|
514
|
+
const results = await queryDocuments(conversationsPath, {
|
|
515
|
+
orderBy: [{ field: 'updated_at', direction: 'DESCENDING' }],
|
|
516
|
+
limit
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
return results.map(doc => ({ id: doc.id, ...doc.data }));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### 2. Collection Path Helpers
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
// From agentbase.me/src/constant/collections.ts
|
|
528
|
+
const BASE = process.env.NODE_ENV === 'development' ? 'e0.agentbase' : 'agentbase';
|
|
529
|
+
|
|
530
|
+
export function getUserConversations(userId: string): string {
|
|
531
|
+
return `${BASE}.users/${userId}/conversations`;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function getUserConversationMessages(userId: string, conversationId: string): string {
|
|
535
|
+
return `${BASE}.users/${userId}/conversations/${conversationId}/messages`;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
export function getUserCredentialsCollection(userId: string): string {
|
|
539
|
+
return `${BASE}.users/${userId}/credentials`;
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### 3. Soft Delete Pattern
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
// Instead of deleting, mark as deleted
|
|
547
|
+
await setDocument('users', userId, {
|
|
548
|
+
deleted: true,
|
|
549
|
+
deleted_at: new Date().toISOString()
|
|
550
|
+
}, { merge: true });
|
|
551
|
+
|
|
552
|
+
// Query non-deleted documents
|
|
553
|
+
const activeUsers = await queryDocuments('users', {
|
|
554
|
+
where: [
|
|
555
|
+
{ field: 'deleted', op: '!=', value: true }
|
|
556
|
+
]
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
### 4. Timestamp Pattern
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
// Use ISO strings for timestamps (not Firestore Timestamp objects)
|
|
564
|
+
const now = new Date().toISOString();
|
|
565
|
+
|
|
566
|
+
await setDocument('users', userId, {
|
|
567
|
+
created_at: now,
|
|
568
|
+
updated_at: now
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// Or use FieldValue.serverTimestamp() for server-side timestamp
|
|
572
|
+
await setDocument('users', userId, {
|
|
573
|
+
created_at: FieldValue.serverTimestamp(),
|
|
574
|
+
updated_at: FieldValue.serverTimestamp()
|
|
575
|
+
});
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
---
|
|
579
|
+
|
|
580
|
+
## remember-mcp Specific Patterns
|
|
581
|
+
|
|
582
|
+
### User Preferences
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import { getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
586
|
+
|
|
587
|
+
export async function getUserPreferences(userId: string): Promise<UserPreferences> {
|
|
588
|
+
const doc = await getDocument('user_preferences', userId);
|
|
589
|
+
|
|
590
|
+
if (!doc) {
|
|
591
|
+
// Create with defaults
|
|
592
|
+
const defaults = DEFAULT_PREFERENCES;
|
|
593
|
+
await setDocument('user_preferences', userId, defaults);
|
|
594
|
+
return defaults;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
return doc as UserPreferences;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export async function updateUserPreferences(
|
|
601
|
+
userId: string,
|
|
602
|
+
updates: Partial<UserPreferences>
|
|
603
|
+
): Promise<void> {
|
|
604
|
+
await setDocument('user_preferences', userId, {
|
|
605
|
+
...updates,
|
|
606
|
+
updated_at: new Date().toISOString()
|
|
607
|
+
}, { merge: true });
|
|
608
|
+
}
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
### User Permissions (Trust System)
|
|
612
|
+
|
|
613
|
+
```typescript
|
|
614
|
+
import { getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
615
|
+
|
|
616
|
+
export async function grantPermission(
|
|
617
|
+
ownerUserId: string,
|
|
618
|
+
accessorUserId: string,
|
|
619
|
+
trustLevel: number,
|
|
620
|
+
options: PermissionOptions
|
|
621
|
+
): Promise<void> {
|
|
622
|
+
const permission: UserPermission = {
|
|
623
|
+
owner_user_id: ownerUserId,
|
|
624
|
+
accessor_user_id: accessorUserId,
|
|
625
|
+
can_access: true,
|
|
626
|
+
trust_level: trustLevel,
|
|
627
|
+
granted_at: new Date().toISOString(),
|
|
628
|
+
...options
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
|
|
632
|
+
await setDocument(permissionsPath, accessorUserId, permission);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export async function checkPermission(
|
|
636
|
+
ownerUserId: string,
|
|
637
|
+
accessorUserId: string
|
|
638
|
+
): Promise<UserPermission | null> {
|
|
639
|
+
const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
|
|
640
|
+
return await getDocument(permissionsPath, accessorUserId);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
export async function listAccessors(ownerUserId: string): Promise<UserPermission[]> {
|
|
644
|
+
const permissionsPath = `user_permissions/${ownerUserId}/allowed_accessors`;
|
|
645
|
+
const results = await queryDocuments(permissionsPath, {
|
|
646
|
+
where: [{ field: 'revoked', op: '!=', value: true }],
|
|
647
|
+
orderBy: [{ field: 'trust_level', direction: 'DESCENDING' }]
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
return results.map(doc => ({ id: doc.id, ...doc.data }));
|
|
651
|
+
}
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
### Templates
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
export async function getDefaultTemplates(): Promise<Template[]> {
|
|
658
|
+
const results = await queryDocuments('templates/default', {
|
|
659
|
+
where: [{ field: 'is_default', op: '==', value: true }],
|
|
660
|
+
orderBy: [{ field: 'usage_count', direction: 'DESCENDING' }]
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
return results.map(doc => ({ id: doc.id, ...doc.data }));
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
export async function getUserTemplates(userId: string): Promise<Template[]> {
|
|
667
|
+
const templatesPath = `users/${userId}/templates`;
|
|
668
|
+
const results = await queryDocuments(templatesPath, {
|
|
669
|
+
orderBy: [{ field: 'created_at', direction: 'DESCENDING' }]
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
return results.map(doc => ({ id: doc.id, ...doc.data }));
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
## Best Practices
|
|
679
|
+
|
|
680
|
+
### 1. Use Service Classes
|
|
681
|
+
|
|
682
|
+
Organize Firestore operations into service classes:
|
|
683
|
+
- `UserPreferencesService`
|
|
684
|
+
- `PermissionsService`
|
|
685
|
+
- `TemplateService`
|
|
686
|
+
- `TrustHistoryService`
|
|
687
|
+
|
|
688
|
+
### 2. Use Collection Path Helpers
|
|
689
|
+
|
|
690
|
+
Create helper functions for collection paths:
|
|
691
|
+
```typescript
|
|
692
|
+
export function getUserPreferencesPath(userId: string): string {
|
|
693
|
+
return `user_preferences/${userId}`;
|
|
694
|
+
}
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
### 3. Use Merge for Updates
|
|
698
|
+
|
|
699
|
+
Prefer `setDocument` with `merge: true` over `updateDocument`:
|
|
700
|
+
```typescript
|
|
701
|
+
// ✅ Good: Creates if doesn't exist
|
|
702
|
+
await setDocument('users', userId, { name: 'John' }, { merge: true });
|
|
703
|
+
|
|
704
|
+
// ❌ Risky: Fails if document doesn't exist
|
|
705
|
+
await updateDocument('users', userId, { name: 'John' });
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
### 4. Use ISO Timestamps
|
|
709
|
+
|
|
710
|
+
Use ISO string timestamps for consistency:
|
|
711
|
+
```typescript
|
|
712
|
+
const now = new Date().toISOString();
|
|
713
|
+
await setDocument('users', userId, {
|
|
714
|
+
created_at: now,
|
|
715
|
+
updated_at: now
|
|
716
|
+
});
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
### 5. Handle Null Returns
|
|
720
|
+
|
|
721
|
+
Always check for null when getting documents:
|
|
722
|
+
```typescript
|
|
723
|
+
const doc = await getDocument('users', userId);
|
|
724
|
+
if (!doc) {
|
|
725
|
+
// Handle missing document
|
|
726
|
+
return DEFAULT_VALUE;
|
|
727
|
+
}
|
|
728
|
+
```
|
|
729
|
+
|
|
730
|
+
### 6. Use Soft Deletes
|
|
731
|
+
|
|
732
|
+
Mark documents as deleted instead of deleting:
|
|
733
|
+
```typescript
|
|
734
|
+
await setDocument('users', userId, {
|
|
735
|
+
deleted: true,
|
|
736
|
+
deleted_at: new Date().toISOString()
|
|
737
|
+
}, { merge: true });
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
---
|
|
741
|
+
|
|
742
|
+
## Error Handling
|
|
743
|
+
|
|
744
|
+
```typescript
|
|
745
|
+
try {
|
|
746
|
+
const doc = await getDocument('users', userId);
|
|
747
|
+
} catch (error) {
|
|
748
|
+
if (error instanceof Error) {
|
|
749
|
+
console.error('Firestore error:', error.message);
|
|
750
|
+
}
|
|
751
|
+
throw new Error('Failed to get user document');
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Testing
|
|
758
|
+
|
|
759
|
+
### Unit Tests (Mock)
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
import { jest } from '@jest/globals';
|
|
763
|
+
|
|
764
|
+
// Mock the firebase-admin-sdk-v8 module
|
|
765
|
+
jest.mock('@prmichaelsen/firebase-admin-sdk-v8', () => ({
|
|
766
|
+
getDocument: jest.fn(),
|
|
767
|
+
setDocument: jest.fn(),
|
|
768
|
+
queryDocuments: jest.fn()
|
|
769
|
+
}));
|
|
770
|
+
|
|
771
|
+
// In tests
|
|
772
|
+
import { getDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
773
|
+
(getDocument as jest.Mock).mockResolvedValue({ name: 'John' });
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### Integration Tests (Real Firestore)
|
|
777
|
+
|
|
778
|
+
```typescript
|
|
779
|
+
import { initializeApp, getDocument, setDocument } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
780
|
+
|
|
781
|
+
beforeAll(() => {
|
|
782
|
+
initializeApp({
|
|
783
|
+
serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
|
|
784
|
+
projectId: process.env.FIREBASE_PROJECT_ID
|
|
785
|
+
});
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
it('should create and retrieve document', async () => {
|
|
789
|
+
const testId = `test_${Date.now()}`;
|
|
790
|
+
|
|
791
|
+
await setDocument('test_collection', testId, { value: 'test' });
|
|
792
|
+
const doc = await getDocument('test_collection', testId);
|
|
793
|
+
|
|
794
|
+
expect(doc).toEqual({ value: 'test' });
|
|
795
|
+
|
|
796
|
+
// Cleanup
|
|
797
|
+
await deleteDocument('test_collection', testId);
|
|
798
|
+
});
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
---
|
|
802
|
+
|
|
803
|
+
## Migration from firebase-admin
|
|
804
|
+
|
|
805
|
+
### Old (firebase-admin)
|
|
806
|
+
|
|
807
|
+
```typescript
|
|
808
|
+
import admin from 'firebase-admin';
|
|
809
|
+
|
|
810
|
+
admin.initializeApp({
|
|
811
|
+
credential: admin.credential.cert(serviceAccount)
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
const db = admin.firestore();
|
|
815
|
+
|
|
816
|
+
// Get document
|
|
817
|
+
const doc = await db.collection('users').doc('user123').get();
|
|
818
|
+
const data = doc.data();
|
|
819
|
+
|
|
820
|
+
// Set document
|
|
821
|
+
await db.collection('users').doc('user123').set({ name: 'John' });
|
|
822
|
+
|
|
823
|
+
// Query
|
|
824
|
+
const snapshot = await db.collection('users')
|
|
825
|
+
.where('active', '==', true)
|
|
826
|
+
.orderBy('createdAt', 'desc')
|
|
827
|
+
.limit(10)
|
|
828
|
+
.get();
|
|
829
|
+
|
|
830
|
+
const users = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
### New (firebase-admin-sdk-v8)
|
|
834
|
+
|
|
835
|
+
```typescript
|
|
836
|
+
import { initializeApp, getDocument, setDocument, queryDocuments } from '@prmichaelsen/firebase-admin-sdk-v8';
|
|
837
|
+
|
|
838
|
+
initializeApp({
|
|
839
|
+
serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),
|
|
840
|
+
projectId: process.env.FIREBASE_PROJECT_ID
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
// Get document
|
|
844
|
+
const data = await getDocument('users', 'user123');
|
|
845
|
+
|
|
846
|
+
// Set document
|
|
847
|
+
await setDocument('users', 'user123', { name: 'John' });
|
|
848
|
+
|
|
849
|
+
// Query
|
|
850
|
+
const users = await queryDocuments('users', {
|
|
851
|
+
where: [{ field: 'active', op: '==', value: true }],
|
|
852
|
+
orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],
|
|
853
|
+
limit: 10
|
|
854
|
+
});
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## Key Differences from firebase-admin
|
|
860
|
+
|
|
861
|
+
### Advantages
|
|
862
|
+
- ✅ Works in Cloudflare Workers and edge runtimes
|
|
863
|
+
- ✅ Zero dependencies (uses Web APIs)
|
|
864
|
+
- ✅ Simpler API (flat functions vs nested objects)
|
|
865
|
+
- ✅ REST-based (no gRPC dependencies)
|
|
866
|
+
|
|
867
|
+
### Limitations
|
|
868
|
+
- ❌ No realtime listeners (use polling or client SDK)
|
|
869
|
+
- ❌ No transactions yet
|
|
870
|
+
- ❌ No custom token creation yet
|
|
871
|
+
- ❌ Slightly higher latency (REST vs gRPC)
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
## remember-mcp Integration
|
|
876
|
+
|
|
877
|
+
### Update package.json
|
|
878
|
+
|
|
879
|
+
```json
|
|
880
|
+
{
|
|
881
|
+
"dependencies": {
|
|
882
|
+
"@prmichaelsen/firebase-admin-sdk-v8": "^2.2.0"
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
### Remove firebase-admin
|
|
888
|
+
|
|
889
|
+
Since we're using firebase-admin-sdk-v8, we should remove the standard firebase-admin:
|
|
890
|
+
|
|
891
|
+
```bash
|
|
892
|
+
npm uninstall firebase-admin
|
|
893
|
+
npm install @prmichaelsen/firebase-admin-sdk-v8
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
### Update config.ts
|
|
897
|
+
|
|
898
|
+
```typescript
|
|
899
|
+
export const config = {
|
|
900
|
+
firebase: {
|
|
901
|
+
serviceAccount: process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY || '',
|
|
902
|
+
projectId: process.env.FIREBASE_PROJECT_ID || '',
|
|
903
|
+
}
|
|
904
|
+
};
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
### Update .env.example
|
|
908
|
+
|
|
909
|
+
```env
|
|
910
|
+
# Firebase Configuration (using firebase-admin-sdk-v8)
|
|
911
|
+
FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{"type":"service_account",...}'
|
|
912
|
+
FIREBASE_PROJECT_ID=remember-mcp-dev
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## Reference Examples
|
|
918
|
+
|
|
919
|
+
### From agentbase.me
|
|
920
|
+
|
|
921
|
+
- **Auth**: [`src/lib/auth/session.ts`](/home/prmichaelsen/agentbase.me/src/lib/auth/session.ts)
|
|
922
|
+
- **Conversations**: [`src/services/conversation-database.service.ts`](/home/prmichaelsen/agentbase.me/src/services/conversation-database.service.ts)
|
|
923
|
+
- **Credentials**: [`src/services/credentials-database.service.ts`](/home/prmichaelsen/agentbase.me/src/services/credentials-database.service.ts)
|
|
924
|
+
- **Collection Paths**: [`src/constant/collections.ts`](/home/prmichaelsen/agentbase.me/src/constant/collections.ts)
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## Summary
|
|
929
|
+
|
|
930
|
+
The `@prmichaelsen/firebase-admin-sdk-v8` library provides a REST-based Firebase Admin SDK that:
|
|
931
|
+
|
|
932
|
+
1. **Works in edge runtimes** (Cloudflare Workers, Vercel Edge)
|
|
933
|
+
2. **Simple API** with flat functions (getDocument, setDocument, etc.)
|
|
934
|
+
3. **Zero dependencies** (uses Web APIs)
|
|
935
|
+
4. **Full Firestore CRUD** with advanced queries
|
|
936
|
+
5. **Field value operations** (increment, arrayUnion, serverTimestamp)
|
|
937
|
+
6. **Batch operations** for atomic writes
|
|
938
|
+
|
|
939
|
+
**For remember-mcp**, we should:
|
|
940
|
+
- ✅ Use this library instead of firebase-admin
|
|
941
|
+
- ✅ Follow the service class pattern from agentbase.me
|
|
942
|
+
- ✅ Use collection path helpers for multi-tenancy
|
|
943
|
+
- ✅ Use ISO timestamps and soft deletes
|
|
944
|
+
- ✅ Handle null returns and errors properly
|
|
945
|
+
|
|
946
|
+
---
|
|
947
|
+
|
|
948
|
+
**Status**: Reference Pattern
|
|
949
|
+
**Library Version**: 2.2.0+
|
|
950
|
+
**Recommended For**: remember-mcp Firestore integration
|