@push.rocks/smartconfig 6.0.0

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/readme.md ADDED
@@ -0,0 +1,517 @@
1
+ # @push.rocks/smartconfig 🚀
2
+
3
+ **Supercharge your npm projects with powerful configuration management, tool orchestration, and persistent key-value storage.**
4
+
5
+ ## Install 📦
6
+
7
+ ```bash
8
+ # Using npm
9
+ npm install @push.rocks/smartconfig --save
10
+
11
+ # Using pnpm (recommended)
12
+ pnpm add @push.rocks/smartconfig
13
+ ```
14
+
15
+ ## Overview 🎯
16
+
17
+ `@push.rocks/smartconfig` is your Swiss Army knife for npm project configuration. It eliminates configuration sprawl by centralizing tool settings, providing intelligent key-value storage, and offering powerful environment variable mapping with automatic type conversions.
18
+
19
+ ### Why smartconfig?
20
+
21
+ - **🎛️ Centralized Configuration**: Manage all your tool configs in one `smartconfig.json` file
22
+ - **💾 Persistent Storage**: Smart key-value store with multiple storage strategies
23
+ - **🔐 Environment Mapping**: Sophisticated env var handling with automatic type conversion
24
+ - **🏗️ TypeScript First**: Full type safety and IntelliSense support
25
+ - **⚡ Zero Config**: Works out of the box with sensible defaults
26
+ - **🔄 Reactive**: Built-in change detection and observables
27
+
28
+ ## Core Concepts 🏗️
29
+
30
+ ### 1. Smartconfig Configuration Management
31
+
32
+ Stop scattering configuration across dozens of files. Centralize everything in `smartconfig.json`:
33
+
34
+ ```typescript
35
+ import { Smartconfig } from '@push.rocks/smartconfig';
36
+
37
+ // Initialize with current directory
38
+ const smartconfig = new Smartconfig();
39
+
40
+ // Or specify a custom path
41
+ const smartconfig = new Smartconfig('/path/to/project');
42
+
43
+ // Get merged configuration for any tool
44
+ const eslintConfig = smartconfig.dataFor<EslintConfig>('eslint', {
45
+ // Default values if not in smartconfig.json
46
+ extends: 'standard',
47
+ rules: {}
48
+ });
49
+ ```
50
+
51
+ **smartconfig.json example:**
52
+ ```json
53
+ {
54
+ "eslint": {
55
+ "extends": "@company/eslint-config",
56
+ "rules": {
57
+ "no-console": "warn"
58
+ }
59
+ },
60
+ "prettier": {
61
+ "semi": false,
62
+ "singleQuote": true
63
+ }
64
+ }
65
+ ```
66
+
67
+ ### 2. KeyValueStore - Persistent Data Storage
68
+
69
+ A flexible key-value store that persists data between script executions:
70
+
71
+ ```typescript
72
+ import { KeyValueStore } from '@push.rocks/smartconfig';
73
+
74
+ interface UserSettings {
75
+ username: string;
76
+ apiKey: string;
77
+ preferences: {
78
+ theme: 'light' | 'dark';
79
+ notifications: boolean;
80
+ };
81
+ }
82
+
83
+ // Different storage strategies
84
+ const kvStore = new KeyValueStore<UserSettings>({
85
+ typeArg: 'userHomeDir', // Store in user's home directory
86
+ identityArg: 'myApp',
87
+ mandatoryKeys: ['username', 'apiKey']
88
+ });
89
+
90
+ // Write operations
91
+ await kvStore.writeKey('username', 'john_doe');
92
+ await kvStore.writeKey('preferences', {
93
+ theme: 'dark',
94
+ notifications: true
95
+ });
96
+
97
+ // Read operations
98
+ const username = await kvStore.readKey('username');
99
+ const allData = await kvStore.readAll();
100
+
101
+ // Check for missing mandatory keys
102
+ const missingKeys = await kvStore.getMissingMandatoryKeys();
103
+ if (missingKeys.length > 0) {
104
+ console.log('Missing required configuration:', missingKeys);
105
+ }
106
+
107
+ // Wait for keys to be present
108
+ await kvStore.waitForKeysPresent(['apiKey']);
109
+ ```
110
+
111
+ **Storage Types:**
112
+ - `userHomeDir`: Store in user's home directory
113
+ - `custom`: Specify your own path
114
+ - `ephemeral`: In-memory only (perfect for testing)
115
+
116
+ ### 3. AppData - Advanced Environment Management 🌟
117
+
118
+ The crown jewel of smartconfig - sophisticated environment variable mapping with automatic type conversion:
119
+
120
+ ```typescript
121
+ import { AppData } from '@push.rocks/smartconfig';
122
+
123
+ interface AppConfig {
124
+ apiUrl: string;
125
+ apiKey: string;
126
+ port: number;
127
+ features: {
128
+ analytics: boolean;
129
+ payment: boolean;
130
+ };
131
+ cache: {
132
+ ttl: number;
133
+ redis: {
134
+ host: string;
135
+ password: string;
136
+ };
137
+ };
138
+ }
139
+
140
+ const appData = await AppData.createAndInit<AppConfig>({
141
+ dirPath: '/app/config', // Optional: defaults to smart path selection
142
+ requiredKeys: ['apiKey', 'apiUrl'],
143
+ envMapping: {
144
+ apiUrl: 'API_URL', // Simple mapping
145
+ apiKey: 'hard:development-key-123', // Hardcoded value
146
+ port: 'hard:3000', // Hardcoded number
147
+ features: {
148
+ analytics: 'boolean:ENABLE_ANALYTICS', // Force boolean conversion
149
+ payment: 'hard_boolean:true' // Hardcoded boolean
150
+ },
151
+ cache: {
152
+ ttl: 'json:CACHE_CONFIG', // Parse JSON from env var
153
+ redis: {
154
+ host: 'REDIS_HOST',
155
+ password: 'base64:REDIS_PASSWORD_B64' // Decode base64
156
+ }
157
+ }
158
+ },
159
+ overwriteObject: {
160
+ // Force these values regardless of env vars
161
+ port: 8080
162
+ }
163
+ });
164
+
165
+ const store = await appData.getKvStore();
166
+ const apiUrl = await store.readKey('apiUrl');
167
+ ```
168
+
169
+ ## AppData Special Cases & Conversions 🎯
170
+
171
+ ### Environment Variable Prefixes
172
+
173
+ AppData supports sophisticated type conversion through prefixes:
174
+
175
+ | Prefix | Description | Example | Result |
176
+ |--------|-------------|---------|--------|
177
+ | `hard:` | Hardcoded value | `hard:myvalue` | `"myvalue"` |
178
+ | `hard_boolean:` | Hardcoded boolean | `hard_boolean:true` | `true` |
179
+ | `hard_json:` | Hardcoded JSON | `hard_json:{"key":"value"}` | `{key: "value"}` |
180
+ | `hard_base64:` | Hardcoded base64 | `hard_base64:SGVsbG8=` | `"Hello"` |
181
+ | `boolean:` | Env var as boolean | `boolean:FEATURE_FLAG` | `true/false` |
182
+ | `json:` | Parse env var as JSON | `json:CONFIG_JSON` | Parsed object |
183
+ | `base64:` | Decode env var from base64 | `base64:SECRET_B64` | Decoded string |
184
+
185
+ ### Automatic Suffix Detection
186
+
187
+ Variables ending with certain suffixes get automatic conversion:
188
+
189
+ ```typescript
190
+ {
191
+ envMapping: {
192
+ // Automatically parsed as JSON if MY_CONFIG_JSON="{"enabled":true}"
193
+ config: 'MY_CONFIG_JSON',
194
+
195
+ // Automatically decoded from base64 if SECRET_KEY_BASE64="SGVsbG8="
196
+ secret: 'SECRET_KEY_BASE64'
197
+ }
198
+ }
199
+ ```
200
+
201
+ ### Complex Examples
202
+
203
+ ```typescript
204
+ const appData = await AppData.createAndInit({
205
+ envMapping: {
206
+ // Simple environment variable
207
+ apiUrl: 'API_URL',
208
+
209
+ // Hardcoded values with type conversion
210
+ debugMode: 'hard_boolean:false',
211
+ maxRetries: 'hard:5',
212
+ defaultConfig: 'hard_json:{"timeout":30,"retries":3}',
213
+
214
+ // Environment variables with conversion
215
+ features: 'json:FEATURE_FLAGS', // Expects: {"feature1":true,"feature2":false}
216
+ isProduction: 'boolean:IS_PROD', // Expects: "true" or "false"
217
+ apiSecret: 'base64:API_SECRET', // Expects: base64 encoded string
218
+
219
+ // Nested structures
220
+ database: {
221
+ host: 'DB_HOST',
222
+ port: 'hard:5432',
223
+ credentials: {
224
+ user: 'DB_USER',
225
+ password: 'base64:DB_PASSWORD_ENCODED',
226
+ ssl: 'boolean:DB_USE_SSL'
227
+ }
228
+ }
229
+ },
230
+
231
+ // Override any env mappings
232
+ overwriteObject: {
233
+ debugMode: true, // Force debug mode regardless of env
234
+ database: {
235
+ host: 'localhost' // Force localhost for development
236
+ }
237
+ }
238
+ });
239
+ ```
240
+
241
+ ### Boolean Conversion Rules
242
+
243
+ AppData intelligently handles boolean conversions:
244
+
245
+ 1. **String "true"/"false"**: Converted to boolean
246
+ 2. **With `boolean:` prefix**: Any env var value is converted (`"true"` → `true`, anything else → `false`)
247
+ 3. **With `hard_boolean:` prefix**: Hardcoded boolean value
248
+ 4. **Regular env vars**: Strings remain strings unless prefixed
249
+
250
+ ```typescript
251
+ // Environment: FEATURE_A="true", FEATURE_B="yes", FEATURE_C="1"
252
+ {
253
+ envMapping: {
254
+ featureA: 'FEATURE_A', // Result: "true" (string)
255
+ featureB: 'boolean:FEATURE_B', // Result: false (only "true" → true)
256
+ featureC: 'boolean:FEATURE_C', // Result: false (only "true" → true)
257
+ featureD: 'hard_boolean:true' // Result: true (hardcoded)
258
+ }
259
+ }
260
+ ```
261
+
262
+ ### Static Helper Functions
263
+
264
+ AppData provides convenient static methods for directly accessing and converting environment variables without creating an instance:
265
+
266
+ ```typescript
267
+ import { AppData } from '@push.rocks/smartconfig';
268
+
269
+ // Get environment variable as boolean
270
+ const isEnabled = await AppData.valueAsBoolean('FEATURE_ENABLED');
271
+ // Returns: true if "true", false otherwise
272
+
273
+ // Get environment variable as parsed JSON
274
+ interface Config {
275
+ timeout: number;
276
+ retries: number;
277
+ }
278
+ const config = await AppData.valueAsJson<Config>('SERVICE_CONFIG');
279
+ // Returns: Parsed object or undefined
280
+
281
+ // Get environment variable as base64 decoded string
282
+ const secret = await AppData.valueAsBase64('ENCODED_SECRET');
283
+ // Returns: Decoded string or undefined
284
+
285
+ // Get environment variable as string
286
+ const apiUrl = await AppData.valueAsString('API_URL');
287
+ // Returns: String value or undefined
288
+
289
+ // Get environment variable as number
290
+ const port = await AppData.valueAsNumber('PORT');
291
+ // Returns: Number value or undefined
292
+ ```
293
+
294
+ These static methods are perfect for:
295
+ - Quick environment variable access without setup
296
+ - Simple type conversions in utility functions
297
+ - One-off configuration checks
298
+ - Scenarios where you don't need the full AppData instance
299
+
300
+ ## Advanced Patterns 🎨
301
+
302
+ ### Reactive Configuration
303
+
304
+ Subscribe to configuration changes:
305
+
306
+ ```typescript
307
+ const kvStore = new KeyValueStore<Config>({
308
+ typeArg: 'custom',
309
+ identityArg: 'myApp'
310
+ });
311
+
312
+ // Subscribe to changes
313
+ kvStore.changeSubject.subscribe((newData) => {
314
+ console.log('Configuration changed:', newData);
315
+ });
316
+
317
+ // Changes trigger notifications
318
+ await kvStore.writeKey('theme', 'dark');
319
+ ```
320
+
321
+ ### Testing with Ephemeral Storage
322
+
323
+ Perfect for unit tests - no file system pollution:
324
+
325
+ ```typescript
326
+ const testStore = new KeyValueStore<TestData>({
327
+ typeArg: 'ephemeral',
328
+ identityArg: 'test'
329
+ });
330
+
331
+ // All operations work normally, but nothing persists to disk
332
+ await testStore.writeKey('testKey', 'testValue');
333
+ ```
334
+
335
+ ### Smart Path Resolution
336
+
337
+ AppData automatically selects the best storage location:
338
+
339
+ 1. Checks for `/app/data` (containerized environments)
340
+ 2. Falls back to `/data` (alternate container path)
341
+ 3. Uses `.nogit/appdata` (local development)
342
+
343
+ ```typescript
344
+ // Automatic path selection
345
+ const appData = await AppData.createAndInit({
346
+ // No dirPath specified - smart detection
347
+ requiredKeys: ['apiKey']
348
+ });
349
+
350
+ // Or force ephemeral for testing
351
+ const testData = await AppData.createAndInit({
352
+ ephemeral: true, // No disk persistence
353
+ requiredKeys: ['testKey']
354
+ });
355
+ ```
356
+
357
+ ### Waiting for Configuration
358
+
359
+ Block until required configuration is available:
360
+
361
+ ```typescript
362
+ const appData = await AppData.createAndInit<Config>({
363
+ requiredKeys: ['apiKey', 'apiUrl']
364
+ });
365
+
366
+ // Wait for specific key
367
+ const apiKey = await appData.waitForAndGetKey('apiKey');
368
+
369
+ // Check missing keys
370
+ const missingKeys = await appData.logMissingKeys();
371
+ // Logs: "The following mandatory keys are missing in the appdata:
372
+ // -> apiKey,
373
+ // -> apiUrl"
374
+ ```
375
+
376
+ ## Real-World Example 🌍
377
+
378
+ Here's a complete example of a CLI tool using smartconfig:
379
+
380
+ ```typescript
381
+ import { Smartconfig, AppData, KeyValueStore } from '@push.rocks/smartconfig';
382
+
383
+ interface CliConfig {
384
+ githubToken: string;
385
+ openaiKey: string;
386
+ model: 'gpt-3' | 'gpt-4';
387
+ cache: {
388
+ enabled: boolean;
389
+ ttl: number;
390
+ };
391
+ }
392
+
393
+ class MyCLI {
394
+ private smartconfig: Smartconfig;
395
+ private appData: AppData<CliConfig>;
396
+ private cache: KeyValueStore<{[key: string]: any}>;
397
+
398
+ async initialize() {
399
+ // Load tool configuration
400
+ this.smartconfig = new Smartconfig();
401
+ const config = this.smartconfig.dataFor<any>('mycli', {
402
+ defaultModel: 'gpt-3'
403
+ });
404
+
405
+ // Setup app data with env mapping
406
+ this.appData = await AppData.createAndInit<CliConfig>({
407
+ requiredKeys: ['githubToken', 'openaiKey'],
408
+ envMapping: {
409
+ githubToken: 'GITHUB_TOKEN',
410
+ openaiKey: 'base64:OPENAI_KEY_ENCODED',
411
+ model: 'hard:gpt-4',
412
+ cache: {
413
+ enabled: 'boolean:ENABLE_CACHE',
414
+ ttl: 'hard:3600'
415
+ }
416
+ }
417
+ });
418
+
419
+ // Initialize cache
420
+ this.cache = new KeyValueStore({
421
+ typeArg: 'userHomeDir',
422
+ identityArg: 'mycli-cache'
423
+ });
424
+
425
+ // Check for missing configuration
426
+ const missingKeys = await this.appData.logMissingKeys();
427
+ if (missingKeys.length > 0) {
428
+ console.error('Please configure the missing keys');
429
+ process.exit(1);
430
+ }
431
+ }
432
+
433
+ async run() {
434
+ await this.initialize();
435
+
436
+ const config = await this.appData.getKvStore();
437
+ const settings = await config.readAll();
438
+
439
+ console.log(`Using model: ${settings.model}`);
440
+ console.log(`Cache enabled: ${settings.cache.enabled}`);
441
+
442
+ // Use the configuration...
443
+ }
444
+ }
445
+
446
+ // Run the CLI
447
+ const cli = new MyCLI();
448
+ cli.run();
449
+ ```
450
+
451
+ ## API Reference 📚
452
+
453
+ ### Smartconfig Class
454
+
455
+ ```typescript
456
+ new Smartconfig(cwdArg?: string)
457
+ ```
458
+ - `cwdArg`: Optional working directory path
459
+
460
+ **Methods:**
461
+ - `dataFor<T>(toolName: string, defaultOptions: T): T` - Get merged configuration
462
+
463
+ ### KeyValueStore Class
464
+
465
+ ```typescript
466
+ new KeyValueStore<T>(options: {
467
+ typeArg: 'custom' | 'userHomeDir' | 'ephemeral';
468
+ identityArg: string;
469
+ customPath?: string;
470
+ mandatoryKeys?: Array<keyof T>;
471
+ })
472
+ ```
473
+
474
+ **Methods:**
475
+ - `readKey(key: string): Promise<T>` - Read single value
476
+ - `writeKey(key: string, value: T): Promise<void>` - Write single value
477
+ - `readAll(): Promise<T>` - Read all values
478
+ - `writeAll(data: T): Promise<void>` - Write all values
479
+ - `deleteKey(key: string): Promise<void>` - Delete a key
480
+ - `getMissingMandatoryKeys(): Promise<string[]>` - Check missing required keys
481
+ - `waitForKeysPresent(keys: string[]): Promise<void>` - Wait for keys
482
+
483
+ ### AppData Class
484
+
485
+ ```typescript
486
+ await AppData.createAndInit<T>(options: {
487
+ dirPath?: string;
488
+ requiredKeys?: Array<keyof T>;
489
+ ephemeral?: boolean;
490
+ envMapping?: PartialDeep<T>;
491
+ overwriteObject?: PartialDeep<T>;
492
+ })
493
+ ```
494
+
495
+ **Methods:**
496
+ - `getKvStore(): Promise<KeyValueStore<T>>` - Get underlying store
497
+ - `logMissingKeys(): Promise<Array<keyof T>>` - Log and return missing keys
498
+ - `waitForAndGetKey<K>(key: K): Promise<T[K]>` - Wait for and retrieve key
499
+
500
+ ## License and Legal Information
501
+
502
+ This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
503
+
504
+ **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
505
+
506
+ ### Trademarks
507
+
508
+ This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
509
+
510
+ ### Company Information
511
+
512
+ Task Venture Capital GmbH
513
+ Registered at District court Bremen HRB 35230 HB, Germany
514
+
515
+ For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
516
+
517
+ By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.