{"_id":"@adamclayd/firebase-admin","_rev":"2-f287eba8c1d892b4de7bd5198fdd4eac","name":"@adamclayd/firebase-admin","dist-tags":{"latest":"2.11.5"},"versions":{"2.11.0":{"name":"@adamclayd/firebase-admin","version":"2.11.0","keywords":["firebase","admin","cloudflare","workers","edge","firestore","auth","authentication","storage","jwt","emulators","emulator","emulated","emulation","emulate","local","localhost"],"author":"","license":"MIT","_id":"@adamclayd/firebase-admin@2.11.0","maintainers":[{"name":"1443456","email":"adamclayd@gmail.com"}],"homepage":"https://github.com/adamclayd/firebase-admin-sdk-v8#readme","bugs":{"url":"https://github.com/adamclayd/firebase-admin-sdk-v8/issues"},"dist":{"shasum":"9c14537af0bbc41a2d8378988df711d99027e080","tarball":"https://registry.npmjs.org/@adamclayd/firebase-admin/-/firebase-admin-2.11.0.tgz","fileCount":34,"integrity":"sha512-hd4UNxvmVBY+UYDXe3+8mytJzG+TMWmMsfeGEIm7IqEMFBSNCnx3IGyfZgXQ18//U/SdZIKanqxgFUHdLRuB9Q==","signatures":[{"sig":"MEUCIDR3rWpsgoZBIx61vBHi+yv79XPQtcJe/bZVe+LhwsKZAiEAyqrEiBbRn3/WiyvCk1SHHPxootgJKbvuF0xq6+YMR1U=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1092169},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"b7f79e64b3aa33796be4635ff14b2981f9c346f6","scripts":{"dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"jest","build":"tsup src/index.ts --format cjs,esm --dts","test:all":"npm test && npm run test:e2e","test:e2e":"node --env-file=.env node_modules/jest/bin/jest.js --config jest.e2e.config.js","typecheck":"tsc --noEmit","test:watch":"jest --watch","prepublishOnly":"npm run build","test:e2e:watch":"node --env-file=.env node_modules/jest/bin/jest.js --config jest.e2e.config.js --watch"},"_npmUser":{"name":"1443456","email":"adamclayd@gmail.com"},"repository":{"url":"git+https://github.com/adamclayd/firebase-admin-sdk-v8.git","type":"git"},"_npmVersion":"11.3.0","description":"Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs with support for the Firebase emulators for local development","directories":{},"_nodeVersion":"26.3.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jest":"^30.2.0","tsup":"^8.0.1","ts-jest":"^29.4.6","typescript":"^5.3.3","@types/jest":"^30.0.0","@types/node":"^20.11.0"},"_npmOperationalInternal":{"tmp":"tmp/firebase-admin_2.11.0_1785871453151_0.9308102728074705","host":"s3://npm-registry-packages-npm-production"}},"2.11.5":{"name":"@adamclayd/firebase-admin","version":"2.11.5","description":"Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs with support for the Firebase emulators for local development","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","repository":{"type":"git","url":"git+https://github.com/adamclayd/firebase-admin-sdk-v8.git"},"bugs":{"url":"https://github.com/adamclayd/firebase-admin-sdk-v8/issues"},"homepage":"https://github.com/adamclayd/firebase-admin-sdk-v8#readme","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts","dev":"tsup src/index.ts --format cjs,esm --dts --watch","typecheck":"tsc --noEmit","test":"jest","test:watch":"jest --watch","test:e2e":"node --env-file=.env node_modules/jest/bin/jest.js --config jest.e2e.config.js","test:e2e:watch":"node --env-file=.env node_modules/jest/bin/jest.js --config jest.e2e.config.js --watch","test:all":"npm test && npm run test:e2e","prepublishOnly":"npm run build"},"publishConfig":{"access":"public"},"keywords":["firebase","admin","cloudflare","workers","edge","firestore","auth","authentication","storage","jwt","emulators","emulator","emulated","emulation","emulate","local","localhost"],"author":"","license":"MIT","devDependencies":{"@types/jest":"^30.0.0","@types/node":"^20.11.0","jest":"^30.2.0","ts-jest":"^29.4.6","tsup":"^8.0.1","typescript":"^5.3.3"},"_id":"@adamclayd/firebase-admin@2.11.5","gitHead":"0441f8660e280977246d4fa230e89d8097af6a28","_nodeVersion":"26.3.0","_npmVersion":"11.3.0","dist":{"integrity":"sha512-xSKRPPvGsoAKBMykiwKXBnX1vxyQVBBvyk+CgwhtoNQZUiwXcoLzQOurP4g5CupgNXBn3DS5xOtD89jKfFAXSQ==","shasum":"3cfcd9de8060c0991da4a3f8c7c60636218a4986","tarball":"https://registry.npmjs.org/@adamclayd/firebase-admin/-/firebase-admin-2.11.5.tgz","fileCount":35,"unpackedSize":1055607,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIAWOmapn8+WG29Uc7rjYxAeINq9mruGbd3gueL9Y/XUxAiAO2IGapJAcIv3QSsVXC8rhNiL5+1lw7UoOYKgsRHH4Tg=="}]},"_npmUser":{"name":"1443456","email":"adamclayd@gmail.com"},"directories":{},"maintainers":[{"name":"1443456","email":"adamclayd@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/firebase-admin_2.11.5_1786347738987_0.519162068518211"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-04T19:24:13.039Z","modified":"2026-08-10T07:42:19.373Z","2.11.0":"2026-08-04T19:24:13.352Z","2.11.5":"2026-08-10T07:42:19.174Z"},"bugs":{"url":"https://github.com/adamclayd/firebase-admin-sdk-v8/issues"},"license":"MIT","homepage":"https://github.com/adamclayd/firebase-admin-sdk-v8#readme","keywords":["firebase","admin","cloudflare","workers","edge","firestore","auth","authentication","storage","jwt","emulators","emulator","emulated","emulation","emulate","local","localhost"],"repository":{"type":"git","url":"git+https://github.com/adamclayd/firebase-admin-sdk-v8.git"},"description":"Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs with support for the Firebase emulators for local development","maintainers":[{"name":"1443456","email":"adamclayd@gmail.com"}],"readme":"# Firebase Admin\r\n\r\n> Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs\r\n\r\n[![npm version](badges/npm-version.svg)](https://www.npmjs.com/package/@adamclayd/firebase-admin)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\r\n[![Unit Tests](badges/unit-tests.svg)](https://github.com/adamclayd/firebase-admin/actions/workflows/test.yml)\r\n[![E2E Tests](badges/e2e-tests.svg)](https://github.com/adamclayd/firebase-admin/actions/workflows/e2e-tests.yml)\r\n\r\nThis library provides Firebase Admin SDK functionality for Cloudflare Workers and other edge runtimes. It uses REST APIs and JWT token generation instead of the Node.js Admin SDK, making it compatible with environments that don't support Node.js. You can also use the Firebase emulator for local development.\r\n\r\n## ✨ Features\r\n\r\n- ✅ **Zero Dependencies** - No external dependencies, pure Web APIs (crypto.subtle, fetch)\r\n- ✅ **JWT Token Generation** - Service account authentication\r\n- ✅ **ID Token Verification** - Verify Firebase ID tokens (supports v9 and v10 formats)\r\n- ✅ **Session Cookies** - Create and verify long-lived session cookies (up to 14 days)\r\n- ✅ **User Management** - Create, read, update, delete users via Admin API\r\n- ✅ **Firebase v10 Compatible** - Supports both old and new token issuer formats\r\n- ✅ **Firestore REST API** - Full CRUD operations via REST\r\n- ✅ **Field Value Operations** - increment, arrayUnion, arrayRemove, serverTimestamp, delete\r\n- ✅ **Advanced Queries** - where, orderBy, limit, offset, cursor pagination\r\n- ✅ **Batch Operations** - Atomic multi-document writes\r\n- ✅ **Merge Operations** - Merge documents with existing data\r\n- ✅ **OAuth Access Tokens** - Generate admin API access tokens\r\n- ✅ **Token Caching** - Automatic token refresh before expiry\r\n- ✅ **TypeScript** - Full type definitions included\r\n- ✅ **Emulators** - Use with Firebase emulators for local development\r\n\r\n## 📦 Installation\r\n\r\n```bash\r\nnpm install @adamclayd/firebase-admin\r\n```\r\n\r\n## 🚀 Quick Start\r\n\r\n### 1. Initialize the SDK\r\n\r\n**Option A: Cloudflare Workers / Edge Runtimes (Recommended)**\r\n\r\n```typescript\r\nimport { initializeApp, verifyIdToken } from '@adamclayd/firebase-admin';\r\n\r\nexport default {\r\n  async fetch(request: Request, env: Env): Promise<Response> {\r\n    // Initialize with env variables\r\n    initializeApp({\r\n      serviceAccount: env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY,\r\n      projectId: env.FIREBASE_PROJECT_ID,\r\n\r\n      apiKey: env.FIREBASE_API_KEY,\r\n\r\n      // optional - use for local development\r\n      authEmulatorHost: env.FIREBASE_AUTH_EMULATOR_HOST,\r\n      firestoreEmulatorHost: env.FIREBASE_FIRESTORE_EMULATOR_HOST,\r\n      storageEmulatorHost: env.FIREBASE_STORAGE_EMULATOR_HOST,\r\n    });\r\n\r\n    // Now use the SDK\r\n    const token = request.headers.get('authorization')?.split('Bearer ')[1];\r\n    const user = await verifyIdToken(token);\r\n    \r\n    return new Response(JSON.stringify({ user }));\r\n  }\r\n};\r\n```\r\n\r\n**Option B: Node.js / Traditional Environments**\r\n\r\n```typescript\r\nimport { initializeApp } from '@adamclayd/firebase-admin';\r\n\r\n// Option 1: Explicit initialization\r\ninitializeApp({\r\n  serviceAccount: JSON.parse(process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY!),\r\n  projectId: process.env.FIREBASE_PROJECT_ID,\r\n\r\n  apiKey: process.env.FIREBASE_API_KEY,\r\n\r\n  // optional - use for local development\r\n  authEmulatorHost: process.env.FIREBASE_AUTH_EMULATOR_HOST,\r\n  firestoreEmulatorHost: process.env.FIREBASE_FIRESTORE_EMULATOR_HOST,\r\n  storageEmulatorHost: process.env.FIREBASE_STORAGE_EMULATOR_HOST\r\n});\r\n\r\n// Option 2: Auto-detect from process.env (no initialization needed)\r\n// The SDK will automatically use process.env if initializeApp() is not called\r\n```\r\n\r\n**Environment Variables (if not using initializeApp):**\r\n```env\r\nFIREBASE_ADMIN_SERVICE_ACCOUNT_KEY='{\"type\":\"service_account\",...}'\r\nFIREBASE_PROJECT_ID=your-project-id\r\nFIREBASE_API_KEY=your-api-key\r\n\r\n# use for local development\r\nFIREBASE_AUTH_EMULATOR_HOST=localhost:9099\r\nFIREBASE_FIRESTORE_EMULATOR_HOST=localhost:8080\r\nFIREBASE_STORAGE_EMULATOR_HOST=localhost:9199\r\n```\r\n\r\n* **Notes:**\r\n  * If a particular emulator setting is provided it will override the production setting. So make sure you have all of the emulator environment variables and `initializeApp()` settings cleared when deploying to production. \r\n  * On worker environments like Cloudflare it does not default to any environment variables so you have to call `initializeApp()` with the appropriate settings for your environment.\r\n\r\n\r\n### Initializing And Starting The Emulators\r\nIf you are going to be using the Firebase emulators for local development you need to run these commands:\r\n\r\nInstall Firebase Cli:\r\n```bash\r\nnpm install -g firebase-tools\r\n```\r\n\r\nInitialize:\r\n```bash\r\nfirebase init emulators\r\n# > select Firestore, Authentication, an/or Storage when asked\r\n```\r\n\r\nStart:\r\n```bash\r\nfirebase emulators:start\r\n```\r\n\r\n### 2. Verify ID Tokens\r\n\r\n```typescript\r\nimport { verifyIdToken, getUserFromToken } from '@adamclayd/firebase-admin';\r\n\r\nconst authHeader = request.headers.get('authorization');\r\nconst idToken = authHeader?.split('Bearer ')[1];\r\n\r\ntry {\r\n  const user = await getUserFromToken(idToken);\r\n  console.log('User:', user.email, user.displayName);\r\n} catch (error) {\r\n  return new Response('Invalid token', { status: 401 });\r\n}\r\n```\r\n\r\n### 3. Session Cookies (Long-Lived Sessions)\r\n\r\n```typescript\r\nimport { createSessionCookie, verifySessionCookie } from '@adamclayd/firebase-admin';\r\n\r\n// Create 14-day session cookie from ID token\r\nconst sessionCookie = await createSessionCookie(idToken, {\r\n  expiresIn: 60 * 60 * 24 * 14 * 1000\r\n});\r\n\r\n// Set as HTTP-only cookie\r\nresponse.headers.set('Set-Cookie',\r\n  `session=${sessionCookie}; Max-Age=1209600; HttpOnly; Secure; SameSite=Strict`\r\n);\r\n\r\n// Verify session cookie\r\nconst user = await verifySessionCookie(cookie);\r\n```\r\n\r\n### 4. Basic Firestore Operations\r\n\r\n```typescript\r\nimport { setDocument, getDocument, updateDocument, FieldValue } from '@adamclayd/firebase-admin';\r\n\r\n// Set a document (create or overwrite)\r\nawait setDocument('users', 'user123', {\r\n  name: 'John Doe',\r\n  email: 'john@example.com',\r\n  createdAt: FieldValue.serverTimestamp(),\r\n});\r\n\r\n// Get a document\r\nconst user = await getDocument('users', 'user123');\r\n\r\n// Update with field values\r\nawait updateDocument('users', 'user123', {\r\n  loginCount: FieldValue.increment(1),\r\n  lastLogin: FieldValue.serverTimestamp(),\r\n});\r\n```\r\n\r\n### 4. Advanced Queries\r\n\r\n```typescript\r\nimport { queryDocuments } from '@adamclayd/firebase-admin';\r\n\r\nconst activeUsers = await queryDocuments('users', {\r\n  where: [\r\n    { field: 'active', op: '==', value: true },\r\n    { field: 'age', op: '>=', value: 18 }\r\n  ],\r\n  orderBy: [{ field: 'createdAt', direction: 'DESCENDING' }],\r\n  limit: 10\r\n});\r\n```\r\n\r\n## 📚 API Reference\r\n\r\n### Authentication\r\n\r\n#### Configure For Production Or Local Emulators\r\n```typescript\r\nimport { initializeApp } from '@adamclayd/firebase-admin';\r\n\r\n// For local emulators\r\ninitializeApp({\r\n  authEmulatorHost: env.FIREBASE_AUTH_EMULATOR_HOST, // 127.0.0.1:9099\r\n  projectId: env.FIREBASE_PROJECT_ID,  // test-project-id\r\n  apiKey: env.FIREBASE_API_KEY // test-api-key\r\n});\r\n\r\n// For production\r\ninitializeApp({\r\n  serviceAccount: JSON.parse(env.FIREBASE_SERVICE_ACCOUNT),\r\n  projectId: env.FIREBASE_PROJECT_ID,\r\n  apiKey: env.FIREBASE_API_KEY\r\n});\r\n```\r\n* **Notes For Emulator:**\r\n  * Your Authentication URLs will point the emulator instead of the production Firebase Auth service.\r\n  * If you have the emulator configured it will override the production settings.\r\n  * `projectId` is required for the emulator to work. It can be any string. Just make sure the ones on your frontend match the ones on the backend\r\n  * `apiKey` is not required unless you plan to use a call like like `signInWithCustomToken` that requires an API key. It can be any string. Just make sure the frontend API key match the ones on the backend\r\n  * `authEmulatorHost` will fall back to `FIREBASE_AUTH_EMULATOR_HOST` environment variable if not provided or undefined.\r\n\r\n#### `verifyIdToken(idToken: string): Promise<DecodedIdToken>`\r\n\r\nVerify a Firebase ID token and return the decoded token.\r\n\r\n```typescript\r\nconst decoded = await verifyIdToken(idToken);\r\nconsole.log('User ID:', decoded.uid);\r\n```\r\n\r\n#### `getUserFromToken(idToken: string): Promise<UserInfo>`\r\n\r\nGet user information from a verified ID token.\r\n\r\n```typescript\r\nconst user = await getUserFromToken(idToken);\r\n// Returns: { uid, email, emailVerified, displayName, photoURL }\r\n```\r\n\r\n### User Management\r\n\r\n#### `getUserByEmail(email: string): Promise<UserRecord | null>`\r\n\r\nLook up a Firebase user by email address.\r\n\r\n```typescript\r\nconst user = await getUserByEmail('user@example.com');\r\nif (user) {\r\n  console.log('User ID:', user.uid);\r\n  console.log('Email verified:', user.emailVerified);\r\n}\r\n```\r\n\r\n#### `getUserByUid(uid: string): Promise<UserRecord | null>`\r\n\r\nLook up a Firebase user by UID.\r\n\r\n```typescript\r\nconst user = await getUserByUid('user123');\r\nif (user) {\r\n  console.log('Email:', user.email);\r\n  console.log('Display name:', user.displayName);\r\n}\r\n```\r\n\r\n#### `createUser(properties: CreateUserRequest): Promise<UserRecord>`\r\n\r\nCreate a new Firebase user.\r\n\r\n```typescript\r\nconst newUser = await createUser({\r\n  email: 'newuser@example.com',\r\n  password: 'securePassword123',\r\n  displayName: 'New User',\r\n  emailVerified: false,\r\n});\r\nconsole.log('Created user:', newUser.uid);\r\n```\r\n\r\n#### `updateUser(uid: string, properties: UpdateUserRequest): Promise<UserRecord>`\r\n\r\nUpdate an existing Firebase user.\r\n\r\n```typescript\r\nconst updatedUser = await updateUser('user123', {\r\n  displayName: 'Updated Name',\r\n  photoURL: 'https://example.com/photo.jpg',\r\n  emailVerified: true,\r\n});\r\n```\r\n\r\n#### `deleteUser(uid: string): Promise<void>`\r\n\r\nDelete a Firebase user.\r\n\r\n```typescript\r\nawait deleteUser('user123');\r\n```\r\n\r\n#### `listUsers(maxResults?: number, pageToken?: string): Promise<ListUsersResult>`\r\n\r\nList all users with pagination.\r\n\r\n```typescript\r\n// List first 100 users\r\nconst result = await listUsers(100);\r\nconsole.log('Users:', result.users.length);\r\n\r\n// Get next page\r\nif (result.pageToken) {\r\n  const nextPage = await listUsers(100, result.pageToken);\r\n}\r\n```\r\n\r\n#### `setCustomUserClaims(uid: string, customClaims: Record<string, any> | null): Promise<void>`\r\n\r\nSet custom claims on a user's ID token for role-based access control.\r\n\r\n```typescript\r\n// Set custom claims\r\nawait setCustomUserClaims('user123', {\r\n  role: 'admin',\r\n  premium: true,\r\n  permissions: ['read', 'write', 'delete'],\r\n});\r\n\r\n// Clear custom claims\r\nawait setCustomUserClaims('user123', null);\r\n```\r\n\r\n### Firestore - Basic Operations\r\n\r\n#### Configure For Production Or Local Emulators\r\n```typescript\r\nimport { initializeApp } from '@adamclayd/firebase-admin';\r\n\r\n// For local emulators\r\ninitializeApp({\r\n  firestoreEmulatorHost: env.FIRESTORE_EMULATOR_HOST, // 127.0.0.1:8080\r\n  projectId: env.FIREBASE_PROJECT_ID,  // test-project-id\r\n});\r\n\r\n// For production\r\ninitializeApp({\r\n  serviceAccount: JSON.parse(env.FIREBASE_SERVICE_ACCOUNT),\r\n  projectId: env.FIREBASE_PROJECT_ID,\r\n});\r\n```\r\n* **Notes For Emulator:**\r\n  * Your Firestore URLs will point the emulator instead of the production Firebase Firestore service.\r\n  * If you have the emulator configured it will override the production settings.\r\n  * `projectId` is required for the emulator to work. It can be any string. Just make sure the ones on your frontend match the ones on the backend\r\n  * `firestoreEmulatorHost` will fall back to `FIREBASE_FIRESTORE_EMULATOR_HOST` environment variable if not provided or undefined.\r\n\r\n#### `setDocument(collectionPath, documentId, data, options?): Promise<void>`\r\n\r\nCreate or overwrite a document. Supports merge options.\r\n\r\n```typescript\r\n// Overwrite\r\nawait setDocument('users', 'user123', { name: 'John', age: 30 });\r\n\r\n// Merge with existing\r\nawait setDocument('users', 'user123', { age: 31 }, { merge: true });\r\n\r\n// Merge specific fields\r\nawait setDocument('users', 'user123', { age: 31, city: 'NYC' }, { \r\n  mergeFields: ['age'] \r\n});\r\n```\r\n\r\n#### `addDocument(collectionPath, data, documentId?): Promise<DocumentReference>`\r\n\r\nAdd a document with auto-generated or custom ID. Returns a DocumentReference with `id` and `path` properties.\r\n\r\n```typescript\r\nconst docRef = await addDocument('posts', { title: 'Hello' });\r\nconsole.log('Created:', docRef.id); // Auto-generated ID\r\n\r\nconst customDocRef = await addDocument('posts', { title: 'Hi' }, 'custom-id');\r\nconsole.log('Created:', customDocRef.id); // 'custom-id'\r\n```\r\n\r\n#### `getDocument(collectionPath, documentId): Promise<DataObject | null>`\r\n\r\nGet a document by ID.\r\n\r\n```typescript\r\nconst user = await getDocument('users', 'user123');\r\n```\r\n\r\n#### `updateDocument(collectionPath, documentId, data): Promise<void>`\r\n\r\nUpdate specific fields in a document.\r\n\r\n```typescript\r\nawait updateDocument('users', 'user123', { lastLogin: new Date() });\r\n```\r\n\r\n#### `deleteDocument(collectionPath, documentId): Promise<void>`\r\n\r\nDelete a document.\r\n\r\n```typescript\r\nawait deleteDocument('users', 'user123');\r\n```\r\n\r\n### Firestore - Field Values\r\n\r\n#### `FieldValue.serverTimestamp()`\r\n\r\nSet field to server timestamp.\r\n\r\n```typescript\r\nawait setDocument('posts', 'post1', {\r\n  createdAt: FieldValue.serverTimestamp()\r\n});\r\n```\r\n\r\n#### `FieldValue.increment(n)`\r\n\r\nIncrement a numeric field.\r\n\r\n```typescript\r\nawait updateDocument('users', 'user1', {\r\n  loginCount: FieldValue.increment(1),\r\n  points: FieldValue.increment(10)\r\n});\r\n```\r\n\r\n#### `FieldValue.arrayUnion(...elements)`\r\n\r\nAdd elements to an array (no duplicates).\r\n\r\n```typescript\r\nawait updateDocument('posts', 'post1', {\r\n  tags: FieldValue.arrayUnion('javascript', 'typescript')\r\n});\r\n```\r\n\r\n#### `FieldValue.arrayRemove(...elements)`\r\n\r\nRemove elements from an array.\r\n\r\n```typescript\r\nawait updateDocument('posts', 'post1', {\r\n  tags: FieldValue.arrayRemove('outdated')\r\n});\r\n```\r\n\r\n#### `FieldValue.delete()`\r\n\r\nDelete a field from a document.\r\n\r\n```typescript\r\nawait updateDocument('users', 'user1', {\r\n  temporaryField: FieldValue.delete()\r\n});\r\n```\r\n\r\n### Firestore - Queries\r\n\r\n#### `queryDocuments(collectionPath, options?): Promise<Array<{ id, data }>>`\r\n\r\nQuery documents with advanced filtering.\r\n\r\n**Query Options:**\r\n- `where`: Array of filters `{ field, op, value }`\r\n- `orderBy`: Array of orders `{ field, direction }`\r\n- `limit`: Maximum number of results\r\n- `offset`: Number of results to skip\r\n- `startAt`, `startAfter`, `endAt`, `endBefore`: Cursor pagination\r\n\r\n**Where Operators:**\r\n- `==`, `!=`, `<`, `<=`, `>`, `>=`\r\n- `array-contains`, `array-contains-any`\r\n- `in`, `not-in`\r\n\r\n```typescript\r\n// Simple query\r\nconst users = await queryDocuments('users');\r\n\r\n// With filters\r\nconst activeAdults = await queryDocuments('users', {\r\n  where: [\r\n    { field: 'active', op: '==', value: true },\r\n    { field: 'age', op: '>=', value: 18 }\r\n  ]\r\n});\r\n\r\n// With ordering and limit\r\nconst topUsers = await queryDocuments('users', {\r\n  where: [{ field: 'active', op: '==', value: true }],\r\n  orderBy: [{ field: 'points', direction: 'DESCENDING' }],\r\n  limit: 10\r\n});\r\n\r\n// Cursor pagination\r\nconst results = await queryDocuments('users', {\r\n  orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],\r\n  startAfter: [lastCreatedAt],\r\n  limit: 20\r\n});\r\n```\r\n\r\n### Firestore - Batch Operations\r\n\r\n#### `batchWrite(operations): Promise<BatchWriteResult>`\r\n\r\nPerform multiple write operations atomically.\r\n\r\n```typescript\r\nawait batchWrite([\r\n  {\r\n    type: 'set',\r\n    collectionPath: 'users',\r\n    documentId: 'user1',\r\n    data: { name: 'John' }\r\n  },\r\n  {\r\n    type: 'update',\r\n    collectionPath: 'users',\r\n    documentId: 'user2',\r\n    data: { lastLogin: FieldValue.serverTimestamp() }\r\n  },\r\n  {\r\n    type: 'delete',\r\n    collectionPath: 'users',\r\n    documentId: 'user3'\r\n  }\r\n]);\r\n```\r\n\r\n### Token Generation\r\n\r\n#### `getAdminAccessToken(emulated: boolean = false): Promise<string>`\r\n\r\nGet an OAuth access token for Firebase Admin API. Automatically cached and refreshed.\r\n\r\n```typescript\r\n// if your are on the production environment\r\nconst token = await getAdminAccessToken();\r\n\r\n// if your are on the emulator environment\r\nconst token = await getAdminAccessToken(true);\r\n```\r\n\r\n#### Adding Custom Token Cache Store\r\nIf you need to use a different cache for the admin access token, you can provide your own implementation of `CacheAdminAccessTokenStore` to the `initializeApp()` function. The default assigns it to one variable in memory and will expire in exp - 60 seconds. The default will not work in a worker environment like Cloudflare because the variable will not persist between requests. See the example below:\r\n\r\n```typescript\r\nimport { initializeApp } from '@adamclayd/firebase-admin';\r\nimport { CacheAdminAccessTokenStore, TokenResponse } from '@adamclayd/firebase-admin';\r\nimport { Redis } from \"@upstash/redis/cloudflare\";\r\n\r\nclass RedisTokenStore extends CacheAdminAccessTokenStore {\r\n  protected constructor(token: symbol, private redis: Redis) {}\r\n\r\n  async get(): Promise<string | null> {\r\n    return await this.redis.getex('firebase:admin-access-token');\r\n  }\r\n\r\n  async set(data: TokenResponse): Promise<void> {\r\n    return await this.redis.set('firebase:admin-access-token', data.access_token, { ex: data.expires_in - 60 });\r\n  }\r\n\r\n  async clear() {\r\n    await this.redis.del('firebase:admin-access-token');\r\n  }\r\n}\r\n\r\nexport default {\r\n  async fetch(request: Request, env: Env) {\r\n    initializeApp({\r\n      serviceAccount: env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY,\r\n      projectId: env.FIREBASE_PROJECT_ID,\r\n      apiKey: env.FIREBASE_API_KEY,\r\n\r\n      // provide your custom token store\r\n      cachedAdminAccessTokenStore: RedisTokenStore.getinstance(Redis.fromEnv(env)),\r\n    });\r\n\r\n    // use firebase-admin\r\n  }\r\n}\r\n```\r\n\r\nYour app will now use the Cloudflare Redis instance for admin access token caching. If your app is hosted in a stateless mannor you would want to make a custom implemention because the admin token is not cahced in a stateless environment. So it would have to generate a new admin access token for every call. Implementing it would keep your server call from having to make an extra api request.\r\n\r\n - Implemention\r\n   - `constuctor` must accept at least a token parameter of type `symbol` as its first parameter to be passed to the base class constructor. This is to ensure that no child classes get initialiated directly with the `new` operator\r\n   - All implementations come with a static `getInstance` method inherited from the abstract base class that will return the saved instance or a new instance of the token store so that there is only ever one instance of it implemented. You can pass whatever additional parameters that your constructor accepts to it.\r\n   - `get` must be implemented with no parameters and must return a `Promise<string | undefined>`\r\n   - `set` must be implemented with a single `TokenResponse` parameter and must return a `Promise<void>`\r\n   - `clear` must be implemented with no parameters and must return a `Promise<void>`\r\n\r\n#### Clearing Admin Access Token Cache\r\n```typescript\r\nimport { getAdminTokenStore } from '@adamclayd/firebase-admin';\r\n\r\nawait getAdminTokenStore().clear();\r\n```\r\n\r\n### Storage - Resumable Uploads\r\n\r\n#### Configure For Production Or Local Emulators\r\n```typescript\r\nimport { initializeApp } from '@adamclayd/firebase-admin';\r\n\r\n// For local emulators\r\ninitializeApp({\r\n  storageEmulatorHost: env.FIREBASE_STORAGE_EMULATOR_HOST, // 127.0.0.1:9099\r\n  projectId: env.FIREBASE_PROJECT_ID,  // test-project-id\r\n});\r\n\r\n// For production\r\ninitializeApp({\r\n  serviceAccount: JSON.parse(env.FIREBASE_SERVICE_ACCOUNT),\r\n  projectId: env.FIREBASE_PROJECT_ID,\r\n});\r\n```\r\n* **Notes For Emulator:**\r\n  * Your Storage URLs will point the emulator instead of the production Firebase Storage service.\r\n  * If you have the emulator configured it will override the production settings.\r\n  * `projectId` is required for the emulator to work. It can be any string. Just make sure the ones on your frontend match the ones on the backend\r\n  * `storageEmulatorHost` will fall back to `FIREBASE_STORAGE_EMULATOR_HOST` environment variable if not provided or undefined.\r\n\r\n#### `uploadFileResumable(bucket, path, data, contentType, options?): Promise<FileMetadata>`\r\n\r\nUpload large files with resumable upload support. Suitable for files >10MB, unreliable networks, or when progress tracking is needed.\r\n\r\n**Features:**\r\n- ✅ Chunked uploads (configurable chunk size)\r\n- ✅ Progress tracking with callbacks\r\n- ✅ Resume interrupted uploads\r\n- ✅ Memory efficient (doesn't load entire file at once)\r\n- ✅ Automatic retry on chunk failure\r\n\r\n```typescript\r\nimport { uploadFileResumable } from '@adamclayd/firebase-admin';\r\n\r\n// Upload large file with progress tracking\r\nconst data = await fetch('https://example.com/large-video.mp4');\r\nconst buffer = await data.arrayBuffer();\r\n\r\nconst metadata = await uploadFileResumable(\r\n  'my-bucket.appspot.com',\r\n  'videos/large.mp4',\r\n  buffer,\r\n  'video/mp4',\r\n  {\r\n    chunkSize: 512 * 1024, // 512KB chunks (default: 256KB)\r\n    onProgress: (uploaded, total) => {\r\n      const percent = (uploaded / total * 100).toFixed(2);\r\n      console.log(`Upload progress: ${percent}%`);\r\n    },\r\n    metadata: { userId: '123', category: 'videos' },\r\n  }\r\n);\r\n\r\nconsole.log('Upload complete:', metadata);\r\n```\r\n\r\n**Resume interrupted upload:**\r\n\r\n```typescript\r\nlet sessionUri: string;\r\n\r\ntry {\r\n  const metadata = await uploadFileResumable(\r\n    bucket,\r\n    path,\r\n    data,\r\n    contentType,\r\n    {\r\n      onProgress: (uploaded, total) => {\r\n        // Save session URI for resume\r\n        sessionUri = /* get from response */;\r\n      },\r\n    }\r\n  );\r\n} catch (error) {\r\n  // Resume from where it left off\r\n  const metadata = await uploadFileResumable(\r\n    bucket,\r\n    path,\r\n    data,\r\n    contentType,\r\n    {\r\n      resumeToken: sessionUri, // Resume from previous session\r\n    }\r\n  );\r\n}\r\n```\r\n\r\n**When to use:**\r\n- Files larger than 10MB\r\n- Unreliable network conditions\r\n- Need progress reporting\r\n- Files that may exceed memory limits\r\n\r\n**When to use simple `uploadFile()` instead:**\r\n- Small files (<10MB)\r\n- Reliable network\r\n- No progress tracking needed\r\n- Edge runtime with memory constraints\r\n\r\n## 💡 Examples\r\n\r\nSee [EXAMPLES.md](./EXAMPLES.md) for comprehensive examples including:\r\n- Authentication patterns\r\n- Field value operations\r\n- Complex queries\r\n- Batch operations\r\n- Real-world use cases\r\n\r\n## 🔧 Advanced Usage\r\n\r\n### Cloudflare Workers Example\r\n\r\n```typescript\r\nexport default {\r\n  async fetch(request: Request, env: Env): Promise<Response> {\r\n    process.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY = env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY;\r\n    process.env.PUBLIC_FIREBASE_PROJECT_ID = env.PUBLIC_FIREBASE_PROJECT_ID;\r\n\r\n    const authHeader = request.headers.get('authorization');\r\n    const idToken = authHeader?.split('Bearer ')[1];\r\n\r\n    if (!idToken) {\r\n      return new Response('Unauthorized', { status: 401 });\r\n    }\r\n\r\n    try {\r\n      const user = await getUserFromToken(idToken);\r\n      \r\n      // Track user activity\r\n      await updateDocument('users', user.uid, {\r\n        lastSeen: FieldValue.serverTimestamp(),\r\n        visitCount: FieldValue.increment(1)\r\n      });\r\n\r\n      return new Response(JSON.stringify({ user }), {\r\n        headers: { 'Content-Type': 'application/json' },\r\n      });\r\n    } catch (error) {\r\n      return new Response('Error: ' + error.message, { status: 500 });\r\n    }\r\n  },\r\n};\r\n```\r\n\r\n### Leaderboard Example\r\n\r\n```typescript\r\nimport { queryDocuments, updateDocument, FieldValue } from '@adamclayd/firebase-admin';\r\n\r\nasync function getTopPlayers(limit = 10) {\r\n  return await queryDocuments('players', {\r\n    where: [{ field: 'active', op: '==', value: true }],\r\n    orderBy: [{ field: 'score', direction: 'DESCENDING' }],\r\n    limit\r\n  });\r\n}\r\n\r\nasync function updatePlayerScore(playerId: string, points: number) {\r\n  await updateDocument('players', playerId, {\r\n    score: FieldValue.increment(points),\r\n    lastPlayed: FieldValue.serverTimestamp()\r\n  });\r\n}\r\n```\r\n\r\n### Bulk Operations Example\r\n\r\n```typescript\r\nimport { batchWrite, FieldValue } from '@adamclayd/firebase-admin';\r\n\r\nasync function bulkUpdateUsers(userIds: string[], updates: any) {\r\n  const operations = userIds.map(userId => ({\r\n    type: 'update' as const,\r\n    collectionPath: 'users',\r\n    documentId: userId,\r\n    data: {\r\n      ...updates,\r\n      updatedAt: FieldValue.serverTimestamp()\r\n    }\r\n  }));\r\n  \r\n  // Process in chunks of 500 (Firestore batch limit)\r\n  for (let i = 0; i < operations.length; i += 500) {\r\n    const chunk = operations.slice(i, i + 500);\r\n    await batchWrite(chunk);\r\n  }\r\n}\r\n```\r\n\r\n## 🆚 Comparison with Node.js Admin SDK\r\n\r\n| Feature | Node.js Admin SDK | This Library |\r\n|---------|------------------|--------------|\r\n| **Environment** | Node.js only | Cloudflare Workers, Edge, Deno, Bun |\r\n| **Auth** | Admin SDK methods | JWT + REST API |\r\n| **Firestore** | Native SDK | REST API |\r\n| **Field Values** | ✅ Full support | ✅ Full support |\r\n| **Queries** | ✅ Full support | ✅ Full support |\r\n| **Batch Writes** | ✅ Full support | ✅ Full support |\r\n| **Token Verification** | Built-in | firebase-auth-cloudflare-workers |\r\n| **Dependencies** | Heavy (Node.js) | Lightweight (Web APIs) |\r\n| **Cold Starts** | Slower | Faster |\r\n| **Bundle Size** | Large | Small |\r\n\r\n## 🤝 Contributing\r\n\r\nContributions are welcome! Please feel free to submit a Pull Request.\r\n\r\n## 📄 License\r\n\r\nMIT\r\n\r\n## 🔗 Related Projects\r\n\r\n- [firebase-auth-cloudflare-workers](https://github.com/Code-Hex/firebase-auth-cloudflare-workers) - ID token verification library\r\n- [Firebase REST API Documentation](https://firebase.google.com/docs/firestore/use-rest-api)\r\n\r\n## 📝 Notes\r\n\r\n- This library is designed for server-side use only (admin operations)\r\n- For client-side Firebase, use the official Firebase JS SDK\r\n- Service account credentials should be kept secure and never exposed to clients\r\n- Token caching is automatic and refreshes 1 minute before expiry\r\n- Batch operations support up to 500 operations per batch (Firestore limit)\r\n\r\n## 🐛 Troubleshooting\r\n\r\n### \"FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY not set\"\r\n\r\nMake sure you've set the environment variable with your service account JSON:\r\n\r\n```typescript\r\nprocess.env.FIREBASE_ADMIN_SERVICE_ACCOUNT_KEY = JSON.stringify(serviceAccount);\r\n```\r\n\r\n### \"Failed to verify ID token\"\r\n\r\nEnsure the token is:\r\n1. A valid Firebase ID token (not an access token)\r\n2. Not expired\r\n3. From the correct Firebase project\r\n\r\n### TypeScript Errors\r\n\r\nMake sure you have the required dev dependencies:\r\n\r\n```bash\r\nnpm install -D @types/node typescript\r\n```\r\n\r\n### Query Performance\r\n\r\nFor better query performance:\r\n- Create composite indexes for multi-field queries\r\n- Use cursor pagination instead of offset for large datasets\r\n- Limit query results to reasonable sizes\r\n\r\n## 📊 Feature Comparison Table\r\n\r\n| Feature | Supported | Notes |\r\n|---------|-----------|-------|\r\n| ID Token Verification | ✅ | Supports v9 and v10 token formats |\r\n| Custom Token Creation | ✅ | createCustomToken() |\r\n| Custom Token Exchange | ✅ | signInWithCustomToken() |\r\n| User Management | ✅ | Create, read, update, delete, list users |\r\n| Firestore CRUD | ✅ | Full support |\r\n| Firestore Queries | ✅ | where, orderBy, limit, cursors |\r\n| Firestore Batch | ✅ | Up to 500 operations |\r\n| Firestore Transactions | ❌ | Not yet implemented |\r\n| **Realtime Listeners** | **❌** | **See explanation below** |\r\n| Field Values | ✅ | increment, arrayUnion, serverTimestamp, etc. |\r\n| Realtime Database | ❌ | Not planned |\r\n| Cloud Storage | ✅ | Upload, download, delete, signed URLs |\r\n| Cloud Messaging | ❌ | Not yet implemented |\r\n\r\n## ⚠️ Realtime Listeners Not Supported\r\n\r\nThis library **does not support** Firestore realtime listeners (`onSnapshot()`). Here's why:\r\n\r\n### Technical Limitation\r\n\r\n**This library uses the Firestore REST API**, which is:\r\n- ✅ Stateless (request/response only)\r\n- ✅ Compatible with edge runtimes (Cloudflare Workers, Vercel Edge)\r\n- ❌ **No persistent connections**\r\n- ❌ **No server-push capabilities**\r\n- ❌ **No streaming support**\r\n\r\n**Realtime listeners require**:\r\n- Persistent connections (WebSocket or gRPC)\r\n- Bidirectional streaming\r\n- Server-push architecture\r\n\r\nThe Firestore REST API simply doesn't provide these capabilities.\r\n\r\n### Why Not Implement gRPC?\r\n\r\nWhile Firestore does offer a gRPC API with streaming support, implementing it would require:\r\n\r\n1. **Complex Protocol Implementation**\r\n   - HTTP/2 framing\r\n   - gRPC message framing\r\n   - Protobuf encoding/decoding\r\n   - Authentication flow\r\n   - Reconnection logic\r\n   - ~100+ hours of development\r\n\r\n2. **Runtime Limitations**\r\n   - Cloudflare Workers doesn't support full gRPC (only gRPC-Web)\r\n   - gRPC-Web requires a proxy server\r\n   - Can't connect directly to Firestore's gRPC endpoint\r\n   - Would only work in Durable Objects, not regular Workers\r\n\r\n3. **Maintenance Burden**\r\n   - Must keep up with Firestore protocol changes\r\n   - Complex debugging and error handling\r\n   - High ongoing maintenance cost\r\n\r\n### Alternatives\r\n\r\nIf you need realtime updates, consider these approaches:\r\n\r\n#### 1. **Polling (Simple)**\r\n```typescript\r\n// Poll for changes every 5 seconds\r\nsetInterval(async () => {\r\n  const doc = await getDocument('users', 'user123');\r\n  // Handle updates\r\n}, 5000);\r\n```\r\n\r\n**Pros**: Simple, works everywhere\r\n**Cons**: 5-second delay, polling costs\r\n\r\n#### 2. **Durable Objects + Polling (Better)**\r\n```typescript\r\n// Durable Object polls once, broadcasts to many clients\r\nexport class FirestoreSync {\r\n  async poll() {\r\n    const doc = await getDocument('users', 'user123');\r\n    // Broadcast to all connected WebSocket clients\r\n    for (const ws of this.sessions) {\r\n      ws.send(JSON.stringify(doc));\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n**Pros**: One poll serves many clients, WebSocket push to clients\r\n**Cons**: Still polling-based, Cloudflare-specific\r\n\r\n#### 3. **Hybrid Architecture (Best)**\r\n```typescript\r\n// Use firebase-admin-node for realtime in Node.js\r\nimport admin from 'firebase-admin';\r\n\r\nadmin.firestore().collection('users').doc('user123')\r\n  .onSnapshot((snapshot) => {\r\n    // True realtime updates\r\n    console.log('Update:', snapshot.data());\r\n  });\r\n\r\n// Use this library for CRUD in edge functions\r\nimport { getDocument } from '@adamclayd/firebase-admin';\r\nconst doc = await getDocument('users', 'user123');\r\n```\r\n\r\n**Pros**: True realtime where needed, edge performance for CRUD\r\n**Cons**: Requires separate Node.js service\r\n\r\n#### 4. **Firebase Client SDK (Frontend)**\r\n```typescript\r\n// Use Firebase Client SDK in browser/mobile\r\nimport { onSnapshot, doc } from 'firebase/firestore';\r\n\r\nonSnapshot(doc(db, 'users', 'user123'), (snapshot) => {\r\n  console.log('Update:', snapshot.data());\r\n});\r\n```\r\n\r\n**Pros**: True realtime, built-in, well-supported\r\n**Cons**: Client-side only, requires Firebase Auth\r\n\r\n### Recommendation\r\n\r\n- **For edge runtimes**: Use polling or Durable Objects pattern\r\n- **For true realtime**: Use `firebase-admin-node` in Node.js\r\n- **For client apps**: Use Firebase Client SDK\r\n- **For hybrid**: Use this library for CRUD + Node.js for realtime\r\n\r\n### Related\r\n\r\n- [firebase-admin-node](https://github.com/firebase/firebase-admin-node) - Full Admin SDK with realtime support\r\n- [Firebase Client SDK](https://firebase.google.com/docs/firestore/query-data/listen) - Client-side realtime listeners\r\n\r\n## 🗺️ Roadmap\r\n\r\n- [x] Custom token creation ✅ (v2.2.0)\r\n- [x] Custom token exchange ✅ (v2.2.0)\r\n- [x] Cloud Storage operations ✅ (v2.2.0)\r\n- [ ] User management (create, update, delete users)\r\n- [ ] Firestore transactions\r\n- [ ] More comprehensive error handling\r\n- [ ] Rate limiting helpers\r\n- [ ] Retry logic for failed operations\r\n- [ ] Storage unit tests (currently only e2e)\r\n","readmeFilename":"README.md"}