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.
- package/.claude/settings.local.json +3 -2
- package/.gitignore FROM BEM TEMPALTE +74 -0
- package/CLAUDE.md +539 -0
- package/README.md +836 -38
- package/REFACTOR-BEM-API.md +72 -0
- package/REFACTOR-MIDDLEWARE.md +62 -0
- package/REFACTOR-PAYMENT.md +61 -0
- package/TODO.md +18 -0
- package/firebase-debug.log +84 -0
- package/package.json +10 -10
- package/src/cli/cli-refactored.js +14 -0
- package/src/cli/commands/base-command.js +102 -0
- package/src/cli/commands/deploy.js +0 -5
- package/src/cli/commands/emulators.js +69 -0
- package/src/cli/commands/index.js +2 -0
- package/src/cli/commands/serve.js +5 -7
- package/src/cli/commands/setup-tests/emulators-config.js +77 -0
- package/src/cli/commands/setup-tests/env-file.js +109 -0
- package/src/cli/commands/setup-tests/env-runtime-config-deprecated.js +45 -0
- package/src/cli/commands/setup-tests/firestore-rules-file.js +11 -5
- package/src/cli/commands/setup-tests/gitignore.js +99 -18
- package/src/cli/commands/setup-tests/helpers/merge-line-files.js +181 -0
- package/src/cli/commands/setup-tests/hosting-folder.js +3 -3
- package/src/cli/commands/setup-tests/hosting-rewrites.js +1 -2
- package/src/cli/commands/setup-tests/index.js +10 -8
- package/src/cli/commands/setup-tests/legacy-tests-cleanup.js +43 -0
- package/src/cli/commands/setup-tests/npm-project-scripts.js +45 -0
- package/src/cli/commands/setup.js +12 -10
- package/src/cli/commands/test.js +206 -11
- package/src/cli/commands/watch.js +121 -0
- package/src/manager/functions/core/actions/api/admin/create-post.js +1 -1
- package/src/manager/functions/core/actions/api/admin/database-write.js +1 -1
- package/src/manager/functions/core/actions/api/admin/edit-post.js +11 -2
- package/src/manager/functions/core/actions/api/admin/get-stats.js +9 -0
- package/src/manager/functions/core/actions/api/admin/send-email.js +416 -192
- package/src/manager/functions/core/actions/api/admin/send-notification.js +66 -8
- package/src/manager/functions/core/actions/api/admin/sync-users.js +2 -2
- package/src/manager/functions/core/actions/api/admin/write-repo-content.js +11 -3
- package/src/manager/functions/core/actions/api/general/fetch-post.js +10 -1
- package/src/manager/functions/core/actions/api/general/generate-uuid.js +1 -1
- package/src/manager/functions/core/actions/api/general/send-email.js +2 -2
- package/src/manager/functions/core/actions/api/handler/create-post.js +4 -5
- package/src/manager/functions/core/actions/api/special/setup-electron-manager-client.js +2 -3
- package/src/manager/functions/core/actions/api/test/health.js +24 -0
- package/src/manager/functions/core/actions/api/user/create-custom-token.js +1 -3
- package/src/manager/functions/core/actions/api/user/delete.js +1 -1
- package/src/manager/functions/core/actions/api/user/get-active-sessions.js +2 -4
- package/src/manager/functions/core/actions/api/user/get-subscription-info.js +7 -8
- package/src/manager/functions/core/actions/api/user/regenerate-api-keys.js +1 -2
- package/src/manager/functions/core/actions/api/user/sign-out-all-sessions.js +2 -3
- package/src/manager/functions/core/actions/api/user/sign-up.js +182 -57
- package/src/manager/functions/core/actions/api/user/submit-feedback.js +1 -2
- package/src/manager/functions/core/actions/api.js +4 -12
- package/src/manager/functions/core/actions/create-post-handler.js +8 -8
- package/src/manager/functions/core/actions/generate-uuid.js +1 -1
- package/src/manager/functions/core/actions/sign-up-handler.js +6 -6
- package/src/manager/functions/core/admin/create-post.js +3 -4
- package/src/manager/functions/core/cron/daily/ghostii-auto-publisher.js +2 -2
- package/src/manager/functions/core/events/auth/before-create.js +36 -81
- package/src/manager/functions/core/events/auth/before-signin.js +19 -12
- package/src/manager/functions/core/events/auth/on-create.js +145 -349
- package/src/manager/functions/core/events/auth/on-delete.js +109 -46
- package/src/manager/helpers/analytics.js +1 -1
- package/src/manager/helpers/api-manager.js +4 -4
- package/src/manager/helpers/assistant.js +7 -1
- package/src/manager/helpers/subscription-resolver-new.js +44 -45
- package/src/manager/helpers/subscription-resolver.js +44 -45
- package/src/manager/helpers/usage.js +1 -4
- package/src/manager/helpers/user.js +84 -66
- package/src/manager/index.js +31 -58
- package/src/test/run-tests.js +48 -0
- package/src/test/runner.js +705 -0
- package/src/test/test-accounts.js +396 -0
- package/src/test/utils/assertions.js +189 -0
- package/src/test/utils/firestore-rules-client.js +284 -0
- package/src/test/utils/http-client.js +266 -0
- package/templates/_.env +27 -0
- package/templates/_.gitignore +54 -0
- package/templates/backend-manager-config.json +1 -3
- package/templates/firestore.rules +3 -3
- package/src/cli/commands/setup-tests/backend-manager-tests-file.js +0 -23
- package/src/cli/commands/setup-tests/env-runtime-config.js +0 -68
- package/src/cli/commands/setup-tests/npm-dist-script.js +0 -20
- package/src/cli/commands/setup-tests/npm-start-script.js +0 -20
- package/src/manager/functions/core/actions/api/test/create-test-accounts.js +0 -27
- package/src/manager/functions/core/actions/api/user/sign-up copy.js +0 -544
- package/src/manager/functions/core/events/auth/on-create copy.js +0 -121
- package/src/manager/functions/test/create-test-accounts.js +0 -144
- package/templates/backend-manager-tests.js +0 -153
- package/templates/gitignore.md +0 -4
- /package/{src/cli → _backup}/cli.js +0 -0
|
@@ -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
|