backend-manager 5.0.36 → 5.0.38

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/CLAUDE.md ADDED
@@ -0,0 +1,446 @@
1
+ # Backend Manager (BEM) - Claude Code Instructions
2
+
3
+ This document provides instructions for Claude Code when working with Backend Manager projects.
4
+
5
+ ## Project Identity
6
+
7
+ **Backend Manager (BEM)** is an NPM package that provides powerful backend features for Firebase Cloud Functions projects, including authentication, rate limiting, analytics, and more.
8
+
9
+ **This repository** (`backend-manager`) is the BEM library itself. If you're working here, you're contributing to the library, not consuming it.
10
+
11
+ **Consumer projects** are Firebase projects that `require('backend-manager')` in their `functions/index.js`. These have:
12
+ - `functions/` directory with `index.js` that calls `Manager.init(exports, {...})`
13
+ - `backend-manager-config.json` configuration file
14
+ - `service-account.json` for Firebase credentials
15
+ - Optional `routes/` and `schemas/` directories for custom endpoints
16
+
17
+ ## Architecture Overview
18
+
19
+ ### Manager Class
20
+ The core `Manager` class (in `src/manager/index.js`) extends EventEmitter and orchestrates all functionality:
21
+ - Initializes Firebase Admin SDK
22
+ - Sets up built-in Cloud Functions (`bm_api`, auth events, cron)
23
+ - Provides factory methods for helper classes
24
+ - Manages configuration from multiple sources
25
+
26
+ ### Dual-Mode Support
27
+ BEM supports two deployment modes:
28
+ - **Firebase Functions** (`projectType: 'firebase'`): Cloud Functions with Firebase triggers
29
+ - **Custom Server** (`projectType: 'custom'`): Express server for non-Firebase deployments
30
+
31
+ ### Helper Factory Pattern
32
+ All helpers are accessed via factory methods on the Manager instance:
33
+ ```javascript
34
+ Manager.Assistant({ req, res }) // Request handler
35
+ Manager.User(data) // User properties
36
+ Manager.Analytics({ assistant }) // GA4 events
37
+ Manager.Usage() // Rate limiting
38
+ Manager.Middleware(req, res) // Request pipeline
39
+ Manager.Settings() // Schema validation
40
+ Manager.Utilities() // Batch operations
41
+ Manager.Metadata(doc) // Timestamps/tags
42
+ Manager.storage({ name }) // Local JSON storage (lowdb)
43
+ ```
44
+
45
+ ## Directory Structure
46
+
47
+ ### BEM Library (this repo)
48
+ ```
49
+ src/
50
+ manager/
51
+ index.js # Main Manager class
52
+ helpers/ # Helper classes
53
+ assistant.js # Request/response handling
54
+ user.js # User property structure
55
+ analytics.js # GA4 integration
56
+ usage.js # Rate limiting
57
+ middleware.js # Request pipeline
58
+ settings.js # Schema validation
59
+ utilities.js # Batch operations
60
+ metadata.js # Timestamps/tags
61
+ functions/core/ # Built-in functions
62
+ actions/
63
+ api.js # Main bm_api handler
64
+ api/{category}/{action}.js # API command handlers
65
+ events/
66
+ auth/ # Auth event handlers
67
+ firestore/ # Firestore triggers
68
+ cron/
69
+ daily.js # Daily cron runner
70
+ daily/{job}.js # Individual cron jobs
71
+ routes/ # Built-in routes
72
+ schemas/ # Built-in schemas
73
+ cli/
74
+ cli-refactored.js # CLI entry point
75
+ commands/ # CLI commands
76
+ templates/
77
+ backend-manager-config.json # Config template
78
+ ```
79
+
80
+ ### Consumer Project Structure
81
+ ```
82
+ functions/
83
+ index.js # Manager.init() + custom functions
84
+ backend-manager-config.json # App configuration
85
+ service-account.json # Firebase credentials
86
+ routes/
87
+ {endpoint}/
88
+ index.js # All methods handler
89
+ get.js # GET handler
90
+ post.js # POST handler
91
+ schemas/
92
+ {endpoint}/
93
+ index.js # Schema definition
94
+ hooks/
95
+ cron/
96
+ daily/
97
+ {job}.js # Custom daily jobs
98
+ ```
99
+
100
+ ## Code Patterns
101
+
102
+ ### Short-Circuit Returns
103
+ Use early returns instead of nested conditionals:
104
+ ```javascript
105
+ // CORRECT
106
+ function handler(data) {
107
+ if (!data) {
108
+ return assistant.errorify('Missing data', { code: 400 });
109
+ }
110
+
111
+ // Main logic here
112
+ return assistant.respond({ success: true });
113
+ }
114
+
115
+ // INCORRECT
116
+ function handler(data) {
117
+ if (data) {
118
+ // Main logic here
119
+ return assistant.respond({ success: true });
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Logical Operators on New Lines
125
+ Place operators at the start of continuation lines:
126
+ ```javascript
127
+ // CORRECT
128
+ const isValid = hasPermission
129
+ || isAdmin
130
+ || isOwner;
131
+
132
+ // INCORRECT
133
+ const isValid = hasPermission ||
134
+ isAdmin ||
135
+ isOwner;
136
+ ```
137
+
138
+ ### Firestore Document Access
139
+ Use shorthand `.doc()` path:
140
+ ```javascript
141
+ // CORRECT
142
+ admin.firestore().doc('users/abc123')
143
+
144
+ // INCORRECT
145
+ admin.firestore().collection('users').doc('abc123')
146
+ ```
147
+
148
+ ### Template Strings for Requires
149
+ ```javascript
150
+ // CORRECT
151
+ require(`${functionsDir}/node_modules/backend-manager`)
152
+
153
+ // INCORRECT
154
+ require(functionsDir + '/node_modules/backend-manager')
155
+ ```
156
+
157
+ ### Prefer fs-jetpack
158
+ Use `fs-jetpack` over `fs` or `fs-extra` for file operations.
159
+
160
+ ## Creating New Components
161
+
162
+ ### New API Command
163
+
164
+ Create `src/manager/functions/core/actions/api/{category}/{action}.js`:
165
+
166
+ ```javascript
167
+ function Module() {}
168
+
169
+ Module.prototype.main = function () {
170
+ const self = this;
171
+ const Manager = self.Manager;
172
+ const Api = self.Api;
173
+ const assistant = self.assistant;
174
+ const payload = self.payload;
175
+
176
+ return new Promise(async function(resolve, reject) {
177
+ // Validate input
178
+ if (!payload.data.payload.requiredField) {
179
+ return reject(assistant.errorify('Missing required field', { code: 400 }));
180
+ }
181
+
182
+ // Business logic here
183
+ const result = { success: true };
184
+
185
+ // Log and return
186
+ assistant.log('Action completed', result);
187
+ return resolve({ data: result });
188
+ });
189
+ };
190
+
191
+ module.exports = Module;
192
+ ```
193
+
194
+ ### New Route (Consumer Project)
195
+
196
+ Create `routes/{name}/index.js`:
197
+
198
+ ```javascript
199
+ function Route() {}
200
+
201
+ Route.prototype.main = async function (assistant) {
202
+ const Manager = assistant.Manager;
203
+ const usage = assistant.usage;
204
+ const user = assistant.usage.user;
205
+ const analytics = assistant.analytics;
206
+ const settings = assistant.settings;
207
+
208
+ // Check authentication if needed
209
+ if (!user.authenticated) {
210
+ return assistant.respond('Authentication required', { code: 401 });
211
+ }
212
+
213
+ // Track usage
214
+ await usage.validate('requests');
215
+ usage.increment('requests');
216
+ await usage.update();
217
+
218
+ // Send response
219
+ assistant.respond({ success: true, data: settings });
220
+ };
221
+
222
+ module.exports = Route;
223
+ ```
224
+
225
+ ### New Schema (Consumer Project)
226
+
227
+ Create `schemas/{name}/index.js`:
228
+
229
+ ```javascript
230
+ module.exports = function (assistant, settings, options) {
231
+ const user = options.user;
232
+
233
+ return {
234
+ defaults: {
235
+ fieldName: {
236
+ types: ['string'],
237
+ default: 'default value',
238
+ required: false,
239
+ },
240
+ numericField: {
241
+ types: ['number'],
242
+ default: 10,
243
+ min: 1,
244
+ max: 100,
245
+ },
246
+ },
247
+ // Override for premium users
248
+ premium: {
249
+ numericField: {
250
+ max: 1000,
251
+ },
252
+ },
253
+ };
254
+ };
255
+ ```
256
+
257
+ ### New Event Handler
258
+
259
+ Create `src/manager/functions/core/events/{type}/{event}.js`:
260
+
261
+ ```javascript
262
+ function Module() {}
263
+
264
+ Module.prototype.init = function (Manager, payload) {
265
+ const self = this;
266
+ self.Manager = Manager;
267
+ self.assistant = Manager.Assistant();
268
+ self.libraries = Manager.libraries;
269
+ self.user = payload.user;
270
+ self.context = payload.context;
271
+ return self;
272
+ };
273
+
274
+ Module.prototype.main = function () {
275
+ const self = this;
276
+ const Manager = self.Manager;
277
+ const assistant = self.assistant;
278
+
279
+ return new Promise(async function(resolve, reject) {
280
+ const { admin } = self.libraries;
281
+
282
+ assistant.log('Event triggered', self.user);
283
+
284
+ // Event logic here
285
+
286
+ return resolve(self);
287
+ });
288
+ };
289
+
290
+ module.exports = Module;
291
+ ```
292
+
293
+ ### New Cron Job (Consumer Project)
294
+
295
+ Create `hooks/cron/daily/{job}.js`:
296
+
297
+ ```javascript
298
+ function Job() {}
299
+
300
+ Job.prototype.main = function () {
301
+ const self = this;
302
+ const Manager = self.Manager;
303
+ const assistant = self.assistant;
304
+
305
+ return new Promise(async function(resolve, reject) {
306
+ assistant.log('Running daily job...');
307
+
308
+ // Job logic here
309
+
310
+ return resolve();
311
+ });
312
+ };
313
+
314
+ module.exports = Job;
315
+ ```
316
+
317
+ ## Common Operations
318
+
319
+ ### Authenticate User
320
+ ```javascript
321
+ const user = await assistant.authenticate();
322
+ if (!user.authenticated) {
323
+ return assistant.errorify('Authentication required', { code: 401 });
324
+ }
325
+ ```
326
+
327
+ ### Read/Write Firestore
328
+ ```javascript
329
+ const { admin } = Manager.libraries;
330
+
331
+ // Read
332
+ const doc = await admin.firestore().doc('users/abc123').get();
333
+ const data = doc.data();
334
+
335
+ // Write
336
+ await admin.firestore().doc('users/abc123').set({ field: 'value' }, { merge: true });
337
+ ```
338
+
339
+ ### Handle Errors
340
+ ```javascript
341
+ // Send error response
342
+ assistant.errorify('Something went wrong', { code: 500, sentry: true });
343
+
344
+ // Or throw to reject
345
+ return reject(assistant.errorify('Bad request', { code: 400 }));
346
+ ```
347
+
348
+ ### Send Response
349
+ ```javascript
350
+ // Success
351
+ assistant.respond({ success: true, data: result });
352
+
353
+ // With custom status
354
+ assistant.respond({ created: true }, { code: 201 });
355
+
356
+ // Redirect
357
+ assistant.respond('https://example.com', { code: 302 });
358
+ ```
359
+
360
+ ### Use Hooks (Consumer Project)
361
+ ```javascript
362
+ Manager.handlers.bm_api = function (mod, position) {
363
+ const assistant = mod.assistant;
364
+ const command = assistant.request.data.command;
365
+
366
+ return new Promise(async function(resolve, reject) {
367
+ if (position === 'pre' && command === 'user:sign-up') {
368
+ // Before sign-up logic
369
+ }
370
+ return resolve();
371
+ });
372
+ };
373
+ ```
374
+
375
+ ## File Naming Conventions
376
+
377
+ | Type | Location | Naming |
378
+ |------|----------|--------|
379
+ | Routes | `routes/{name}/` | `index.js` or `{method}.js` |
380
+ | Schemas | `schemas/{name}/` | `index.js` or `{method}.js` |
381
+ | API Commands | `actions/api/{category}/` | `{action}.js` |
382
+ | Auth Events | `events/auth/` | `{event}.js` |
383
+ | Cron Jobs | `cron/daily/` or `hooks/cron/daily/` | `{job}.js` |
384
+
385
+ ## Testing
386
+
387
+ ```bash
388
+ # Run all tests
389
+ npm test
390
+
391
+ # Test files are in test/
392
+ test/
393
+ cli-commands.test.js
394
+ usage.js
395
+ user.js
396
+ payment-resolver/
397
+ ai/
398
+ ```
399
+
400
+ ## Common Mistakes to Avoid
401
+
402
+ 1. **Don't modify Manager internals directly** - Use factory methods and public APIs
403
+
404
+ 2. **Always use `assistant.respond()` for responses** - Don't use `res.send()` directly
405
+
406
+ 3. **Match schema names to route names** - If route is `myEndpoint`, schema should be `myEndpoint`
407
+
408
+ 4. **Always await async operations** - Don't forget `await` on Firestore operations
409
+
410
+ 5. **Handle errors properly** - Use `assistant.errorify()` with appropriate status codes
411
+
412
+ 6. **Don't call `respond()` multiple times** - Only one response per request
413
+
414
+ 7. **Use short-circuit returns** - Return early from error conditions
415
+
416
+ 8. **Increment usage before update** - Call `usage.increment()` then `usage.update()`
417
+
418
+ ## Key Files Reference
419
+
420
+ | Purpose | File |
421
+ |---------|------|
422
+ | Main Manager class | `src/manager/index.js` |
423
+ | Request/response handling | `src/manager/helpers/assistant.js` |
424
+ | Middleware pipeline | `src/manager/helpers/middleware.js` |
425
+ | Schema validation | `src/manager/helpers/settings.js` |
426
+ | Rate limiting | `src/manager/helpers/usage.js` |
427
+ | User properties | `src/manager/helpers/user.js` |
428
+ | Batch utilities | `src/manager/helpers/utilities.js` |
429
+ | Main API handler | `src/manager/functions/core/actions/api.js` |
430
+ | Config template | `templates/backend-manager-config.json` |
431
+ | CLI entry | `src/cli/cli-refactored.js` |
432
+
433
+ ## Environment Detection
434
+
435
+ ```javascript
436
+ assistant.isDevelopment() // true when ENVIRONMENT !== 'production' or in emulator
437
+ assistant.isProduction() // true when ENVIRONMENT === 'production'
438
+ ```
439
+
440
+ ## Response Headers
441
+
442
+ BEM automatically sets `bm-properties` header with:
443
+ - `code`: HTTP status code
444
+ - `tag`: Function name and execution ID
445
+ - `usage`: Current usage stats
446
+ - `schema`: Resolved schema info