@xbg.solutions/create-backend 1.0.1 → 1.0.2
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/lib/commands/init.js +55 -12
- package/lib/commands/init.js.map +1 -1
- package/package.json +1 -1
- package/src/project-template/.claude/settings.local.json +57 -0
- package/src/project-template/.claude/skills/bpbe/api/skill.md +403 -0
- package/src/project-template/.claude/skills/bpbe/data/skill.md +442 -0
- package/src/project-template/.claude/skills/bpbe/services/skill.md +497 -0
- package/src/project-template/.claude/skills/bpbe/setup/skill.md +301 -0
- package/src/project-template/.claude/skills/bpbe/skill.md +153 -0
- package/src/project-template/.claude/skills/bpbe/utils/skill.md +527 -0
- package/src/project-template/.claude/skills/skill.md +30 -0
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Utilities in the XBG boilerplate backend: logger with PII sanitization, AES-256-GCM hashing, token handler with blacklisting, multi-level cache, and all communication connectors (email, SMS, push, CRM, LLM, etc.)."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XBG Boilerplate Backend — Utilities
|
|
6
|
+
|
|
7
|
+
Each utility is a standalone npm package under `@xbg.solutions/utils-*`. Install only what you need.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Logger
|
|
12
|
+
|
|
13
|
+
**Package:** `@xbg.solutions/utils-logger`
|
|
14
|
+
|
|
15
|
+
Structured JSON logger with automatic PII sanitization and correlation ID tracking.
|
|
16
|
+
|
|
17
|
+
### Import and Basic Use
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { logger } from '@xbg.solutions/utils-logger';
|
|
21
|
+
|
|
22
|
+
logger.debug('Detailed trace', { userId, queryOptions });
|
|
23
|
+
logger.info('Operation completed', { userId, entityId: product.id });
|
|
24
|
+
logger.warn('Retrying after failure', { attempt: 2, error: err.message });
|
|
25
|
+
logger.error('Operation failed', err, { requestId, userId });
|
|
26
|
+
// ^^^ Error object is second param for error()
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### PII Sanitization — Automatic
|
|
30
|
+
|
|
31
|
+
The logger automatically redacts these field names from logged objects:
|
|
32
|
+
- `password`, `passwordHash`, `secret`, `token`, `apiKey`, `authorization`
|
|
33
|
+
- `email`, `phone`, `ssn`, `creditCard`, `cvv`
|
|
34
|
+
- `firstName`, `lastName`, `address`
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// This is safe to log — email will be redacted to '[REDACTED]'
|
|
38
|
+
logger.info('User created', {
|
|
39
|
+
userId: 'uid-123',
|
|
40
|
+
email: 'user@example.com', // ← becomes '[REDACTED]' in output
|
|
41
|
+
name: 'John Doe', // ← also redacted (firstName/lastName components)
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Structured Logging Fields
|
|
46
|
+
|
|
47
|
+
Always include `requestId` for correlation across log lines:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
logger.info('Product updated', {
|
|
51
|
+
requestId: context.requestId,
|
|
52
|
+
userId: context.userId,
|
|
53
|
+
productId: product.id,
|
|
54
|
+
priceChange: { old: existing.price, new: product.price },
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Request-Scoped Logger
|
|
59
|
+
|
|
60
|
+
The logging middleware attaches a logger to `req` with the requestId pre-set:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// In a controller (if needed):
|
|
64
|
+
import { Request } from 'express';
|
|
65
|
+
const reqLogger = (req as any).logger ?? logger;
|
|
66
|
+
reqLogger.info('Custom log', { data });
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Hashing — PII Encryption (AES-256-GCM)
|
|
72
|
+
|
|
73
|
+
**Package:** `@xbg.solutions/utils-hashing`
|
|
74
|
+
|
|
75
|
+
Provides **reversible** AES-256-GCM encryption for PII fields. This is encryption, not one-way hashing — the name `hashFields` is the API surface, but the data can be decrypted.
|
|
76
|
+
|
|
77
|
+
### Setup
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# Generate encryption key (64 hex chars = 32 bytes for AES-256)
|
|
81
|
+
openssl rand -hex 32
|
|
82
|
+
# Add to .env:
|
|
83
|
+
PII_ENCRYPTION_KEY=your-64-char-hex-key
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Encrypting Fields
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { hashValue, hashFields } from '@xbg.solutions/utils-hashing';
|
|
90
|
+
|
|
91
|
+
// Encrypt a single value
|
|
92
|
+
const encrypted = hashValue('user@example.com');
|
|
93
|
+
// Format: "base64iv:base64ciphertext:base64authtag"
|
|
94
|
+
|
|
95
|
+
// Encrypt specific PII fields on a data object (uses hashed-fields-lookup config)
|
|
96
|
+
const encryptedUserData = hashFields(userData, 'user');
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Decrypting Fields
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { unhashValue, unhashFields } from '@xbg.solutions/utils-hashing';
|
|
103
|
+
|
|
104
|
+
// Decrypt a single value
|
|
105
|
+
const plaintext = unhashValue(encrypted);
|
|
106
|
+
|
|
107
|
+
// Decrypt specific fields on a retrieved entity
|
|
108
|
+
const decryptedUser = unhashFields(user, ['user.email', 'user.phone']);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Which Fields Are Hashed
|
|
112
|
+
|
|
113
|
+
Configured in the hashing package's hashed-fields-lookup. To check:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { isHashedField } from '@xbg.solutions/utils-hashing';
|
|
117
|
+
|
|
118
|
+
// Check if a field path should be hashed:
|
|
119
|
+
isHashedField('user.email') // → true
|
|
120
|
+
isHashedField('user.phoneNumber') // → true
|
|
121
|
+
isHashedField('user.name') // → false
|
|
122
|
+
isHashedField('product.name') // → false
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Anti-Examples
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// ❌ Don't decrypt eagerly — only decrypt when you need plaintext
|
|
129
|
+
const users = await userRepo.findAll({});
|
|
130
|
+
const decrypted = users.map(u => unhashFields(u, ['user.email'])); // ← only if you're sending PII to client
|
|
131
|
+
|
|
132
|
+
// ✅ Keep encrypted in storage, decrypt only at response boundary
|
|
133
|
+
const user = await userRepo.findById(id);
|
|
134
|
+
if (sendingToClient) {
|
|
135
|
+
return unhashFields(user, ['user.email', 'user.phone']);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ❌ Don't compare encrypted values directly
|
|
139
|
+
if (user.email === 'test@example.com') { ... } // always false — it's encrypted
|
|
140
|
+
|
|
141
|
+
// ✅ Encrypt the search value first, or use a separate search index
|
|
142
|
+
const encryptedEmail = hashValue('test@example.com');
|
|
143
|
+
const users = await userRepo.findAll({ where: [{ field: 'email', operator: '==', value: encryptedEmail }] });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Token Handler — JWT & Blacklisting
|
|
149
|
+
|
|
150
|
+
**Package:** `@xbg.solutions/utils-token-handler`
|
|
151
|
+
|
|
152
|
+
Wraps Firebase Auth token verification with blacklisting support and normalized token shape.
|
|
153
|
+
|
|
154
|
+
### Basic Verification
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { tokenHandler } from '@xbg.solutions/utils-token-handler';
|
|
158
|
+
import { logger } from '@xbg.solutions/utils-logger';
|
|
159
|
+
|
|
160
|
+
const result = await tokenHandler.verifyAndUnpack(bearerToken, logger);
|
|
161
|
+
if (!result.isValid) {
|
|
162
|
+
// result.error contains reason, result.isBlacklisted indicates revocation
|
|
163
|
+
return res.status(401).json({ error: result.error });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Normalized token
|
|
167
|
+
const { authUID, userUID, email, customClaims } = result.token!;
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Token Blacklisting
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { tokenHandler } from '@xbg.solutions/utils-token-handler';
|
|
174
|
+
import { logger } from '@xbg.solutions/utils-logger';
|
|
175
|
+
|
|
176
|
+
// Blacklist a single token (e.g., on logout)
|
|
177
|
+
const tokenId = await tokenHandler.getTokenIdentifier(rawToken);
|
|
178
|
+
await tokenHandler.blacklistToken(tokenId, authUID, 'LOGOUT', tokenExpiresAt, null, logger);
|
|
179
|
+
|
|
180
|
+
// Blacklist all tokens for a user (e.g., password change, account compromise)
|
|
181
|
+
await tokenHandler.blacklistAllUserTokens(authUID, 'PASSWORD_CHANGE', null, logger);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Blacklisted tokens are checked on every `verifyAndUnpack()` call. Firebase's own revocation list is also checked (`checkRevoked: true`). Cleanup of expired blacklist entries runs on schedule.
|
|
185
|
+
|
|
186
|
+
### Normalized Token Shape
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
interface NormalizedToken<TCustomClaims> {
|
|
190
|
+
authUID: string; // Firebase Auth UID
|
|
191
|
+
userUID: string | null; // App-specific user ID (from custom claims)
|
|
192
|
+
email: string | null;
|
|
193
|
+
emailVerified: boolean;
|
|
194
|
+
phoneNumber: string | null;
|
|
195
|
+
issuedAt: number; // Unix timestamp
|
|
196
|
+
expiresAt: number;
|
|
197
|
+
issuer: string;
|
|
198
|
+
customClaims: TCustomClaims;
|
|
199
|
+
rawClaims: Record<string, any>;
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### API Key Auth (Service-to-Service)
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { requireApiKey } from '@xbg.solutions/backend-core';
|
|
207
|
+
|
|
208
|
+
// In controller routes:
|
|
209
|
+
this.router.post(
|
|
210
|
+
'/webhook',
|
|
211
|
+
requireApiKey([process.env.WEBHOOK_SECRET!]),
|
|
212
|
+
this.handleWebhook.bind(this)
|
|
213
|
+
);
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Cache Connector
|
|
219
|
+
|
|
220
|
+
**Package:** `@xbg.solutions/utils-cache-connector`
|
|
221
|
+
|
|
222
|
+
Progressive multi-level cache. Opt-in per repository. Global kill switch.
|
|
223
|
+
|
|
224
|
+
### Providers
|
|
225
|
+
|
|
226
|
+
| Provider | Latency | Scope | Use for |
|
|
227
|
+
|---|---|---|---|
|
|
228
|
+
| `memory` | ~1ms | Single function instance | Hot data, auth tokens |
|
|
229
|
+
| `firestore` | ~50-100ms | All instances | Shared config, user sessions |
|
|
230
|
+
| `redis` | ~5ms | All instances | High-traffic data (requires Redis) |
|
|
231
|
+
| `noop` | 0ms | None | Default when cache disabled |
|
|
232
|
+
|
|
233
|
+
### Configuration
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# .env
|
|
237
|
+
CACHE_ENABLED=true
|
|
238
|
+
CACHE_DEFAULT_PROVIDER=memory
|
|
239
|
+
CACHE_DEFAULT_TTL=300
|
|
240
|
+
CACHE_NAMESPACE=myapp
|
|
241
|
+
|
|
242
|
+
# For Redis:
|
|
243
|
+
CACHE_REDIS_HOST=localhost
|
|
244
|
+
CACHE_REDIS_PORT=6379
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Using in Repositories
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { BaseRepository } from '@xbg.solutions/backend-core';
|
|
251
|
+
|
|
252
|
+
export class SessionRepository extends BaseRepository<Session> {
|
|
253
|
+
protected collectionName = 'sessions';
|
|
254
|
+
|
|
255
|
+
protected cacheConfig = {
|
|
256
|
+
enabled: true,
|
|
257
|
+
provider: 'memory' as const,
|
|
258
|
+
ttl: 900, // 15 minutes — matches JWT expiry
|
|
259
|
+
keyPrefix: 'session',
|
|
260
|
+
tags: ['sessions'],
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
protected fromFirestore(id: string, data: DocumentData): Session { ... }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Usage
|
|
267
|
+
const session = await sessionRepo.findByIdCached('sess-123');
|
|
268
|
+
const fresh = await sessionRepo.findByIdCached('sess-123', { forceRefresh: true, ttl: 60 });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Direct Cache Access (Advanced)
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import { getCacheConnector } from '@xbg.solutions/utils-cache-connector';
|
|
275
|
+
|
|
276
|
+
const cache = getCacheConnector();
|
|
277
|
+
|
|
278
|
+
// Set
|
|
279
|
+
await cache.set('my-key', { data: 'value' }, { ttl: 300, provider: 'memory' });
|
|
280
|
+
|
|
281
|
+
// Get
|
|
282
|
+
const value = await cache.get<MyType>('my-key', { provider: 'memory' });
|
|
283
|
+
|
|
284
|
+
// Delete
|
|
285
|
+
await cache.delete('my-key', { provider: 'memory' });
|
|
286
|
+
|
|
287
|
+
// Invalidate by tag (invalidates all keys with this tag)
|
|
288
|
+
await cache.invalidateByTags(['sessions'], { provider: 'memory' });
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Communication Connectors
|
|
294
|
+
|
|
295
|
+
All connectors follow the same pattern: import the connector singleton from its `@xbg.solutions/utils-*` package, call methods, providers handle the transport.
|
|
296
|
+
|
|
297
|
+
### Email Connector
|
|
298
|
+
|
|
299
|
+
**Package:** `@xbg.solutions/utils-email-connector`
|
|
300
|
+
|
|
301
|
+
Providers: Mailjet (implemented), Ortto, SendGrid, AWS SES (stub/planned)
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { emailConnector } from '@xbg.solutions/utils-email-connector';
|
|
305
|
+
|
|
306
|
+
// Simple email
|
|
307
|
+
await emailConnector.sendEmail({
|
|
308
|
+
to: 'user@example.com',
|
|
309
|
+
subject: 'Welcome to the platform',
|
|
310
|
+
html: '<h1>Welcome!</h1><p>Your account is ready.</p>',
|
|
311
|
+
text: 'Welcome! Your account is ready.',
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// With sender override
|
|
315
|
+
await emailConnector.sendEmail({
|
|
316
|
+
to: 'user@example.com',
|
|
317
|
+
from: { email: 'noreply@myapp.com', name: 'MyApp' },
|
|
318
|
+
subject: 'Password reset',
|
|
319
|
+
html: '<p>Click <a href="...">here</a> to reset.</p>',
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Configure in `.env`:
|
|
324
|
+
```bash
|
|
325
|
+
EMAIL_PROVIDER=mailjet # mailjet | sendgrid | ses
|
|
326
|
+
MAILJET_API_KEY=...
|
|
327
|
+
MAILJET_SECRET_KEY=...
|
|
328
|
+
EMAIL_FROM_ADDRESS=noreply@myapp.com
|
|
329
|
+
EMAIL_FROM_NAME=MyApp
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### SMS Connector
|
|
333
|
+
|
|
334
|
+
**Package:** `@xbg.solutions/utils-sms-connector`
|
|
335
|
+
|
|
336
|
+
Providers: Twilio (implemented), MessageBird, AWS SNS (stub/planned)
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { smsConnector } from '@xbg.solutions/utils-sms-connector';
|
|
340
|
+
|
|
341
|
+
await smsConnector.sendMessage({
|
|
342
|
+
to: '+12025551234',
|
|
343
|
+
body: 'Your verification code is: 847293',
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Configure:
|
|
348
|
+
```bash
|
|
349
|
+
SMS_PROVIDER=twilio
|
|
350
|
+
TWILIO_ACCOUNT_SID=...
|
|
351
|
+
TWILIO_AUTH_TOKEN=...
|
|
352
|
+
TWILIO_FROM_NUMBER=+15005550006
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Push Notifications Connector
|
|
356
|
+
|
|
357
|
+
**Package:** `@xbg.solutions/utils-push-notifications-connector`
|
|
358
|
+
|
|
359
|
+
Provider: Firebase Cloud Messaging (FCM)
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
import { pushNotificationsConnector } from '@xbg.solutions/utils-push-notifications-connector';
|
|
363
|
+
|
|
364
|
+
// Single device
|
|
365
|
+
await pushNotificationsConnector.send({
|
|
366
|
+
token: deviceFcmToken,
|
|
367
|
+
notification: {
|
|
368
|
+
title: 'New Message',
|
|
369
|
+
body: 'You have a new message from Alice',
|
|
370
|
+
},
|
|
371
|
+
data: { messageId: 'msg-123', type: 'chat' },
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Multiple devices
|
|
375
|
+
await pushNotificationsConnector.sendMulticast({
|
|
376
|
+
tokens: [token1, token2, token3],
|
|
377
|
+
notification: { title: 'System update', body: 'Maintenance in 1 hour' },
|
|
378
|
+
});
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### CRM Connector
|
|
382
|
+
|
|
383
|
+
**Package:** `@xbg.solutions/utils-crm-connector`
|
|
384
|
+
|
|
385
|
+
Providers: HubSpot (implemented), Salesforce, Attio (stub/planned)
|
|
386
|
+
|
|
387
|
+
```typescript
|
|
388
|
+
import { crmConnector } from '@xbg.solutions/utils-crm-connector';
|
|
389
|
+
|
|
390
|
+
// Create contact
|
|
391
|
+
const contact = await crmConnector.createContact({
|
|
392
|
+
email: 'lead@company.com',
|
|
393
|
+
firstName: 'Jane',
|
|
394
|
+
lastName: 'Smith',
|
|
395
|
+
company: 'Acme Corp',
|
|
396
|
+
phone: '+12025551234',
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// Update contact
|
|
400
|
+
await crmConnector.updateContact(contact.id, {
|
|
401
|
+
lifecycleStage: 'customer',
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Create deal
|
|
405
|
+
await crmConnector.createDeal({
|
|
406
|
+
name: 'Acme Corp - Enterprise',
|
|
407
|
+
amount: 50000,
|
|
408
|
+
contactId: contact.id,
|
|
409
|
+
});
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
Configure:
|
|
413
|
+
```bash
|
|
414
|
+
CRM_PROVIDER=hubspot
|
|
415
|
+
HUBSPOT_ACCESS_TOKEN=...
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### LLM Connector
|
|
419
|
+
|
|
420
|
+
**Package:** `@xbg.solutions/utils-llm-connector`
|
|
421
|
+
|
|
422
|
+
Providers: Claude/Anthropic, OpenAI, Gemini
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { llmConnector } from '@xbg.solutions/utils-llm-connector';
|
|
426
|
+
|
|
427
|
+
const response = await llmConnector.complete({
|
|
428
|
+
messages: [
|
|
429
|
+
{ role: 'user', content: 'Summarize this product description: ...' }
|
|
430
|
+
],
|
|
431
|
+
maxTokens: 200,
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// response.content: string
|
|
435
|
+
// response.usage: { inputTokens, outputTokens }
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Configure:
|
|
439
|
+
```bash
|
|
440
|
+
LLM_PROVIDER=anthropic # anthropic | openai | gemini
|
|
441
|
+
ANTHROPIC_API_KEY=...
|
|
442
|
+
ANTHROPIC_MODEL=claude-sonnet-4-6
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
### Other Connectors
|
|
446
|
+
|
|
447
|
+
| Package | Description |
|
|
448
|
+
|---|---|
|
|
449
|
+
| `@xbg.solutions/utils-document-connector` | E-signature (PandaDoc) |
|
|
450
|
+
| `@xbg.solutions/utils-journey-connector` | Marketing automation (Ortto) |
|
|
451
|
+
| `@xbg.solutions/utils-survey-connector` | Surveys (Typeform) |
|
|
452
|
+
| `@xbg.solutions/utils-work-mgmt-connector` | Tasks (ClickUp/Notion) |
|
|
453
|
+
| `@xbg.solutions/utils-erp-connector` | HR/Finance (Workday) |
|
|
454
|
+
| `@xbg.solutions/utils-address-validation` | Google Maps validation |
|
|
455
|
+
| `@xbg.solutions/utils-realtime-connector` | Firebase Realtime DB |
|
|
456
|
+
| `@xbg.solutions/utils-firebase-event-bridge` | Firebase → internal events |
|
|
457
|
+
| `@xbg.solutions/utils-firestore-connector` | Multi-DB Firestore setup |
|
|
458
|
+
| `@xbg.solutions/utils-timezone` | Timezone conversion utils |
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
## Subscribing to Events for Communication Side Effects
|
|
463
|
+
|
|
464
|
+
Register subscribers in your project's `src/subscribers/` directory:
|
|
465
|
+
|
|
466
|
+
```typescript
|
|
467
|
+
import { eventBus, EventType, UserCreatedPayload } from '@xbg.solutions/utils-events';
|
|
468
|
+
import { emailConnector } from '@xbg.solutions/utils-email-connector';
|
|
469
|
+
|
|
470
|
+
export function registerCommunicationSubscribers(): void {
|
|
471
|
+
eventBus.subscribe<UserCreatedPayload>(
|
|
472
|
+
EventType.USER_CREATED,
|
|
473
|
+
async ({ userUID, accountUID }) => {
|
|
474
|
+
await emailConnector.sendEmail({
|
|
475
|
+
to: '...', // fetch user email from repo
|
|
476
|
+
subject: 'Welcome!',
|
|
477
|
+
html: '<p>Your account is ready.</p>',
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Import and call `registerCommunicationSubscribers()` in your `src/index.ts` or `app.ts` at startup.
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## Anti-Examples
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// ❌ Don't import logger from wrong path
|
|
492
|
+
import { logger } from '../../logger'; // wrong — use the package
|
|
493
|
+
import { Logger } from './logger'; // creates new instance, wrong
|
|
494
|
+
|
|
495
|
+
// ✅ Always import the singleton from the package
|
|
496
|
+
import { logger } from '@xbg.solutions/utils-logger';
|
|
497
|
+
|
|
498
|
+
// ❌ Don't log raw PII
|
|
499
|
+
logger.info('User login', { email: user.email }); // ← logs plaintext email to Cloud Logging
|
|
500
|
+
|
|
501
|
+
// ✅ Log identifiers, not PII values
|
|
502
|
+
logger.info('User login', { userId: user.id });
|
|
503
|
+
|
|
504
|
+
// ❌ Don't call connectors without checking feature flags
|
|
505
|
+
await emailConnector.send({ ... });
|
|
506
|
+
|
|
507
|
+
// ✅ Check feature is enabled
|
|
508
|
+
import { isFeatureEnabled } from '@xbg.solutions/backend-core';
|
|
509
|
+
if (isFeatureEnabled('notifications')) {
|
|
510
|
+
await emailConnector.send({ ... });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// ❌ Don't block on communication failures
|
|
514
|
+
try {
|
|
515
|
+
await smsConnector.sendMessage({ ... });
|
|
516
|
+
} catch (e) {
|
|
517
|
+
throw e; // ← don't let SMS failure break the user flow
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ✅ Log and continue
|
|
521
|
+
try {
|
|
522
|
+
await smsConnector.sendMessage({ ... });
|
|
523
|
+
} catch (e) {
|
|
524
|
+
logger.warn('SMS send failed', { error: e, userId });
|
|
525
|
+
// Continue — SMS is non-critical
|
|
526
|
+
}
|
|
527
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Root skill index for XBG agentic builder libraries. Routes to the appropriate sub-skill based on the library and topic."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# XBG Agentic Builder Libraries
|
|
6
|
+
|
|
7
|
+
This repository contains libraries designed to accelerate AI-assisted application development. Each library has its own skill tree with detailed guidance for Claude.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Available Libraries
|
|
12
|
+
|
|
13
|
+
| Library | Skill Path | Description |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Boilerplate Backend (bpbe)** | `bpbe/` | Production-ready Node.js/TypeScript backend on Firebase Functions + Firestore + Express.js |
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Boilerplate Backend — Quick Navigation
|
|
20
|
+
|
|
21
|
+
A 3-layer (Controller → Service → Repository) backend distributed as `@xbg.solutions/*` npm packages. AI generates business logic from declarative data models.
|
|
22
|
+
|
|
23
|
+
| Topic | Skill | Use when you need to... |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| Overview | `bpbe/skill.md` | Understand the architecture, philosophy, and how the pieces fit together |
|
|
26
|
+
| Setup | `bpbe/setup/skill.md` | Create a new project, configure .env, npm scripts, Firebase config, validation |
|
|
27
|
+
| Data | `bpbe/data/skill.md` | Define entities, repositories, DataModelSpecification, code generator, Firestore patterns |
|
|
28
|
+
| Services | `bpbe/services/skill.md` | Business logic, lifecycle hooks, access control, events, auth patterns |
|
|
29
|
+
| Utilities | `bpbe/utils/skill.md` | Logger, PII hashing, token handler, cache, email/SMS/push/CRM/LLM connectors |
|
|
30
|
+
| API | `bpbe/api/skill.md` | Controllers, routes, middleware, response shapes, wiring up index.ts |
|