backend-manager 5.0.37 → 5.0.39

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.
Files changed (91) hide show
  1. package/.claude/settings.local.json +3 -2
  2. package/.gitignore FROM BEM TEMPALTE +74 -0
  3. package/CLAUDE.md +539 -0
  4. package/README.md +836 -38
  5. package/REFACTOR-BEM-API.md +72 -0
  6. package/REFACTOR-MIDDLEWARE.md +62 -0
  7. package/REFACTOR-PAYMENT.md +61 -0
  8. package/TODO.md +18 -0
  9. package/firebase-debug.log +84 -0
  10. package/package.json +10 -10
  11. package/src/cli/cli-refactored.js +14 -0
  12. package/src/cli/commands/base-command.js +102 -0
  13. package/src/cli/commands/deploy.js +0 -5
  14. package/src/cli/commands/emulators.js +69 -0
  15. package/src/cli/commands/index.js +2 -0
  16. package/src/cli/commands/serve.js +5 -7
  17. package/src/cli/commands/setup-tests/emulators-config.js +77 -0
  18. package/src/cli/commands/setup-tests/env-file.js +109 -0
  19. package/src/cli/commands/setup-tests/env-runtime-config-deprecated.js +45 -0
  20. package/src/cli/commands/setup-tests/firestore-rules-file.js +11 -5
  21. package/src/cli/commands/setup-tests/gitignore.js +99 -18
  22. package/src/cli/commands/setup-tests/helpers/merge-line-files.js +181 -0
  23. package/src/cli/commands/setup-tests/hosting-folder.js +3 -3
  24. package/src/cli/commands/setup-tests/hosting-rewrites.js +1 -2
  25. package/src/cli/commands/setup-tests/index.js +10 -8
  26. package/src/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
  27. package/src/cli/commands/setup-tests/npm-project-scripts.js +45 -0
  28. package/src/cli/commands/setup.js +12 -10
  29. package/src/cli/commands/test.js +206 -11
  30. package/src/cli/commands/watch.js +121 -0
  31. package/src/manager/functions/core/actions/api/admin/create-post.js +1 -1
  32. package/src/manager/functions/core/actions/api/admin/database-write.js +1 -1
  33. package/src/manager/functions/core/actions/api/admin/edit-post.js +11 -2
  34. package/src/manager/functions/core/actions/api/admin/get-stats.js +9 -0
  35. package/src/manager/functions/core/actions/api/admin/send-email.js +416 -192
  36. package/src/manager/functions/core/actions/api/admin/send-notification.js +66 -8
  37. package/src/manager/functions/core/actions/api/admin/sync-users.js +2 -2
  38. package/src/manager/functions/core/actions/api/admin/write-repo-content.js +11 -3
  39. package/src/manager/functions/core/actions/api/general/fetch-post.js +10 -1
  40. package/src/manager/functions/core/actions/api/general/generate-uuid.js +1 -1
  41. package/src/manager/functions/core/actions/api/general/send-email.js +2 -2
  42. package/src/manager/functions/core/actions/api/handler/create-post.js +4 -5
  43. package/src/manager/functions/core/actions/api/special/setup-electron-manager-client.js +2 -3
  44. package/src/manager/functions/core/actions/api/test/health.js +24 -0
  45. package/src/manager/functions/core/actions/api/user/create-custom-token.js +1 -3
  46. package/src/manager/functions/core/actions/api/user/delete.js +1 -1
  47. package/src/manager/functions/core/actions/api/user/get-active-sessions.js +2 -4
  48. package/src/manager/functions/core/actions/api/user/get-subscription-info.js +7 -8
  49. package/src/manager/functions/core/actions/api/user/regenerate-api-keys.js +1 -2
  50. package/src/manager/functions/core/actions/api/user/sign-out-all-sessions.js +2 -3
  51. package/src/manager/functions/core/actions/api/user/sign-up.js +182 -57
  52. package/src/manager/functions/core/actions/api/user/submit-feedback.js +1 -2
  53. package/src/manager/functions/core/actions/api.js +4 -12
  54. package/src/manager/functions/core/actions/create-post-handler.js +8 -8
  55. package/src/manager/functions/core/actions/generate-uuid.js +1 -1
  56. package/src/manager/functions/core/actions/sign-up-handler.js +6 -6
  57. package/src/manager/functions/core/admin/create-post.js +3 -4
  58. package/src/manager/functions/core/cron/daily/ghostii-auto-publisher.js +2 -2
  59. package/src/manager/functions/core/events/auth/before-create.js +36 -81
  60. package/src/manager/functions/core/events/auth/before-signin.js +19 -12
  61. package/src/manager/functions/core/events/auth/on-create.js +145 -349
  62. package/src/manager/functions/core/events/auth/on-delete.js +109 -46
  63. package/src/manager/helpers/analytics.js +1 -1
  64. package/src/manager/helpers/api-manager.js +4 -4
  65. package/src/manager/helpers/assistant.js +7 -1
  66. package/src/manager/helpers/subscription-resolver-new.js +44 -45
  67. package/src/manager/helpers/subscription-resolver.js +44 -45
  68. package/src/manager/helpers/usage.js +1 -4
  69. package/src/manager/helpers/user.js +84 -66
  70. package/src/manager/index.js +31 -58
  71. package/src/test/run-tests.js +48 -0
  72. package/src/test/runner.js +705 -0
  73. package/src/test/test-accounts.js +396 -0
  74. package/src/test/utils/assertions.js +189 -0
  75. package/src/test/utils/firestore-rules-client.js +284 -0
  76. package/src/test/utils/http-client.js +266 -0
  77. package/templates/_.env +27 -0
  78. package/templates/_.gitignore +54 -0
  79. package/templates/backend-manager-config.json +1 -3
  80. package/templates/firestore.rules +3 -3
  81. package/src/cli/commands/setup-tests/backend-manager-tests-file.js +0 -23
  82. package/src/cli/commands/setup-tests/env-runtime-config.js +0 -68
  83. package/src/cli/commands/setup-tests/npm-dist-script.js +0 -20
  84. package/src/cli/commands/setup-tests/npm-start-script.js +0 -20
  85. package/src/manager/functions/core/actions/api/test/create-test-accounts.js +0 -27
  86. package/src/manager/functions/core/actions/api/user/sign-up copy.js +0 -544
  87. package/src/manager/functions/core/events/auth/on-create copy.js +0 -121
  88. package/src/manager/functions/test/create-test-accounts.js +0 -144
  89. package/templates/backend-manager-tests.js +0 -153
  90. package/templates/gitignore.md +0 -4
  91. /package/{src/cli → _backup}/cli.js +0 -0
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(rg:*)"
4
+ "Bash(rg:*)",
5
+ "Bash(mkdir:*)"
5
6
  ],
6
7
  "deny": []
7
8
  }
8
- }
9
+ }
@@ -0,0 +1,74 @@
1
+ # ========== Default Values ==========
2
+ # macOS
3
+ .DS_Store
4
+
5
+ # Logs
6
+ logs
7
+ *.log
8
+ npm-debug.log*
9
+ yarn-debug.log*
10
+ yarn-error.log*
11
+ firebase-debug.log*
12
+
13
+ # Firebase cache
14
+ .firebase/
15
+
16
+ # Firebase config
17
+ # Uncomment this if you'd like others to create their own Firebase project.
18
+ # For a team working on the same Firebase project(s), it is recommended to leave
19
+ # it commented so all members can deploy to the same project(s) in .firebaserc.
20
+ .firebaserc
21
+ functions/service-account*.json
22
+
23
+ # Runtime data
24
+ pids
25
+ *.pid
26
+ *.seed
27
+ *.pid.lock
28
+
29
+ # Directory for instrumented libs generated by jscoverage/JSCover
30
+ lib-cov
31
+
32
+ # Coverage directory used by tools like istanbul
33
+ coverage
34
+
35
+ # nyc test coverage
36
+ .nyc_output
37
+
38
+ # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
39
+ .grunt
40
+
41
+ # Bower dependency directory (https://bower.io/)
42
+ bower_components
43
+
44
+ # node-waf configuration
45
+ .lock-wscript
46
+
47
+ # Compiled binary addons (http://nodejs.org/api/addons.html)
48
+ build/Release
49
+
50
+ # Dependency directories
51
+ node_modules/
52
+
53
+ # Optional npm cache directory
54
+ .npm
55
+
56
+ # Optional eslint cache
57
+ .eslintcache
58
+
59
+ # Optional REPL history
60
+ .node_repl_history
61
+
62
+ # Output of 'npm pack'
63
+ *.tgz
64
+
65
+ # Yarn Integrity file
66
+ .yarn-integrity
67
+
68
+ # dotenv environment variables file
69
+ .env
70
+
71
+
72
+ # ========== Custom Values ==========
73
+ # Add your custom ignore patterns below this line
74
+ # ...
package/CLAUDE.md ADDED
@@ -0,0 +1,539 @@
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
+ ### Running Tests
388
+ ```bash
389
+ # Option 1: Two terminals
390
+ npx bm emulators # Terminal 1 - keeps emulators running
391
+ npx bm test # Terminal 2 - runs tests
392
+
393
+ # Option 2: Single command (auto-starts emulators)
394
+ npx bm test
395
+ ```
396
+
397
+ ### Filtering Tests
398
+ ```bash
399
+ npx bm test rules/ # Run rules tests (both BEM and project)
400
+ npx bm test bem:rules/ # Only BEM's rules tests
401
+ npx bm test project:rules/ # Only project's rules tests
402
+ npx bm test user/ admin/ # Multiple paths
403
+ ```
404
+
405
+ ### Test Locations
406
+ - **BEM core tests:** `test/`
407
+ - **Project tests:** `functions/test/bem/`
408
+
409
+ Use `bem:` or `project:` prefix to filter by source.
410
+
411
+ ### Test Types
412
+
413
+ | Type | Use When | Behavior |
414
+ |------|----------|----------|
415
+ | Standalone | Single logical test | Runs once |
416
+ | Suite (`type: 'suite'`) | Sequential dependent tests | Shared state, stops on failure |
417
+ | Group (`type: 'group'`) | Multiple independent tests | Continues on failure |
418
+
419
+ ### Standalone Test
420
+ ```javascript
421
+ module.exports = {
422
+ description: 'Test name',
423
+ auth: 'none', // none, user, admin, premium-active, premium-expired
424
+ timeout: 10000,
425
+ async run({ http, assert, accounts, firestore, state, waitFor }) { },
426
+ async cleanup({ ... }) { }, // Optional
427
+ };
428
+ ```
429
+
430
+ ### Suite (Sequential with Shared State)
431
+ ```javascript
432
+ module.exports = {
433
+ description: 'Suite name',
434
+ type: 'suite',
435
+ tests: [
436
+ { name: 'step-1', async run({ state }) { state.value = 'shared'; } },
437
+ { name: 'step-2', async run({ state }) { /* state.value available */ } },
438
+ ],
439
+ };
440
+ ```
441
+
442
+ ### Group (Independent Tests)
443
+ ```javascript
444
+ module.exports = {
445
+ description: 'Group name',
446
+ type: 'group',
447
+ tests: [
448
+ { name: 'test-1', auth: 'admin', async run({ http, assert }) { } },
449
+ { name: 'test-2', auth: 'none', async run({ http, assert }) { } },
450
+ ],
451
+ };
452
+ ```
453
+
454
+ ### Context Object
455
+ | Property | Description |
456
+ |----------|-------------|
457
+ | `http` | HTTP client (`http.command()`, `http.as('admin').command()`) |
458
+ | `assert` | Assertion helpers (see below) |
459
+ | `accounts` | Test accounts `{ basic, admin, premium-active, ... }` |
460
+ | `firestore` | Direct DB access (`get`, `set`, `delete`, `exists`) |
461
+ | `state` | Shared state (suites only) |
462
+ | `waitFor` | Polling helper `waitFor(condition, timeout, interval)` |
463
+
464
+ ### Assert Methods
465
+ ```javascript
466
+ assert.ok(value, message) // Truthy
467
+ assert.equal(a, b, message) // Strict equality
468
+ assert.notEqual(a, b, message) // Not equal
469
+ assert.deepEqual(a, b, message) // Deep equality
470
+ assert.match(value, /regex/, message) // Regex match
471
+ assert.isSuccess(response, message) // Response success
472
+ assert.isError(response, code, message) // Response error with code
473
+ assert.hasProperty(obj, 'path.to.prop', msg) // Property exists
474
+ assert.propertyEquals(obj, 'path', value, msg) // Property value
475
+ assert.isType(value, 'string', message) // Type check
476
+ assert.contains(array, value, message) // Array includes
477
+ assert.inRange(value, min, max, message) // Number range
478
+ assert.fail(message) // Explicit fail
479
+ ```
480
+
481
+ ### Auth Levels
482
+ `none`, `user`/`basic`, `admin`, `premium-active`, `premium-expired`
483
+
484
+ ### Key Test Files
485
+ | File | Purpose |
486
+ |------|---------|
487
+ | `src/test/runner.js` | Test runner |
488
+ | `test/` | BEM core tests |
489
+ | `src/test/utils/assertions.js` | Assert helpers |
490
+ | `src/test/utils/http-client.js` | HTTP client |
491
+ | `src/test/test-accounts.js` | Test account definitions |
492
+
493
+ ## Common Mistakes to Avoid
494
+
495
+ 1. **Don't modify Manager internals directly** - Use factory methods and public APIs
496
+
497
+ 2. **Always use `assistant.respond()` for responses** - Don't use `res.send()` directly
498
+
499
+ 3. **Match schema names to route names** - If route is `myEndpoint`, schema should be `myEndpoint`
500
+
501
+ 4. **Always await async operations** - Don't forget `await` on Firestore operations
502
+
503
+ 5. **Handle errors properly** - Use `assistant.errorify()` with appropriate status codes
504
+
505
+ 6. **Don't call `respond()` multiple times** - Only one response per request
506
+
507
+ 7. **Use short-circuit returns** - Return early from error conditions
508
+
509
+ 8. **Increment usage before update** - Call `usage.increment()` then `usage.update()`
510
+
511
+ ## Key Files Reference
512
+
513
+ | Purpose | File |
514
+ |---------|------|
515
+ | Main Manager class | `src/manager/index.js` |
516
+ | Request/response handling | `src/manager/helpers/assistant.js` |
517
+ | Middleware pipeline | `src/manager/helpers/middleware.js` |
518
+ | Schema validation | `src/manager/helpers/settings.js` |
519
+ | Rate limiting | `src/manager/helpers/usage.js` |
520
+ | User properties | `src/manager/helpers/user.js` |
521
+ | Batch utilities | `src/manager/helpers/utilities.js` |
522
+ | Main API handler | `src/manager/functions/core/actions/api.js` |
523
+ | Config template | `templates/backend-manager-config.json` |
524
+ | CLI entry | `src/cli/cli-refactored.js` |
525
+
526
+ ## Environment Detection
527
+
528
+ ```javascript
529
+ assistant.isDevelopment() // true when ENVIRONMENT !== 'production' or in emulator
530
+ assistant.isProduction() // true when ENVIRONMENT === 'production'
531
+ ```
532
+
533
+ ## Response Headers
534
+
535
+ BEM automatically sets `bm-properties` header with:
536
+ - `code`: HTTP status code
537
+ - `tag`: Function name and execution ID
538
+ - `usage`: Current usage stats
539
+ - `schema`: Resolved schema info