nestjs-firebase-admin 0.2.3 → 0.3.4

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/.nvmrc CHANGED
@@ -1 +1 @@
1
- lts/hydrogen
1
+ lts/jod
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Hebert F Barros
3
+ Copyright (c) 2025 Hebert F Barros
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -11,11 +11,17 @@
11
11
  [![Free. Built on open source. Runs everywhere.](https://img.shields.io/badge/VS_Code-0078D4?style=flat&logo=visual%20studio%20code&logoColor=white)](https://code.visualstudio.com/)
12
12
  [![GitHub Actions](https://img.shields.io/badge/github%20actions-%232671E5.svg?style=flat&logo=githubactions&logoColor=white)](https://github.com/hebertcisco/nestjs-firebase-admin/actions)
13
13
 
14
- > Firebase Admin SDK for Nestjs :fire:
14
+ > Firebase Admin SDK for NestJS :fire:
15
+
16
+ ## Requirements
17
+
18
+ - **Node.js**: >= 20
19
+ - **NPM**: >= 10
20
+ - **NestJS**: >= 7.0.0
15
21
 
16
22
  ## Installation
17
23
 
18
- > Install with yarn or npm: `yarn` or `npm`:
24
+ Install the package using `yarn`, `npm`, or `pnpm`:
19
25
 
20
26
  ```bash
21
27
  # yarn
@@ -32,17 +38,15 @@ npm i nestjs-firebase-admin --save
32
38
  pnpm add nestjs-firebase-admin --save
33
39
  ```
34
40
 
35
- ### Usage example
41
+ ## Usage Example
42
+
43
+ Here is an example of how to configure the `AdminModule` in NestJS:
36
44
 
37
45
  ```ts
38
46
  // common.module.ts
39
47
  import { Module } from '@nestjs/common';
40
-
41
- import { TypeOrmModule } from '@nestjs/typeorm';
42
48
  import { AdminModule } from 'nestjs-firebase-admin';
43
49
 
44
- import { CommonService } from './common.service';
45
-
46
50
  @Module({
47
51
  imports: [
48
52
  AdminModule.register({
@@ -54,20 +58,147 @@ import { CommonService } from './common.service';
54
58
  databaseURL: 'https://my-project-id.firebaseio.com',
55
59
  }),
56
60
  ],
57
- providers: [CommonService],
58
61
  })
59
62
  export class CommonModule {}
60
63
  ```
61
64
 
62
- ## 🤝 Contributing
65
+ ### Asynchronous Registration
66
+
67
+ If you need asynchronous configuration, use the `registerAsync` method:
68
+
69
+ ```ts
70
+ import { Module } from '@nestjs/common';
71
+ import { AdminModule } from 'nestjs-firebase-admin';
72
+
73
+ @Module({
74
+ imports: [
75
+ AdminModule.registerAsync({
76
+ useFactory: async () => ({
77
+ credential: {
78
+ projectId: 'my-project-id',
79
+ clientEmail: 'my-client-email',
80
+ privateKey: 'my-private-key',
81
+ },
82
+ databaseURL: 'https://my-project-id.firebaseio.com',
83
+ }),
84
+ }),
85
+ ],
86
+ })
87
+ export class AppModule {}
88
+ ```
89
+
90
+ ## DatabaseService
91
+
92
+ The `DatabaseService` provides a clean and type-safe interface for interacting with the Firebase Realtime Database. This service is automatically injected when you import the `AdminModule`.
93
+
94
+ ### Features
95
+
96
+ - Full CRUD operations (Create, Read, Update, Delete)
97
+ - TypeScript type support
98
+ - Asynchronous methods with Promises
99
+ - Database reference handling
100
+ - List operations with unique keys
101
+
102
+ ### Usage Example
103
+
104
+ ```ts
105
+ import { Injectable } from '@nestjs/common';
106
+ import { DatabaseService } from 'nestjs-firebase-admin';
107
+
108
+ @Injectable()
109
+ export class UsersService {
110
+ constructor(private readonly databaseService: DatabaseService) {}
111
+
112
+ // Get data from a path
113
+ async getUser(userId: string) {
114
+ return this.databaseService.get<User>(`users/${userId}`);
115
+ }
116
+
117
+ // Set data at a path
118
+ async createUser(userId: string, userData: User) {
119
+ await this.databaseService.set(`users/${userId}`, userData);
120
+ }
121
+
122
+ // Update specific fields
123
+ async updateUser(userId: string, updates: Partial<User>) {
124
+ await this.databaseService.update(`users/${userId}`, updates);
125
+ }
126
+
127
+ // Remove data
128
+ async deleteUser(userId: string) {
129
+ await this.databaseService.remove(`users/${userId}`);
130
+ }
131
+
132
+ // Add to a list
133
+ async addUser(userData: User) {
134
+ const newKey = await this.databaseService.push('users', userData);
135
+ return newKey;
136
+ }
137
+
138
+ // Get a database reference
139
+ async getUsersRef() {
140
+ return this.databaseService.ref('users');
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Available Methods
146
+
147
+ | Method | Description | Documentation |
148
+ |--------|-------------|---------------|
149
+ | `ref(path)` | Gets a reference to a specific path | [Firebase Ref](https://firebase.google.com/docs/database/admin/retrieve-data#section-queries) |
150
+ | `get<T>(path)` | Retrieves data from a specific path | [Read Data](https://firebase.google.com/docs/database/admin/retrieve-data#section-read-once) |
151
+ | `set<T>(path, data)` | Sets data at a specific path | [Set Data](https://firebase.google.com/docs/database/admin/save-data#section-set) |
152
+ | `update<T>(path, data)` | Updates specific fields at a path | [Update Data](https://firebase.google.com/docs/database/admin/save-data#section-update) |
153
+ | `remove(path)` | Removes data from a specific path | [Delete Data](https://firebase.google.com/docs/database/admin/save-data#section-delete) |
154
+ | `push<T>(path, data)` | Adds data to a list | [Push Data](https://firebase.google.com/docs/database/admin/save-data#section-push) |
155
+
156
+ ## Testing
157
+
158
+ To run tests, use the following commands:
159
+
160
+ ### Unit Tests
161
+
162
+ ```bash
163
+ npm test
164
+ ```
165
+
166
+ ### Coverage Tests
167
+
168
+ ```bash
169
+ npm run test:cov
170
+ ```
171
+
172
+ ### Watch Mode Tests
173
+
174
+ ```bash
175
+ npm run test:watch
176
+ ```
177
+
178
+ ### Debug Tests
179
+
180
+ ```bash
181
+ npm run test:debug
182
+ ```
183
+
184
+ ## Available Scripts
185
+
186
+ The available scripts in `package.json` include:
187
+
188
+ - **Build**: `npm run build`
189
+ - **Lint**: `npm run lint`
190
+ - **Format**: `npm run format`
191
+ - **Release**: `npm run release`
192
+
193
+ ## Contributing
63
194
 
64
- Contributions, issues and feature requests are welcome!<br />Feel free to check [issues page](issues).
195
+ Contributions, issues, and feature requests are welcome!<br />Feel free to check the [issues page](https://github.com/hebertcisco/nestjs-firebase-admin/issues).
65
196
 
66
- ## Show your support
197
+ ## Show Your Support
67
198
 
68
199
  Give a ⭐️ if this project helped you!
69
200
 
70
- Or buy me a coffee 🙌🏾
201
+ Or buy the author a coffee 🙌🏾
71
202
 
72
203
  <a href="https://www.buymeacoffee.com/hebertcisco">
73
204
  <img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=hebertcisco&button_colour=FFDD00&font_colour=000000&font_family=Inter&outline_colour=000000&coffee_colour=ffffff" />
@@ -75,5 +206,4 @@ Or buy me a coffee 🙌🏾
75
206
 
76
207
  ## 📝 License
77
208
 
78
- Copyright © 2024 [Hebert F Barros](https://github.com/hebertcisco).<br />
79
- This project is [MIT](LICENSE) licensed.
209
+ This project is under the [MIT](LICENSE) license.
@@ -21,6 +21,7 @@ const common_1 = require("@nestjs/common");
21
21
  const random_string_generator_util_1 = require("@nestjs/common/utils/random-string-generator.util");
22
22
  const admin_constants_1 = require("./admin.constants");
23
23
  const admin_service_1 = require("./admin.service");
24
+ const database_service_1 = require("./database.service");
24
25
  let AdminModule = AdminModule_1 = class AdminModule {
25
26
  static register(options) {
26
27
  return {
@@ -89,7 +90,7 @@ let AdminModule = AdminModule_1 = class AdminModule {
89
90
  exports.AdminModule = AdminModule;
90
91
  exports.AdminModule = AdminModule = AdminModule_1 = __decorate([
91
92
  (0, common_1.Module)({
92
- providers: [admin_service_1.AdminService],
93
- exports: [admin_service_1.AdminService],
93
+ providers: [admin_service_1.AdminService, database_service_1.DatabaseService],
94
+ exports: [admin_service_1.AdminService, database_service_1.DatabaseService],
94
95
  })
95
96
  ], AdminModule);
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import { Observable } from 'rxjs';
3
2
  import Admin from 'firebase-admin';
4
3
  import type { App } from 'firebase-admin/app';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const testing_1 = require("@nestjs/testing");
13
+ const admin_service_1 = require("./admin.service");
14
+ const node_http_1 = require("node:http");
15
+ const rxjs_1 = require("rxjs");
16
+ const FIREBASE_ADMIN_INSTANCE_TOKEN = 'FIREBASE_ADMIN_INSTANCE_TOKEN';
17
+ jest.mock('firebase-admin/app', () => {
18
+ return {
19
+ getApp: jest.fn().mockReturnValue({ name: 'app-name', options: {} }),
20
+ getApps: jest.fn().mockReturnValue([{ name: 'app-name', options: {} }]),
21
+ deleteApp: jest.fn().mockResolvedValue(undefined),
22
+ applicationDefault: jest.fn().mockReturnValue('default-credential'),
23
+ };
24
+ });
25
+ jest.mock('firebase-admin', () => {
26
+ return {
27
+ initializeApp: jest.fn().mockReturnValue({ name: 'app-name', options: {} }),
28
+ credential: {
29
+ cert: jest.fn().mockReturnValue('mocked-credential'),
30
+ },
31
+ };
32
+ });
33
+ const firebaseAdmin = jest.requireMock('firebase-admin');
34
+ const firebaseAdminApp = jest.requireMock('firebase-admin/app');
35
+ describe('AdminService', () => {
36
+ let service;
37
+ const mockOptions = {
38
+ credential: {
39
+ projectId: 'mock-project',
40
+ clientEmail: 'mock@example.com',
41
+ privateKey: 'mock-key',
42
+ },
43
+ databaseURL: 'https://mock-project.firebaseio.com',
44
+ storageBucket: 'mock-project.appspot.com',
45
+ projectId: 'mock-project',
46
+ };
47
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
48
+ jest.clearAllMocks();
49
+ const module = yield testing_1.Test.createTestingModule({
50
+ providers: [
51
+ admin_service_1.AdminService,
52
+ {
53
+ provide: FIREBASE_ADMIN_INSTANCE_TOKEN,
54
+ useValue: mockOptions,
55
+ },
56
+ ],
57
+ }).compile();
58
+ service = module.get(admin_service_1.AdminService);
59
+ }));
60
+ it('must be defined', () => {
61
+ expect(service).toBeDefined();
62
+ });
63
+ describe('constructor', () => {
64
+ it('must initialize the Firebase Admin SDK with the provided options', () => {
65
+ expect(firebaseAdmin.initializeApp).toHaveBeenCalledWith(Object.assign(Object.assign({}, mockOptions), { credential: 'mocked-credential' }));
66
+ });
67
+ it('must pass the correct credentials to the cert method', () => {
68
+ expect(firebaseAdmin.credential.cert).toHaveBeenCalledWith(mockOptions.credential);
69
+ });
70
+ });
71
+ describe('applicationDefault', () => {
72
+ it('should return default credentials with no arguments', () => {
73
+ const result = service.applicationDefault();
74
+ expect(firebaseAdminApp.applicationDefault).toHaveBeenCalledWith(undefined);
75
+ expect(result).toBe('default-credential');
76
+ });
77
+ it('must pass HTTP agent when provided', () => {
78
+ const httpAgent = new node_http_1.Agent();
79
+ const result = service.applicationDefault(httpAgent);
80
+ expect(firebaseAdminApp.applicationDefault).toHaveBeenCalledWith(httpAgent);
81
+ expect(result).toBe('default-credential');
82
+ });
83
+ });
84
+ describe('deleteApp', () => {
85
+ it('should call deleteApp function with the given app', () => __awaiter(void 0, void 0, void 0, function* () {
86
+ const mockApp = { name: 'app-to-delete' };
87
+ yield service.deleteApp(mockApp);
88
+ expect(firebaseAdminApp.deleteApp).toHaveBeenCalledWith(mockApp);
89
+ }));
90
+ it('should return a resolved promise', () => __awaiter(void 0, void 0, void 0, function* () {
91
+ const mockApp = { name: 'app-to-delete' };
92
+ yield expect(service.deleteApp(mockApp)).resolves.toBeUndefined();
93
+ }));
94
+ it('should propagate errors from deleteApp', () => __awaiter(void 0, void 0, void 0, function* () {
95
+ firebaseAdminApp.deleteApp.mockRejectedValueOnce(new Error('Delete error'));
96
+ const mockApp = { name: 'app-with-error' };
97
+ yield expect(service.deleteApp(mockApp)).rejects.toThrow('Delete error');
98
+ }));
99
+ });
100
+ describe('getApps', () => {
101
+ it('should return all initialized apps', () => {
102
+ const mockApps = [{ name: 'app1' }, { name: 'app2' }];
103
+ firebaseAdminApp.getApps.mockReturnValueOnce(mockApps);
104
+ const result = service.getApps;
105
+ expect(firebaseAdminApp.getApps).toHaveBeenCalled();
106
+ expect(result).toEqual(mockApps);
107
+ });
108
+ it('should return an empty array when there are no apps', () => {
109
+ firebaseAdminApp.getApps.mockReturnValueOnce([]);
110
+ const result = service.getApps;
111
+ expect(result).toEqual([]);
112
+ });
113
+ });
114
+ describe('getApp', () => {
115
+ it('should return the default app', () => {
116
+ const mockApp = { name: 'default-app' };
117
+ firebaseAdminApp.getApp.mockReturnValueOnce(mockApp);
118
+ const result = service.getApp;
119
+ expect(firebaseAdminApp.getApp).toHaveBeenCalled();
120
+ expect(result).toEqual(mockApp);
121
+ });
122
+ it('must propagate getApp errors', () => {
123
+ firebaseAdminApp.getApp.mockImplementationOnce(() => {
124
+ throw new Error('App not found');
125
+ });
126
+ expect(() => service.getApp).toThrow('App not found');
127
+ });
128
+ });
129
+ describe('admin', () => {
130
+ it('should return Firebase Admin SDK instance', () => {
131
+ const adminModule = service.admin();
132
+ expect(adminModule).toBeDefined();
133
+ expect(adminModule.initializeApp).toBe(firebaseAdmin.initializeApp);
134
+ expect(adminModule.credential.cert).toBe(firebaseAdmin.credential.cert);
135
+ });
136
+ });
137
+ describe('initializeAppObservable', () => {
138
+ it('must return an Observable with the app instance', () => {
139
+ return new Promise(done => {
140
+ const observable = service.initializeAppObservable();
141
+ expect(observable).toBeInstanceOf(rxjs_1.Observable);
142
+ observable.subscribe({
143
+ next: (app) => {
144
+ expect(app).toBeDefined();
145
+ expect(app).toEqual(service.appRef);
146
+ },
147
+ complete: () => {
148
+ done();
149
+ },
150
+ error: (_err) => {
151
+ done();
152
+ },
153
+ });
154
+ });
155
+ });
156
+ it('must complete the Observable after issuing the app', () => {
157
+ return new Promise(done => {
158
+ const completeSpy = jest.fn();
159
+ service.initializeAppObservable().subscribe({
160
+ next: () => { },
161
+ complete: () => {
162
+ completeSpy();
163
+ expect(completeSpy).toHaveBeenCalledTimes(1);
164
+ done();
165
+ },
166
+ error: (_err) => {
167
+ done();
168
+ },
169
+ });
170
+ });
171
+ });
172
+ it('must return a valid cleanup function when unsubscribing', () => {
173
+ const observable = service.initializeAppObservable();
174
+ const subscription = observable.subscribe(() => { });
175
+ expect(() => subscription.unsubscribe()).not.toThrow();
176
+ });
177
+ });
178
+ describe('appRef', () => {
179
+ it('should return the result of initializeApp', () => {
180
+ const mockApp = { name: 'initialized-app' };
181
+ firebaseAdmin.initializeApp.mockReturnValueOnce(mockApp);
182
+ const result = service.appRef;
183
+ expect(firebaseAdmin.initializeApp).toHaveBeenCalled();
184
+ expect(result).toEqual(mockApp);
185
+ });
186
+ });
187
+ describe('initializeApp', () => {
188
+ it('should call initializeApp from Admin SDK with correct options', () => {
189
+ firebaseAdmin.initializeApp.mockClear();
190
+ service.initializeApp();
191
+ expect(firebaseAdmin.initializeApp).toHaveBeenCalledWith(Object.assign(Object.assign({}, mockOptions), { credential: 'mocked-credential' }));
192
+ });
193
+ it('should return the initialized app instance', () => {
194
+ const mockApp = { name: 'new-app', options: {} };
195
+ firebaseAdmin.initializeApp.mockReturnValueOnce(mockApp);
196
+ const result = service.initializeApp();
197
+ expect(result).toEqual(mockApp);
198
+ });
199
+ it('must propagate initializeApp errors', () => {
200
+ firebaseAdmin.initializeApp.mockImplementationOnce(() => {
201
+ throw new Error('Initialization error');
202
+ });
203
+ expect(() => service.initializeApp()).toThrow('Initialization error');
204
+ });
205
+ });
206
+ describe('complex use cases', () => {
207
+ it('should allow you to get an app and then delete it', () => __awaiter(void 0, void 0, void 0, function* () {
208
+ const mockApp = { name: 'app-to-manage' };
209
+ firebaseAdminApp.getApp.mockReturnValueOnce(mockApp);
210
+ const app = service.getApp;
211
+ yield service.deleteApp(app);
212
+ expect(firebaseAdminApp.getApp).toHaveBeenCalled();
213
+ expect(firebaseAdminApp.deleteApp).toHaveBeenCalledWith(mockApp);
214
+ }));
215
+ it('should return the same app when appRef is called multiple times in a row', () => {
216
+ const mockApp = { name: 'reused-app' };
217
+ firebaseAdmin.initializeApp.mockClear();
218
+ firebaseAdmin.initializeApp.mockReturnValue(mockApp);
219
+ const app1 = service.appRef;
220
+ const app2 = service.appRef;
221
+ expect(app1).toBe(mockApp);
222
+ expect(app2).toBe(mockApp);
223
+ expect(firebaseAdmin.initializeApp).toHaveBeenCalledTimes(2);
224
+ });
225
+ it('should be defined', () => {
226
+ expect(service).toBeDefined();
227
+ });
228
+ it('should return an array of apps', () => {
229
+ expect(service.getApps).toBeInstanceOf(Array);
230
+ });
231
+ it('should return an app', () => {
232
+ expect(service.getApp).toBeInstanceOf(Object);
233
+ });
234
+ it('should return an app reference', () => {
235
+ expect(service.appRef).toBeInstanceOf(Object);
236
+ });
237
+ it('should return an observable of an app', () => {
238
+ expect(service.initializeAppObservable()).toBeInstanceOf(Object);
239
+ });
240
+ it('should return a credential', () => {
241
+ expect(service.applicationDefault()).toBe('default-credential');
242
+ });
243
+ });
244
+ });
@@ -0,0 +1,10 @@
1
+ export declare class DatabaseService {
2
+ private database;
3
+ constructor();
4
+ ref(path: string): import("@firebase/database-types").Reference;
5
+ get<T>(path: string): Promise<T>;
6
+ set<T>(path: string, data: T): Promise<void>;
7
+ update<T>(path: string, data: Partial<T>): Promise<void>;
8
+ remove(path: string): Promise<void>;
9
+ push<T>(path: string, data: T): Promise<string>;
10
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
12
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
13
+ return new (P || (P = Promise))(function (resolve, reject) {
14
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
15
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
16
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
17
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
18
+ });
19
+ };
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.DatabaseService = void 0;
22
+ const common_1 = require("@nestjs/common");
23
+ const database_1 = require("firebase-admin/database");
24
+ let DatabaseService = class DatabaseService {
25
+ constructor() {
26
+ this.database = (0, database_1.getDatabase)();
27
+ }
28
+ ref(path) {
29
+ return this.database.ref(path);
30
+ }
31
+ get(path) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ const snapshot = yield this.database.ref(path).get();
34
+ return snapshot.val();
35
+ });
36
+ }
37
+ set(path, data) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ yield this.database.ref(path).set(data);
40
+ });
41
+ }
42
+ update(path, data) {
43
+ return __awaiter(this, void 0, void 0, function* () {
44
+ yield this.database.ref(path).update(data);
45
+ });
46
+ }
47
+ remove(path) {
48
+ return __awaiter(this, void 0, void 0, function* () {
49
+ yield this.database.ref(path).remove();
50
+ });
51
+ }
52
+ push(path, data) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ const ref = yield this.database.ref(path).push(data);
55
+ return ref.key || '';
56
+ });
57
+ }
58
+ };
59
+ exports.DatabaseService = DatabaseService;
60
+ exports.DatabaseService = DatabaseService = __decorate([
61
+ (0, common_1.Injectable)(),
62
+ __metadata("design:paramtypes", [])
63
+ ], DatabaseService);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ const testing_1 = require("@nestjs/testing");
13
+ const database_service_1 = require("./database.service");
14
+ const DatabaseService_mock_1 = require("../../tests/mocks/DatabaseService.mock");
15
+ describe('DatabaseService', () => {
16
+ let service;
17
+ let mockService;
18
+ beforeEach(() => __awaiter(void 0, void 0, void 0, function* () {
19
+ const module = yield testing_1.Test.createTestingModule({
20
+ providers: [
21
+ {
22
+ provide: database_service_1.DatabaseService,
23
+ useClass: DatabaseService_mock_1.DatabaseServiceMock,
24
+ },
25
+ ],
26
+ }).compile();
27
+ service = module.get(database_service_1.DatabaseService);
28
+ mockService = service;
29
+ }));
30
+ afterEach(() => {
31
+ if (mockService.resetMocks) {
32
+ mockService.resetMocks();
33
+ }
34
+ });
35
+ it('should be defined', () => {
36
+ expect(service).toBeDefined();
37
+ });
38
+ describe('ref', () => {
39
+ it('should return a database reference', () => {
40
+ const ref = service.ref('test-path');
41
+ expect(ref).toBeDefined();
42
+ expect(ref.get).toBeDefined();
43
+ expect(ref.set).toBeDefined();
44
+ expect(ref.update).toBeDefined();
45
+ expect(ref.remove).toBeDefined();
46
+ expect(ref.push).toBeDefined();
47
+ });
48
+ it('should handle different path formats', () => {
49
+ const ref1 = service.ref('users/123/profile');
50
+ expect(ref1).toBeDefined();
51
+ const ref2 = service.ref('');
52
+ expect(ref2).toBeDefined();
53
+ const ref3 = service.ref('users/user.name@example.com');
54
+ expect(ref3).toBeDefined();
55
+ });
56
+ });
57
+ describe('get', () => {
58
+ it('should get data from a path', () => __awaiter(void 0, void 0, void 0, function* () {
59
+ const data = yield service.get('test-path');
60
+ expect(data).toBe('mock-data');
61
+ }));
62
+ it('should handle non-existent paths', () => __awaiter(void 0, void 0, void 0, function* () {
63
+ const data = yield service.get('non-existent-path');
64
+ expect(data).toBe('mock-data');
65
+ }));
66
+ it('should handle error during data retrieval', () => __awaiter(void 0, void 0, void 0, function* () {
67
+ const mockRef = service.ref('error-path');
68
+ mockRef.get = jest.fn().mockRejectedValue(new Error('Database error'));
69
+ yield expect(service.get('error-path')).rejects.toThrow('Database error');
70
+ }));
71
+ it('should call get method with correct path', () => __awaiter(void 0, void 0, void 0, function* () {
72
+ const path = 'test/specific/path';
73
+ const spyRef = jest.spyOn(service, 'ref');
74
+ yield service.get(path);
75
+ expect(spyRef).toHaveBeenCalledWith(path);
76
+ }));
77
+ it('should handle null value response', () => __awaiter(void 0, void 0, void 0, function* () {
78
+ const mockRef = service.ref('null-path');
79
+ mockRef.get = jest.fn().mockResolvedValue({ val: () => null });
80
+ const result = yield service.get('null-path');
81
+ expect(result).toBeNull();
82
+ }));
83
+ });
84
+ describe('set', () => {
85
+ it('should set data at a path', () => __awaiter(void 0, void 0, void 0, function* () {
86
+ const testData = { name: 'test' };
87
+ yield expect(service.set('test-path', testData)).resolves.not.toThrow();
88
+ }));
89
+ it('should handle complex data structures', () => __awaiter(void 0, void 0, void 0, function* () {
90
+ const testData = {
91
+ name: 'test',
92
+ nested: {
93
+ field: 'value',
94
+ array: [1, 2, 3],
95
+ },
96
+ };
97
+ yield expect(service.set('test-path', testData)).resolves.not.toThrow();
98
+ }));
99
+ it('should handle error during set operation', () => __awaiter(void 0, void 0, void 0, function* () {
100
+ const mockRef = service.ref('error-path');
101
+ mockRef.set = jest.fn().mockRejectedValue(new Error('Permission denied'));
102
+ yield expect(service.set('error-path', { test: 'data' })).rejects.toThrow('Permission denied');
103
+ }));
104
+ it('should call set method with correct parameters', () => __awaiter(void 0, void 0, void 0, function* () {
105
+ const path = 'users/123';
106
+ const data = { name: 'John Doe', age: 30 };
107
+ const refSpy = jest.spyOn(service, 'ref');
108
+ const setSpy = jest.spyOn(service.ref(path), 'set');
109
+ yield service.set(path, data);
110
+ expect(refSpy).toHaveBeenCalledWith(path);
111
+ expect(setSpy).toHaveBeenCalledWith(data);
112
+ }));
113
+ it('should set primitive data types', () => __awaiter(void 0, void 0, void 0, function* () {
114
+ yield expect(service.set('string-path', 'test-string')).resolves.not.toThrow();
115
+ yield expect(service.set('number-path', 42)).resolves.not.toThrow();
116
+ yield expect(service.set('bool-path', true)).resolves.not.toThrow();
117
+ yield expect(service.set('null-path', null)).resolves.not.toThrow();
118
+ }));
119
+ });
120
+ describe('update', () => {
121
+ it('should update data at a path', () => __awaiter(void 0, void 0, void 0, function* () {
122
+ const testData = { name: 'updated' };
123
+ yield expect(service.update('test-path', testData)).resolves.not.toThrow();
124
+ }));
125
+ it('should handle partial updates', () => __awaiter(void 0, void 0, void 0, function* () {
126
+ const testData = { 'nested.field': 'new-value' };
127
+ yield expect(service.update('test-path', testData)).resolves.not.toThrow();
128
+ }));
129
+ it('should handle error during update operation', () => __awaiter(void 0, void 0, void 0, function* () {
130
+ const mockRef = service.ref('error-path');
131
+ mockRef.update = jest.fn().mockRejectedValue(new Error('Update failed'));
132
+ yield expect(service.update('error-path', { name: 'test' })).rejects.toThrow('Update failed');
133
+ }));
134
+ it('should call update method with correct parameters', () => __awaiter(void 0, void 0, void 0, function* () {
135
+ const path = 'users/profile';
136
+ const data = { lastActive: Date.now(), status: 'online' };
137
+ const refSpy = jest.spyOn(service, 'ref');
138
+ const updateSpy = jest.spyOn(service.ref(path), 'update');
139
+ yield service.update(path, data);
140
+ expect(refSpy).toHaveBeenCalledWith(path);
141
+ expect(updateSpy).toHaveBeenCalledWith(data);
142
+ }));
143
+ it('should update with empty object', () => __awaiter(void 0, void 0, void 0, function* () {
144
+ yield expect(service.update('test-path', {})).resolves.not.toThrow();
145
+ }));
146
+ });
147
+ describe('remove', () => {
148
+ it('should remove data at a path', () => __awaiter(void 0, void 0, void 0, function* () {
149
+ yield expect(service.remove('test-path')).resolves.not.toThrow();
150
+ }));
151
+ it('should handle non-existent paths', () => __awaiter(void 0, void 0, void 0, function* () {
152
+ yield expect(service.remove('non-existent-path')).resolves.not.toThrow();
153
+ }));
154
+ it('should handle error during remove operation', () => __awaiter(void 0, void 0, void 0, function* () {
155
+ const mockRef = service.ref('error-path');
156
+ mockRef.remove = jest.fn().mockRejectedValue(new Error('Delete failed'));
157
+ yield expect(service.remove('error-path')).rejects.toThrow('Delete failed');
158
+ }));
159
+ it('should call remove method with correct path', () => __awaiter(void 0, void 0, void 0, function* () {
160
+ const path = 'users/123/sessions';
161
+ const refSpy = jest.spyOn(service, 'ref');
162
+ const removeSpy = jest.spyOn(service.ref(path), 'remove');
163
+ yield service.remove(path);
164
+ expect(refSpy).toHaveBeenCalledWith(path);
165
+ expect(removeSpy).toHaveBeenCalled();
166
+ }));
167
+ });
168
+ describe('push', () => {
169
+ it('should push data to a path and return a key', () => __awaiter(void 0, void 0, void 0, function* () {
170
+ const testData = { name: 'new-item' };
171
+ const key = yield service.push('test-path', testData);
172
+ expect(key).toBe('mock-key');
173
+ }));
174
+ it('should handle empty key case', () => __awaiter(void 0, void 0, void 0, function* () {
175
+ const mockRef = service.ref('test-path');
176
+ mockRef.push = jest.fn().mockResolvedValue({ key: null });
177
+ const testData = { name: 'new-item' };
178
+ const key = yield service.push('test-path', testData);
179
+ expect(key).toBe('');
180
+ }));
181
+ it('should handle error during push operation', () => __awaiter(void 0, void 0, void 0, function* () {
182
+ const mockRef = service.ref('error-path');
183
+ mockRef.push = jest.fn().mockRejectedValue(new Error('Push failed'));
184
+ yield expect(service.push('error-path', { name: 'test' })).rejects.toThrow('Push failed');
185
+ }));
186
+ it('should call push method with correct parameters', () => __awaiter(void 0, void 0, void 0, function* () {
187
+ const path = 'messages';
188
+ const data = { text: 'Hello world', timestamp: Date.now() };
189
+ const refSpy = jest.spyOn(service, 'ref');
190
+ const pushSpy = jest.spyOn(service.ref(path), 'push');
191
+ yield service.push(path, data);
192
+ expect(refSpy).toHaveBeenCalledWith(path);
193
+ expect(pushSpy).toHaveBeenCalledWith(data);
194
+ }));
195
+ it('should push different data types', () => __awaiter(void 0, void 0, void 0, function* () {
196
+ yield expect(service.push('list/objects', { id: 1 })).resolves.toBe('mock-key');
197
+ yield expect(service.push('list/arrays', [1, 2, 3])).resolves.toBe('mock-key');
198
+ yield expect(service.push('list/strings', 'test string')).resolves.toBe('mock-key');
199
+ yield expect(service.push('list/numbers', 42)).resolves.toBe('mock-key');
200
+ }));
201
+ });
202
+ describe('combined operations', () => {
203
+ it('should perform get and update operations sequentially', () => __awaiter(void 0, void 0, void 0, function* () {
204
+ const path = 'users/profile';
205
+ const getSpy = jest.spyOn(service, 'get');
206
+ const updateSpy = jest.spyOn(service, 'update');
207
+ const data = yield service.get(path);
208
+ yield service.update(path, { lastAccess: Date.now() });
209
+ expect(getSpy).toHaveBeenCalledWith(path);
210
+ expect(updateSpy).toHaveBeenCalled();
211
+ }));
212
+ it('should perform push and then get the pushed data', () => __awaiter(void 0, void 0, void 0, function* () {
213
+ const basePath = 'posts';
214
+ const postData = { title: 'Test Post', content: 'Content' };
215
+ const key = yield service.push(basePath, postData);
216
+ expect(key).toBe('mock-key');
217
+ const path = `${basePath}/${key}`;
218
+ const getSpy = jest.spyOn(service, 'get');
219
+ yield service.get(path);
220
+ expect(getSpy).toHaveBeenCalledWith(path);
221
+ }));
222
+ it('should handle transaction-like operations', () => __awaiter(void 0, void 0, void 0, function* () {
223
+ const itemPath = 'items/123';
224
+ const itemData = { name: 'Updated Item', updated: true };
225
+ const refPath = 'users/456/items/123';
226
+ const refData = { updated: true, timestamp: Date.now() };
227
+ const setSpy = jest.spyOn(service, 'set');
228
+ const updateSpy = jest.spyOn(service, 'update');
229
+ yield service.set(itemPath, itemData);
230
+ yield service.update(refPath, refData);
231
+ expect(setSpy).toHaveBeenCalledWith(itemPath, itemData);
232
+ expect(updateSpy).toHaveBeenCalledWith(refPath, refData);
233
+ }));
234
+ });
235
+ });
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ describe('AdminModuleOptionsFactory', () => {
13
+ it('should define a method to create admin options', () => {
14
+ class TestFactory {
15
+ createAdminOptions() {
16
+ return { credential: { projectId: 'test' } };
17
+ }
18
+ }
19
+ const factory = new TestFactory();
20
+ expect(factory.createAdminOptions()).toEqual({
21
+ credential: { projectId: 'test' },
22
+ });
23
+ });
24
+ });
25
+ describe('AdminModuleAsyncOptions', () => {
26
+ it('should allow useExisting, useClass, or useFactory for async options', () => {
27
+ const asyncOptions = {
28
+ useFactory: () => __awaiter(void 0, void 0, void 0, function* () { return ({ credential: { projectId: 'test' } }); }),
29
+ inject: [],
30
+ };
31
+ expect(asyncOptions.useFactory).toBeDefined();
32
+ expect(asyncOptions.inject).toEqual([]);
33
+ });
34
+ it('should support extraProviders', () => {
35
+ const extraProvider = { provide: 'TEST', useValue: 'value' };
36
+ const asyncOptions = {
37
+ useFactory: () => __awaiter(void 0, void 0, void 0, function* () { return ({ credential: { projectId: 'test' } }); }),
38
+ inject: [],
39
+ extraProviders: [extraProvider],
40
+ };
41
+ expect(asyncOptions.extraProviders).toContain(extraProvider);
42
+ });
43
+ });
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { Agent } from 'node:http';
3
2
  import type { ServiceAccount } from 'firebase-admin/app';
4
3
  export type AdminConfig = {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ describe('Admin Types', () => {
4
+ it('should define AdminConfig with required and optional properties', () => {
5
+ const config = {
6
+ credential: {
7
+ projectId: 'test-project',
8
+ clientEmail: 'test@example.com',
9
+ privateKey: 'test-key',
10
+ },
11
+ databaseURL: 'https://test.firebaseio.com',
12
+ storageBucket: 'test-bucket',
13
+ projectId: 'test-project-id',
14
+ httpAgent: undefined,
15
+ };
16
+ expect(config.credential.projectId).toBe('test-project');
17
+ expect(config.databaseURL).toBe('https://test.firebaseio.com');
18
+ expect(config.storageBucket).toBe('test-bucket');
19
+ expect(config.projectId).toBe('test-project-id');
20
+ expect(config.httpAgent).toBeUndefined();
21
+ });
22
+ it('should define AdminModuleOptions as an alias for AdminConfig', () => {
23
+ const options = {
24
+ credential: {
25
+ projectId: 'test-project',
26
+ clientEmail: 'test@example.com',
27
+ privateKey: 'test-key',
28
+ },
29
+ };
30
+ expect(options.credential.projectId).toBe('test-project');
31
+ expect(options.credential.clientEmail).toBe('test@example.com');
32
+ expect(options.credential.privateKey).toBe('test-key');
33
+ });
34
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-firebase-admin",
3
- "version": "0.2.3",
3
+ "version": "0.3.4",
4
4
  "description": "Firebase Admin SDK for Nestjs",
5
5
  "author": "Hebert Cisco",
6
6
  "license": "MIT",
@@ -30,55 +30,45 @@
30
30
  "postversion": "git push && git push --tags"
31
31
  },
32
32
  "dependencies": {
33
- "@nestjs/common": "10.2.8",
34
- "@nestjs/core": "10.4.3",
35
- "@nestjs/platform-express": "10.4.5",
36
- "firebase-admin": "12.0.0",
37
- "nest-shared": "^5.0.3"
33
+ "@nestjs/common": "10.4.17",
34
+ "@nestjs/core": "10.4.13",
35
+ "@nestjs/platform-express": "10.4.13",
36
+ "firebase-admin": "12.7.0",
37
+ "nest-shared": "^5.0.6"
38
38
  },
39
39
  "engines": {
40
- "node": ">=16",
41
- "npm": ">=8"
40
+ "node": ">=20",
41
+ "npm": ">=10"
42
42
  },
43
- "os": [
44
- "darwin",
45
- "linux",
46
- "win32"
47
- ],
48
- "cpu": [
49
- "x64",
50
- "arm",
51
- "!mips"
52
- ],
53
43
  "devDependencies": {
54
- "@commitlint/cli": "18.2.0",
55
- "@commitlint/config-angular": "18.1.0",
56
- "@nestjs/testing": "10.2.8",
57
- "@types/jest": "29.5.7",
58
- "@types/node": "20.8.10",
59
- "@types/supertest": "^2.0.12",
60
- "@typescript-eslint/eslint-plugin": "^6.4.1",
61
- "@typescript-eslint/parser": "^6.4.1",
62
- "eslint": "8.52.0",
63
- "eslint-config-prettier": "9.0.0",
64
- "eslint-plugin-import": "2.29.0",
65
- "eslint-plugin-jest": "^27.2.3",
66
- "eslint-plugin-prettier": "^5.0.0",
44
+ "@commitlint/cli": "18.6.1",
45
+ "@commitlint/config-angular": "18.6.1",
46
+ "@nestjs/testing": "10.4.13",
47
+ "@types/jest": "29.5.14",
48
+ "@types/node": "20.17.9",
49
+ "@types/supertest": "^2.0.16",
50
+ "@typescript-eslint/eslint-plugin": "^6.21.0",
51
+ "@typescript-eslint/parser": "^6.21.0",
52
+ "eslint": "8.57.1",
53
+ "eslint-config-prettier": "9.1.0",
54
+ "eslint-plugin-import": "2.31.0",
55
+ "eslint-plugin-jest": "^27.9.0",
56
+ "eslint-plugin-prettier": "^5.2.1",
67
57
  "husky": "8.0.3",
68
58
  "jest": "29.7.0",
69
- "lint-staged": "15.0.2",
70
- "prettier": "^3.0.2",
71
- "reflect-metadata": "0.1.13",
72
- "release-it": "16.2.1",
73
- "rimraf": "5.0.5",
59
+ "lint-staged": "15.2.10",
60
+ "prettier": "^3.4.2",
61
+ "reflect-metadata": "0.2.2",
62
+ "release-it": "16.3.0",
63
+ "rimraf": "5.0.10",
74
64
  "rxjs": "7.8.1",
75
- "supertest": "^6.3.3",
76
- "ts-jest": "29.1.1",
77
- "typescript": "5.2.2"
65
+ "supertest": "^7.1.0",
66
+ "ts-jest": "29.2.5",
67
+ "typescript": "5.7.2"
78
68
  },
79
69
  "peerDependencies": {
80
70
  "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.2.1 || ^10.2.1",
81
- "reflect-metadata": "^0.1.12",
71
+ "reflect-metadata": "^0.2.2",
82
72
  "rxjs": "^6.0.0 || ^7.0.0"
83
73
  },
84
74
  "lint-staged": {