@bloomneo/appkit 1.5.1 → 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.
Files changed (111) hide show
  1. package/AGENTS.md +195 -0
  2. package/CHANGELOG.md +253 -0
  3. package/README.md +147 -799
  4. package/bin/commands/generate.js +7 -7
  5. package/cookbook/README.md +26 -0
  6. package/cookbook/api-key-service.ts +106 -0
  7. package/cookbook/auth-protected-crud.ts +112 -0
  8. package/cookbook/file-upload-pipeline.ts +113 -0
  9. package/cookbook/multi-tenant-saas.ts +87 -0
  10. package/cookbook/real-time-chat.ts +121 -0
  11. package/dist/auth/auth.d.ts +21 -4
  12. package/dist/auth/auth.d.ts.map +1 -1
  13. package/dist/auth/auth.js +56 -44
  14. package/dist/auth/auth.js.map +1 -1
  15. package/dist/auth/defaults.d.ts +1 -1
  16. package/dist/auth/defaults.js +35 -35
  17. package/dist/cache/cache.d.ts +29 -6
  18. package/dist/cache/cache.d.ts.map +1 -1
  19. package/dist/cache/cache.js +72 -44
  20. package/dist/cache/cache.js.map +1 -1
  21. package/dist/cache/defaults.js +25 -25
  22. package/dist/cache/index.d.ts +19 -10
  23. package/dist/cache/index.d.ts.map +1 -1
  24. package/dist/cache/index.js +21 -18
  25. package/dist/cache/index.js.map +1 -1
  26. package/dist/config/defaults.d.ts +1 -1
  27. package/dist/config/defaults.js +8 -8
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.js +2 -2
  31. package/dist/database/adapters/prisma.js +2 -2
  32. package/dist/database/defaults.d.ts +1 -1
  33. package/dist/database/defaults.js +4 -4
  34. package/dist/database/index.js +2 -2
  35. package/dist/database/index.js.map +1 -1
  36. package/dist/email/defaults.js +20 -20
  37. package/dist/error/defaults.d.ts +1 -1
  38. package/dist/error/defaults.js +12 -12
  39. package/dist/error/error.d.ts +12 -0
  40. package/dist/error/error.d.ts.map +1 -1
  41. package/dist/error/error.js +19 -0
  42. package/dist/error/error.js.map +1 -1
  43. package/dist/error/index.d.ts +14 -3
  44. package/dist/error/index.d.ts.map +1 -1
  45. package/dist/error/index.js +14 -3
  46. package/dist/error/index.js.map +1 -1
  47. package/dist/event/defaults.js +30 -30
  48. package/dist/logger/defaults.d.ts +1 -1
  49. package/dist/logger/defaults.js +40 -40
  50. package/dist/logger/index.d.ts +1 -0
  51. package/dist/logger/index.d.ts.map +1 -1
  52. package/dist/logger/index.js.map +1 -1
  53. package/dist/logger/logger.d.ts +8 -0
  54. package/dist/logger/logger.d.ts.map +1 -1
  55. package/dist/logger/logger.js +13 -3
  56. package/dist/logger/logger.js.map +1 -1
  57. package/dist/logger/transports/console.js +1 -1
  58. package/dist/logger/transports/http.d.ts +1 -1
  59. package/dist/logger/transports/http.js +1 -1
  60. package/dist/logger/transports/webhook.d.ts +1 -1
  61. package/dist/logger/transports/webhook.js +1 -1
  62. package/dist/queue/defaults.d.ts +2 -2
  63. package/dist/queue/defaults.js +38 -38
  64. package/dist/security/defaults.d.ts +1 -1
  65. package/dist/security/defaults.js +29 -29
  66. package/dist/security/index.d.ts +1 -1
  67. package/dist/security/index.js +3 -3
  68. package/dist/security/security.d.ts +1 -1
  69. package/dist/security/security.js +4 -4
  70. package/dist/storage/defaults.js +19 -19
  71. package/dist/util/defaults.d.ts +1 -1
  72. package/dist/util/defaults.js +34 -34
  73. package/dist/util/env.d.ts +35 -0
  74. package/dist/util/env.d.ts.map +1 -0
  75. package/dist/util/env.js +50 -0
  76. package/dist/util/env.js.map +1 -0
  77. package/dist/util/errors.d.ts +52 -0
  78. package/dist/util/errors.d.ts.map +1 -0
  79. package/dist/util/errors.js +82 -0
  80. package/dist/util/errors.js.map +1 -0
  81. package/examples/.env.example +80 -0
  82. package/examples/README.md +16 -0
  83. package/examples/auth.ts +228 -0
  84. package/examples/cache.ts +36 -0
  85. package/examples/config.ts +45 -0
  86. package/examples/database.ts +69 -0
  87. package/examples/email.ts +53 -0
  88. package/examples/error.ts +50 -0
  89. package/examples/event.ts +42 -0
  90. package/examples/logger.ts +41 -0
  91. package/examples/queue.ts +58 -0
  92. package/examples/security.ts +46 -0
  93. package/examples/storage.ts +44 -0
  94. package/examples/util.ts +47 -0
  95. package/llms.txt +591 -0
  96. package/package.json +19 -10
  97. package/src/auth/README.md +850 -0
  98. package/src/cache/README.md +756 -0
  99. package/src/config/README.md +604 -0
  100. package/src/database/README.md +818 -0
  101. package/src/email/README.md +759 -0
  102. package/src/error/README.md +660 -0
  103. package/src/event/README.md +729 -0
  104. package/src/logger/README.md +435 -0
  105. package/src/queue/README.md +851 -0
  106. package/src/security/README.md +612 -0
  107. package/src/storage/README.md +1008 -0
  108. package/src/util/README.md +955 -0
  109. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  110. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  111. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
@@ -3,12 +3,12 @@
3
3
  * @module @bloomneo/appkit/queue
4
4
  * @file src/queue/defaults.ts
5
5
  *
6
- * @llm-rule WHEN: App startup - need to parse VOILA_QUEUE_* environment variables and detect transports
6
+ * @llm-rule WHEN: App startup - need to parse BLOOM_QUEUE_* environment variables and detect transports
7
7
  * @llm-rule AVOID: Calling multiple times - expensive validation, use lazy loading in get()
8
8
  * @llm-rule NOTE: Called once at startup, cached globally for performance like auth/logging modules
9
9
  */
10
10
  /**
11
- * Get smart defaults using direct VOILA_QUEUE_* environment access
11
+ * Get smart defaults using direct BLOOM_QUEUE_* environment access
12
12
  * @llm-rule WHEN: App startup to get production-ready queue configuration
13
13
  * @llm-rule AVOID: Calling repeatedly - validates environment each time, expensive operation
14
14
  * @llm-rule NOTE: Called once at startup, cached globally for performance
@@ -26,44 +26,44 @@ export function getSmartDefaults() {
26
26
  return {
27
27
  transport,
28
28
  // Core settings - direct env access
29
- concurrency: parseInt(process.env.VOILA_QUEUE_CONCURRENCY || (isProduction ? '10' : '5')),
30
- maxAttempts: parseInt(process.env.VOILA_QUEUE_MAX_ATTEMPTS || '3'),
31
- retryDelay: parseInt(process.env.VOILA_QUEUE_RETRY_DELAY || '5000'),
32
- retryBackoff: process.env.VOILA_QUEUE_RETRY_BACKOFF || 'exponential',
29
+ concurrency: parseInt(process.env.BLOOM_QUEUE_CONCURRENCY || (isProduction ? '10' : '5')),
30
+ maxAttempts: parseInt(process.env.BLOOM_QUEUE_MAX_ATTEMPTS || '3'),
31
+ retryDelay: parseInt(process.env.BLOOM_QUEUE_RETRY_DELAY || '5000'),
32
+ retryBackoff: process.env.BLOOM_QUEUE_RETRY_BACKOFF || 'exponential',
33
33
  // Job management - direct env access
34
- defaultPriority: parseInt(process.env.VOILA_QUEUE_DEFAULT_PRIORITY || '0'),
35
- removeOnComplete: parseInt(process.env.VOILA_QUEUE_REMOVE_COMPLETE || (isProduction ? '100' : '10')),
36
- removeOnFail: parseInt(process.env.VOILA_QUEUE_REMOVE_FAILED || (isProduction ? '500' : '50')),
34
+ defaultPriority: parseInt(process.env.BLOOM_QUEUE_DEFAULT_PRIORITY || '0'),
35
+ removeOnComplete: parseInt(process.env.BLOOM_QUEUE_REMOVE_COMPLETE || (isProduction ? '100' : '10')),
36
+ removeOnFail: parseInt(process.env.BLOOM_QUEUE_REMOVE_FAILED || (isProduction ? '500' : '50')),
37
37
  // Memory transport config - direct env access
38
38
  memory: {
39
- maxJobs: parseInt(process.env.VOILA_QUEUE_MEMORY_MAX_JOBS || (isDevelopment ? '1000' : '100')),
40
- cleanupInterval: parseInt(process.env.VOILA_QUEUE_MEMORY_CLEANUP || '30000'),
39
+ maxJobs: parseInt(process.env.BLOOM_QUEUE_MEMORY_MAX_JOBS || (isDevelopment ? '1000' : '100')),
40
+ cleanupInterval: parseInt(process.env.BLOOM_QUEUE_MEMORY_CLEANUP || '30000'),
41
41
  },
42
42
  // Redis transport config - direct env access
43
43
  redis: {
44
44
  url: process.env.REDIS_URL || null,
45
- keyPrefix: process.env.VOILA_QUEUE_REDIS_PREFIX || 'queue',
46
- maxRetriesPerRequest: parseInt(process.env.VOILA_QUEUE_REDIS_RETRIES || '3'),
47
- retryDelayOnFailover: parseInt(process.env.VOILA_QUEUE_REDIS_FAILOVER_DELAY || '100'),
45
+ keyPrefix: process.env.BLOOM_QUEUE_REDIS_PREFIX || 'queue',
46
+ maxRetriesPerRequest: parseInt(process.env.BLOOM_QUEUE_REDIS_RETRIES || '3'),
47
+ retryDelayOnFailover: parseInt(process.env.BLOOM_QUEUE_REDIS_FAILOVER_DELAY || '100'),
48
48
  },
49
49
  // Database transport config - direct env access
50
50
  database: {
51
51
  url: process.env.DATABASE_URL || null,
52
- tableName: process.env.VOILA_QUEUE_DB_TABLE || 'queue_jobs',
53
- batchSize: parseInt(process.env.VOILA_QUEUE_DB_BATCH || '50'),
54
- pollInterval: parseInt(process.env.VOILA_QUEUE_DB_POLL || (isProduction ? '5000' : '2000')),
52
+ tableName: process.env.BLOOM_QUEUE_DB_TABLE || 'queue_jobs',
53
+ batchSize: parseInt(process.env.BLOOM_QUEUE_DB_BATCH || '50'),
54
+ pollInterval: parseInt(process.env.BLOOM_QUEUE_DB_POLL || (isProduction ? '5000' : '2000')),
55
55
  },
56
56
  // Worker config - direct env access
57
57
  worker: {
58
58
  enabled: workerEnabled,
59
- gracefulShutdownTimeout: parseInt(process.env.VOILA_QUEUE_SHUTDOWN_TIMEOUT || '30000'),
60
- stalledInterval: parseInt(process.env.VOILA_QUEUE_STALLED_INTERVAL || '30000'),
61
- maxStalledCount: parseInt(process.env.VOILA_QUEUE_MAX_STALLED || '1'),
59
+ gracefulShutdownTimeout: parseInt(process.env.BLOOM_QUEUE_SHUTDOWN_TIMEOUT || '30000'),
60
+ stalledInterval: parseInt(process.env.BLOOM_QUEUE_STALLED_INTERVAL || '30000'),
61
+ maxStalledCount: parseInt(process.env.BLOOM_QUEUE_MAX_STALLED || '1'),
62
62
  },
63
63
  // Service identification - direct env access
64
64
  service: {
65
- name: process.env.VOILA_SERVICE_NAME || process.env.npm_package_name || 'app',
66
- version: process.env.VOILA_SERVICE_VERSION || process.env.npm_package_version || '1.0.0',
65
+ name: process.env.BLOOM_SERVICE_NAME || process.env.npm_package_name || 'app',
66
+ version: process.env.BLOOM_SERVICE_VERSION || process.env.npm_package_version || '1.0.0',
67
67
  environment: nodeEnv,
68
68
  },
69
69
  };
@@ -75,7 +75,7 @@ export function getSmartDefaults() {
75
75
  */
76
76
  function getTransport() {
77
77
  // Manual override wins (like auth module pattern)
78
- const manual = process.env.VOILA_QUEUE_TRANSPORT?.toLowerCase();
78
+ const manual = process.env.BLOOM_QUEUE_TRANSPORT?.toLowerCase();
79
79
  if (manual === 'memory' || manual === 'redis' || manual === 'database') {
80
80
  return manual;
81
81
  }
@@ -95,7 +95,7 @@ function getTransport() {
95
95
  */
96
96
  function getWorkerEnabled(isDevelopment) {
97
97
  // Explicit worker mode setting
98
- const workerEnv = process.env.VOILA_QUEUE_WORKER;
98
+ const workerEnv = process.env.BLOOM_QUEUE_WORKER;
99
99
  if (workerEnv !== undefined) {
100
100
  return workerEnv.toLowerCase() === 'true';
101
101
  }
@@ -116,24 +116,24 @@ function getWorkerEnabled(isDevelopment) {
116
116
  */
117
117
  export function validateEnvironment() {
118
118
  // Validate concurrency
119
- const concurrency = process.env.VOILA_QUEUE_CONCURRENCY;
119
+ const concurrency = process.env.BLOOM_QUEUE_CONCURRENCY;
120
120
  if (concurrency && (isNaN(parseInt(concurrency)) || parseInt(concurrency) < 1 || parseInt(concurrency) > 100)) {
121
- throw new Error(`Invalid VOILA_QUEUE_CONCURRENCY: "${concurrency}". Must be number between 1 and 100`);
121
+ throw new Error(`Invalid BLOOM_QUEUE_CONCURRENCY: "${concurrency}". Must be number between 1 and 100`);
122
122
  }
123
123
  // Validate max attempts
124
- const maxAttempts = process.env.VOILA_QUEUE_MAX_ATTEMPTS;
124
+ const maxAttempts = process.env.BLOOM_QUEUE_MAX_ATTEMPTS;
125
125
  if (maxAttempts && (isNaN(parseInt(maxAttempts)) || parseInt(maxAttempts) < 1 || parseInt(maxAttempts) > 10)) {
126
- throw new Error(`Invalid VOILA_QUEUE_MAX_ATTEMPTS: "${maxAttempts}". Must be number between 1 and 10`);
126
+ throw new Error(`Invalid BLOOM_QUEUE_MAX_ATTEMPTS: "${maxAttempts}". Must be number between 1 and 10`);
127
127
  }
128
128
  // Validate retry backoff
129
- const backoff = process.env.VOILA_QUEUE_RETRY_BACKOFF;
129
+ const backoff = process.env.BLOOM_QUEUE_RETRY_BACKOFF;
130
130
  if (backoff && !['fixed', 'exponential'].includes(backoff)) {
131
- throw new Error(`Invalid VOILA_QUEUE_RETRY_BACKOFF: "${backoff}". Must be: fixed, exponential`);
131
+ throw new Error(`Invalid BLOOM_QUEUE_RETRY_BACKOFF: "${backoff}". Must be: fixed, exponential`);
132
132
  }
133
133
  // Validate transport selection
134
- const transport = process.env.VOILA_QUEUE_TRANSPORT;
134
+ const transport = process.env.BLOOM_QUEUE_TRANSPORT;
135
135
  if (transport && !['memory', 'redis', 'database'].includes(transport)) {
136
- throw new Error(`Invalid VOILA_QUEUE_TRANSPORT: "${transport}". Must be: memory, redis, database`);
136
+ throw new Error(`Invalid BLOOM_QUEUE_TRANSPORT: "${transport}". Must be: memory, redis, database`);
137
137
  }
138
138
  // Validate Redis URL if provided
139
139
  const redisUrl = process.env.REDIS_URL;
@@ -146,15 +146,15 @@ export function validateEnvironment() {
146
146
  throw new Error(`Invalid DATABASE_URL: "${dbUrl}". Must be valid database connection string`);
147
147
  }
148
148
  // Validate worker setting
149
- const worker = process.env.VOILA_QUEUE_WORKER;
149
+ const worker = process.env.BLOOM_QUEUE_WORKER;
150
150
  if (worker && !['true', 'false'].includes(worker.toLowerCase())) {
151
- throw new Error(`Invalid VOILA_QUEUE_WORKER: "${worker}". Must be: true, false`);
151
+ throw new Error(`Invalid BLOOM_QUEUE_WORKER: "${worker}". Must be: true, false`);
152
152
  }
153
153
  // Validate numeric values
154
- validateNumericEnv('VOILA_QUEUE_RETRY_DELAY', 1000, 300000); // 1s to 5min
155
- validateNumericEnv('VOILA_QUEUE_MEMORY_MAX_JOBS', 100, 100000); // 100 to 100k
156
- validateNumericEnv('VOILA_QUEUE_DB_POLL', 1000, 60000); // 1s to 1min
157
- validateNumericEnv('VOILA_QUEUE_SHUTDOWN_TIMEOUT', 5000, 120000); // 5s to 2min
154
+ validateNumericEnv('BLOOM_QUEUE_RETRY_DELAY', 1000, 300000); // 1s to 5min
155
+ validateNumericEnv('BLOOM_QUEUE_MEMORY_MAX_JOBS', 100, 100000); // 100 to 100k
156
+ validateNumericEnv('BLOOM_QUEUE_DB_POLL', 1000, 60000); // 1s to 1min
157
+ validateNumericEnv('BLOOM_QUEUE_SHUTDOWN_TIMEOUT', 5000, 120000); // 5s to 2min
158
158
  }
159
159
  /**
160
160
  * Validate numeric environment variable
@@ -48,7 +48,7 @@ export interface SecurityError extends Error {
48
48
  [key: string]: any;
49
49
  }
50
50
  /**
51
- * Gets smart defaults using VOILA_SECURITY_* environment variables
51
+ * Gets smart defaults using BLOOM_SECURITY_* environment variables
52
52
  * @llm-rule WHEN: App startup to get production-ready security configuration
53
53
  * @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
54
54
  * @llm-rule NOTE: Automatically configures CSRF, rate limiting, and encryption from environment
@@ -8,7 +8,7 @@
8
8
  * @llm-rule NOTE: Called once at startup, cached globally for performance
9
9
  */
10
10
  /**
11
- * Gets smart defaults using VOILA_SECURITY_* environment variables
11
+ * Gets smart defaults using BLOOM_SECURITY_* environment variables
12
12
  * @llm-rule WHEN: App startup to get production-ready security configuration
13
13
  * @llm-rule AVOID: Calling repeatedly - expensive validation, cache the result
14
14
  * @llm-rule NOTE: Automatically configures CSRF, rate limiting, and encryption from environment
@@ -22,28 +22,28 @@ export function getSmartDefaults() {
22
22
  return {
23
23
  // CSRF configuration with fallback to auth secret
24
24
  csrf: {
25
- secret: process.env.VOILA_SECURITY_CSRF_SECRET || process.env.VOILA_AUTH_SECRET || '',
26
- tokenField: process.env.VOILA_SECURITY_CSRF_FIELD || '_csrf',
27
- headerField: process.env.VOILA_SECURITY_CSRF_HEADER || 'x-csrf-token',
28
- expiryMinutes: parseInt(process.env.VOILA_SECURITY_CSRF_EXPIRY || '60'),
25
+ secret: process.env.BLOOM_SECURITY_CSRF_SECRET || process.env.BLOOM_AUTH_SECRET || '',
26
+ tokenField: process.env.BLOOM_SECURITY_CSRF_FIELD || '_csrf',
27
+ headerField: process.env.BLOOM_SECURITY_CSRF_HEADER || 'x-csrf-token',
28
+ expiryMinutes: parseInt(process.env.BLOOM_SECURITY_CSRF_EXPIRY || '60'),
29
29
  },
30
30
  // Rate limiting with production-ready defaults
31
31
  rateLimit: {
32
- maxRequests: parseInt(process.env.VOILA_SECURITY_RATE_LIMIT || '100'),
33
- windowMs: parseInt(process.env.VOILA_SECURITY_RATE_WINDOW || String(15 * 60 * 1000)), // 15 minutes
34
- message: process.env.VOILA_SECURITY_RATE_MESSAGE || 'Too many requests, please try again later',
32
+ maxRequests: parseInt(process.env.BLOOM_SECURITY_RATE_LIMIT || '100'),
33
+ windowMs: parseInt(process.env.BLOOM_SECURITY_RATE_WINDOW || String(15 * 60 * 1000)), // 15 minutes
34
+ message: process.env.BLOOM_SECURITY_RATE_MESSAGE || 'Too many requests, please try again later',
35
35
  },
36
36
  // Input sanitization configuration
37
37
  sanitization: {
38
- maxLength: parseInt(process.env.VOILA_SECURITY_MAX_INPUT_LENGTH || '1000'),
39
- allowedTags: process.env.VOILA_SECURITY_ALLOWED_TAGS
40
- ? process.env.VOILA_SECURITY_ALLOWED_TAGS.split(',').map(tag => tag.trim())
38
+ maxLength: parseInt(process.env.BLOOM_SECURITY_MAX_INPUT_LENGTH || '1000'),
39
+ allowedTags: process.env.BLOOM_SECURITY_ALLOWED_TAGS
40
+ ? process.env.BLOOM_SECURITY_ALLOWED_TAGS.split(',').map(tag => tag.trim())
41
41
  : [],
42
- stripAllTags: process.env.VOILA_SECURITY_STRIP_ALL_TAGS === 'true',
42
+ stripAllTags: process.env.BLOOM_SECURITY_STRIP_ALL_TAGS === 'true',
43
43
  },
44
44
  // Encryption configuration with AES-256-GCM
45
45
  encryption: {
46
- key: process.env.VOILA_SECURITY_ENCRYPTION_KEY,
46
+ key: process.env.BLOOM_SECURITY_ENCRYPTION_KEY,
47
47
  algorithm: 'aes-256-gcm',
48
48
  ivLength: 16,
49
49
  tagLength: 16,
@@ -67,52 +67,52 @@ export function getSmartDefaults() {
67
67
  function validateEnvironment() {
68
68
  const nodeEnv = process.env.NODE_ENV || 'development';
69
69
  // Validate CSRF secret in production
70
- const csrfSecret = process.env.VOILA_SECURITY_CSRF_SECRET || process.env.VOILA_AUTH_SECRET;
70
+ const csrfSecret = process.env.BLOOM_SECURITY_CSRF_SECRET || process.env.BLOOM_AUTH_SECRET;
71
71
  if (!csrfSecret && nodeEnv === 'production') {
72
- console.warn('[Bloomneo AppKit] VOILA_SECURITY_CSRF_SECRET not set. ' +
72
+ console.warn('[Bloomneo AppKit] BLOOM_SECURITY_CSRF_SECRET not set. ' +
73
73
  'CSRF protection will not work in production. ' +
74
- 'Set VOILA_SECURITY_CSRF_SECRET or VOILA_AUTH_SECRET environment variable.');
74
+ 'Set BLOOM_SECURITY_CSRF_SECRET or BLOOM_AUTH_SECRET environment variable.');
75
75
  }
76
76
  // Validate encryption key if provided
77
- const encryptionKey = process.env.VOILA_SECURITY_ENCRYPTION_KEY;
77
+ const encryptionKey = process.env.BLOOM_SECURITY_ENCRYPTION_KEY;
78
78
  if (encryptionKey) {
79
79
  validateEncryptionKey(encryptionKey);
80
80
  }
81
81
  // Validate rate limit values
82
- const rateLimit = process.env.VOILA_SECURITY_RATE_LIMIT;
82
+ const rateLimit = process.env.BLOOM_SECURITY_RATE_LIMIT;
83
83
  if (rateLimit) {
84
84
  const rateLimitNum = parseInt(rateLimit);
85
85
  if (isNaN(rateLimitNum) || rateLimitNum <= 0) {
86
- throw new Error(`Invalid VOILA_SECURITY_RATE_LIMIT: "${rateLimit}". Must be a positive number.`);
86
+ throw new Error(`Invalid BLOOM_SECURITY_RATE_LIMIT: "${rateLimit}". Must be a positive number.`);
87
87
  }
88
88
  }
89
- const rateWindow = process.env.VOILA_SECURITY_RATE_WINDOW;
89
+ const rateWindow = process.env.BLOOM_SECURITY_RATE_WINDOW;
90
90
  if (rateWindow) {
91
91
  const rateWindowNum = parseInt(rateWindow);
92
92
  if (isNaN(rateWindowNum) || rateWindowNum <= 0) {
93
- throw new Error(`Invalid VOILA_SECURITY_RATE_WINDOW: "${rateWindow}". Must be a positive number (milliseconds).`);
93
+ throw new Error(`Invalid BLOOM_SECURITY_RATE_WINDOW: "${rateWindow}". Must be a positive number (milliseconds).`);
94
94
  }
95
95
  }
96
96
  // Validate max input length
97
- const maxLength = process.env.VOILA_SECURITY_MAX_INPUT_LENGTH;
97
+ const maxLength = process.env.BLOOM_SECURITY_MAX_INPUT_LENGTH;
98
98
  if (maxLength) {
99
99
  const maxLengthNum = parseInt(maxLength);
100
100
  if (isNaN(maxLengthNum) || maxLengthNum <= 0) {
101
- throw new Error(`Invalid VOILA_SECURITY_MAX_INPUT_LENGTH: "${maxLength}". Must be a positive number.`);
101
+ throw new Error(`Invalid BLOOM_SECURITY_MAX_INPUT_LENGTH: "${maxLength}". Must be a positive number.`);
102
102
  }
103
103
  }
104
104
  // Validate CSRF expiry
105
- const csrfExpiry = process.env.VOILA_SECURITY_CSRF_EXPIRY;
105
+ const csrfExpiry = process.env.BLOOM_SECURITY_CSRF_EXPIRY;
106
106
  if (csrfExpiry) {
107
107
  const csrfExpiryNum = parseInt(csrfExpiry);
108
108
  if (isNaN(csrfExpiryNum) || csrfExpiryNum <= 0) {
109
- throw new Error(`Invalid VOILA_SECURITY_CSRF_EXPIRY: "${csrfExpiry}". Must be a positive number (minutes).`);
109
+ throw new Error(`Invalid BLOOM_SECURITY_CSRF_EXPIRY: "${csrfExpiry}". Must be a positive number (minutes).`);
110
110
  }
111
111
  }
112
112
  // Production-specific warnings
113
113
  if (nodeEnv === 'production') {
114
114
  if (!encryptionKey) {
115
- console.warn('[Bloomneo AppKit] VOILA_SECURITY_ENCRYPTION_KEY not set. ' +
115
+ console.warn('[Bloomneo AppKit] BLOOM_SECURITY_ENCRYPTION_KEY not set. ' +
116
116
  'Data encryption will not be available in production.');
117
117
  }
118
118
  }
@@ -130,16 +130,16 @@ function validateEnvironment() {
130
130
  */
131
131
  function validateEncryptionKey(key) {
132
132
  if (typeof key !== 'string') {
133
- throw new Error('VOILA_SECURITY_ENCRYPTION_KEY must be a string.');
133
+ throw new Error('BLOOM_SECURITY_ENCRYPTION_KEY must be a string.');
134
134
  }
135
135
  // Check if it's a valid hex string
136
136
  if (!/^[0-9a-fA-F]+$/.test(key)) {
137
- throw new Error('VOILA_SECURITY_ENCRYPTION_KEY must be a valid hexadecimal string. ' +
137
+ throw new Error('BLOOM_SECURITY_ENCRYPTION_KEY must be a valid hexadecimal string. ' +
138
138
  'Generate one using: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
139
139
  }
140
140
  // Check length (should be 64 hex characters for 32 bytes)
141
141
  if (key.length !== 64) {
142
- throw new Error(`VOILA_SECURITY_ENCRYPTION_KEY must be 64 hex characters (32 bytes). ` +
142
+ throw new Error(`BLOOM_SECURITY_ENCRYPTION_KEY must be 64 hex characters (32 bytes). ` +
143
143
  `Current length: ${key.length}. ` +
144
144
  `Generate one using: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`);
145
145
  }
@@ -52,7 +52,7 @@ declare function isProduction(): boolean;
52
52
  * Generate a secure encryption key for production use
53
53
  * @llm-rule WHEN: Setting up encryption for the first time or rotating keys
54
54
  * @llm-rule AVOID: Using weak or predictable keys - always use this for key generation
55
- * @llm-rule NOTE: Returns 64-character hex string suitable for VOILA_SECURITY_ENCRYPTION_KEY
55
+ * @llm-rule NOTE: Returns 64-character hex string suitable for BLOOM_SECURITY_ENCRYPTION_KEY
56
56
  */
57
57
  declare function generateKey(): string;
58
58
  /**
@@ -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 VOILA_SECURITY_ENCRYPTION_KEY
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('VOILA_SECURITY_CSRF_SECRET or VOILA_AUTH_SECRET');
116
+ missing.push('BLOOM_SECURITY_CSRF_SECRET or BLOOM_AUTH_SECRET');
117
117
  }
118
118
  if (checks.encryption && !config.encryption.key) {
119
- missing.push('VOILA_SECURITY_ENCRYPTION_KEY');
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 VOILA_SECURITY_ENCRYPTION_KEY
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 VOILA_SECURITY_CSRF_SECRET or VOILA_AUTH_SECRET environment variable', 500);
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 VOILA_SECURITY_ENCRYPTION_KEY environment variable', 500);
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 VOILA_SECURITY_ENCRYPTION_KEY environment variable', 500);
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 VOILA_SECURITY_ENCRYPTION_KEY
334
+ * @llm-rule NOTE: Returns 64-character hex string suitable for BLOOM_SECURITY_ENCRYPTION_KEY
335
335
  */
336
336
  generateKey() {
337
337
  try {
@@ -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.VOILA_STORAGE_DIR || './uploads',
31
- baseUrl: process.env.VOILA_STORAGE_BASE_URL || '/uploads',
32
- maxFileSize: parseInt(process.env.VOILA_STORAGE_MAX_SIZE || '52428800'), // 50MB default
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.VOILA_STORAGE_CREATE_DIRS !== 'false',
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.VOILA_STORAGE_SIGNED_EXPIRY || '3600'), // 1 hour
45
- cdnUrl: process.env.VOILA_STORAGE_CDN_URL,
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.VOILA_STORAGE_SIGNED_EXPIRY || '3600'), // 1 hour
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.VOILA_STORAGE_STRATEGY?.toLowerCase();
73
+ const explicit = process.env.BLOOM_STORAGE_STRATEGY?.toLowerCase();
74
74
  if (explicit && ['local', 's3', 'r2'].includes(explicit)) {
75
75
  return explicit;
76
76
  }
@@ -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.VOILA_STORAGE_ALLOWED_TYPES;
98
+ const envTypes = process.env.BLOOM_STORAGE_ALLOWED_TYPES;
99
99
  if (!envTypes) {
100
100
  // Safe defaults - common web file types
101
101
  return [
@@ -108,7 +108,7 @@ function parseAllowedTypes() {
108
108
  if (envTypes === '*') {
109
109
  if (process.env.NODE_ENV === 'production') {
110
110
  console.warn('[Bloomneo AppKit] SECURITY WARNING: All file types allowed in production. ' +
111
- 'Set VOILA_STORAGE_ALLOWED_TYPES to specific types for security.');
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.VOILA_STORAGE_STRATEGY;
125
+ const strategy = process.env.BLOOM_STORAGE_STRATEGY;
126
126
  if (strategy && !['local', 's3', 'r2'].includes(strategy.toLowerCase())) {
127
- throw new Error(`Invalid VOILA_STORAGE_STRATEGY: "${strategy}". Must be "local", "s3", or "r2"`);
127
+ throw new Error(`Invalid BLOOM_STORAGE_STRATEGY: "${strategy}". Must be "local", "s3", or "r2"`);
128
128
  }
129
129
  // Validate numeric values
130
- validateNumericEnv('VOILA_STORAGE_MAX_SIZE', 1048576, 1073741824); // 1MB to 1GB
131
- validateNumericEnv('VOILA_STORAGE_SIGNED_EXPIRY', 60, 604800); // 1 minute to 7 days
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();
@@ -214,14 +214,14 @@ function validateR2Config() {
214
214
  * Validates local configuration
215
215
  */
216
216
  function validateLocalConfig() {
217
- const dir = process.env.VOILA_STORAGE_DIR;
217
+ const dir = process.env.BLOOM_STORAGE_DIR;
218
218
  if (dir && (dir.includes('..') || dir.startsWith('/') && process.env.NODE_ENV === 'production')) {
219
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.VOILA_STORAGE_BASE_URL;
222
+ const baseUrl = process.env.BLOOM_STORAGE_BASE_URL;
223
223
  if (baseUrl && !baseUrl.startsWith('/') && !isValidUrl(baseUrl)) {
224
- throw new Error(`Invalid VOILA_STORAGE_BASE_URL: "${baseUrl}". Must be a path or valid URL`);
224
+ throw new Error(`Invalid BLOOM_STORAGE_BASE_URL: "${baseUrl}". Must be a path or valid URL`);
225
225
  }
226
226
  }
227
227
  /**
@@ -237,10 +237,10 @@ function validateProductionConfig() {
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.VOILA_STORAGE_CDN_URL || process.env.CLOUDFLARE_R2_CDN_URL;
240
+ const cdnUrl = process.env.BLOOM_STORAGE_CDN_URL || process.env.CLOUDFLARE_R2_CDN_URL;
241
241
  if (!cdnUrl && strategy !== 'local') {
242
242
  console.warn('[Bloomneo AppKit] No CDN URL configured in production. ' +
243
- 'Set VOILA_STORAGE_CDN_URL for better performance.');
243
+ 'Set BLOOM_STORAGE_CDN_URL for better performance.');
244
244
  }
245
245
  }
246
246
  /**
@@ -51,7 +51,7 @@ export interface UtilConfig {
51
51
  environment: EnvironmentConfig;
52
52
  }
53
53
  /**
54
- * Gets smart defaults using VOILA_UTIL_* environment variables
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