@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.
- 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 +25 -25
- 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 +8 -8
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +4 -4
- package/dist/database/adapters/mongoose.js +2 -2
- package/dist/database/adapters/prisma.js +2 -2
- 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 +20 -20
- package/dist/error/defaults.d.ts +1 -1
- package/dist/error/defaults.js +12 -12
- 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 +30 -30
- 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 +1 -1
- package/dist/logger/transports/http.d.ts +1 -1
- package/dist/logger/transports/http.js +1 -1
- package/dist/logger/transports/webhook.d.ts +1 -1
- package/dist/logger/transports/webhook.js +1 -1
- 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 +29 -29
- 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 +19 -19
- package/dist/util/defaults.d.ts +1 -1
- package/dist/util/defaults.js +34 -34
- 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/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/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,69 +72,69 @@ 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
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
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
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
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.
|
|
134
|
+
if (process.env.BLOOM_UTIL_DEBUG === 'true') {
|
|
135
135
|
console.warn('[Bloomneo AppKit] Debug mode enabled in production. This may impact performance.');
|
|
136
136
|
}
|
|
137
|
-
if (process.env.
|
|
137
|
+
if (process.env.BLOOM_UTIL_LOG_OPS === 'true') {
|
|
138
138
|
console.warn('[Bloomneo AppKit] Operation logging enabled in production. This may impact performance.');
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/util/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,SAAS,GAAG,wDAAwD,CAAC;AAE3E,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAS;IACf,OAAO,CAAS;IAEzB,YAAY,MAAc,EAAE,OAAe,EAAE,IAAa;QACxD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACtD,KAAK,CAAC,sBAAsB,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,OAAO,GAAG,GAAG,CAAC;IACrB,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,OAAe,EAAE,IAAa;IACvE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI;YACd,CAAC,CAAC,sBAAsB,OAAO,iBAAiB,IAAI,EAAE;YACtD,CAAC,CAAC,sBAAsB,OAAO,eAAe,CAAC;QACjD,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,uBAAuB,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CACzB,MAAc,EACd,IAAY,EACZ,KAA2B,EAC3B,IAAa;IAEb,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,GAAG,GAAG,IAAI;YACd,CAAC,CAAC,KAAK,IAAI,mBAAmB,IAAI,EAAE;YACpC,CAAC,CAAC,KAAK,IAAI,iBAAiB,CAAC;QAC/B,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,OAAe,EAAE,IAAa;IACtE,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY;QAAE,OAAO;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,sCAAsC;IACtC,OAAO,CAAC,IAAI,CAAC,sBAAsB,MAAM,KAAK,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Bloomneo AppKit — canonical environment template
|
|
2
|
+
#
|
|
3
|
+
# Copy this to `.env` in your project root and fill in real values.
|
|
4
|
+
# All env vars use the BLOOM_ prefix. The legacy VOILA_ prefix from
|
|
5
|
+
# @voilajsx/appkit was removed in 1.5.2 — there is no fallback.
|
|
6
|
+
#
|
|
7
|
+
# Generate secrets with: openssl rand -base64 32
|
|
8
|
+
|
|
9
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
10
|
+
# REQUIRED for production
|
|
11
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
# JWT signing key for authClass.get().signToken() / verifyToken()
|
|
14
|
+
# Minimum 32 characters.
|
|
15
|
+
BLOOM_AUTH_SECRET=replace-me-with-openssl-rand-base64-32
|
|
16
|
+
|
|
17
|
+
# Prisma connection string. Any Prisma-supported URL works.
|
|
18
|
+
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
|
|
19
|
+
|
|
20
|
+
# CSRF token signing for securityClass.get().csrf() / requireCsrf()
|
|
21
|
+
# Minimum 32 characters.
|
|
22
|
+
BLOOM_SECURITY_CSRF_SECRET=replace-me-with-openssl-rand-base64-32
|
|
23
|
+
|
|
24
|
+
# AES-256-GCM encryption key for securityClass.get().encrypt() / decrypt()
|
|
25
|
+
# Exactly 64 hex characters (32 bytes).
|
|
26
|
+
BLOOM_SECURITY_ENCRYPTION_KEY=replace-me-with-openssl-rand-hex-32
|
|
27
|
+
|
|
28
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
29
|
+
# OPTIONAL — auto-scaling kicks in when set
|
|
30
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
# Distributed cache + queue + events (memory by default)
|
|
33
|
+
# REDIS_URL=redis://localhost:6379
|
|
34
|
+
|
|
35
|
+
# Cloud storage (local disk by default)
|
|
36
|
+
# AWS_S3_BUCKET=my-bucket
|
|
37
|
+
# AWS_REGION=us-east-1
|
|
38
|
+
# AWS_ACCESS_KEY_ID=...
|
|
39
|
+
# AWS_SECRET_ACCESS_KEY=...
|
|
40
|
+
|
|
41
|
+
# Cloudflare R2 (alternative to S3)
|
|
42
|
+
# R2_BUCKET=my-bucket
|
|
43
|
+
# R2_ACCOUNT_ID=...
|
|
44
|
+
# R2_ACCESS_KEY_ID=...
|
|
45
|
+
# R2_SECRET_ACCESS_KEY=...
|
|
46
|
+
|
|
47
|
+
# Production email (console transport by default)
|
|
48
|
+
# RESEND_API_KEY=re_...
|
|
49
|
+
# or
|
|
50
|
+
# SMTP_HOST=smtp.example.com
|
|
51
|
+
# SMTP_PORT=587
|
|
52
|
+
# SMTP_USER=...
|
|
53
|
+
# SMTP_PASS=...
|
|
54
|
+
|
|
55
|
+
# Multi-tenant database filtering
|
|
56
|
+
# When set to "auto", database queries auto-filter by tenant_id.
|
|
57
|
+
# BLOOM_DB_TENANT=auto
|
|
58
|
+
|
|
59
|
+
# Per-organization databases (multi-org SaaS)
|
|
60
|
+
# Each ORG_<NAME> is a separate connection string used by databaseClass.org('<name>').get()
|
|
61
|
+
# ORG_ACME=postgresql://acme.aws.com/prod
|
|
62
|
+
# ORG_GLOBEX=postgresql://globex.aws.com/prod
|
|
63
|
+
|
|
64
|
+
# Background queue persistence (memory by default)
|
|
65
|
+
# BLOOM_QUEUE_DB=true # use database-backed queue when REDIS_URL not set
|
|
66
|
+
|
|
67
|
+
# Logger transports
|
|
68
|
+
# BLOOM_LOGGER_FILE_PATH=/var/log/myapp.log
|
|
69
|
+
# BLOOM_LOGGER_HTTP_URL=https://logs.example.com/ingest
|
|
70
|
+
# BLOOM_LOGGER_WEBHOOK_URL=https://hooks.slack.com/services/...
|
|
71
|
+
|
|
72
|
+
# Service identification (used by logger metadata)
|
|
73
|
+
# BLOOM_SERVICE_NAME=myapp
|
|
74
|
+
# BLOOM_SERVICE_VERSION=1.0.0
|
|
75
|
+
|
|
76
|
+
# Default user password for seeds (development only)
|
|
77
|
+
# DEFAULT_USER_PASSWORD=changeme123
|
|
78
|
+
|
|
79
|
+
# Frontend API key (for browser → API auth — generated by `appkit generate app`)
|
|
80
|
+
# BLOOM_FRONTEND_KEY=bloom_<random>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
One minimal `.ts` file per module. Each example is the smallest runnable shape
|
|
4
|
+
of the canonical pattern — copy, modify the data, ship.
|
|
5
|
+
|
|
6
|
+
For composed multi-module patterns (auth-protected CRUD, multi-tenant API,
|
|
7
|
+
file upload + queue, real-time chat), see [`../cookbook/`](../cookbook/).
|
|
8
|
+
|
|
9
|
+
## Conventions
|
|
10
|
+
|
|
11
|
+
- Always import from `@bloomneo/appkit` (or the subpath form).
|
|
12
|
+
- Always use `xxxClass.get()` to obtain a module instance.
|
|
13
|
+
- Always cache the module instance at module scope, not inside route handlers.
|
|
14
|
+
- Use semantic error throwers (`error.badRequest()`, `error.unauthorized()`),
|
|
15
|
+
never `throw new Error(...)` in route handlers.
|
|
16
|
+
- See [`.env.example`](./.env.example) for the canonical environment template.
|