@quatrain/auth-firebase 1.1.16 → 1.1.17

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.
@@ -0,0 +1,20 @@
1
+ import { User } from '@quatrain/backend';
2
+ import { AbstractAuthAdapter, AuthParameters } from '@quatrain/auth';
3
+ import { UpdateRequest } from 'firebase-admin/auth';
4
+ export declare class FirebaseAuthAdapter extends AbstractAuthAdapter {
5
+ constructor(params?: AuthParameters);
6
+ /**
7
+ * Register new user in authentication
8
+ * @param user
9
+ * @returns user unique id
10
+ */
11
+ register(user: User, clearPassword?: string): Promise<string>;
12
+ getAuthToken(bearer: string): Promise<import("firebase-admin/auth").DecodedIdToken>;
13
+ signup(login: string, password: string): Promise<void>;
14
+ signout(user: User): Promise<any>;
15
+ update(user: User, updatable: UpdateRequest): Promise<any>;
16
+ delete(user: User): Promise<any>;
17
+ revokeAuthToken(token: string): Promise<void>;
18
+ setCustomUserClaims(id: string, claims: any): Promise<void>;
19
+ refreshToken(refreshToken: string): Promise<any>;
20
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.FirebaseAuthAdapter = void 0;
36
+ const auth_1 = require("@quatrain/auth");
37
+ const auth_2 = require("firebase-admin/auth");
38
+ const app_1 = require("firebase-admin/app");
39
+ const nativeFetch = __importStar(require("node-fetch-native"));
40
+ class FirebaseAuthAdapter extends auth_1.AbstractAuthAdapter {
41
+ constructor(params = {}) {
42
+ super(params);
43
+ if ((0, app_1.getApps)().length === 0) {
44
+ (0, app_1.initializeApp)(params.config);
45
+ }
46
+ }
47
+ /**
48
+ * Register new user in authentication
49
+ * @param user
50
+ * @returns user unique id
51
+ */
52
+ register(user, clearPassword) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ try {
55
+ const { name: displayName, email, phone: phoneNumber, password, disabled = false, } = user._;
56
+ const authData = {
57
+ uid: user.uid,
58
+ email,
59
+ phoneNumber,
60
+ password: clearPassword || password,
61
+ disabled,
62
+ displayName,
63
+ };
64
+ auth_1.Auth.log(`[FAA] Adding user '${displayName}'`);
65
+ const userRecord = yield (0, auth_2.getAuth)().createUser(authData);
66
+ return userRecord.uid;
67
+ }
68
+ catch (err) {
69
+ throw new auth_1.AuthenticationError(err.message);
70
+ }
71
+ });
72
+ }
73
+ getAuthToken(bearer) {
74
+ return __awaiter(this, void 0, void 0, function* () {
75
+ return yield (0, auth_2.getAuth)().verifyIdToken(bearer);
76
+ });
77
+ }
78
+ signup(login, password) {
79
+ return __awaiter(this, void 0, void 0, function* () { });
80
+ }
81
+ signout(user) {
82
+ return __awaiter(this, void 0, void 0, function* () { });
83
+ }
84
+ update(user, updatable) {
85
+ return __awaiter(this, void 0, void 0, function* () {
86
+ auth_1.Auth.log('auth data to update', JSON.stringify(updatable));
87
+ try {
88
+ if (Object.keys(updatable).length > 0) {
89
+ auth_1.Auth.log(`Updating ${updatable.displayName} Auth record`);
90
+ yield (0, auth_2.getAuth)().updateUser(user.uid, updatable);
91
+ }
92
+ }
93
+ catch (e) { }
94
+ });
95
+ }
96
+ delete(user) {
97
+ return __awaiter(this, void 0, void 0, function* () {
98
+ return yield (0, auth_2.getAuth)().deleteUser(user.uid);
99
+ });
100
+ }
101
+ revokeAuthToken(token) {
102
+ return __awaiter(this, void 0, void 0, function* () { });
103
+ }
104
+ setCustomUserClaims(id, claims) {
105
+ return __awaiter(this, void 0, void 0, function* () { });
106
+ }
107
+ refreshToken(refreshToken) {
108
+ return __awaiter(this, void 0, void 0, function* () {
109
+ if (!this._params.config.apiKey) {
110
+ auth_1.Auth.warn(`Can't get refresh token, no API key provided`);
111
+ return {};
112
+ }
113
+ const response = yield nativeFetch.fetch(`https://securetoken.googleapis.com/v1/token?key=${this._params.config.apiKey}`, {
114
+ method: 'POST',
115
+ body: JSON.stringify({
116
+ grant_type: 'refresh_token',
117
+ refresh_token: refreshToken,
118
+ }),
119
+ headers: { 'Content-Type': 'application/json' }, //, crossdomain: true },
120
+ });
121
+ const data = response.json();
122
+ return data;
123
+ });
124
+ }
125
+ }
126
+ exports.FirebaseAuthAdapter = FirebaseAuthAdapter;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,303 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ const FirebaseAuthAdapter_1 = require("./FirebaseAuthAdapter");
36
+ const auth_1 = require("@quatrain/auth");
37
+ const auth_2 = require("firebase-admin/auth");
38
+ const app_1 = require("firebase-admin/app");
39
+ const nativeFetch = __importStar(require("node-fetch-native"));
40
+ // Mock the dependencies
41
+ jest.mock('firebase-admin/auth');
42
+ jest.mock('firebase-admin/app');
43
+ jest.mock('node-fetch-native');
44
+ describe('FirebaseAuthAdapter', () => {
45
+ let adapter;
46
+ let mockAuth;
47
+ beforeEach(() => {
48
+ // Clear all mocks before each test
49
+ jest.clearAllMocks();
50
+ // Mock Firebase Admin SDK
51
+ mockAuth = {
52
+ createUser: jest.fn(),
53
+ verifyIdToken: jest.fn(),
54
+ updateUser: jest.fn(),
55
+ deleteUser: jest.fn(),
56
+ };
57
+ auth_2.getAuth.mockReturnValue(mockAuth);
58
+ app_1.getApps.mockReturnValue([]);
59
+ // Create adapter instance
60
+ adapter = new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
61
+ config: {
62
+ projectId: 'test-project',
63
+ apiKey: 'test-api-key',
64
+ },
65
+ });
66
+ });
67
+ afterEach(() => {
68
+ jest.restoreAllMocks();
69
+ });
70
+ describe('Constructor', () => {
71
+ it('should initialize Firebase app when no apps exist', () => {
72
+ expect(app_1.getApps).toHaveBeenCalled();
73
+ expect(app_1.initializeApp).toHaveBeenCalledWith({
74
+ projectId: 'test-project',
75
+ apiKey: 'test-api-key',
76
+ });
77
+ });
78
+ it('should not initialize Firebase app when app already exists', () => {
79
+ ;
80
+ app_1.getApps.mockReturnValue([{ name: 'existing-app' }]);
81
+ new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
82
+ config: {
83
+ projectId: 'test-project',
84
+ },
85
+ });
86
+ // initializeApp should still be called once from the first adapter creation
87
+ expect(app_1.initializeApp).toHaveBeenCalledTimes(1);
88
+ });
89
+ });
90
+ describe('register()', () => {
91
+ const mockUser = {
92
+ uid: 'user-123',
93
+ _: {
94
+ name: 'John Doe',
95
+ email: 'john@example.com',
96
+ phone: '+1234567890',
97
+ password: 'hashed-password',
98
+ disabled: false,
99
+ },
100
+ };
101
+ it('should successfully register a new user', () => __awaiter(void 0, void 0, void 0, function* () {
102
+ const mockUserRecord = {
103
+ uid: 'user-123',
104
+ email: 'john@example.com',
105
+ };
106
+ mockAuth.createUser.mockResolvedValue(mockUserRecord);
107
+ const result = yield adapter.register(mockUser, 'clearPassword123');
108
+ expect(mockAuth.createUser).toHaveBeenCalledWith({
109
+ uid: 'user-123',
110
+ email: 'john@example.com',
111
+ phoneNumber: '+1234567890',
112
+ password: 'clearPassword123',
113
+ disabled: false,
114
+ displayName: 'John Doe',
115
+ });
116
+ expect(result).toBe('user-123');
117
+ }));
118
+ it('should use hashed password when clearPassword is not provided', () => __awaiter(void 0, void 0, void 0, function* () {
119
+ const mockUserRecord = {
120
+ uid: 'user-123',
121
+ email: 'john@example.com',
122
+ };
123
+ mockAuth.createUser.mockResolvedValue(mockUserRecord);
124
+ yield adapter.register(mockUser);
125
+ expect(mockAuth.createUser).toHaveBeenCalledWith({
126
+ uid: 'user-123',
127
+ email: 'john@example.com',
128
+ phoneNumber: '+1234567890',
129
+ password: 'hashed-password',
130
+ disabled: false,
131
+ displayName: 'John Doe',
132
+ });
133
+ }));
134
+ it('should handle user with default disabled value', () => __awaiter(void 0, void 0, void 0, function* () {
135
+ const userWithoutDisabled = {
136
+ uid: 'user-456',
137
+ _: {
138
+ name: 'Jane Doe',
139
+ email: 'jane@example.com',
140
+ phone: '+0987654321',
141
+ password: 'password456',
142
+ },
143
+ };
144
+ mockAuth.createUser.mockResolvedValue({ uid: 'user-456' });
145
+ yield adapter.register(userWithoutDisabled, 'password');
146
+ const createUserCall = mockAuth.createUser.mock.calls[0][0];
147
+ expect(createUserCall.disabled).toBe(false);
148
+ }));
149
+ it('should throw AuthenticationError on Firebase error', () => __awaiter(void 0, void 0, void 0, function* () {
150
+ mockAuth.createUser.mockRejectedValue(new Error('Email already exists'));
151
+ yield expect(adapter.register(mockUser, 'password')).rejects.toThrow(auth_1.AuthenticationError);
152
+ yield expect(adapter.register(mockUser, 'password')).rejects.toThrow('Email already exists');
153
+ }));
154
+ });
155
+ describe('getAuthToken()', () => {
156
+ it('should successfully verify auth token', () => __awaiter(void 0, void 0, void 0, function* () {
157
+ const mockDecodedToken = {
158
+ uid: 'user-123',
159
+ email: 'test@example.com',
160
+ iat: Date.now(),
161
+ };
162
+ mockAuth.verifyIdToken.mockResolvedValue(mockDecodedToken);
163
+ const result = yield adapter.getAuthToken('bearer-token-123');
164
+ expect(mockAuth.verifyIdToken).toHaveBeenCalledWith('bearer-token-123');
165
+ expect(result).toEqual(mockDecodedToken);
166
+ }));
167
+ it('should throw error when token is invalid', () => __awaiter(void 0, void 0, void 0, function* () {
168
+ mockAuth.verifyIdToken.mockRejectedValue(new Error('Invalid token'));
169
+ yield expect(adapter.getAuthToken('invalid-token')).rejects.toThrow('Invalid token');
170
+ }));
171
+ });
172
+ describe('update()', () => {
173
+ const mockUser = {
174
+ uid: 'user-123',
175
+ _: {
176
+ email: 'test@example.com',
177
+ },
178
+ };
179
+ it('should successfully update user', () => __awaiter(void 0, void 0, void 0, function* () {
180
+ const updatable = {
181
+ email: 'newemail@example.com',
182
+ displayName: 'New Name',
183
+ };
184
+ mockAuth.updateUser.mockResolvedValue({});
185
+ yield adapter.update(mockUser, updatable);
186
+ expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable);
187
+ }));
188
+ it('should skip update when updatable is empty', () => __awaiter(void 0, void 0, void 0, function* () {
189
+ yield adapter.update(mockUser, {});
190
+ expect(mockAuth.updateUser).not.toHaveBeenCalled();
191
+ }));
192
+ it('should silently handle errors during update', () => __awaiter(void 0, void 0, void 0, function* () {
193
+ const updatable = { displayName: 'New Name' };
194
+ mockAuth.updateUser.mockRejectedValue(new Error('Update failed'));
195
+ // Should not throw
196
+ yield expect(adapter.update(mockUser, updatable)).resolves.toBeUndefined();
197
+ expect(mockAuth.updateUser).toHaveBeenCalledWith('user-123', updatable);
198
+ }));
199
+ });
200
+ describe('delete()', () => {
201
+ const mockUser = {
202
+ uid: 'user-123',
203
+ };
204
+ it('should successfully delete user', () => __awaiter(void 0, void 0, void 0, function* () {
205
+ mockAuth.deleteUser.mockResolvedValue(undefined);
206
+ yield adapter.delete(mockUser);
207
+ expect(mockAuth.deleteUser).toHaveBeenCalledWith('user-123');
208
+ }));
209
+ it('should throw error on deletion failure', () => __awaiter(void 0, void 0, void 0, function* () {
210
+ mockAuth.deleteUser.mockRejectedValue(new Error('User not found'));
211
+ yield expect(adapter.delete(mockUser)).rejects.toThrow('User not found');
212
+ }));
213
+ });
214
+ describe('refreshToken()', () => {
215
+ it('should successfully refresh token', () => __awaiter(void 0, void 0, void 0, function* () {
216
+ const mockResponse = {
217
+ access_token: 'new-access-token',
218
+ refresh_token: 'new-refresh-token',
219
+ expires_in: 3600,
220
+ };
221
+ const mockFetchResponse = {
222
+ json: jest.fn().mockResolvedValue(mockResponse),
223
+ };
224
+ nativeFetch.fetch.mockResolvedValue(mockFetchResponse);
225
+ const result = yield adapter.refreshToken('old-refresh-token');
226
+ expect(nativeFetch.fetch).toHaveBeenCalledWith('https://securetoken.googleapis.com/v1/token?key=test-api-key', {
227
+ method: 'POST',
228
+ body: JSON.stringify({
229
+ grant_type: 'refresh_token',
230
+ refresh_token: 'old-refresh-token',
231
+ }),
232
+ headers: { 'Content-Type': 'application/json' },
233
+ });
234
+ // Note: Implementation doesn't await json(), so result is a promise
235
+ const resultValue = yield result;
236
+ expect(resultValue).toEqual(mockResponse);
237
+ }));
238
+ it('should return empty object when API key is missing', () => __awaiter(void 0, void 0, void 0, function* () {
239
+ const adapterWithoutKey = new FirebaseAuthAdapter_1.FirebaseAuthAdapter({
240
+ config: {
241
+ projectId: 'test-project',
242
+ },
243
+ });
244
+ const result = yield adapterWithoutKey.refreshToken('refresh-token');
245
+ expect(result).toEqual({});
246
+ expect(nativeFetch.fetch).not.toHaveBeenCalled();
247
+ }));
248
+ it('should construct correct URL with API key', () => __awaiter(void 0, void 0, void 0, function* () {
249
+ const mockFetchResponse = {
250
+ json: jest.fn().mockResolvedValue({}),
251
+ };
252
+ nativeFetch.fetch.mockResolvedValue(mockFetchResponse);
253
+ yield adapter.refreshToken('refresh-token');
254
+ const callArgs = nativeFetch.fetch.mock.calls[0];
255
+ expect(callArgs[0]).toBe('https://securetoken.googleapis.com/v1/token?key=test-api-key');
256
+ }));
257
+ });
258
+ describe('Unimplemented Methods (Stubs)', () => {
259
+ describe('signup()', () => {
260
+ it('should have signup method defined', () => {
261
+ expect(adapter.signup).toBeDefined();
262
+ expect(typeof adapter.signup).toBe('function');
263
+ });
264
+ it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
265
+ const result = yield adapter.signup('test@example.com', 'password');
266
+ expect(result).toBeUndefined();
267
+ }));
268
+ });
269
+ describe('signout()', () => {
270
+ it('should have signout method defined', () => {
271
+ expect(adapter.signout).toBeDefined();
272
+ expect(typeof adapter.signout).toBe('function');
273
+ });
274
+ it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
275
+ const mockUser = { uid: 'user-123' };
276
+ const result = yield adapter.signout(mockUser);
277
+ expect(result).toBeUndefined();
278
+ }));
279
+ });
280
+ describe('revokeAuthToken()', () => {
281
+ it('should have revokeAuthToken method defined', () => {
282
+ expect(adapter.revokeAuthToken).toBeDefined();
283
+ expect(typeof adapter.revokeAuthToken).toBe('function');
284
+ });
285
+ it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
286
+ const result = yield adapter.revokeAuthToken('token-123');
287
+ expect(result).toBeUndefined();
288
+ }));
289
+ });
290
+ describe('setCustomUserClaims()', () => {
291
+ it('should have setCustomUserClaims method defined', () => {
292
+ expect(adapter.setCustomUserClaims).toBeDefined();
293
+ expect(typeof adapter.setCustomUserClaims).toBe('function');
294
+ });
295
+ it('should return undefined (not implemented)', () => __awaiter(void 0, void 0, void 0, function* () {
296
+ const result = yield adapter.setCustomUserClaims('user-123', {
297
+ role: 'admin',
298
+ });
299
+ expect(result).toBeUndefined();
300
+ }));
301
+ });
302
+ });
303
+ });
@@ -0,0 +1,2 @@
1
+ import { FirebaseAuthAdapter } from './FirebaseAuthAdapter';
2
+ export { FirebaseAuthAdapter };
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FirebaseAuthAdapter = void 0;
4
+ const FirebaseAuthAdapter_1 = require("./FirebaseAuthAdapter");
5
+ Object.defineProperty(exports, "FirebaseAuthAdapter", { enumerable: true, get: function () { return FirebaseAuthAdapter_1.FirebaseAuthAdapter; } });
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
1
  {
2
- "name": "@quatrain/auth-firebase",
3
- "version": "1.1.16",
4
- "license": "AGPL-3.0-only",
5
- "main": "lib/index.js",
6
- "types": "lib/index.d.ts",
7
- "bun": "src/index.ts",
8
- "files": [
9
- "LICENSE.md",
10
- "src/",
11
- "lib/",
12
- "README.md"
13
- ],
14
- "author": "Quatrain Développement SAS <developers@quatrain.com>",
15
- "peerDependencies": {
16
- "@quatrain/core": "^1.1.43"
17
- },
18
- "dependencies": {
19
- "@quatrain/auth": "^1.1.21",
20
- "@quatrain/backend": "^1.1.27",
21
- "@quatrain/core": "^1.1.43",
22
- "firebase-admin": "^13.0.1",
23
- "node-fetch-native": "^1.6.4"
24
- },
25
- "devDependencies": {
26
- "@tsconfig/recommended": "^1.0.1",
27
- "@types/jest": "^30.0.0",
28
- "@types/node": "^22.10.1",
29
- "jest": "^30.2.0",
30
- "jest-node-exports-resolver": "^1.1.6",
31
- "jest-serial-runner": "^1.2.1",
32
- "trace-unhandled": "^2.0.1",
33
- "ts-jest": "^29.4.1",
34
- "ts-node": "^10.9.1",
35
- "typescript": "^5.2.2"
36
- },
37
- "scripts": {
38
- "test-ci": "jest --runInBand",
39
- "build": "tsc",
40
- "wbuild": "tsc --watch",
41
- "bump-to": "yarn version"
42
- }
43
- }
2
+ "name": "@quatrain/auth-firebase",
3
+ "version": "1.1.17",
4
+ "license": "AGPL-3.0-only",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bun": "src/index.ts",
8
+ "files": [
9
+ "LICENSE.md",
10
+ "src/",
11
+ "dist/",
12
+ "README.md"
13
+ ],
14
+ "author": "Quatrain Développement SAS <developers@quatrain.com>",
15
+ "peerDependencies": {
16
+ "@quatrain/core": "^1.1.44"
17
+ },
18
+ "dependencies": {
19
+ "@quatrain/auth": "^1.1.22",
20
+ "@quatrain/backend": "^1.1.28",
21
+ "@quatrain/core": "^1.1.44",
22
+ "firebase-admin": "^13.0.1",
23
+ "node-fetch-native": "^1.6.4"
24
+ },
25
+ "devDependencies": {
26
+ "@tsconfig/recommended": "^1.0.1",
27
+ "@types/jest": "^30.0.0",
28
+ "@types/node": "^22.10.1",
29
+ "jest": "^30.2.0",
30
+ "jest-node-exports-resolver": "^1.1.6",
31
+ "jest-serial-runner": "^1.2.1",
32
+ "trace-unhandled": "^2.0.1",
33
+ "ts-jest": "^29.4.1",
34
+ "ts-node": "^10.9.1",
35
+ "typescript": "^5.2.2"
36
+ },
37
+ "scripts": {
38
+ "test-ci": "jest --runInBand",
39
+ "build": "tsc",
40
+ "wbuild": "tsc --watch",
41
+ "bump-to": "yarn version"
42
+ }
43
+ }