backend-manager 3.2.115 → 3.2.117

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "backend-manager",
3
- "version": "3.2.115",
3
+ "version": "3.2.117",
4
4
  "description": "Quick tools for developing Firebase functions",
5
5
  "main": "src/manager/index.js",
6
6
  "bin": {
@@ -45,8 +45,8 @@
45
45
  "cors": "^2.8.5",
46
46
  "dotenv": "^16.4.5",
47
47
  "express": "^4.19.2",
48
- "firebase-admin": "^11.11.1",
49
- "firebase-functions": "^4.9.0",
48
+ "firebase-admin": "^12.1.0",
49
+ "firebase-functions": "^5.0.1",
50
50
  "fs-jetpack": "^5.1.0",
51
51
  "glob": "^10.3.12",
52
52
  "hcaptcha": "^0.1.1",
@@ -50,6 +50,11 @@ Middleware.prototype.run = function (libPath, options) {
50
50
  const routesDir = path.resolve(options.routesDir, libPath.replace('.js', ''));
51
51
  const schemasDir = path.resolve(options.schemasDir);
52
52
 
53
+ // Wakeup trigger (quit immediately if wakeup is true to avoid cold start on a future request)
54
+ if (data.wakeup) {
55
+ return assistant.respond({wakeup: true});
56
+ }
57
+
53
58
  // Load library
54
59
  let library;
55
60
  try {
@@ -18,7 +18,9 @@ Utilities.prototype.iterateCollection = function (callback, options) {
18
18
 
19
19
  return new Promise(function(resolve, reject) {
20
20
  let batch = -1;
21
+ let collectionCount = 0;
21
22
 
23
+ // Set defaults
22
24
  options = options || {};
23
25
  options.collection = options.collection || '';
24
26
  options.batchSize = options.batchSize || 1000;
@@ -31,21 +33,26 @@ Utilities.prototype.iterateCollection = function (callback, options) {
31
33
  : options.prefetchCursor;
32
34
  options.log = options.log;
33
35
 
36
+ // List all documents in a collection
34
37
  async function listAllDocuments(nextPageToken) {
35
- let query = admin.firestore().collection(options.collection)
38
+ let query = admin.firestore().collection(options.collection);
36
39
 
40
+ // Insert where clauses
37
41
  options.where
38
42
  .forEach(clause => {
39
43
  query = query.where(clause.field, clause.operator, clause.value);
40
44
  });
41
45
 
46
+ // Insert orderBy
42
47
  if (options.orderBy) {
43
48
  query = query.orderBy(options.orderBy);
44
49
  }
45
50
 
51
+ // Process the first batch differently
46
52
  if (batch === -1) {
47
53
  let prefetchedCursor = null;
48
54
 
55
+ // Prefetch the cursor
49
56
  if (options.prefetchCursor) {
50
57
  prefetchedCursor = await admin.firestore().doc(`${options.collection}/${options.startAt || options.startAfter}`)
51
58
  .get()
@@ -56,11 +63,30 @@ Utilities.prototype.iterateCollection = function (callback, options) {
56
63
  }
57
64
  }
58
65
 
66
+ // Insert startAt or startAfter
59
67
  if (options.startAt) {
60
68
  query = query.startAt(prefetchedCursor || options.startAt);
61
69
  } else if (options.startAfter) {
62
70
  query = query.startAfter(prefetchedCursor || options.startAfter);
63
71
  }
72
+
73
+ // Calculate count
74
+ const collectionCountResult = await query.count().get()
75
+ .then((r) => r.data().count)
76
+ .catch((e) => e);
77
+
78
+ // Check for errors
79
+ if (collectionCountResult instanceof Error) {
80
+ return reject(collectionCountResult);
81
+ }
82
+
83
+ // Set collection count
84
+ collectionCount = collectionCountResult;
85
+
86
+ // Log
87
+ if (options.log) {
88
+ console.log('Total count:', collectionCount);
89
+ }
64
90
  }
65
91
 
66
92
  // Start at next page
@@ -71,6 +97,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
71
97
  // batchSize
72
98
  query = query.limit(options.batchSize);
73
99
 
100
+ // Get
74
101
  query.get()
75
102
  .then(async (snap) => {
76
103
  const lastVisible = snap.docs[snap.docs.length - 1];
@@ -85,22 +112,26 @@ Utilities.prototype.iterateCollection = function (callback, options) {
85
112
  console.log('Processing batch:', batch);
86
113
  }
87
114
 
88
- callback({
89
- snap: snap,
90
- docs: snap.docs.map(x => x),
91
- }, batch)
92
- .then(r => {
93
- // Construct a new query starting at this document
94
- if (lastVisible) {
95
- listAllDocuments(lastVisible)
96
- } else {
97
- return resolve();
98
- }
99
- })
100
- .catch((e) => {
101
- console.error('Callback failed', e);
102
- return reject(e);
103
- });
115
+ callback(
116
+ {
117
+ snap: snap,
118
+ docs: snap.docs.map(x => x),
119
+ },
120
+ batch,
121
+ collectionCount,
122
+ )
123
+ .then(r => {
124
+ // Construct a new query starting at this document
125
+ if (lastVisible) {
126
+ listAllDocuments(lastVisible)
127
+ } else {
128
+ return resolve();
129
+ }
130
+ })
131
+ .catch((e) => {
132
+ console.error('Callback failed', e);
133
+ return reject(e);
134
+ });
104
135
 
105
136
  })
106
137
  .catch((e) => {
@@ -109,6 +140,7 @@ Utilities.prototype.iterateCollection = function (callback, options) {
109
140
  });
110
141
  }
111
142
 
143
+ // Run
112
144
  listAllDocuments();
113
145
  });
114
146
  };
@@ -68,7 +68,9 @@ Manager.prototype.init = function (exporter, options) {
68
68
  // Load libraries
69
69
  self.libraries = {
70
70
  // Third-party
71
- functions: options.projectType === 'firebase' ? require('firebase-functions') : null,
71
+ functions: options.projectType === 'firebase'
72
+ ? require('firebase-functions')
73
+ : null,
72
74
  admin: require('firebase-admin'),
73
75
  cors: require('cors')({ origin: true }),
74
76
  sentry: null,
@@ -1,326 +0,0 @@
1
- const fetch = require('wonderful-fetch');
2
- const jetpack = require('fs-jetpack');
3
- const powertools = require('node-powertools');
4
- const _ = require('lodash');
5
- const JSON5 = require('json5');
6
-
7
- const TOKEN_COST_TABLE = {
8
- // Mar 6th, 2024
9
- 'gpt-4-turbo-preview': {
10
- input: 0.0100,
11
- output: 0.0300,
12
- },
13
- 'gpt-4-1106-preview': {
14
- input: 0.0100,
15
- output: 0.0300,
16
- },
17
- 'gpt-4': {
18
- input: 0.0300,
19
- output: 0.0600,
20
- },
21
- 'gpt-3.5-turbo': {
22
- input: 0.0005,
23
- output: 0.0015,
24
- },
25
-
26
- // Nov 6th, 2023
27
- // 'gpt-4-turbo-preview': {
28
- // input: 0.0100,
29
- // output: 0.0300,
30
- // },
31
- // 'gpt-4-1106-preview': {
32
- // input: 0.0100,
33
- // output: 0.0300,
34
- // },
35
- // 'gpt-4': {
36
- // input: 0.0300,
37
- // output: 0.0600,
38
- // },
39
- // 'gpt-3.5-turbo': {
40
- // input: 0.0010,
41
- // output: 0.0020,
42
- // },
43
- }
44
-
45
- function OpenAI(assistant, key) {
46
- const self = this;
47
-
48
- self.assistant = assistant;
49
- self.Manager = assistant.Manager;
50
- self.user = assistant.user;
51
- self.key = key;
52
-
53
- self.tokens = {
54
- total: {
55
- count: 0,
56
- price: 0,
57
- },
58
- input: {
59
- count: 0,
60
- price: 0,
61
- },
62
- output: {
63
- count: 0,
64
- price: 0,
65
- },
66
- }
67
-
68
- return self;
69
- }
70
-
71
- OpenAI.prototype.request = function (options) {
72
- const self = this;
73
- const Manager = self.Manager;
74
- const assistant = self.assistant;
75
-
76
- return new Promise(async function(resolve, reject) {
77
- options = _.merge({}, options);
78
-
79
- options.model = typeof options.model === 'undefined' ? 'gpt-3.5-turbo' : options.model;
80
- options.timeout = typeof options.timeout === 'undefined' ? 120000 : options.timeout;
81
- options.moderate = typeof options.moderate === 'undefined' ? true : options.moderate;
82
- options.log = typeof options.log === 'undefined' ? false : options.log;
83
- options.user = options.user || assistant.getUser();
84
-
85
- options.retries = typeof options.retries === 'undefined' ? 0 : options.retries;
86
- options.retryTriggers = typeof options.retryTriggers === 'undefined' ? ['network', 'parse'] : options.retryTriggers;
87
-
88
- options.prompt = options.prompt || {};
89
- options.prompt.path = options.prompt.path || '';
90
- options.prompt.content = options.prompt.content || '';
91
- options.prompt.settings = options.prompt.settings || {};
92
-
93
- options.message = options.message || {};
94
- options.message.path = options.message.path || '';
95
- options.message.content = options.message.content || '';
96
- options.message.settings = options.message.settings || {};
97
-
98
- options.history = options.history || {};
99
- options.history.messages = options.history.messages || [];
100
- options.history.limit = typeof options.history.limit === 'undefined' ? 5 : options.history.limit;
101
-
102
- options.response = typeof options.response === 'undefined' ? undefined : options.response;
103
- options.temperature = typeof options.temperature === 'undefined' ? 0.7 : options.temperature;
104
- options.maxTokens = typeof options.maxTokens === 'undefined' ? 512 : options.maxTokens;
105
-
106
- let attempt = 0;
107
-
108
- function _log() {
109
- if (!options.log) {
110
- return;
111
- }
112
-
113
- assistant.log('callOpenAI():', ...arguments);
114
- }
115
-
116
- function _load(input) {
117
- // console.log('*** input!!!', input.content.slice(0, 50), input.path);
118
- // console.log('*** input.content', input.content.slice(0, 50));
119
- // console.log('*** input.path', input.path);
120
- // _log('Loading', input);
121
-
122
- let content = '';
123
-
124
- // Load content
125
- if (input.path) {
126
- const exists = jetpack.exists(input.path);
127
- if (!exists) {
128
- return new Error(`Path ${input.path} not found`);
129
- } else if (exists === 'dir') {
130
- return new Error(`Path ${input.path} is a directory`);
131
- }
132
-
133
- try {
134
- content = jetpack.read(input.path);
135
- } catch (e) {
136
- return new Error(`Error reading file ${input.path}: ${e}`);
137
- }
138
- } else {
139
- content = input.content;
140
- }
141
-
142
- return powertools.template(content, input.settings).trim();
143
- }
144
-
145
- // Log
146
- _log('Starting', self.key, options);
147
-
148
- // Load prompt
149
- const prompt = _load(options.prompt);
150
- const message = _load(options.message);
151
- const user = options.user?.auth?.uid || assistant.request.geolocation.ip;
152
- const responseFormat = options.response === 'json' && !options.model.includes('gpt-3.5')
153
- ? { type: 'json_object' }
154
- : undefined;
155
-
156
- // Log
157
- _log('Prompt', prompt);
158
- _log('Message', message);
159
- _log('User', user);
160
-
161
- // Check for errors
162
- if (prompt instanceof Error) {
163
- return reject(assistant.errorify(`Error loading prompt: ${prompt}`, {code: 400}));
164
- }
165
-
166
- if (message instanceof Error) {
167
- return reject(assistant.errorify(`Error loading message: ${message}`, {code: 400}));
168
- }
169
-
170
- // Format history
171
- options.history.messages.forEach((m) => {
172
- m.role = m.role || 'system';
173
- m.content = (m.content || '').trim();
174
- });
175
-
176
- // Request
177
- function _request(mode, options) {
178
- return new Promise(async function(resolve, reject) {
179
- let resultPath = '';
180
- const request = {
181
- url: '',
182
- method: 'post',
183
- response: 'json',
184
- // log: true,
185
- tries: 1,
186
- timeout: options.timeout,
187
- headers: {
188
- 'Authorization': `Bearer ${Manager.config.openai.key}`,
189
- },
190
- body: {},
191
- }
192
-
193
- if (mode === 'chatgpt') {
194
- request.url = 'https://api.openai.com/v1/chat/completions';
195
- options.history.messages = options.history.messages.slice(-options.history.limit);
196
- options.history.messages.unshift({
197
- role: 'system',
198
- content: prompt,
199
- });
200
-
201
- // Set last history item
202
- const lastHistory = options.history.messages[options.history.messages.length - 1];
203
-
204
- // If message is different than last message in history, add it
205
- if (lastHistory?.content !== message) {
206
- options.history.messages.push({
207
- role: 'user',
208
- content: message,
209
- });
210
- }
211
-
212
- // Log message
213
- _log('Messages', options.history.messages);
214
-
215
- request.body = {
216
- model: options.model,
217
- response_format: responseFormat,
218
- messages: options.history.messages,
219
- temperature: options.temperature,
220
- max_tokens: options.maxTokens,
221
- user: user,
222
- }
223
- resultPath = 'choices[0].message.content';
224
- } else if (mode === 'moderation') {
225
- request.url = 'https://api.openai.com/v1/moderations';
226
- request.body = {
227
- input: message,
228
- user: user,
229
- }
230
- resultPath = 'results[0]';
231
- }
232
-
233
- // Request
234
- await fetch(request.url, request)
235
- .then((r) => {
236
- // Set token counts
237
- self.tokens.total.count += r?.usage?.total_tokens || 0;
238
- self.tokens.input.count += r?.usage?.prompt_tokens || 0;
239
- self.tokens.output.count += r?.usage?.completion_tokens || 0;
240
-
241
- // Set token prices
242
- self.tokens.total.price = (self.tokens.total.count / 1000) * TOKEN_COST_TABLE[options.model].input;
243
- self.tokens.input.price = (self.tokens.input.count / 1000) * TOKEN_COST_TABLE[options.model].input;
244
- self.tokens.output.price = (self.tokens.output.count / 1000) * TOKEN_COST_TABLE[options.model].output;
245
-
246
- return resolve(_.get(r, resultPath));
247
- })
248
- .catch((e) => {
249
- return reject(e);
250
- })
251
- });
252
- }
253
-
254
- // Moderate if needed
255
- let moderation = null;
256
- if (options.moderate) {
257
- moderation = await _request('moderation', options)
258
- .then(async (r) => {
259
- _log('Moderated', r);
260
-
261
- return r;
262
- })
263
- .catch((e) => e);
264
-
265
- // Check for moderation flag
266
- if (moderation.flagged) {
267
- return reject(assistant.errorify(`This request is inappropriate`, {code: 451}));
268
- }
269
- }
270
-
271
- function _attempt() {
272
- const retries = options.retries;
273
- const triggers = options.retryTriggers;
274
-
275
- // Increment attempt
276
- attempt++;
277
-
278
- // Log
279
- _log(`Request ${attempt}/${retries}`);
280
-
281
- // Request
282
- _request('chatgpt', options)
283
- .then((r) => {
284
- _log('Response', r);
285
-
286
- // Try to parse JSON response if needed
287
- try {
288
- const content = options.response === 'json' ? JSON5.parse(r) : r;
289
-
290
- // Return
291
- return resolve({
292
- content: content,
293
- tokens: self.tokens,
294
- moderation: moderation,
295
- })
296
- } catch (e) {
297
- assistant.error('Error parsing response', r, e);
298
-
299
- // Retry
300
- if (attempt < retries && triggers.includes('parse')) {
301
- return _attempt();
302
- }
303
-
304
- // Return
305
- return reject(e);
306
- }
307
- })
308
- .catch((e) => {
309
- assistant.error('Error requesting', e);
310
-
311
- // Retry
312
- if (attempt < retries && triggers.includes('network')) {
313
- return _attempt();
314
- }
315
-
316
- // Return
317
- return reject(e);
318
- });
319
- }
320
-
321
- // Make attempt
322
- _attempt();
323
- });
324
- }
325
-
326
- module.exports = OpenAI;