@arikajs/config 0.0.5

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @arikajs/config
2
+
3
+ ## 0.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - feat: implement CSRF protection and standardized middleware architecture
8
+
9
+ - Added session-based CSRF protection middleware.
10
+ - Refactored middleware into project-level stubs for better customization.
11
+ - Standardized auth controller stubs.
12
+ - Enhanced CLI project scaffolding.
13
+ - Added session and localization packages.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ArikaJs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+
2
+ ## Arika Config
3
+
4
+ `@arikajs/config` is a powerful, lightweight, and framework-agnostic configuration management library for Node.js.
5
+
6
+ It provides a unified way to manage environment variables and application configurations using fluent dot-notation access, type safety, schema validation, and automatic environment loading.
7
+
8
+ ---
9
+
10
+ ## ✨ Features
11
+
12
+ - **🎯 Dot-notation access**: Access nested configurations easily (e.g., `app.name`)
13
+ - **🚀 O(1) Config Caching**: Flat cache built on boot for maximum performance
14
+ - **🛡️ Schema Validation**: Validate config types, required fields, enums, and patterns before boot
15
+ - **🌍 Environment Integration**: Built-in `.env` file support with intelligent type casting
16
+ - **🔀 Environment Merging**: Auto-load `database.production.js` on top of `database.js`
17
+ - **📡 Change Listeners**: React to config changes in real-time during setup
18
+ - **🧊 Deep Freeze**: Immutable config after boot — no accidental mutations
19
+ - **🔑 Encrypted Values**: Auto-decrypt `enc:` prefixed secrets using `@arikajs/encryption`
20
+ - **🟦 TypeScript-first**: Full type safety and intellisense out of the box
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ ```bash
27
+ npm install @arikajs/config
28
+ ```
29
+
30
+ ---
31
+
32
+ ## 🚀 Quick Start
33
+
34
+ ### 1️⃣ Basic Usage
35
+
36
+ ```ts
37
+ import { Repository } from '@arikajs/config';
38
+
39
+ const config = new Repository({
40
+ app: {
41
+ name: 'ArikaJS',
42
+ env: 'production'
43
+ },
44
+ database: {
45
+ connection: 'mysql',
46
+ port: 3306
47
+ }
48
+ });
49
+
50
+ // Access values using dot-notation
51
+ const appName = config.get('app.name'); // 'ArikaJS'
52
+ const dbPort = config.get('database.port', 5432); // 3306
53
+ ```
54
+
55
+ ### 2️⃣ Environment Variables
56
+
57
+ ```ts
58
+ import { env } from '@arikajs/config';
59
+
60
+ // Automatically casts 'true'/'false' strings to booleans
61
+ const debug = env('APP_DEBUG', false);
62
+
63
+ // Automatically casts 'null' string to null type
64
+ const key = env('APP_KEY');
65
+ ```
66
+
67
+ ---
68
+
69
+ ## 🚀 O(1) Config Caching (Feature 1)
70
+
71
+ When `markAsBooted()` is called, the entire nested config tree is flattened into a `Map<string, any>`. All subsequent `get()` calls use this flat cache for **O(1)** lookup instead of traversing the object tree.
72
+
73
+ ```ts
74
+ const config = new Repository({ app: { name: 'Arika' } });
75
+
76
+ // Pre-boot: walks nested objects (O(depth))
77
+ config.get('app.name');
78
+
79
+ config.markAsBooted();
80
+
81
+ // Post-boot: instant Map lookup (O(1))
82
+ config.get('app.name'); // ⚡ Blazing fast
83
+ ```
84
+
85
+ This makes a measurable difference in high-traffic applications where `config.get()` is called thousands of times per second.
86
+
87
+ ---
88
+
89
+ ## 🛡️ Schema Validation (Feature 2)
90
+
91
+ Define a schema to validate your config before boot. If validation fails, the app crashes immediately with clear error messages — not deep inside a service.
92
+
93
+ ```ts
94
+ config.defineSchema({
95
+ 'app.name': { type: 'string', required: true },
96
+ 'app.env': {
97
+ type: 'string',
98
+ enum: ['development', 'production', 'testing']
99
+ },
100
+ 'app.key': {
101
+ type: 'string',
102
+ required: true,
103
+ min: 32,
104
+ pattern: /^base64:[A-Za-z0-9+/=]+$/,
105
+ message: 'APP_KEY must be a valid base64 string of at least 32 characters.'
106
+ },
107
+ 'server.port': { type: 'number', min: 1, max: 65535 },
108
+ });
109
+
110
+ // This will throw if validation fails
111
+ config.markAsBooted();
112
+ ```
113
+
114
+ ### Supported Validation Rules
115
+
116
+ | Rule | Description |
117
+ | :--- | :--- |
118
+ | `type` | `'string'`, `'number'`, `'boolean'`, `'object'`, `'array'` |
119
+ | `required` | Fail if value is `undefined` or `null` |
120
+ | `enum` | Value must be one of the listed options |
121
+ | `min` / `max` | For numbers: value range. For strings: character length |
122
+ | `pattern` | RegExp that strings must match |
123
+ | `message` | Custom error message |
124
+ | `children` | Nested schema for objects |
125
+
126
+ ---
127
+
128
+ ## 🔀 Environment Merging (Feature 3)
129
+
130
+ Automatically load environment-specific config overrides without any manual `env()` calls.
131
+
132
+ ```
133
+ config/
134
+ ├── database.js # Base config (loaded first)
135
+ ├── database.production.js # Production overrides (deep-merged on top)
136
+ ├── database.testing.js # Testing overrides
137
+ └── app.js
138
+ ```
139
+
140
+ ```ts
141
+ config.loadConfigDirectory('./config');
142
+ // If NODE_ENV=production:
143
+ // 1. Loads database.js
144
+ // 2. Deep-merges database.production.js on top
145
+ ```
146
+
147
+ Supported environment suffixes: `.development.`, `.production.`, `.staging.`, `.testing.`, `.local.`
148
+
149
+ ---
150
+
151
+ ## 📡 Change Listeners (Feature 4)
152
+
153
+ React to config changes in real-time during the setup phase.
154
+
155
+ ```ts
156
+ // Listen to a specific key
157
+ config.onChange('database.host', (key, newValue, oldValue) => {
158
+ console.log(`Database host changed from ${oldValue} to ${newValue}`);
159
+ });
160
+
161
+ // Listen to any child of a parent key
162
+ config.onChange('database', (key, newValue, oldValue) => {
163
+ console.log(`Database config changed: ${key}`);
164
+ });
165
+
166
+ // Listen to ALL changes globally
167
+ config.onAnyChange((key, newValue, oldValue) => {
168
+ console.log(`Config changed: ${key} = ${newValue}`);
169
+ });
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 🧊 Deep Freeze (Feature 5)
175
+
176
+ After boot, the entire config tree is recursively frozen using `Object.freeze()`. Any accidental mutation will throw an error in strict mode.
177
+
178
+ ```ts
179
+ config.markAsBooted();
180
+
181
+ const db = config.get('database');
182
+ db.host = 'hacked'; // ❌ TypeError: Cannot assign to read only property
183
+ ```
184
+
185
+ This prevents subtle bugs where a service accidentally mutates shared config objects.
186
+
187
+ ---
188
+
189
+ ## 🔑 Encrypted Config Values (Feature 6)
190
+
191
+ Store sensitive values as encrypted strings prefixed with `enc:`. They are automatically decrypted when accessed via `get()`.
192
+
193
+ ```ts
194
+ const config = new Repository({
195
+ secrets: {
196
+ api_key: 'enc:aGVsbG8td29ybGQ=',
197
+ db_password: 'enc:c2VjcmV0LXBhc3N3b3Jk'
198
+ }
199
+ });
200
+
201
+ // Set a decrypter (e.g., using @arikajs/encryption)
202
+ config.setDecrypter((encrypted) => {
203
+ return Encrypter.decrypt(encrypted);
204
+ });
205
+
206
+ config.get('secrets.api_key'); // Returns decrypted value
207
+ ```
208
+
209
+ This works both before and after boot, and integrates seamlessly with `@arikajs/encryption`.
210
+
211
+ ---
212
+
213
+ ## 📅 Advanced Usage
214
+
215
+ ### Loading from a Directory
216
+ ```ts
217
+ config.loadConfigDirectory(path.join(__dirname, 'config'));
218
+ ```
219
+
220
+ ### Global Helpers
221
+ ```ts
222
+ import { config, env } from '@arikajs/config';
223
+
224
+ const timezone = config('app.timezone', 'UTC');
225
+ const debug = env('APP_DEBUG', false);
226
+ ```
227
+
228
+ ---
229
+
230
+ ## 🏗 Architecture
231
+
232
+ ```text
233
+ config/
234
+ ├── src/
235
+ │ ├── EnvLoader.ts
236
+ │ ├── helpers.ts
237
+ │ ├── index.ts
238
+ │ └── Repository.ts
239
+ ├── tests/
240
+ ├── package.json
241
+ ├── tsconfig.json
242
+ └── README.md
243
+ ```
244
+
245
+ ## 📄 License
246
+
247
+ `@arikajs/config` is open-source software licensed under the **MIT License**.
248
+
249
+ ---
250
+
251
+ ## 🧭 Philosophy
252
+
253
+ > "Configuration should be simple to define and impossible to break."
@@ -0,0 +1,10 @@
1
+ export declare class EnvLoader {
2
+ /**
3
+ * Load environment variables from the given path.
4
+ */
5
+ static load(basePath: string): void;
6
+ /**
7
+ * Get an environment variable with intelligent type casting.
8
+ */
9
+ static get<T = any>(key: string, defaultValue?: T): T;
10
+ }
@@ -0,0 +1,44 @@
1
+ import * as dotenv from 'dotenv';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ export class EnvLoader {
5
+ /**
6
+ * Load environment variables from the given path.
7
+ */
8
+ static load(basePath) {
9
+ const envPath = path.join(basePath, '.env');
10
+ if (fs.existsSync(envPath)) {
11
+ dotenv.config({ path: envPath, override: true });
12
+ }
13
+ // Load .env.example if .env doesn't exist (optional, mostly for local dev)
14
+ const examplePath = path.join(basePath, '.env.example');
15
+ if (!fs.existsSync(envPath) && fs.existsSync(examplePath)) {
16
+ dotenv.config({ path: examplePath, override: true });
17
+ }
18
+ }
19
+ /**
20
+ * Get an environment variable with intelligent type casting.
21
+ */
22
+ static get(key, defaultValue) {
23
+ const value = process.env[key];
24
+ if (value === undefined) {
25
+ return defaultValue;
26
+ }
27
+ switch (value.toLowerCase()) {
28
+ case 'true':
29
+ case '(true)':
30
+ return true;
31
+ case 'false':
32
+ case '(false)':
33
+ return false;
34
+ case 'empty':
35
+ case '(empty)':
36
+ return '';
37
+ case 'null':
38
+ case '(null)':
39
+ return null;
40
+ }
41
+ return value;
42
+ }
43
+ }
44
+ //# sourceMappingURL=EnvLoader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnvLoader.js","sourceRoot":"","sources":["../src/EnvLoader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,OAAO,SAAS;IAClB;;OAEG;IACI,MAAM,CAAC,IAAI,CAAC,QAAgB;QAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE5C,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YACzB,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,2EAA2E;QAC3E,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QACxD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACxD,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC;IACL,CAAC;IAED;;OAEG;IACI,MAAM,CAAC,GAAG,CAAU,GAAW,EAAE,YAAgB;QACpD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE/B,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,OAAO,YAAiB,CAAC;QAC7B,CAAC;QAED,QAAQ,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC;YACZ,KAAK,QAAQ;gBACT,OAAO,IAAW,CAAC;YACvB,KAAK,OAAO,CAAC;YACb,KAAK,SAAS;gBACV,OAAO,KAAY,CAAC;YACxB,KAAK,OAAO,CAAC;YACb,KAAK,SAAS;gBACV,OAAO,EAAS,CAAC;YACrB,KAAK,MAAM,CAAC;YACZ,KAAK,QAAQ;gBACT,OAAO,IAAW,CAAC;QAC3B,CAAC;QAED,OAAO,KAAY,CAAC;IACxB,CAAC;CACJ"}
@@ -0,0 +1,96 @@
1
+ export type SchemaRule = {
2
+ type?: 'string' | 'number' | 'boolean' | 'object' | 'array';
3
+ required?: boolean;
4
+ default?: any;
5
+ enum?: any[];
6
+ min?: number;
7
+ max?: number;
8
+ pattern?: RegExp;
9
+ message?: string;
10
+ children?: Record<string, SchemaRule>;
11
+ };
12
+ export type ConfigSchema = Record<string, SchemaRule | Record<string, SchemaRule>>;
13
+ export interface ValidationError {
14
+ key: string;
15
+ message: string;
16
+ }
17
+ export type ConfigChangeListener = (key: string, newValue: any, oldValue: any) => void;
18
+ export declare class Repository {
19
+ private config;
20
+ private booted;
21
+ private flatCache;
22
+ private cacheBuilt;
23
+ private schema;
24
+ private listeners;
25
+ private globalListeners;
26
+ private decrypter;
27
+ constructor(initialConfig?: Record<string, any>);
28
+ /**
29
+ * Load configuration files from the config directory.
30
+ */
31
+ loadConfigDirectory(configPath: string, environment?: string): void;
32
+ private isConfigFile;
33
+ private isEnvSpecificFile;
34
+ private loadFile;
35
+ /**
36
+ * Get a configuration value using dot notation.
37
+ * Uses flat cache for O(1) lookup when booted.
38
+ */
39
+ get<T = any>(key: string, defaultValue?: T): T;
40
+ /**
41
+ * Check if a configuration key exists.
42
+ */
43
+ has(key: string): boolean;
44
+ /**
45
+ * Set a configuration value using dot notation.
46
+ */
47
+ set(key: string, value: any): void;
48
+ /**
49
+ * Get all configuration.
50
+ */
51
+ all(): Record<string, any>;
52
+ /**
53
+ * Mark the repository as booted (read-only).
54
+ * Builds the flat cache and deep-freezes the config for immutability.
55
+ */
56
+ markAsBooted(): void;
57
+ /**
58
+ * Check if the repository has been booted.
59
+ */
60
+ isBooted(): boolean;
61
+ /**
62
+ * Build a flat Map of all dot-notation keys for O(1) lookups.
63
+ */
64
+ private buildFlatCache;
65
+ /**
66
+ * Define a validation schema for the configuration.
67
+ */
68
+ defineSchema(schema: ConfigSchema): this;
69
+ /**
70
+ * Validate the current config against the defined schema.
71
+ */
72
+ validate(): ValidationError[];
73
+ private validateSchema;
74
+ /**
75
+ * Register a listener for changes to a specific config key.
76
+ */
77
+ onChange(key: string, listener: ConfigChangeListener): this;
78
+ /**
79
+ * Register a global listener for ALL config changes.
80
+ */
81
+ onAnyChange(listener: ConfigChangeListener): this;
82
+ private notifyListeners;
83
+ /**
84
+ * Recursively freeze an object to prevent any mutations.
85
+ */
86
+ private deepFreeze;
87
+ /**
88
+ * Set a decrypter function for auto-decrypting `enc:` prefixed values.
89
+ */
90
+ setDecrypter(fn: (encryptedValue: string) => string): this;
91
+ private decrypt;
92
+ /**
93
+ * Deep merge two objects. Source values override target values.
94
+ */
95
+ private deepMerge;
96
+ }