|
1 | | -= preference-injector |
2 | | -Jonathan D.A. Jewell <jonathan.jewell@gmail.com> |
3 | | -:toc: macro |
4 | | -:icons: font |
5 | | -:source-highlighter: rouge |
6 | | -:experimental: |
7 | | -:url-github: https://github.com/hyperpolymath/preference-injector |
8 | | -:url-gitlab: https://gitlab.com/hyperpolymath/preference-injector |
9 | | -:url-bitbucket: https://bitbucket.org/hyperpolymath/preference-injector |
10 | | -:url-codeberg: https://codeberg.org/hyperpolymath/preference-injector |
| 1 | += Preference Injector |
11 | 2 |
|
12 | | -User preference injection for testing |
| 3 | +A powerful, type-safe preference injection system for dynamic configuration management in Node.js and TypeScript applications. |
13 | 4 |
|
14 | | -image:https://img.shields.io/badge/RSR-Certified-gold[RSR Certified] |
15 | | -image:https://img.shields.io/badge/License-AGPL%20v3-blue[License] |
| 5 | +== Features |
16 | 6 |
|
17 | | -toc::[] |
| 7 | +- *Multiple Providers*: File-based, environment variables, API, and in-memory storage |
| 8 | +- *Priority System*: Resolve conflicts between providers using customizable strategies |
| 9 | +- *Type Safety*: Full TypeScript support with generic type helpers |
| 10 | +- *Caching*: LRU and TTL caching strategies for performance |
| 11 | +- *Validation*: Schema-based and custom validation rules |
| 12 | +- *Encryption*: AES-256-GCM encryption for sensitive preferences |
| 13 | +- *Audit Logging*: Track all preference operations |
| 14 | +- *Migrations*: Version and migrate preference schemas |
| 15 | +- *React Integration*: Hooks and context providers |
| 16 | +- *Express Middleware*: RESTful API and request helpers |
| 17 | +- *CLI Tool*: Command-line interface for preference management |
18 | 18 |
|
19 | | -== Overview |
| 19 | +== Installation |
20 | 20 |
|
21 | | -preference-injector is part of the link:https://rhodium.sh[Rhodium Standard] (RSR) ecosystem. |
| 21 | +```bash |
| 22 | +npm install @hyperpolymath/preference-injector |
| 23 | +``` |
22 | 24 |
|
23 | | -Domain: *software-development* |
| 25 | +== Quick Start |
24 | 26 |
|
25 | | -== Installation |
| 27 | +```typescript |
| 28 | +import { PreferenceInjector, MemoryProvider } from '@hyperpolymath/preference-injector'; |
26 | 29 |
|
27 | | -[source,bash] |
28 | | ----- |
29 | | -# Clone from GitHub (primary) |
30 | | -git clone {url-github} |
| 30 | +// Create an injector with an in-memory provider |
| 31 | +const injector = new PreferenceInjector({ |
| 32 | + providers: [new MemoryProvider()], |
| 33 | +}); |
31 | 34 |
|
32 | | -# Or from mirrors |
33 | | -git clone {url-gitlab} |
34 | | -git clone {url-codeberg} |
35 | | ----- |
| 35 | +await injector.initialize(); |
36 | 36 |
|
37 | | -== RSR Stack |
| 37 | +// Set preferences |
| 38 | +await injector.set('theme', 'dark'); |
| 39 | +await injector.set('fontSize', 14); |
38 | 40 |
|
39 | | -This project follows RSR conventions: |
| 41 | +// Get preferences |
| 42 | +const theme = await injector.get('theme'); |
| 43 | +console.log('Theme:', theme); // 'dark' |
40 | 44 |
|
41 | | -* ✅ ReScript for frontend/logic |
42 | | -* ✅ Deno for JS runtime |
43 | | -* ✅ WASM for performance-critical code |
44 | | -* ✅ Rust/OCaml/Haskell for systems/proofs |
45 | | -* ✅ Guile/Scheme for configuration |
46 | | -* ❌ No TypeScript |
47 | | -* ❌ No Go |
48 | | -* ❌ No npm |
| 45 | +// Get with type safety |
| 46 | +const fontSize = await injector.getTyped<number>('fontSize'); |
| 47 | +console.log('Font Size:', fontSize); // 14 |
| 48 | +``` |
49 | 49 |
|
50 | | -== Mirrors |
| 50 | +== Providers |
51 | 51 |
|
52 | | -[cols="1,2"] |
53 | | -|=== |
54 | | -| Platform | URL |
| 52 | +=== Memory Provider |
55 | 53 |
|
56 | | -| GitHub (primary) | {url-github} |
57 | | -| GitLab | {url-gitlab} |
58 | | -| Bitbucket | {url-bitbucket} |
59 | | -| Codeberg | {url-codeberg} |
60 | | -|=== |
| 54 | +In-memory storage for runtime preferences: |
61 | 55 |
|
62 | | -== License |
| 56 | +```typescript |
| 57 | +import { MemoryProvider, PreferencePriority } from '@hyperpolymath/preference-injector'; |
| 58 | + |
| 59 | +const provider = new MemoryProvider(PreferencePriority.NORMAL); |
| 60 | +``` |
| 61 | + |
| 62 | +=== File Provider |
| 63 | + |
| 64 | +JSON or .env file-based storage: |
| 65 | + |
| 66 | +```typescript |
| 67 | +import { FileProvider } from '@hyperpolymath/preference-injector'; |
| 68 | + |
| 69 | +const provider = new FileProvider({ |
| 70 | + filePath: './config.json', |
| 71 | + priority: PreferencePriority.NORMAL, |
| 72 | + watchForChanges: true, |
| 73 | + format: 'json', // or 'env' |
| 74 | +}); |
| 75 | +``` |
| 76 | + |
| 77 | +=== Environment Provider |
| 78 | + |
| 79 | +Environment variable integration: |
| 80 | + |
| 81 | +```typescript |
| 82 | +import { EnvProvider } from '@hyperpolymath/preference-injector'; |
| 83 | + |
| 84 | +const provider = new EnvProvider({ |
| 85 | + prefix: 'APP_', |
| 86 | + priority: PreferencePriority.HIGHEST, |
| 87 | + parseValues: true, |
| 88 | +}); |
| 89 | +``` |
| 90 | + |
| 91 | +=== API Provider |
| 92 | + |
| 93 | +Remote configuration service: |
| 94 | + |
| 95 | +```typescript |
| 96 | +import { ApiProvider } from '@hyperpolymath/preference-injector'; |
| 97 | + |
| 98 | +const provider = new ApiProvider({ |
| 99 | + baseUrl: 'https://config.example.com', |
| 100 | + apiKey: 'your-api-key', |
| 101 | + priority: PreferencePriority.NORMAL, |
| 102 | + timeout: 5000, |
| 103 | + retries: 3, |
| 104 | +}); |
| 105 | +``` |
| 106 | + |
| 107 | +== Multi-Provider Setup |
| 108 | + |
| 109 | +Use multiple providers with automatic conflict resolution: |
| 110 | + |
| 111 | +```typescript |
| 112 | +const injector = new PreferenceInjector({ |
| 113 | + providers: [ |
| 114 | + new EnvProvider({ priority: PreferencePriority.HIGHEST }), |
| 115 | + new FileProvider({ filePath: './config.json', priority: PreferencePriority.NORMAL }), |
| 116 | + new MemoryProvider(PreferencePriority.LOW), |
| 117 | + ], |
| 118 | + conflictResolution: ConflictResolution.HIGHEST_PRIORITY, |
| 119 | +}); |
| 120 | +``` |
| 121 | + |
| 122 | +== Validation |
| 123 | + |
| 124 | +Add validation rules to ensure preference values are valid: |
| 125 | + |
| 126 | +```typescript |
| 127 | +import { CommonValidationRules } from '@hyperpolymath/preference-injector'; |
| 128 | + |
| 129 | +const validator = injector.getValidator(); |
| 130 | + |
| 131 | +// Add built-in validation rules |
| 132 | +validator.addRule('email', CommonValidationRules.email()); |
| 133 | +validator.addRule('age', CommonValidationRules.numberRange(0, 150)); |
| 134 | +validator.addRule('username', CommonValidationRules.stringLength(3, 20)); |
| 135 | + |
| 136 | +// Set with validation |
| 137 | +await injector.set('email', 'user@example.com', { validate: true }); |
| 138 | +``` |
| 139 | + |
| 140 | +== Schema Validation |
| 141 | + |
| 142 | +Define schemas for your preferences: |
| 143 | + |
| 144 | +```typescript |
| 145 | +import { SchemaBuilder } from '@hyperpolymath/preference-injector'; |
| 146 | + |
| 147 | +const schema = new SchemaBuilder() |
| 148 | + .string('apiUrl', { required: true, pattern: /^https?:\/\// }) |
| 149 | + .number('timeout', { min: 0, max: 30000, default: 5000 }) |
| 150 | + .boolean('debug', { default: false }) |
| 151 | + .build(); |
| 152 | + |
| 153 | +const schemaValidator = new SchemaValidator(schema); |
| 154 | +``` |
| 155 | + |
| 156 | +== Encryption |
| 157 | + |
| 158 | +Encrypt sensitive preferences: |
| 159 | + |
| 160 | +```typescript |
| 161 | +import { AESEncryptionService } from '@hyperpolymath/preference-injector'; |
| 162 | + |
| 163 | +const encryptionService = new AESEncryptionService('your-secret-password'); |
| 164 | +injector.setEncryptionService(encryptionService); |
| 165 | + |
| 166 | +// Store encrypted |
| 167 | +await injector.set('apiKey', 'secret-key', { encrypt: true }); |
| 168 | + |
| 169 | +// Retrieve and decrypt |
| 170 | +const apiKey = await injector.get('apiKey', { decrypt: true }); |
| 171 | +``` |
| 172 | + |
| 173 | +== Caching |
| 174 | + |
| 175 | +Enable caching for better performance: |
63 | 176 |
|
64 | | -Licensed under AGPL-3.0-or-later OR LicenseRef-Palimpsest-0.5. |
| 177 | +```typescript |
| 178 | +const injector = new PreferenceInjector({ |
| 179 | + providers: [provider], |
| 180 | + enableCache: true, |
| 181 | + cacheTTL: 3600000, // 1 hour |
| 182 | +}); |
| 183 | +``` |
65 | 184 |
|
66 | | -See link:LICENSE[LICENSE] for details. |
| 185 | +== Audit Logging |
| 186 | + |
| 187 | +Track all preference operations: |
| 188 | + |
| 189 | +```typescript |
| 190 | +const injector = new PreferenceInjector({ |
| 191 | + providers: [provider], |
| 192 | + enableAudit: true, |
| 193 | +}); |
| 194 | + |
| 195 | +// Get audit log |
| 196 | +const auditLogger = injector.getAuditLogger(); |
| 197 | +const entries = auditLogger.getEntries(); |
| 198 | +``` |
| 199 | + |
| 200 | +== React Integration |
| 201 | + |
| 202 | +Use preferences in React applications: |
| 203 | + |
| 204 | +```tsx |
| 205 | +import { PreferenceProvider, usePreference } from '@hyperpolymath/preference-injector'; |
| 206 | + |
| 207 | +function App() { |
| 208 | + return ( |
| 209 | + <PreferenceProvider injector={injector}> |
| 210 | + <ThemeToggle /> |
| 211 | + </PreferenceProvider> |
| 212 | + ); |
| 213 | +} |
| 214 | + |
| 215 | +function ThemeToggle() { |
| 216 | + const [theme, setTheme, loading] = usePreference<string>('theme', 'light'); |
| 217 | + |
| 218 | + if (loading) return <div>Loading...</div>; |
| 219 | + |
| 220 | + return ( |
| 221 | + <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> |
| 222 | + Current: {theme} |
| 223 | + </button> |
| 224 | + ); |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +== Express Integration |
| 229 | + |
| 230 | +Add preference management to Express applications: |
| 231 | + |
| 232 | +```typescript |
| 233 | +import express from 'express'; |
| 234 | +import { |
| 235 | + preferenceMiddleware, |
| 236 | + createPreferenceRouter, |
| 237 | +} from '@hyperpolymath/preference-injector'; |
| 238 | + |
| 239 | +const app = express(); |
| 240 | + |
| 241 | +// Attach preferences to requests |
| 242 | +app.use(preferenceMiddleware({ injector })); |
| 243 | + |
| 244 | +// Add REST API |
| 245 | +app.use('/api', createPreferenceRouter(injector)); |
| 246 | + |
| 247 | +// Use in routes |
| 248 | +app.get('/config', async (req, res) => { |
| 249 | + const value = await req.getPreference('setting'); |
| 250 | + res.json({ value }); |
| 251 | +}); |
| 252 | +``` |
| 253 | + |
| 254 | +== CLI Tool |
| 255 | + |
| 256 | +Manage preferences from the command line: |
| 257 | + |
| 258 | +```bash |
| 259 | += Get a preference |
| 260 | +preference-injector get theme |
| 261 | + |
| 262 | += Set a preference |
| 263 | +preference-injector set theme dark |
| 264 | + |
| 265 | += List all preferences |
| 266 | +preference-injector -f config.json list |
| 267 | + |
| 268 | += Delete a preference |
| 269 | +preference-injector delete old-key |
| 270 | + |
| 271 | += Clear all preferences |
| 272 | +preference-injector clear |
| 273 | +``` |
| 274 | + |
| 275 | +== Migrations |
| 276 | + |
| 277 | +Version and migrate preference schemas: |
| 278 | + |
| 279 | +```typescript |
| 280 | +import { MigrationManager, createMigration } from '@hyperpolymath/preference-injector'; |
| 281 | + |
| 282 | +const manager = new MigrationManager(); |
| 283 | + |
| 284 | +manager.register( |
| 285 | + createMigration( |
| 286 | + 1, |
| 287 | + 'rename-theme-to-color-scheme', |
| 288 | + async (prefs) => { |
| 289 | + // Migration logic |
| 290 | + return prefs; |
| 291 | + }, |
| 292 | + async (prefs) => { |
| 293 | + // Rollback logic |
| 294 | + return prefs; |
| 295 | + } |
| 296 | + ) |
| 297 | +); |
| 298 | + |
| 299 | +await manager.migrateToLatest(preferences, 0); |
| 300 | +``` |
| 301 | + |
| 302 | +== API Documentation |
| 303 | + |
| 304 | +For detailed API documentation, see [API.md](./docs/API.md). |
| 305 | + |
| 306 | +== Examples |
| 307 | + |
| 308 | +Check the [examples](./examples) directory for more usage examples: |
| 309 | + |
| 310 | +- [Basic Usage](./examples/basic-usage.ts) |
| 311 | +- [React Integration](./examples/react-example.tsx) |
| 312 | +- [Express Integration](./examples/express-example.ts) |
67 | 313 |
|
68 | 314 | == Contributing |
69 | 315 |
|
70 | | -See link:CONTRIBUTING.adoc[CONTRIBUTING.adoc]. |
| 316 | +Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. |
| 317 | + |
| 318 | +== License |
| 319 | + |
| 320 | +MIT |
71 | 321 |
|
72 | | -== Metadata |
| 322 | +== Changelog |
73 | 323 |
|
74 | | -* Domain: software-development |
75 | | -* Framework: RSR (Rhodium Standard Repository) |
76 | | -* Dublin Core: link:.well-known/dc.xml[.well-known/dc.xml] |
| 324 | +See [CHANGELOG.md](./CHANGELOG.md) for release history. |
0 commit comments