@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.
Files changed (118) 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 +29 -29
  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 +11 -11
  28. package/dist/config/index.d.ts +3 -3
  29. package/dist/config/index.js +4 -4
  30. package/dist/database/adapters/mongoose.d.ts +4 -4
  31. package/dist/database/adapters/mongoose.js +7 -7
  32. package/dist/database/adapters/prisma.d.ts +4 -4
  33. package/dist/database/adapters/prisma.js +7 -7
  34. package/dist/database/defaults.d.ts +1 -1
  35. package/dist/database/defaults.js +4 -4
  36. package/dist/database/index.js +2 -2
  37. package/dist/database/index.js.map +1 -1
  38. package/dist/email/defaults.js +26 -26
  39. package/dist/email/index.js +7 -7
  40. package/dist/email/strategies/resend.js +1 -1
  41. package/dist/error/defaults.d.ts +1 -1
  42. package/dist/error/defaults.js +13 -13
  43. package/dist/error/error.d.ts +12 -0
  44. package/dist/error/error.d.ts.map +1 -1
  45. package/dist/error/error.js +19 -0
  46. package/dist/error/error.js.map +1 -1
  47. package/dist/error/index.d.ts +14 -3
  48. package/dist/error/index.d.ts.map +1 -1
  49. package/dist/error/index.js +14 -3
  50. package/dist/error/index.js.map +1 -1
  51. package/dist/event/defaults.js +35 -35
  52. package/dist/event/index.js +7 -7
  53. package/dist/logger/defaults.d.ts +1 -1
  54. package/dist/logger/defaults.js +40 -40
  55. package/dist/logger/index.d.ts +1 -0
  56. package/dist/logger/index.d.ts.map +1 -1
  57. package/dist/logger/index.js.map +1 -1
  58. package/dist/logger/logger.d.ts +8 -0
  59. package/dist/logger/logger.d.ts.map +1 -1
  60. package/dist/logger/logger.js +13 -3
  61. package/dist/logger/logger.js.map +1 -1
  62. package/dist/logger/transports/console.js +2 -2
  63. package/dist/logger/transports/http.d.ts +1 -1
  64. package/dist/logger/transports/http.js +2 -2
  65. package/dist/logger/transports/webhook.d.ts +1 -1
  66. package/dist/logger/transports/webhook.js +3 -3
  67. package/dist/queue/defaults.d.ts +2 -2
  68. package/dist/queue/defaults.js +38 -38
  69. package/dist/security/defaults.d.ts +1 -1
  70. package/dist/security/defaults.js +30 -30
  71. package/dist/security/index.d.ts +1 -1
  72. package/dist/security/index.js +3 -3
  73. package/dist/security/security.d.ts +1 -1
  74. package/dist/security/security.js +4 -4
  75. package/dist/storage/defaults.js +26 -26
  76. package/dist/storage/index.js +3 -3
  77. package/dist/util/defaults.d.ts +1 -1
  78. package/dist/util/defaults.js +41 -41
  79. package/dist/util/env.d.ts +35 -0
  80. package/dist/util/env.d.ts.map +1 -0
  81. package/dist/util/env.js +50 -0
  82. package/dist/util/env.js.map +1 -0
  83. package/dist/util/errors.d.ts +52 -0
  84. package/dist/util/errors.d.ts.map +1 -0
  85. package/dist/util/errors.js +82 -0
  86. package/dist/util/errors.js.map +1 -0
  87. package/dist/util/util.js +1 -1
  88. package/examples/.env.example +80 -0
  89. package/examples/README.md +16 -0
  90. package/examples/auth.ts +228 -0
  91. package/examples/cache.ts +36 -0
  92. package/examples/config.ts +45 -0
  93. package/examples/database.ts +69 -0
  94. package/examples/email.ts +53 -0
  95. package/examples/error.ts +50 -0
  96. package/examples/event.ts +42 -0
  97. package/examples/logger.ts +41 -0
  98. package/examples/queue.ts +58 -0
  99. package/examples/security.ts +46 -0
  100. package/examples/storage.ts +44 -0
  101. package/examples/util.ts +47 -0
  102. package/llms.txt +591 -0
  103. package/package.json +19 -10
  104. package/src/auth/README.md +850 -0
  105. package/src/cache/README.md +756 -0
  106. package/src/config/README.md +604 -0
  107. package/src/database/README.md +818 -0
  108. package/src/email/README.md +759 -0
  109. package/src/error/README.md +660 -0
  110. package/src/event/README.md +729 -0
  111. package/src/logger/README.md +435 -0
  112. package/src/queue/README.md +851 -0
  113. package/src/security/README.md +612 -0
  114. package/src/storage/README.md +1008 -0
  115. package/src/util/README.md +955 -0
  116. package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
  117. package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
  118. package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
@@ -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
  }
@@ -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('[VoilaJSX AppKit] No cloud storage configured in production. ' +
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.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 [
@@ -107,8 +107,8 @@ function parseAllowedTypes() {
107
107
  }
108
108
  if (envTypes === '*') {
109
109
  if (process.env.NODE_ENV === 'production') {
110
- console.warn('[VoilaJSX AppKit] SECURITY WARNING: All file types allowed in production. ' +
111
- 'Set VOILA_STORAGE_ALLOWED_TYPES to specific types for security.');
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.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();
@@ -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(`[VoilaJSX AppKit] Unusual NODE_ENV: "${nodeEnv}". ` +
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.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
- console.warn(`[VoilaJSX AppKit] Potentially unsafe storage directory: "${dir}". ` +
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
  /**
@@ -232,15 +232,15 @@ function validateLocalConfig() {
232
232
  function validateProductionConfig() {
233
233
  const strategy = detectStorageStrategy();
234
234
  if (strategy === 'local') {
235
- console.warn('[VoilaJSX AppKit] Using local storage in production. ' +
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.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
- console.warn('[VoilaJSX AppKit] No CDN URL configured in production. ' +
243
- 'Set VOILA_STORAGE_CDN_URL for better performance.');
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('[VoilaJSX AppKit] Local storage not recommended for production');
342
+ console.warn('[Bloomneo AppKit] Local storage not recommended for production');
343
343
  }
344
344
  return {
345
345
  strategy,
@@ -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('[VoilaJSX AppKit] Using local storage in production. ' +
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('[VoilaJSX AppKit] No cloud storage configured in production. ' +
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('[VoilaJSX AppKit] Storage configuration validation failed:', error.message);
113
+ console.error('[Bloomneo AppKit] Storage configuration validation failed:', error.message);
114
114
  }
115
115
  }
116
116
  /**
@@ -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
@@ -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 VOILA_UTIL_* environment variables
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.VOILA_UTIL_CACHE !== 'false' && !isTest,
27
- maxSize: parseInt(process.env.VOILA_UTIL_CACHE_SIZE || '1000'),
28
- ttl: parseInt(process.env.VOILA_UTIL_CACHE_TTL || '300000'), // 5 minutes
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.VOILA_UTIL_PERFORMANCE !== 'false',
33
- memoization: process.env.VOILA_UTIL_MEMOIZATION !== 'false' && !isTest,
34
- largeArrayThreshold: parseInt(process.env.VOILA_UTIL_ARRAY_THRESHOLD || '10000'),
35
- chunkSizeLimit: parseInt(process.env.VOILA_UTIL_CHUNK_LIMIT || '100000'),
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.VOILA_UTIL_DEBUG === 'true' || isDevelopment,
40
- logOperations: process.env.VOILA_UTIL_LOG_OPS === 'true' || isDevelopment,
41
- trackPerformance: process.env.VOILA_UTIL_TRACK_PERF === 'true' || isDevelopment,
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.VOILA_UTIL_SLUGIFY_LOWERCASE !== 'false',
46
- strict: process.env.VOILA_UTIL_SLUGIFY_STRICT === 'true',
47
- locale: process.env.VOILA_UTIL_LOCALE || 'en',
48
- replacement: process.env.VOILA_UTIL_SLUGIFY_REPLACEMENT || '-',
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.VOILA_UTIL_LOCALE || 'en-US',
53
- currency: process.env.VOILA_UTIL_CURRENCY || 'USD',
54
- dateFormat: process.env.VOILA_UTIL_DATE_FORMAT || 'YYYY-MM-DD',
55
- numberPrecision: parseInt(process.env.VOILA_UTIL_NUMBER_PRECISION || '2'),
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.VOILA_UTIL_CACHE_SIZE;
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 VOILA_UTIL_CACHE_SIZE: "${cacheSize}". Must be a positive number.`);
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(`[VoilaJSX AppKit] Large cache size: ${cacheSizeNum}. This may impact memory usage.`);
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.VOILA_UTIL_CACHE_TTL;
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 VOILA_UTIL_CACHE_TTL: "${cacheTTL}". Must be a positive number (milliseconds).`);
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.VOILA_UTIL_ARRAY_THRESHOLD;
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 VOILA_UTIL_ARRAY_THRESHOLD: "${arrayThreshold}". Must be a positive number.`);
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.VOILA_UTIL_CHUNK_LIMIT;
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 VOILA_UTIL_CHUNK_LIMIT: "${chunkLimit}". Must be a positive number.`);
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.VOILA_UTIL_NUMBER_PRECISION;
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 VOILA_UTIL_NUMBER_PRECISION: "${numberPrecision}". Must be between 0 and 20.`);
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.VOILA_UTIL_LOCALE;
118
+ const locale = process.env.BLOOM_UTIL_LOCALE;
119
119
  if (locale && !isValidLocale(locale)) {
120
- console.warn(`[VoilaJSX AppKit] Invalid locale: "${locale}". Using default 'en-US'.`);
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.VOILA_UTIL_CURRENCY;
123
+ const currency = process.env.BLOOM_UTIL_CURRENCY;
124
124
  if (currency && !isValidCurrency(currency)) {
125
- console.warn(`[VoilaJSX AppKit] Invalid currency: "${currency}". Using default 'USD'.`);
125
+ console.warn(`[Bloomneo AppKit] Invalid currency: "${currency}". Using default 'USD'.`);
126
126
  }
127
127
  // Validate slugify replacement
128
- const replacement = process.env.VOILA_UTIL_SLUGIFY_REPLACEMENT;
128
+ const replacement = process.env.BLOOM_UTIL_SLUGIFY_REPLACEMENT;
129
129
  if (replacement && replacement.length > 5) {
130
- console.warn(`[VoilaJSX AppKit] Long slugify replacement: "${replacement}". Consider using shorter replacement.`);
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.VOILA_UTIL_DEBUG === 'true') {
135
- console.warn('[VoilaJSX AppKit] Debug mode enabled in production. This may impact performance.');
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.VOILA_UTIL_LOG_OPS === 'true') {
138
- console.warn('[VoilaJSX AppKit] Operation logging enabled in production. This may impact performance.');
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(`[VoilaJSX AppKit] Unusual NODE_ENV: "${nodeEnv}". ` +
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"}
@@ -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