@bloomneo/appkit 1.2.9 → 1.5.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/AGENTS.md +195 -0
- package/CHANGELOG.md +253 -0
- package/README.md +147 -799
- package/bin/commands/generate.js +7 -7
- package/cookbook/README.md +26 -0
- package/cookbook/api-key-service.ts +106 -0
- package/cookbook/auth-protected-crud.ts +112 -0
- package/cookbook/file-upload-pipeline.ts +113 -0
- package/cookbook/multi-tenant-saas.ts +87 -0
- package/cookbook/real-time-chat.ts +121 -0
- package/dist/auth/auth.d.ts +21 -4
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/auth/auth.js +56 -44
- package/dist/auth/auth.js.map +1 -1
- package/dist/auth/defaults.d.ts +1 -1
- package/dist/auth/defaults.js +35 -35
- package/dist/cache/cache.d.ts +29 -6
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +72 -44
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/defaults.js +29 -29
- package/dist/cache/index.d.ts +19 -10
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +21 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +11 -11
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +4 -4
- package/dist/database/adapters/mongoose.d.ts +4 -4
- package/dist/database/adapters/mongoose.js +7 -7
- package/dist/database/adapters/prisma.d.ts +4 -4
- package/dist/database/adapters/prisma.js +7 -7
- package/dist/database/defaults.d.ts +1 -1
- package/dist/database/defaults.js +4 -4
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/defaults.js +26 -26
- package/dist/email/index.js +7 -7
- package/dist/email/strategies/resend.js +1 -1
- package/dist/error/defaults.d.ts +1 -1
- package/dist/error/defaults.js +13 -13
- package/dist/error/error.d.ts +12 -0
- package/dist/error/error.d.ts.map +1 -1
- package/dist/error/error.js +19 -0
- package/dist/error/error.js.map +1 -1
- package/dist/error/index.d.ts +14 -3
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +14 -3
- package/dist/error/index.js.map +1 -1
- package/dist/event/defaults.js +35 -35
- package/dist/event/index.js +7 -7
- package/dist/logger/defaults.d.ts +1 -1
- package/dist/logger/defaults.js +40 -40
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +13 -3
- package/dist/logger/logger.js.map +1 -1
- package/dist/logger/transports/console.js +2 -2
- package/dist/logger/transports/http.d.ts +1 -1
- package/dist/logger/transports/http.js +2 -2
- package/dist/logger/transports/webhook.d.ts +1 -1
- package/dist/logger/transports/webhook.js +3 -3
- package/dist/queue/defaults.d.ts +2 -2
- package/dist/queue/defaults.js +38 -38
- package/dist/security/defaults.d.ts +1 -1
- package/dist/security/defaults.js +30 -30
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/security.d.ts +1 -1
- package/dist/security/security.js +4 -4
- package/dist/storage/defaults.js +26 -26
- package/dist/storage/index.js +3 -3
- package/dist/util/defaults.d.ts +1 -1
- package/dist/util/defaults.js +41 -41
- package/dist/util/env.d.ts +35 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +50 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/errors.d.ts +52 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +82 -0
- package/dist/util/errors.js.map +1 -0
- package/dist/util/util.js +1 -1
- package/examples/.env.example +80 -0
- package/examples/README.md +16 -0
- package/examples/auth.ts +228 -0
- package/examples/cache.ts +36 -0
- package/examples/config.ts +45 -0
- package/examples/database.ts +69 -0
- package/examples/email.ts +53 -0
- package/examples/error.ts +50 -0
- package/examples/event.ts +42 -0
- package/examples/logger.ts +41 -0
- package/examples/queue.ts +58 -0
- package/examples/security.ts +46 -0
- package/examples/storage.ts +44 -0
- package/examples/util.ts +47 -0
- package/llms.txt +591 -0
- package/package.json +19 -10
- package/src/auth/README.md +850 -0
- package/src/cache/README.md +756 -0
- package/src/config/README.md +604 -0
- package/src/database/README.md +818 -0
- package/src/email/README.md +759 -0
- package/src/error/README.md +660 -0
- package/src/event/README.md +729 -0
- package/src/logger/README.md +435 -0
- package/src/queue/README.md +851 -0
- package/src/security/README.md +612 -0
- package/src/storage/README.md +1008 -0
- package/src/util/README.md +955 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
package/dist/security/index.js
CHANGED
|
@@ -78,7 +78,7 @@ function isProduction() {
|
|
|
78
78
|
* Generate a secure encryption key for production use
|
|
79
79
|
* @llm-rule WHEN: Setting up encryption for the first time or rotating keys
|
|
80
80
|
* @llm-rule AVOID: Using weak or predictable keys - always use this for key generation
|
|
81
|
-
* @llm-rule NOTE: Returns 64-character hex string suitable for
|
|
81
|
+
* @llm-rule NOTE: Returns 64-character hex string suitable for BLOOM_SECURITY_ENCRYPTION_KEY
|
|
82
82
|
*/
|
|
83
83
|
function generateKey() {
|
|
84
84
|
const security = get();
|
|
@@ -113,10 +113,10 @@ function validateRequired(checks = {}) {
|
|
|
113
113
|
const config = getConfig();
|
|
114
114
|
const missing = [];
|
|
115
115
|
if (checks.csrf && !config.csrf.secret) {
|
|
116
|
-
missing.push('
|
|
116
|
+
missing.push('BLOOM_SECURITY_CSRF_SECRET or BLOOM_AUTH_SECRET');
|
|
117
117
|
}
|
|
118
118
|
if (checks.encryption && !config.encryption.key) {
|
|
119
|
-
missing.push('
|
|
119
|
+
missing.push('BLOOM_SECURITY_ENCRYPTION_KEY');
|
|
120
120
|
}
|
|
121
121
|
if (missing.length > 0) {
|
|
122
122
|
throw new Error(`Missing required security configuration: ${missing.join(', ')}\n` +
|
|
@@ -111,7 +111,7 @@ export declare class SecurityClass {
|
|
|
111
111
|
* Generates a cryptographically secure 256-bit encryption key
|
|
112
112
|
* @llm-rule WHEN: Setting up encryption for the first time or rotating keys
|
|
113
113
|
* @llm-rule AVOID: Using weak or predictable keys - always use this method for key generation
|
|
114
|
-
* @llm-rule NOTE: Returns 64-character hex string suitable for
|
|
114
|
+
* @llm-rule NOTE: Returns 64-character hex string suitable for BLOOM_SECURITY_ENCRYPTION_KEY
|
|
115
115
|
*/
|
|
116
116
|
generateKey(): string;
|
|
117
117
|
/**
|
|
@@ -30,7 +30,7 @@ export class SecurityClass {
|
|
|
30
30
|
forms(options = {}) {
|
|
31
31
|
const csrfSecret = options.secret || this.config.csrf.secret;
|
|
32
32
|
if (!csrfSecret) {
|
|
33
|
-
throw createSecurityError('CSRF secret required. Set
|
|
33
|
+
throw createSecurityError('CSRF secret required. Set BLOOM_SECURITY_CSRF_SECRET or BLOOM_AUTH_SECRET environment variable', 500);
|
|
34
34
|
}
|
|
35
35
|
const tokenField = options.tokenField || this.config.csrf.tokenField;
|
|
36
36
|
const headerField = options.headerField || this.config.csrf.headerField;
|
|
@@ -242,7 +242,7 @@ export class SecurityClass {
|
|
|
242
242
|
}
|
|
243
243
|
const encryptionKey = key || this.config.encryption.key;
|
|
244
244
|
if (!encryptionKey) {
|
|
245
|
-
throw createSecurityError('Encryption key required. Provide as argument or set
|
|
245
|
+
throw createSecurityError('Encryption key required. Provide as argument or set BLOOM_SECURITY_ENCRYPTION_KEY environment variable', 500);
|
|
246
246
|
}
|
|
247
247
|
this.validateEncryptionKey(encryptionKey);
|
|
248
248
|
const keyBuffer = typeof encryptionKey === 'string'
|
|
@@ -284,7 +284,7 @@ export class SecurityClass {
|
|
|
284
284
|
}
|
|
285
285
|
const decryptionKey = key || this.config.encryption.key;
|
|
286
286
|
if (!decryptionKey) {
|
|
287
|
-
throw createSecurityError('Decryption key required. Provide as argument or set
|
|
287
|
+
throw createSecurityError('Decryption key required. Provide as argument or set BLOOM_SECURITY_ENCRYPTION_KEY environment variable', 500);
|
|
288
288
|
}
|
|
289
289
|
this.validateEncryptionKey(decryptionKey);
|
|
290
290
|
const keyBuffer = typeof decryptionKey === 'string'
|
|
@@ -331,7 +331,7 @@ export class SecurityClass {
|
|
|
331
331
|
* Generates a cryptographically secure 256-bit encryption key
|
|
332
332
|
* @llm-rule WHEN: Setting up encryption for the first time or rotating keys
|
|
333
333
|
* @llm-rule AVOID: Using weak or predictable keys - always use this method for key generation
|
|
334
|
-
* @llm-rule NOTE: Returns 64-character hex string suitable for
|
|
334
|
+
* @llm-rule NOTE: Returns 64-character hex string suitable for BLOOM_SECURITY_ENCRYPTION_KEY
|
|
335
335
|
*/
|
|
336
336
|
generateKey() {
|
|
337
337
|
try {
|
package/dist/storage/defaults.js
CHANGED
|
@@ -27,11 +27,11 @@ export function getSmartDefaults() {
|
|
|
27
27
|
strategy,
|
|
28
28
|
// Local configuration (only used when strategy is 'local')
|
|
29
29
|
local: {
|
|
30
|
-
dir: process.env.
|
|
31
|
-
baseUrl: process.env.
|
|
32
|
-
maxFileSize: parseInt(process.env.
|
|
30
|
+
dir: process.env.BLOOM_STORAGE_DIR || './uploads',
|
|
31
|
+
baseUrl: process.env.BLOOM_STORAGE_BASE_URL || '/uploads',
|
|
32
|
+
maxFileSize: parseInt(process.env.BLOOM_STORAGE_MAX_SIZE || '52428800'), // 50MB default
|
|
33
33
|
allowedTypes: parseAllowedTypes(),
|
|
34
|
-
createDirs: process.env.
|
|
34
|
+
createDirs: process.env.BLOOM_STORAGE_CREATE_DIRS !== 'false',
|
|
35
35
|
},
|
|
36
36
|
// S3 configuration (only used when strategy is 's3')
|
|
37
37
|
s3: {
|
|
@@ -41,8 +41,8 @@ export function getSmartDefaults() {
|
|
|
41
41
|
accessKeyId: process.env.AWS_ACCESS_KEY_ID || process.env.S3_ACCESS_KEY_ID || '',
|
|
42
42
|
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || process.env.S3_SECRET_ACCESS_KEY || '',
|
|
43
43
|
forcePathStyle: process.env.S3_FORCE_PATH_STYLE === 'true',
|
|
44
|
-
signedUrlExpiry: parseInt(process.env.
|
|
45
|
-
cdnUrl: process.env.
|
|
44
|
+
signedUrlExpiry: parseInt(process.env.BLOOM_STORAGE_SIGNED_EXPIRY || '3600'), // 1 hour
|
|
45
|
+
cdnUrl: process.env.BLOOM_STORAGE_CDN_URL,
|
|
46
46
|
},
|
|
47
47
|
// R2 configuration (only used when strategy is 'r2')
|
|
48
48
|
r2: {
|
|
@@ -51,7 +51,7 @@ export function getSmartDefaults() {
|
|
|
51
51
|
accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID || '',
|
|
52
52
|
secretAccessKey: process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY || '',
|
|
53
53
|
cdnUrl: process.env.CLOUDFLARE_R2_CDN_URL,
|
|
54
|
-
signedUrlExpiry: parseInt(process.env.
|
|
54
|
+
signedUrlExpiry: parseInt(process.env.BLOOM_STORAGE_SIGNED_EXPIRY || '3600'), // 1 hour
|
|
55
55
|
},
|
|
56
56
|
// Environment information
|
|
57
57
|
environment: {
|
|
@@ -70,7 +70,7 @@ export function getSmartDefaults() {
|
|
|
70
70
|
*/
|
|
71
71
|
function detectStorageStrategy() {
|
|
72
72
|
// Explicit override wins (for testing/debugging)
|
|
73
|
-
const explicit = process.env.
|
|
73
|
+
const explicit = process.env.BLOOM_STORAGE_STRATEGY?.toLowerCase();
|
|
74
74
|
if (explicit && ['local', 's3', 'r2'].includes(explicit)) {
|
|
75
75
|
return explicit;
|
|
76
76
|
}
|
|
@@ -83,7 +83,7 @@ function detectStorageStrategy() {
|
|
|
83
83
|
}
|
|
84
84
|
// Default to local for development/single server
|
|
85
85
|
if (process.env.NODE_ENV === 'production') {
|
|
86
|
-
console.warn('[
|
|
86
|
+
console.warn('[Bloomneo AppKit] No cloud storage configured in production. ' +
|
|
87
87
|
'Using local filesystem which may not scale. ' +
|
|
88
88
|
'Set AWS_S3_BUCKET or CLOUDFLARE_R2_BUCKET for cloud storage.');
|
|
89
89
|
}
|
|
@@ -95,7 +95,7 @@ function detectStorageStrategy() {
|
|
|
95
95
|
* @llm-rule AVOID: Allowing all file types in production - security risk
|
|
96
96
|
*/
|
|
97
97
|
function parseAllowedTypes() {
|
|
98
|
-
const envTypes = process.env.
|
|
98
|
+
const envTypes = process.env.BLOOM_STORAGE_ALLOWED_TYPES;
|
|
99
99
|
if (!envTypes) {
|
|
100
100
|
// Safe defaults - common web file types
|
|
101
101
|
return [
|
|
@@ -107,8 +107,8 @@ function parseAllowedTypes() {
|
|
|
107
107
|
}
|
|
108
108
|
if (envTypes === '*') {
|
|
109
109
|
if (process.env.NODE_ENV === 'production') {
|
|
110
|
-
console.warn('[
|
|
111
|
-
'Set
|
|
110
|
+
console.warn('[Bloomneo AppKit] SECURITY WARNING: All file types allowed in production. ' +
|
|
111
|
+
'Set BLOOM_STORAGE_ALLOWED_TYPES to specific types for security.');
|
|
112
112
|
}
|
|
113
113
|
return ['*']; // Allow all types (use with caution)
|
|
114
114
|
}
|
|
@@ -122,13 +122,13 @@ function parseAllowedTypes() {
|
|
|
122
122
|
*/
|
|
123
123
|
function validateEnvironment() {
|
|
124
124
|
// Validate storage strategy if explicitly set
|
|
125
|
-
const strategy = process.env.
|
|
125
|
+
const strategy = process.env.BLOOM_STORAGE_STRATEGY;
|
|
126
126
|
if (strategy && !['local', 's3', 'r2'].includes(strategy.toLowerCase())) {
|
|
127
|
-
throw new Error(`Invalid
|
|
127
|
+
throw new Error(`Invalid BLOOM_STORAGE_STRATEGY: "${strategy}". Must be "local", "s3", or "r2"`);
|
|
128
128
|
}
|
|
129
129
|
// Validate numeric values
|
|
130
|
-
validateNumericEnv('
|
|
131
|
-
validateNumericEnv('
|
|
130
|
+
validateNumericEnv('BLOOM_STORAGE_MAX_SIZE', 1048576, 1073741824); // 1MB to 1GB
|
|
131
|
+
validateNumericEnv('BLOOM_STORAGE_SIGNED_EXPIRY', 60, 604800); // 1 minute to 7 days
|
|
132
132
|
// Validate S3 configuration if S3 strategy detected
|
|
133
133
|
if (shouldValidateS3()) {
|
|
134
134
|
validateS3Config();
|
|
@@ -148,7 +148,7 @@ function validateEnvironment() {
|
|
|
148
148
|
}
|
|
149
149
|
// Validate NODE_ENV
|
|
150
150
|
if (nodeEnv && !['development', 'production', 'test', 'staging'].includes(nodeEnv)) {
|
|
151
|
-
console.warn(`[
|
|
151
|
+
console.warn(`[Bloomneo AppKit] Unusual NODE_ENV: "${nodeEnv}". ` +
|
|
152
152
|
`Expected: development, production, test, or staging`);
|
|
153
153
|
}
|
|
154
154
|
}
|
|
@@ -214,14 +214,14 @@ function validateR2Config() {
|
|
|
214
214
|
* Validates local configuration
|
|
215
215
|
*/
|
|
216
216
|
function validateLocalConfig() {
|
|
217
|
-
const dir = process.env.
|
|
217
|
+
const dir = process.env.BLOOM_STORAGE_DIR;
|
|
218
218
|
if (dir && (dir.includes('..') || dir.startsWith('/') && process.env.NODE_ENV === 'production')) {
|
|
219
|
-
console.warn(`[
|
|
219
|
+
console.warn(`[Bloomneo AppKit] Potentially unsafe storage directory: "${dir}". ` +
|
|
220
220
|
`Consider using a relative path for security.`);
|
|
221
221
|
}
|
|
222
|
-
const baseUrl = process.env.
|
|
222
|
+
const baseUrl = process.env.BLOOM_STORAGE_BASE_URL;
|
|
223
223
|
if (baseUrl && !baseUrl.startsWith('/') && !isValidUrl(baseUrl)) {
|
|
224
|
-
throw new Error(`Invalid
|
|
224
|
+
throw new Error(`Invalid BLOOM_STORAGE_BASE_URL: "${baseUrl}". Must be a path or valid URL`);
|
|
225
225
|
}
|
|
226
226
|
}
|
|
227
227
|
/**
|
|
@@ -232,15 +232,15 @@ function validateLocalConfig() {
|
|
|
232
232
|
function validateProductionConfig() {
|
|
233
233
|
const strategy = detectStorageStrategy();
|
|
234
234
|
if (strategy === 'local') {
|
|
235
|
-
console.warn('[
|
|
235
|
+
console.warn('[Bloomneo AppKit] Using local storage in production. ' +
|
|
236
236
|
'Files will only exist on single server instance. ' +
|
|
237
237
|
'Set AWS_S3_BUCKET or CLOUDFLARE_R2_BUCKET for distributed storage.');
|
|
238
238
|
}
|
|
239
239
|
// Warn about missing CDN in production
|
|
240
|
-
const cdnUrl = process.env.
|
|
240
|
+
const cdnUrl = process.env.BLOOM_STORAGE_CDN_URL || process.env.CLOUDFLARE_R2_CDN_URL;
|
|
241
241
|
if (!cdnUrl && strategy !== 'local') {
|
|
242
|
-
console.warn('[
|
|
243
|
-
'Set
|
|
242
|
+
console.warn('[Bloomneo AppKit] No CDN URL configured in production. ' +
|
|
243
|
+
'Set BLOOM_STORAGE_CDN_URL for better performance.');
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
/**
|
|
@@ -339,7 +339,7 @@ export function getDeploymentConfig(type) {
|
|
|
339
339
|
case 'production':
|
|
340
340
|
const strategy = detectStorageStrategy();
|
|
341
341
|
if (strategy === 'local') {
|
|
342
|
-
console.warn('[
|
|
342
|
+
console.warn('[Bloomneo AppKit] Local storage not recommended for production');
|
|
343
343
|
}
|
|
344
344
|
return {
|
|
345
345
|
strategy,
|
package/dist/storage/index.js
CHANGED
|
@@ -100,17 +100,17 @@ function validateConfig() {
|
|
|
100
100
|
try {
|
|
101
101
|
const strategy = getStrategy();
|
|
102
102
|
if (strategy === 'local' && process.env.NODE_ENV === 'production') {
|
|
103
|
-
console.warn('[
|
|
103
|
+
console.warn('[Bloomneo AppKit] Using local storage in production. ' +
|
|
104
104
|
'Files will only exist on single server instance. ' +
|
|
105
105
|
'Set AWS_S3_BUCKET or CLOUDFLARE_R2_BUCKET for distributed storage.');
|
|
106
106
|
}
|
|
107
107
|
if (process.env.NODE_ENV === 'production' && !hasCloudStorage()) {
|
|
108
|
-
console.warn('[
|
|
108
|
+
console.warn('[Bloomneo AppKit] No cloud storage configured in production. ' +
|
|
109
109
|
'Set AWS_S3_BUCKET or CLOUDFLARE_R2_BUCKET for scalable file storage.');
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
catch (error) {
|
|
113
|
-
console.error('[
|
|
113
|
+
console.error('[Bloomneo AppKit] Storage configuration validation failed:', error.message);
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
/**
|
package/dist/util/defaults.d.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface UtilConfig {
|
|
|
51
51
|
environment: EnvironmentConfig;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
* Gets smart defaults using
|
|
54
|
+
* Gets smart defaults using BLOOM_UTIL_* environment variables
|
|
55
55
|
* @llm-rule WHEN: App startup to get production-ready utility configuration
|
|
56
56
|
* @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
|
|
57
57
|
* @llm-rule NOTE: Called once at startup, cached globally for performance
|
package/dist/util/defaults.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @llm-rule NOTE: Called once at startup, cached globally for performance like other modules
|
|
9
9
|
*/
|
|
10
10
|
/**
|
|
11
|
-
* Gets smart defaults using
|
|
11
|
+
* Gets smart defaults using BLOOM_UTIL_* environment variables
|
|
12
12
|
* @llm-rule WHEN: App startup to get production-ready utility configuration
|
|
13
13
|
* @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
|
|
14
14
|
* @llm-rule NOTE: Called once at startup, cached globally for performance
|
|
@@ -23,36 +23,36 @@ export function getSmartDefaults() {
|
|
|
23
23
|
version: process.env.npm_package_version || '1.0.0',
|
|
24
24
|
// Cache configuration with direct environment access
|
|
25
25
|
cache: {
|
|
26
|
-
enabled: process.env.
|
|
27
|
-
maxSize: parseInt(process.env.
|
|
28
|
-
ttl: parseInt(process.env.
|
|
26
|
+
enabled: process.env.BLOOM_UTIL_CACHE !== 'false' && !isTest,
|
|
27
|
+
maxSize: parseInt(process.env.BLOOM_UTIL_CACHE_SIZE || '1000'),
|
|
28
|
+
ttl: parseInt(process.env.BLOOM_UTIL_CACHE_TTL || '300000'), // 5 minutes
|
|
29
29
|
},
|
|
30
30
|
// Performance optimization settings
|
|
31
31
|
performance: {
|
|
32
|
-
enabled: process.env.
|
|
33
|
-
memoization: process.env.
|
|
34
|
-
largeArrayThreshold: parseInt(process.env.
|
|
35
|
-
chunkSizeLimit: parseInt(process.env.
|
|
32
|
+
enabled: process.env.BLOOM_UTIL_PERFORMANCE !== 'false',
|
|
33
|
+
memoization: process.env.BLOOM_UTIL_MEMOIZATION !== 'false' && !isTest,
|
|
34
|
+
largeArrayThreshold: parseInt(process.env.BLOOM_UTIL_ARRAY_THRESHOLD || '10000'),
|
|
35
|
+
chunkSizeLimit: parseInt(process.env.BLOOM_UTIL_CHUNK_LIMIT || '100000'),
|
|
36
36
|
},
|
|
37
37
|
// Debug configuration - enabled in development
|
|
38
38
|
debug: {
|
|
39
|
-
enabled: process.env.
|
|
40
|
-
logOperations: process.env.
|
|
41
|
-
trackPerformance: process.env.
|
|
39
|
+
enabled: process.env.BLOOM_UTIL_DEBUG === 'true' || isDevelopment,
|
|
40
|
+
logOperations: process.env.BLOOM_UTIL_LOG_OPS === 'true' || isDevelopment,
|
|
41
|
+
trackPerformance: process.env.BLOOM_UTIL_TRACK_PERF === 'true' || isDevelopment,
|
|
42
42
|
},
|
|
43
43
|
// Slugify configuration with locale support
|
|
44
44
|
slugify: {
|
|
45
|
-
lowercase: process.env.
|
|
46
|
-
strict: process.env.
|
|
47
|
-
locale: process.env.
|
|
48
|
-
replacement: process.env.
|
|
45
|
+
lowercase: process.env.BLOOM_UTIL_SLUGIFY_LOWERCASE !== 'false',
|
|
46
|
+
strict: process.env.BLOOM_UTIL_SLUGIFY_STRICT === 'true',
|
|
47
|
+
locale: process.env.BLOOM_UTIL_LOCALE || 'en',
|
|
48
|
+
replacement: process.env.BLOOM_UTIL_SLUGIFY_REPLACEMENT || '-',
|
|
49
49
|
},
|
|
50
50
|
// Format configuration for locale-aware formatting
|
|
51
51
|
format: {
|
|
52
|
-
locale: process.env.
|
|
53
|
-
currency: process.env.
|
|
54
|
-
dateFormat: process.env.
|
|
55
|
-
numberPrecision: parseInt(process.env.
|
|
52
|
+
locale: process.env.BLOOM_UTIL_LOCALE || 'en-US',
|
|
53
|
+
currency: process.env.BLOOM_UTIL_CURRENCY || 'USD',
|
|
54
|
+
dateFormat: process.env.BLOOM_UTIL_DATE_FORMAT || 'YYYY-MM-DD',
|
|
55
|
+
numberPrecision: parseInt(process.env.BLOOM_UTIL_NUMBER_PRECISION || '2'),
|
|
56
56
|
},
|
|
57
57
|
// Environment information
|
|
58
58
|
environment: {
|
|
@@ -72,75 +72,75 @@ export function getSmartDefaults() {
|
|
|
72
72
|
function validateEnvironment() {
|
|
73
73
|
const nodeEnv = process.env.NODE_ENV || 'development';
|
|
74
74
|
// Validate cache configuration
|
|
75
|
-
const cacheSize = process.env.
|
|
75
|
+
const cacheSize = process.env.BLOOM_UTIL_CACHE_SIZE;
|
|
76
76
|
if (cacheSize) {
|
|
77
77
|
const cacheSizeNum = parseInt(cacheSize);
|
|
78
78
|
if (isNaN(cacheSizeNum) || cacheSizeNum <= 0) {
|
|
79
|
-
throw new Error(`Invalid
|
|
79
|
+
throw new Error(`Invalid BLOOM_UTIL_CACHE_SIZE: "${cacheSize}". Must be a positive number.`);
|
|
80
80
|
}
|
|
81
81
|
if (cacheSizeNum > 100000) {
|
|
82
|
-
console.warn(`[
|
|
82
|
+
console.warn(`[Bloomneo AppKit] Large cache size: ${cacheSizeNum}. This may impact memory usage.`);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
// Validate cache TTL
|
|
86
|
-
const cacheTTL = process.env.
|
|
86
|
+
const cacheTTL = process.env.BLOOM_UTIL_CACHE_TTL;
|
|
87
87
|
if (cacheTTL) {
|
|
88
88
|
const cacheTTLNum = parseInt(cacheTTL);
|
|
89
89
|
if (isNaN(cacheTTLNum) || cacheTTLNum <= 0) {
|
|
90
|
-
throw new Error(`Invalid
|
|
90
|
+
throw new Error(`Invalid BLOOM_UTIL_CACHE_TTL: "${cacheTTL}". Must be a positive number (milliseconds).`);
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
// Validate array threshold
|
|
94
|
-
const arrayThreshold = process.env.
|
|
94
|
+
const arrayThreshold = process.env.BLOOM_UTIL_ARRAY_THRESHOLD;
|
|
95
95
|
if (arrayThreshold) {
|
|
96
96
|
const thresholdNum = parseInt(arrayThreshold);
|
|
97
97
|
if (isNaN(thresholdNum) || thresholdNum <= 0) {
|
|
98
|
-
throw new Error(`Invalid
|
|
98
|
+
throw new Error(`Invalid BLOOM_UTIL_ARRAY_THRESHOLD: "${arrayThreshold}". Must be a positive number.`);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
// Validate chunk size limit
|
|
102
|
-
const chunkLimit = process.env.
|
|
102
|
+
const chunkLimit = process.env.BLOOM_UTIL_CHUNK_LIMIT;
|
|
103
103
|
if (chunkLimit) {
|
|
104
104
|
const chunkLimitNum = parseInt(chunkLimit);
|
|
105
105
|
if (isNaN(chunkLimitNum) || chunkLimitNum <= 0) {
|
|
106
|
-
throw new Error(`Invalid
|
|
106
|
+
throw new Error(`Invalid BLOOM_UTIL_CHUNK_LIMIT: "${chunkLimit}". Must be a positive number.`);
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
// Validate number precision
|
|
110
|
-
const numberPrecision = process.env.
|
|
110
|
+
const numberPrecision = process.env.BLOOM_UTIL_NUMBER_PRECISION;
|
|
111
111
|
if (numberPrecision) {
|
|
112
112
|
const precisionNum = parseInt(numberPrecision);
|
|
113
113
|
if (isNaN(precisionNum) || precisionNum < 0 || precisionNum > 20) {
|
|
114
|
-
throw new Error(`Invalid
|
|
114
|
+
throw new Error(`Invalid BLOOM_UTIL_NUMBER_PRECISION: "${numberPrecision}". Must be between 0 and 20.`);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
// Validate locale if provided
|
|
118
|
-
const locale = process.env.
|
|
118
|
+
const locale = process.env.BLOOM_UTIL_LOCALE;
|
|
119
119
|
if (locale && !isValidLocale(locale)) {
|
|
120
|
-
console.warn(`[
|
|
120
|
+
console.warn(`[Bloomneo AppKit] Invalid locale: "${locale}". Using default 'en-US'.`);
|
|
121
121
|
}
|
|
122
122
|
// Validate currency if provided
|
|
123
|
-
const currency = process.env.
|
|
123
|
+
const currency = process.env.BLOOM_UTIL_CURRENCY;
|
|
124
124
|
if (currency && !isValidCurrency(currency)) {
|
|
125
|
-
console.warn(`[
|
|
125
|
+
console.warn(`[Bloomneo AppKit] Invalid currency: "${currency}". Using default 'USD'.`);
|
|
126
126
|
}
|
|
127
127
|
// Validate slugify replacement
|
|
128
|
-
const replacement = process.env.
|
|
128
|
+
const replacement = process.env.BLOOM_UTIL_SLUGIFY_REPLACEMENT;
|
|
129
129
|
if (replacement && replacement.length > 5) {
|
|
130
|
-
console.warn(`[
|
|
130
|
+
console.warn(`[Bloomneo AppKit] Long slugify replacement: "${replacement}". Consider using shorter replacement.`);
|
|
131
131
|
}
|
|
132
132
|
// Production-specific warnings
|
|
133
133
|
if (nodeEnv === 'production') {
|
|
134
|
-
if (process.env.
|
|
135
|
-
console.warn('[
|
|
134
|
+
if (process.env.BLOOM_UTIL_DEBUG === 'true') {
|
|
135
|
+
console.warn('[Bloomneo AppKit] Debug mode enabled in production. This may impact performance.');
|
|
136
136
|
}
|
|
137
|
-
if (process.env.
|
|
138
|
-
console.warn('[
|
|
137
|
+
if (process.env.BLOOM_UTIL_LOG_OPS === 'true') {
|
|
138
|
+
console.warn('[Bloomneo AppKit] Operation logging enabled in production. This may impact performance.');
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
// Validate NODE_ENV
|
|
142
142
|
if (nodeEnv && !['development', 'production', 'test', 'staging'].includes(nodeEnv)) {
|
|
143
|
-
console.warn(`[
|
|
143
|
+
console.warn(`[Bloomneo AppKit] Unusual NODE_ENV: "${nodeEnv}". ` +
|
|
144
144
|
`Expected: development, production, test, or staging`);
|
|
145
145
|
}
|
|
146
146
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable reader for @bloomneo/appkit.
|
|
3
|
+
*
|
|
4
|
+
* Canonical prefix is `BLOOM_`. The legacy `VOILA_*` prefix from the original
|
|
5
|
+
* `@voilajsx/appkit` package was removed entirely in 1.5.2 — there is no
|
|
6
|
+
* backwards-compatibility, no fallback, no deprecation warning. Consumers
|
|
7
|
+
* upgrading must rename their env vars in one go.
|
|
8
|
+
*
|
|
9
|
+
* Internal use only. Consumers should call `configClass.get()` instead of
|
|
10
|
+
* touching env vars directly. This file exists so future code has one
|
|
11
|
+
* canonical helper for reading env vars, instead of scattering
|
|
12
|
+
* `process.env.BLOOM_*` calls across the codebase.
|
|
13
|
+
*
|
|
14
|
+
* @file src/util/env.ts
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Read an env var by its bare name (no prefix).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* env('AUTH_SECRET') // → process.env.BLOOM_AUTH_SECRET
|
|
21
|
+
* env('DB_TENANT') // → process.env.BLOOM_DB_TENANT
|
|
22
|
+
* env('FOO', 'default-val') // → returns 'default-val' if BLOOM_FOO unset
|
|
23
|
+
*/
|
|
24
|
+
export declare function env(name: string, fallback?: string): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Read a numeric env var. Returns the fallback if the var is unset or
|
|
27
|
+
* the value can't be parsed as a finite number.
|
|
28
|
+
*/
|
|
29
|
+
export declare function envNumber(name: string, fallback: number): number;
|
|
30
|
+
/**
|
|
31
|
+
* Read a boolean env var. Truthy: 'true' / '1' / 'yes' / 'on' (case-insensitive).
|
|
32
|
+
* Returns the fallback if the var is unset.
|
|
33
|
+
*/
|
|
34
|
+
export declare function envBool(name: string, fallback: boolean): boolean;
|
|
35
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/util/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGvE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKhE;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAKhE"}
|
package/dist/util/env.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment variable reader for @bloomneo/appkit.
|
|
3
|
+
*
|
|
4
|
+
* Canonical prefix is `BLOOM_`. The legacy `VOILA_*` prefix from the original
|
|
5
|
+
* `@voilajsx/appkit` package was removed entirely in 1.5.2 — there is no
|
|
6
|
+
* backwards-compatibility, no fallback, no deprecation warning. Consumers
|
|
7
|
+
* upgrading must rename their env vars in one go.
|
|
8
|
+
*
|
|
9
|
+
* Internal use only. Consumers should call `configClass.get()` instead of
|
|
10
|
+
* touching env vars directly. This file exists so future code has one
|
|
11
|
+
* canonical helper for reading env vars, instead of scattering
|
|
12
|
+
* `process.env.BLOOM_*` calls across the codebase.
|
|
13
|
+
*
|
|
14
|
+
* @file src/util/env.ts
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Read an env var by its bare name (no prefix).
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* env('AUTH_SECRET') // → process.env.BLOOM_AUTH_SECRET
|
|
21
|
+
* env('DB_TENANT') // → process.env.BLOOM_DB_TENANT
|
|
22
|
+
* env('FOO', 'default-val') // → returns 'default-val' if BLOOM_FOO unset
|
|
23
|
+
*/
|
|
24
|
+
export function env(name, fallback) {
|
|
25
|
+
const value = process.env[`BLOOM_${name}`];
|
|
26
|
+
return value !== undefined ? value : fallback;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read a numeric env var. Returns the fallback if the var is unset or
|
|
30
|
+
* the value can't be parsed as a finite number.
|
|
31
|
+
*/
|
|
32
|
+
export function envNumber(name, fallback) {
|
|
33
|
+
const raw = env(name);
|
|
34
|
+
if (raw === undefined)
|
|
35
|
+
return fallback;
|
|
36
|
+
const parsed = Number(raw);
|
|
37
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read a boolean env var. Truthy: 'true' / '1' / 'yes' / 'on' (case-insensitive).
|
|
41
|
+
* Returns the fallback if the var is unset.
|
|
42
|
+
*/
|
|
43
|
+
export function envBool(name, fallback) {
|
|
44
|
+
const raw = env(name);
|
|
45
|
+
if (raw === undefined)
|
|
46
|
+
return fallback;
|
|
47
|
+
const v = raw.toLowerCase();
|
|
48
|
+
return v === 'true' || v === '1' || v === 'yes' || v === 'on';
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/util/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,GAAG,CAAC,IAAY,EAAE,QAAiB;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAC3C,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,QAAgB;IACtD,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,QAAiB;IACrD,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACtB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACvC,MAAM,CAAC,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC5B,OAAO,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Educational runtime errors for @bloomneo/appkit.
|
|
3
|
+
*
|
|
4
|
+
* Modules throw these (instead of plain TypeError or generic Error) so that
|
|
5
|
+
* humans AND AI coding agents reading the error message get an actionable
|
|
6
|
+
* fix-it-now message that names the missing thing AND points at the canonical
|
|
7
|
+
* pattern in AGENTS.md or the relevant example file.
|
|
8
|
+
*
|
|
9
|
+
* Format is intentionally consistent across all 12 modules:
|
|
10
|
+
*
|
|
11
|
+
* [@bloomneo/appkit] <Module> requires <thing>. <reason>.
|
|
12
|
+
* See: <docs URL or example file>
|
|
13
|
+
*
|
|
14
|
+
* Agents that read errors and self-correct will fetch the link and recover
|
|
15
|
+
* on the next iteration. Without this, agents get cryptic "X is undefined"
|
|
16
|
+
* messages and have to guess.
|
|
17
|
+
*
|
|
18
|
+
* @file src/util/errors.ts
|
|
19
|
+
*/
|
|
20
|
+
export declare class AppKitError extends Error {
|
|
21
|
+
readonly module: string;
|
|
22
|
+
readonly docsUrl: string;
|
|
23
|
+
constructor(module: string, message: string, slug?: string);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Throw if a required env var is missing. Use at module init / first .get()
|
|
27
|
+
* call so the error is loud and immediate, not deferred to the first request.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* requireEnv('Auth', 'BLOOM_AUTH_SECRET',
|
|
31
|
+
* 'Set this to a 32+ character random string. See examples/.env.example.');
|
|
32
|
+
*/
|
|
33
|
+
export declare function requireEnv(module: string, varName: string, hint?: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Throw if `value` is missing or wrongly typed. Use at the top of public
|
|
36
|
+
* methods that depend on caller-provided data.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* const auth = authClass.get();
|
|
40
|
+
* auth.signToken = (payload) => {
|
|
41
|
+
* requireProp('Auth', 'signToken.payload', payload);
|
|
42
|
+
* // ...
|
|
43
|
+
* };
|
|
44
|
+
*/
|
|
45
|
+
export declare function requireProp<T>(module: string, prop: string, value: T | undefined | null, hint?: string): T;
|
|
46
|
+
/**
|
|
47
|
+
* Lighter-weight version that only warns in development. Use for nice-to-have
|
|
48
|
+
* conventions ("you should pass a 32-char secret, not a 16-char one") that
|
|
49
|
+
* shouldn't crash a production app on startup.
|
|
50
|
+
*/
|
|
51
|
+
export declare function warnInDev(module: string, message: string, slug?: string): void;
|
|
52
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAO3D;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CASjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAC3B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAC3B,IAAI,CAAC,EAAE,MAAM,GACZ,CAAC,CAQH;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAK9E"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Educational runtime errors for @bloomneo/appkit.
|
|
3
|
+
*
|
|
4
|
+
* Modules throw these (instead of plain TypeError or generic Error) so that
|
|
5
|
+
* humans AND AI coding agents reading the error message get an actionable
|
|
6
|
+
* fix-it-now message that names the missing thing AND points at the canonical
|
|
7
|
+
* pattern in AGENTS.md or the relevant example file.
|
|
8
|
+
*
|
|
9
|
+
* Format is intentionally consistent across all 12 modules:
|
|
10
|
+
*
|
|
11
|
+
* [@bloomneo/appkit] <Module> requires <thing>. <reason>.
|
|
12
|
+
* See: <docs URL or example file>
|
|
13
|
+
*
|
|
14
|
+
* Agents that read errors and self-correct will fetch the link and recover
|
|
15
|
+
* on the next iteration. Without this, agents get cryptic "X is undefined"
|
|
16
|
+
* messages and have to guess.
|
|
17
|
+
*
|
|
18
|
+
* @file src/util/errors.ts
|
|
19
|
+
*/
|
|
20
|
+
const DOCS_BASE = 'https://github.com/bloomneo/appkit/blob/main/AGENTS.md';
|
|
21
|
+
export class AppKitError extends Error {
|
|
22
|
+
module;
|
|
23
|
+
docsUrl;
|
|
24
|
+
constructor(module, message, slug) {
|
|
25
|
+
const url = slug ? `${DOCS_BASE}#${slug}` : DOCS_BASE;
|
|
26
|
+
super(`[@bloomneo/appkit] ${module}: ${message}\nSee: ${url}`);
|
|
27
|
+
this.name = 'AppKitError';
|
|
28
|
+
this.module = module;
|
|
29
|
+
this.docsUrl = url;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Throw if a required env var is missing. Use at module init / first .get()
|
|
34
|
+
* call so the error is loud and immediate, not deferred to the first request.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* requireEnv('Auth', 'BLOOM_AUTH_SECRET',
|
|
38
|
+
* 'Set this to a 32+ character random string. See examples/.env.example.');
|
|
39
|
+
*/
|
|
40
|
+
export function requireEnv(module, varName, hint) {
|
|
41
|
+
const value = process.env[varName];
|
|
42
|
+
if (value === undefined || value === '') {
|
|
43
|
+
const msg = hint
|
|
44
|
+
? `requires env var \`${varName}\` to be set. ${hint}`
|
|
45
|
+
: `requires env var \`${varName}\` to be set.`;
|
|
46
|
+
throw new AppKitError(module, msg, 'environment-variables');
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Throw if `value` is missing or wrongly typed. Use at the top of public
|
|
52
|
+
* methods that depend on caller-provided data.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const auth = authClass.get();
|
|
56
|
+
* auth.signToken = (payload) => {
|
|
57
|
+
* requireProp('Auth', 'signToken.payload', payload);
|
|
58
|
+
* // ...
|
|
59
|
+
* };
|
|
60
|
+
*/
|
|
61
|
+
export function requireProp(module, prop, value, hint) {
|
|
62
|
+
if (value === undefined || value === null) {
|
|
63
|
+
const msg = hint
|
|
64
|
+
? `\`${prop}\` is required. ${hint}`
|
|
65
|
+
: `\`${prop}\` is required.`;
|
|
66
|
+
throw new AppKitError(module, msg);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Lighter-weight version that only warns in development. Use for nice-to-have
|
|
72
|
+
* conventions ("you should pass a 32-char secret, not a 16-char one") that
|
|
73
|
+
* shouldn't crash a production app on startup.
|
|
74
|
+
*/
|
|
75
|
+
export function warnInDev(module, message, slug) {
|
|
76
|
+
if (process.env.NODE_ENV === 'production')
|
|
77
|
+
return;
|
|
78
|
+
const url = slug ? `${DOCS_BASE}#${slug}` : DOCS_BASE;
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn(`[@bloomneo/appkit] ${module}: ${message}\nSee: ${url}`);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=errors.js.map
|