backend-manager 5.0.37 → 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 +446 -0
- package/README.md +738 -38
- package/TODO.md +18 -0
- package/firebase-debug.log +84 -0
- package/package.json +1 -1
- package/src/manager/index.js +5 -0
- package/templates/backend-manager-config.json +1 -3
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
|