@prmichaelsen/firebase-admin-sdk-v8 2.6.0 → 2.8.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.
@@ -13,7 +13,9 @@
13
13
  "WebFetch(domain:agentbase.me)",
14
14
  "WebSearch",
15
15
  "WebFetch(domain:firebase.google.com)",
16
- "WebFetch(domain:developers.google.com)"
16
+ "WebFetch(domain:developers.google.com)",
17
+ "Bash(npx tsc:*)",
18
+ "Bash(node --env-file=.env node_modules/.bin/jest --config jest.e2e.config.js --testPathPatterns='firestore/operations.e2e' 2>&1 | tail -20)"
17
19
  ]
18
20
  }
19
21
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,33 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.8.0] - 2026-03-12
9
+
10
+ ### Added
11
+ - **Firestore Batch Get by Paths (`getAllByPaths`)**: Fetch multiple documents from different collections in a single REST API call
12
+ - `getAllByPaths([{ collection, id }])` - Batch get up to 100 documents from arbitrary paths via `documents:batchGet` endpoint
13
+ - Returns results in the same order as input refs
14
+ - Missing documents return `null` (no throw)
15
+ - Supports subcollection paths (e.g., `users/uid1/profile`)
16
+ - 7 unit tests + 3 e2e tests
17
+
18
+ ## [2.7.0] - 2026-03-12
19
+
20
+ ### Added
21
+ - **Firestore Batch Get (`getAll`)**: Fetch multiple documents in a single REST API call
22
+ - `getAll(collectionPath, documentIds)` - Batch get up to 100 documents via `documents:batchGet` endpoint
23
+ - Returns results in the same order as input document IDs
24
+ - Missing documents return `null` (no throw)
25
+ - Enforces 100-document limit with clear error message
26
+ - Supports subcollection paths
27
+ - 8 unit tests + 5 e2e tests
28
+
29
+ ### Fixed
30
+ - **FCM**: `sendMessage` now uses `getProjectId()` instead of `getConfig().projectId`, fixing silent failure when `initializeApp()` hasn't been called (falls back to `FIREBASE_PROJECT_ID` env var)
31
+
32
+ ### Changed
33
+ - Total unit tests increased from 498 to 506 (+8 tests)
34
+
8
35
  ## [2.6.0] - 2026-03-07
9
36
 
10
37
  ### Added
package/dist/index.d.mts CHANGED
@@ -640,6 +640,42 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
640
640
  id: string;
641
641
  data: DataObject;
642
642
  }>>;
643
+ /**
644
+ * Batch get multiple documents from Firestore
645
+ * Uses the documents:batchGet REST API endpoint
646
+ *
647
+ * @param collectionPath - The collection containing the documents
648
+ * @param documentIds - Array of document IDs to fetch (max 100)
649
+ * @returns Array of results in the same order as documentIds. Missing documents return null.
650
+ * @throws {Error} If the operation fails or more than 100 documents requested
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const users = await getAll('users', ['user1', 'user2', 'user3']);
655
+ * // users[0] = { name: 'Alice', ... } or null if not found
656
+ * ```
657
+ */
658
+ declare function getAll(collectionPath: string, documentIds: string[]): Promise<(DataObject | null)[]>;
659
+ /**
660
+ * Batch get multiple documents from different collections by their full paths
661
+ * Uses the documents:batchGet REST API endpoint
662
+ *
663
+ * @param documentRefs - Array of { collection, id } tuples pointing to documents (max 100)
664
+ * @returns Array of results in the same order as documentRefs. Missing documents return null.
665
+ * @throws {Error} If the operation fails or more than 100 documents requested
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * const docs = await getAllByPaths([
670
+ * { collection: 'users/uid1/profile', id: 'default' },
671
+ * { collection: 'users/uid2/profile', id: 'default' },
672
+ * ]);
673
+ * ```
674
+ */
675
+ declare function getAllByPaths(documentRefs: Array<{
676
+ collection: string;
677
+ id: string;
678
+ }>): Promise<(DataObject | null)[]>;
643
679
  /**
644
680
  * Perform batch write operations (set, update, delete)
645
681
  *
@@ -1250,4 +1286,4 @@ declare function getAdminAccessToken(): Promise<string>;
1250
1286
  */
1251
1287
  declare function clearTokenCache(): void;
1252
1288
 
1253
- export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1289
+ export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAll, getAllByPaths, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.d.ts CHANGED
@@ -640,6 +640,42 @@ declare function queryDocuments(collectionPath: string, options?: QueryOptions):
640
640
  id: string;
641
641
  data: DataObject;
642
642
  }>>;
643
+ /**
644
+ * Batch get multiple documents from Firestore
645
+ * Uses the documents:batchGet REST API endpoint
646
+ *
647
+ * @param collectionPath - The collection containing the documents
648
+ * @param documentIds - Array of document IDs to fetch (max 100)
649
+ * @returns Array of results in the same order as documentIds. Missing documents return null.
650
+ * @throws {Error} If the operation fails or more than 100 documents requested
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const users = await getAll('users', ['user1', 'user2', 'user3']);
655
+ * // users[0] = { name: 'Alice', ... } or null if not found
656
+ * ```
657
+ */
658
+ declare function getAll(collectionPath: string, documentIds: string[]): Promise<(DataObject | null)[]>;
659
+ /**
660
+ * Batch get multiple documents from different collections by their full paths
661
+ * Uses the documents:batchGet REST API endpoint
662
+ *
663
+ * @param documentRefs - Array of { collection, id } tuples pointing to documents (max 100)
664
+ * @returns Array of results in the same order as documentRefs. Missing documents return null.
665
+ * @throws {Error} If the operation fails or more than 100 documents requested
666
+ *
667
+ * @example
668
+ * ```typescript
669
+ * const docs = await getAllByPaths([
670
+ * { collection: 'users/uid1/profile', id: 'default' },
671
+ * { collection: 'users/uid2/profile', id: 'default' },
672
+ * ]);
673
+ * ```
674
+ */
675
+ declare function getAllByPaths(documentRefs: Array<{
676
+ collection: string;
677
+ id: string;
678
+ }>): Promise<(DataObject | null)[]>;
643
679
  /**
644
680
  * Perform batch write operations (set, update, delete)
645
681
  *
@@ -1250,4 +1286,4 @@ declare function getAdminAccessToken(): Promise<string>;
1250
1286
  */
1251
1287
  declare function clearTokenCache(): void;
1252
1288
 
1253
- export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
1289
+ export { type AndroidConfig, type AndroidNotification, type ApnsConfig, type BatchWrite, type BatchWriteResult, type CreateUserRequest, type CustomClaims, type CustomTokenSignInResponse, type DataObject, type DecodedIdToken, type DocumentReference, type DownloadOptions, type Notification as FcmNotification, type FcmOptions, FieldValue, type FieldValue$1 as FieldValueSentinel, FieldValueType, type FileMetadata, type FirestoreDocument, type FirestoreValue, type ListFilesResult, type ListOptions, type ListUsersResult, type Message, type QueryFilter, type QueryOptions, type QueryOrder, type ResumableUploadOptions, type SendResponse, type ServiceAccount, type SessionCookieOptions, type SetOptions, type SignedUrlOptions, type TokenResponse, type TopicManagementError, type TopicManagementResponse, type UpdateOptions, type UpdateUserRequest, type UploadOptions, type UserInfo, type UserRecord, type WebpushConfig, type WhereFilterOp, addDocument, batchWrite, clearConfig, clearTokenCache, countDocuments, createCustomToken, createSessionCookie, createUser, deleteDocument, deleteFile, deleteUser, downloadFile, fileExists, generateSignedUrl, getAdminAccessToken, getAll, getAllByPaths, getAuth, getConfig, getDocument, getFileMetadata, getProjectId, getServiceAccount, getUserByEmail, getUserByUid, getUserFromToken, initializeApp, iterateCollection, listDocuments, listFiles, listUsers, queryDocuments, sendMessage, setCustomUserClaims, setDocument, signInWithCustomToken, subscribeToTopic, unsubscribeFromTopic, updateDocument, updateUser, uploadFile, uploadFileResumable, verifyIdToken, verifySessionCookie };
package/dist/index.js CHANGED
@@ -216,6 +216,8 @@ __export(index_exports, {
216
216
  fileExists: () => fileExists,
217
217
  generateSignedUrl: () => generateSignedUrl,
218
218
  getAdminAccessToken: () => getAdminAccessToken,
219
+ getAll: () => getAll,
220
+ getAllByPaths: () => getAllByPaths,
219
221
  getAuth: () => getAuth,
220
222
  getConfig: () => getConfig,
221
223
  getDocument: () => getDocument,
@@ -1514,6 +1516,82 @@ async function queryDocuments(collectionPath, options) {
1514
1516
  data: convertFromFirestoreFormat(result.document.fields)
1515
1517
  }));
1516
1518
  }
1519
+ async function getAll(collectionPath, documentIds) {
1520
+ if (documentIds.length === 0) return [];
1521
+ if (documentIds.length > 100) {
1522
+ throw new Error("getAll supports a maximum of 100 documents per request");
1523
+ }
1524
+ for (const id of documentIds) {
1525
+ validateDocumentPath("collectionPath", collectionPath, id);
1526
+ }
1527
+ const accessToken = await getAdminAccessToken();
1528
+ const projectId = getProjectId();
1529
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1530
+ const documents = documentIds.map((id) => `${basePath}/${collectionPath}/${id}`);
1531
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1532
+ const response = await fetch(url, {
1533
+ method: "POST",
1534
+ headers: {
1535
+ "Authorization": `Bearer ${accessToken}`,
1536
+ "Content-Type": "application/json"
1537
+ },
1538
+ body: JSON.stringify({ documents })
1539
+ });
1540
+ if (!response.ok) {
1541
+ const errorText = await response.text();
1542
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1543
+ }
1544
+ const results = await response.json();
1545
+ const resultMap = /* @__PURE__ */ new Map();
1546
+ for (const result of results) {
1547
+ if (result.found) {
1548
+ const name = result.found.name;
1549
+ const data = convertFromFirestoreFormat(result.found.fields);
1550
+ resultMap.set(name, data);
1551
+ } else if (result.missing) {
1552
+ resultMap.set(result.missing, null);
1553
+ }
1554
+ }
1555
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1556
+ }
1557
+ async function getAllByPaths(documentRefs) {
1558
+ if (documentRefs.length === 0) return [];
1559
+ if (documentRefs.length > 100) {
1560
+ throw new Error("getAllByPaths supports a maximum of 100 documents per request");
1561
+ }
1562
+ for (const ref of documentRefs) {
1563
+ validateDocumentPath("collection", ref.collection, ref.id);
1564
+ }
1565
+ const accessToken = await getAdminAccessToken();
1566
+ const projectId = getProjectId();
1567
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1568
+ const documents = documentRefs.map((ref) => `${basePath}/${ref.collection}/${ref.id}`);
1569
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1570
+ const response = await fetch(url, {
1571
+ method: "POST",
1572
+ headers: {
1573
+ "Authorization": `Bearer ${accessToken}`,
1574
+ "Content-Type": "application/json"
1575
+ },
1576
+ body: JSON.stringify({ documents })
1577
+ });
1578
+ if (!response.ok) {
1579
+ const errorText = await response.text();
1580
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1581
+ }
1582
+ const results = await response.json();
1583
+ const resultMap = /* @__PURE__ */ new Map();
1584
+ for (const result of results) {
1585
+ if (result.found) {
1586
+ const name = result.found.name;
1587
+ const data = convertFromFirestoreFormat(result.found.fields);
1588
+ resultMap.set(name, data);
1589
+ } else if (result.missing) {
1590
+ resultMap.set(result.missing, null);
1591
+ }
1592
+ }
1593
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1594
+ }
1517
1595
  async function batchWrite(operations) {
1518
1596
  const accessToken = await getAdminAccessToken();
1519
1597
  const projectId = getProjectId();
@@ -2158,9 +2236,9 @@ async function sendMessage(message) {
2158
2236
  if (targets.length > 1) {
2159
2237
  throw new Error("Only one of token, topic, or condition can be specified");
2160
2238
  }
2161
- const config = getConfig();
2162
2239
  const accessToken = await getAdminAccessToken();
2163
- const url = `${FCM_BASE_URL}/projects/${config.projectId}/messages:send`;
2240
+ const projectId = getProjectId();
2241
+ const url = `${FCM_BASE_URL}/projects/${projectId}/messages:send`;
2164
2242
  const response = await fetch(url, {
2165
2243
  method: "POST",
2166
2244
  headers: {
@@ -2256,6 +2334,8 @@ init_service_account();
2256
2334
  fileExists,
2257
2335
  generateSignedUrl,
2258
2336
  getAdminAccessToken,
2337
+ getAll,
2338
+ getAllByPaths,
2259
2339
  getAuth,
2260
2340
  getConfig,
2261
2341
  getDocument,
package/dist/index.mjs CHANGED
@@ -1266,6 +1266,82 @@ async function queryDocuments(collectionPath, options) {
1266
1266
  data: convertFromFirestoreFormat(result.document.fields)
1267
1267
  }));
1268
1268
  }
1269
+ async function getAll(collectionPath, documentIds) {
1270
+ if (documentIds.length === 0) return [];
1271
+ if (documentIds.length > 100) {
1272
+ throw new Error("getAll supports a maximum of 100 documents per request");
1273
+ }
1274
+ for (const id of documentIds) {
1275
+ validateDocumentPath("collectionPath", collectionPath, id);
1276
+ }
1277
+ const accessToken = await getAdminAccessToken();
1278
+ const projectId = getProjectId();
1279
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1280
+ const documents = documentIds.map((id) => `${basePath}/${collectionPath}/${id}`);
1281
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1282
+ const response = await fetch(url, {
1283
+ method: "POST",
1284
+ headers: {
1285
+ "Authorization": `Bearer ${accessToken}`,
1286
+ "Content-Type": "application/json"
1287
+ },
1288
+ body: JSON.stringify({ documents })
1289
+ });
1290
+ if (!response.ok) {
1291
+ const errorText = await response.text();
1292
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1293
+ }
1294
+ const results = await response.json();
1295
+ const resultMap = /* @__PURE__ */ new Map();
1296
+ for (const result of results) {
1297
+ if (result.found) {
1298
+ const name = result.found.name;
1299
+ const data = convertFromFirestoreFormat(result.found.fields);
1300
+ resultMap.set(name, data);
1301
+ } else if (result.missing) {
1302
+ resultMap.set(result.missing, null);
1303
+ }
1304
+ }
1305
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1306
+ }
1307
+ async function getAllByPaths(documentRefs) {
1308
+ if (documentRefs.length === 0) return [];
1309
+ if (documentRefs.length > 100) {
1310
+ throw new Error("getAllByPaths supports a maximum of 100 documents per request");
1311
+ }
1312
+ for (const ref of documentRefs) {
1313
+ validateDocumentPath("collection", ref.collection, ref.id);
1314
+ }
1315
+ const accessToken = await getAdminAccessToken();
1316
+ const projectId = getProjectId();
1317
+ const basePath = `projects/${projectId}/databases/(default)/documents`;
1318
+ const documents = documentRefs.map((ref) => `${basePath}/${ref.collection}/${ref.id}`);
1319
+ const url = `${FIRESTORE_API}/${basePath}:batchGet`;
1320
+ const response = await fetch(url, {
1321
+ method: "POST",
1322
+ headers: {
1323
+ "Authorization": `Bearer ${accessToken}`,
1324
+ "Content-Type": "application/json"
1325
+ },
1326
+ body: JSON.stringify({ documents })
1327
+ });
1328
+ if (!response.ok) {
1329
+ const errorText = await response.text();
1330
+ throw new Error(`Failed to batch get documents: ${errorText}`);
1331
+ }
1332
+ const results = await response.json();
1333
+ const resultMap = /* @__PURE__ */ new Map();
1334
+ for (const result of results) {
1335
+ if (result.found) {
1336
+ const name = result.found.name;
1337
+ const data = convertFromFirestoreFormat(result.found.fields);
1338
+ resultMap.set(name, data);
1339
+ } else if (result.missing) {
1340
+ resultMap.set(result.missing, null);
1341
+ }
1342
+ }
1343
+ return documents.map((docPath) => resultMap.get(docPath) ?? null);
1344
+ }
1269
1345
  async function batchWrite(operations) {
1270
1346
  const accessToken = await getAdminAccessToken();
1271
1347
  const projectId = getProjectId();
@@ -1903,9 +1979,9 @@ async function sendMessage(message) {
1903
1979
  if (targets.length > 1) {
1904
1980
  throw new Error("Only one of token, topic, or condition can be specified");
1905
1981
  }
1906
- const config = getConfig();
1907
1982
  const accessToken = await getAdminAccessToken();
1908
- const url = `${FCM_BASE_URL}/projects/${config.projectId}/messages:send`;
1983
+ const projectId = getProjectId();
1984
+ const url = `${FCM_BASE_URL}/projects/${projectId}/messages:send`;
1909
1985
  const response = await fetch(url, {
1910
1986
  method: "POST",
1911
1987
  headers: {
@@ -1996,6 +2072,8 @@ export {
1996
2072
  fileExists,
1997
2073
  generateSignedUrl,
1998
2074
  getAdminAccessToken,
2075
+ getAll,
2076
+ getAllByPaths,
1999
2077
  getAuth,
2000
2078
  getConfig,
2001
2079
  getDocument,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prmichaelsen/firebase-admin-sdk-v8",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Firebase Admin SDK for Cloudflare Workers and edge runtimes using REST APIs",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",