hazo_config 1.3.1 → 1.4.1
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/LICENSE +21 -0
- package/dist/components/config_editor.d.ts +1 -1
- package/dist/components/config_editor.d.ts.map +1 -1
- package/dist/components/config_editor.js +13 -17
- package/dist/components/config_viewer.d.ts +1 -1
- package/dist/components/config_viewer.d.ts.map +1 -1
- package/dist/components/config_viewer.js +11 -15
- package/dist/components/example_component.js +4 -8
- package/dist/components/index.js +3 -9
- package/dist/index.js +3 -19
- package/dist/lib/config-loader.d.ts +11 -3
- package/dist/lib/config-loader.d.ts.map +1 -1
- package/dist/lib/config-loader.js +64 -24
- package/dist/lib/index.js +3 -9
- package/dist/lib/mock_config_provider.d.ts +1 -1
- package/dist/lib/mock_config_provider.d.ts.map +1 -1
- package/dist/lib/mock_config_provider.js +1 -5
- package/dist/lib/types.d.ts +12 -0
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -5
- package/dist/lib/utils.js +4 -7
- package/package.json +2 -1
- package/src/components/config_editor.stories.tsx +2 -2
- package/src/components/config_editor.tsx +2 -2
- package/src/components/config_viewer.stories.tsx +2 -2
- package/src/components/config_viewer.tsx +2 -2
- package/src/components/example_component.stories.tsx +1 -1
- package/src/components/example_component.tsx +1 -1
- package/src/lib/config-loader.ts +41 -4
- package/src/lib/mock_config_provider.ts +1 -1
- package/src/lib/types.ts +14 -0
- package/tsconfig.build.json +4 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 pub12
|
|
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.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"config_editor.d.ts","sourceRoot":"","sources":["../../src/components/config_editor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAmLpD,CAAA"}
|
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ConfigEditor = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
5
2
|
// Config editor component for managing configuration
|
|
6
3
|
// Provides a more advanced interface for editing configuration values
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
import { cn } from '../lib/utils.js';
|
|
6
|
+
import { RefreshCw, Save } from 'lucide-react';
|
|
10
7
|
/**
|
|
11
8
|
* Config editor component
|
|
12
9
|
* Provides interface for viewing and editing configuration with refresh and save capabilities
|
|
13
10
|
* @param props - Component props
|
|
14
11
|
* @returns React component
|
|
15
12
|
*/
|
|
16
|
-
const ConfigEditor = ({ config_provider, className, on_update, }) => {
|
|
17
|
-
const [sections, set_sections] =
|
|
18
|
-
const [selected_section, set_selected_section] =
|
|
19
|
-
const [new_key, set_new_key] =
|
|
20
|
-
const [new_value, set_new_value] =
|
|
13
|
+
export const ConfigEditor = ({ config_provider, className, on_update, }) => {
|
|
14
|
+
const [sections, set_sections] = useState({});
|
|
15
|
+
const [selected_section, set_selected_section] = useState(null);
|
|
16
|
+
const [new_key, set_new_key] = useState('');
|
|
17
|
+
const [new_value, set_new_value] = useState('');
|
|
21
18
|
/**
|
|
22
19
|
* Load configuration from provider
|
|
23
20
|
*/
|
|
@@ -32,7 +29,7 @@ const ConfigEditor = ({ config_provider, className, on_update, }) => {
|
|
|
32
29
|
set_selected_section(Object.keys(all_sections)[0]);
|
|
33
30
|
}
|
|
34
31
|
};
|
|
35
|
-
|
|
32
|
+
useEffect(() => {
|
|
36
33
|
load_config();
|
|
37
34
|
}, [config_provider]);
|
|
38
35
|
/**
|
|
@@ -77,9 +74,8 @@ const ConfigEditor = ({ config_provider, className, on_update, }) => {
|
|
|
77
74
|
load_config();
|
|
78
75
|
};
|
|
79
76
|
const current_section_data = selected_section ? sections[selected_section] : null;
|
|
80
|
-
return ((
|
|
77
|
+
return (_jsxs("div", { className: cn('cls_config_editor space-y-4', className), children: [_jsxs("div", { className: "cls_config_editor_header flex items-center justify-between border-b pb-2", children: [_jsx("h2", { className: "text-xl font-semibold", children: "Configuration Editor" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("button", { onClick: handle_refresh, className: "px-3 py-1 border rounded hover:bg-muted flex items-center gap-2", "aria-label": "Refresh", children: [_jsx(RefreshCw, { size: 16 }), "Refresh"] }), _jsxs("button", { onClick: handle_save, className: "px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 flex items-center gap-2", "aria-label": "Save", children: [_jsx(Save, { size: 16 }), "Save"] })] })] }), _jsxs("div", { className: "cls_config_editor_content grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "cls_config_sections_list border rounded p-4", children: [_jsx("h3", { className: "font-semibold mb-2", children: "Sections" }), _jsx("div", { className: "space-y-1", children: Object.keys(sections).map((section_name) => (_jsx("button", { onClick: () => set_selected_section(section_name), className: cn('w-full text-left px-2 py-1 rounded text-sm', selected_section === section_name
|
|
81
78
|
? 'bg-primary text-primary-foreground'
|
|
82
|
-
: 'hover:bg-muted'), children: section_name }, section_name))) })] }), (
|
|
83
|
-
Object.entries(current_section_data).map(([key, value]) => ((
|
|
79
|
+
: 'hover:bg-muted'), children: section_name }, section_name))) })] }), _jsx("div", { className: "cls_config_section_content md:col-span-2 border rounded p-4", children: selected_section ? (_jsxs(_Fragment, { children: [_jsx("h3", { className: "font-semibold mb-4", children: selected_section }), _jsxs("div", { className: "space-y-3", children: [current_section_data &&
|
|
80
|
+
Object.entries(current_section_data).map(([key, value]) => (_jsxs("div", { className: "cls_config_item", children: [_jsx("label", { className: "block text-sm font-medium mb-1", children: key }), _jsx("input", { type: "text", value: value, onChange: (e) => handle_update_value(selected_section, key, e.target.value), className: "w-full px-2 py-1 border rounded" })] }, key))), _jsxs("div", { className: "cls_add_new_key border-t pt-3 mt-3", children: [_jsx("h4", { className: "text-sm font-medium mb-2", children: "Add New Key" }), _jsxs("div", { className: "space-y-2", children: [_jsx("input", { type: "text", placeholder: "Key name", value: new_key, onChange: (e) => set_new_key(e.target.value), className: "w-full px-2 py-1 border rounded text-sm" }), _jsx("input", { type: "text", placeholder: "Value", value: new_value, onChange: (e) => set_new_value(e.target.value), className: "w-full px-2 py-1 border rounded text-sm" }), _jsx("button", { onClick: handle_add_key, className: "px-3 py-1 bg-primary text-primary-foreground rounded hover:bg-primary/90 text-sm", children: "Add Key" })] })] })] })] })) : (_jsx("div", { className: "text-center text-muted-foreground py-8", children: "Select a section to view/edit" })) })] })] }));
|
|
84
81
|
};
|
|
85
|
-
exports.ConfigEditor = ConfigEditor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"config_viewer.d.ts","sourceRoot":"","sources":["../../src/components/config_viewer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAGrD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,eAAe,EAAE,cAAc,CAAA;IAC/B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED;;;;;GAKG;AACH,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CAwIpD,CAAA"}
|
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ConfigViewer = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
5
2
|
// Config viewer component for displaying configuration data
|
|
6
3
|
// Displays configuration sections and values in a readable format
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
import { useState, useEffect } from 'react';
|
|
5
|
+
import { cn } from '../lib/utils.js';
|
|
6
|
+
import { Pencil, CheckCircle2, XCircle } from 'lucide-react';
|
|
10
7
|
/**
|
|
11
8
|
* Config viewer component
|
|
12
9
|
* Displays configuration sections and allows viewing/editing values
|
|
13
10
|
* @param props - Component props
|
|
14
11
|
* @returns React component
|
|
15
12
|
*/
|
|
16
|
-
const ConfigViewer = ({ config_provider, className, on_update, }) => {
|
|
17
|
-
const [sections, set_sections] =
|
|
18
|
-
const [editing, set_editing] =
|
|
19
|
-
const [edit_value, set_edit_value] =
|
|
13
|
+
export const ConfigViewer = ({ config_provider, className, on_update, }) => {
|
|
14
|
+
const [sections, set_sections] = useState({});
|
|
15
|
+
const [editing, set_editing] = useState(null);
|
|
16
|
+
const [edit_value, set_edit_value] = useState('');
|
|
20
17
|
/**
|
|
21
18
|
* Load configuration from provider
|
|
22
19
|
*/
|
|
@@ -39,7 +36,7 @@ const ConfigViewer = ({ config_provider, className, on_update, }) => {
|
|
|
39
36
|
}
|
|
40
37
|
set_sections(all_sections);
|
|
41
38
|
};
|
|
42
|
-
|
|
39
|
+
useEffect(() => {
|
|
43
40
|
load_config();
|
|
44
41
|
}, [config_provider]);
|
|
45
42
|
/**
|
|
@@ -72,9 +69,8 @@ const ConfigViewer = ({ config_provider, className, on_update, }) => {
|
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
71
|
};
|
|
75
|
-
return ((
|
|
72
|
+
return (_jsx("div", { className: cn('cls_config_viewer space-y-4', className), children: Object.keys(sections).length === 0 ? (_jsx("div", { className: "p-4 text-center text-muted-foreground", children: "No configuration sections found" })) : (Object.entries(sections).map(([section_name, section_data]) => (_jsxs("div", { className: "cls_config_section border rounded-lg p-4", children: [_jsx("h3", { className: "text-lg font-semibold mb-3", children: section_name }), _jsx("div", { className: "space-y-2", children: Object.entries(section_data).map(([key, value]) => {
|
|
76
73
|
const is_editing = editing?.section === section_name && editing?.key === key;
|
|
77
|
-
return ((
|
|
74
|
+
return (_jsx("div", { className: "cls_config_item flex items-center gap-2", children: _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium text-muted-foreground", children: key }), is_editing ? (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("input", { type: "text", value: edit_value, onChange: (e) => set_edit_value(e.target.value), className: "flex-1 px-2 py-1 border rounded text-sm", autoFocus: true }), _jsx("button", { onClick: save_edit, className: "text-green-600 hover:text-green-700", "aria-label": "Save", children: _jsx(CheckCircle2, { size: 20 }) }), _jsx("button", { onClick: cancel_edit, className: "text-red-600 hover:text-red-700", "aria-label": "Cancel", children: _jsx(XCircle, { size: 20 }) })] })) : (_jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx("div", { className: "flex-1 px-2 py-1 bg-muted rounded text-sm", children: value || '(empty)' }), _jsx("button", { onClick: () => start_edit(section_name, key), className: "text-blue-600 hover:text-blue-700", "aria-label": "Edit", children: _jsx(Pencil, { size: 18 }) })] }))] }) }, key));
|
|
78
75
|
}) })] }, section_name)))) }));
|
|
79
76
|
};
|
|
80
|
-
exports.ConfigViewer = ConfigViewer;
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
exports.ExampleComponent = void 0;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const utils_1 = require("../lib/utils");
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from '../lib/utils.js';
|
|
6
3
|
/**
|
|
7
4
|
* Example component for demonstrating the component library setup
|
|
8
5
|
* @param props - Component props
|
|
9
6
|
* @returns React component
|
|
10
7
|
*/
|
|
11
|
-
const ExampleComponent = ({ title = 'Example Component', className, }) => {
|
|
12
|
-
return ((
|
|
8
|
+
export const ExampleComponent = ({ title = 'Example Component', className, }) => {
|
|
9
|
+
return (_jsxs("div", { className: cn('cls_example_component p-4 border rounded-lg', className), children: [_jsx("h2", { className: "text-xl font-semibold", children: title }), _jsx("p", { className: "text-muted-foreground mt-2", children: "This is an example component for the config management library." })] }));
|
|
13
10
|
};
|
|
14
|
-
exports.ExampleComponent = ExampleComponent;
|
package/dist/components/index.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Component exports
|
|
3
2
|
// Export all components from this file for easy importing
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Object.defineProperty(exports, "ExampleComponent", { enumerable: true, get: function () { return example_component_js_1.ExampleComponent; } });
|
|
8
|
-
var config_viewer_js_1 = require("./config_viewer.js");
|
|
9
|
-
Object.defineProperty(exports, "ConfigViewer", { enumerable: true, get: function () { return config_viewer_js_1.ConfigViewer; } });
|
|
10
|
-
var config_editor_js_1 = require("./config_editor.js");
|
|
11
|
-
Object.defineProperty(exports, "ConfigEditor", { enumerable: true, get: function () { return config_editor_js_1.ConfigEditor; } });
|
|
3
|
+
export { ExampleComponent } from './example_component.js';
|
|
4
|
+
export { ConfigViewer } from './config_viewer.js';
|
|
5
|
+
export { ConfigEditor } from './config_editor.js';
|
package/dist/index.js
CHANGED
|
@@ -1,21 +1,5 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Main entry point for the component library
|
|
3
2
|
// Export all components and utilities from this file
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
-
}
|
|
10
|
-
Object.defineProperty(o, k2, desc);
|
|
11
|
-
}) : (function(o, m, k, k2) {
|
|
12
|
-
if (k2 === undefined) k2 = k;
|
|
13
|
-
o[k2] = m[k];
|
|
14
|
-
}));
|
|
15
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
-
};
|
|
18
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
__exportStar(require("./components/index.js"), exports);
|
|
20
|
-
__exportStar(require("./lib/utils.js"), exports);
|
|
21
|
-
__exportStar(require("./lib/index.js"), exports);
|
|
3
|
+
export * from './components/index.js';
|
|
4
|
+
export * from './lib/utils.js';
|
|
5
|
+
export * from './lib/index.js';
|
|
@@ -6,23 +6,31 @@
|
|
|
6
6
|
* in-memory caching, sync operations, and preservation of file formatting.
|
|
7
7
|
* Zero dependencies - only Node.js built-ins and the 'ini' package.
|
|
8
8
|
*/
|
|
9
|
-
import type { ConfigProvider, HazoConfigOptions } from './types';
|
|
9
|
+
import type { ConfigProvider, HazoConfigOptions } from './types.js';
|
|
10
10
|
/**
|
|
11
11
|
* HazoConfig class
|
|
12
12
|
*
|
|
13
13
|
* Implements ConfigProvider interface for managing INI configuration files.
|
|
14
|
-
* Provides sync read/write operations with in-memory caching.
|
|
14
|
+
* Provides sync read/write operations with in-memory caching and automatic cache refresh.
|
|
15
15
|
*/
|
|
16
16
|
export declare class HazoConfig implements ConfigProvider {
|
|
17
17
|
private filePath;
|
|
18
18
|
private logger;
|
|
19
19
|
private config;
|
|
20
|
+
private cache_ttl_ms;
|
|
21
|
+
private disable_cache;
|
|
22
|
+
private last_cache_refresh;
|
|
20
23
|
/**
|
|
21
24
|
* Constructor
|
|
22
|
-
* @param options - Configuration options including filePath
|
|
25
|
+
* @param options - Configuration options including filePath, optional logger, cache settings
|
|
23
26
|
* @throws Error if file doesn't exist
|
|
24
27
|
*/
|
|
25
28
|
constructor(options: HazoConfigOptions);
|
|
29
|
+
/**
|
|
30
|
+
* Check if cache needs to be refreshed and refresh if needed
|
|
31
|
+
* This method is called before read operations to ensure cache is up to date
|
|
32
|
+
*/
|
|
33
|
+
private ensure_cache_fresh;
|
|
26
34
|
/**
|
|
27
35
|
* Get a configuration value by section and key
|
|
28
36
|
* @param section - The configuration section name
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/lib/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAU,MAAM,
|
|
1
|
+
{"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/lib/config-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAU,MAAM,YAAY,CAAA;AAa3E;;;;;GAKG;AACH,qBAAa,UAAW,YAAW,cAAc;IAC/C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,kBAAkB,CAAY;IAEtC;;;;OAIG;gBACS,OAAO,EAAE,iBAAiB;IAkBtC;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKrD;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAK/D;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAQtD;;;OAGG;IACH,IAAI,IAAI,IAAI;IAqBZ;;;OAGG;IACH,OAAO,IAAI,IAAI;IA0Cf;;;OAGG;IACH,WAAW,IAAI,MAAM;IAIrB;;;OAGG;IACH,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CASzD"}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Purpose: Main implementation of HazoConfig
|
|
4
3
|
*
|
|
@@ -7,15 +6,10 @@
|
|
|
7
6
|
* in-memory caching, sync operations, and preservation of file formatting.
|
|
8
7
|
* Zero dependencies - only Node.js built-ins and the 'ini' package.
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
exports.HazoConfig = void 0;
|
|
15
|
-
const fs_1 = __importDefault(require("fs"));
|
|
16
|
-
const path_1 = __importDefault(require("path"));
|
|
17
|
-
const ini_1 = __importDefault(require("ini"));
|
|
18
|
-
const types_1 = require("./types");
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import ini from 'ini';
|
|
12
|
+
import { ConfigErrorCode as EC } from './types.js';
|
|
19
13
|
/**
|
|
20
14
|
* No-op logger implementation (default when no logger provided)
|
|
21
15
|
*/
|
|
@@ -29,12 +23,12 @@ const no_op_logger = {
|
|
|
29
23
|
* HazoConfig class
|
|
30
24
|
*
|
|
31
25
|
* Implements ConfigProvider interface for managing INI configuration files.
|
|
32
|
-
* Provides sync read/write operations with in-memory caching.
|
|
26
|
+
* Provides sync read/write operations with in-memory caching and automatic cache refresh.
|
|
33
27
|
*/
|
|
34
|
-
class HazoConfig {
|
|
28
|
+
export class HazoConfig {
|
|
35
29
|
/**
|
|
36
30
|
* Constructor
|
|
37
|
-
* @param options - Configuration options including filePath
|
|
31
|
+
* @param options - Configuration options including filePath, optional logger, cache settings
|
|
38
32
|
* @throws Error if file doesn't exist
|
|
39
33
|
*/
|
|
40
34
|
constructor(options) {
|
|
@@ -56,18 +50,60 @@ class HazoConfig {
|
|
|
56
50
|
writable: true,
|
|
57
51
|
value: {}
|
|
58
52
|
});
|
|
59
|
-
this
|
|
53
|
+
Object.defineProperty(this, "cache_ttl_ms", {
|
|
54
|
+
enumerable: true,
|
|
55
|
+
configurable: true,
|
|
56
|
+
writable: true,
|
|
57
|
+
value: void 0
|
|
58
|
+
});
|
|
59
|
+
Object.defineProperty(this, "disable_cache", {
|
|
60
|
+
enumerable: true,
|
|
61
|
+
configurable: true,
|
|
62
|
+
writable: true,
|
|
63
|
+
value: void 0
|
|
64
|
+
});
|
|
65
|
+
Object.defineProperty(this, "last_cache_refresh", {
|
|
66
|
+
enumerable: true,
|
|
67
|
+
configurable: true,
|
|
68
|
+
writable: true,
|
|
69
|
+
value: 0
|
|
70
|
+
});
|
|
71
|
+
this.filePath = path.resolve(options.filePath);
|
|
60
72
|
this.logger = options.logger || no_op_logger;
|
|
73
|
+
this.cache_ttl_ms = options.cache_ttl_ms ?? 5000; // Default 5 seconds
|
|
74
|
+
this.disable_cache = options.disable_cache ?? false; // Default: caching enabled
|
|
61
75
|
// Validate file exists
|
|
62
|
-
if (!
|
|
76
|
+
if (!fs.existsSync(this.filePath)) {
|
|
63
77
|
const error = new Error(`Configuration file not found: ${this.filePath}`);
|
|
64
|
-
error.code =
|
|
78
|
+
error.code = EC.FILE_NOT_FOUND;
|
|
65
79
|
this.logger.error(`[HazoConfig] Configuration file not found: ${this.filePath}`);
|
|
66
80
|
throw error;
|
|
67
81
|
}
|
|
68
82
|
// Load initial configuration
|
|
69
83
|
this.refresh();
|
|
70
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Check if cache needs to be refreshed and refresh if needed
|
|
87
|
+
* This method is called before read operations to ensure cache is up to date
|
|
88
|
+
*/
|
|
89
|
+
ensure_cache_fresh() {
|
|
90
|
+
if (this.disable_cache) {
|
|
91
|
+
// If caching is disabled, always refresh from disk
|
|
92
|
+
this.refresh();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (this.cache_ttl_ms === 0) {
|
|
96
|
+
// If TTL is 0, cache never expires (only manual refresh)
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const now = Date.now();
|
|
100
|
+
const cache_age = now - this.last_cache_refresh;
|
|
101
|
+
if (cache_age >= this.cache_ttl_ms) {
|
|
102
|
+
// Cache has expired, refresh from disk
|
|
103
|
+
this.logger.debug(`[HazoConfig] Cache expired (age: ${cache_age}ms, ttl: ${this.cache_ttl_ms}ms), refreshing from disk`);
|
|
104
|
+
this.refresh();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
71
107
|
/**
|
|
72
108
|
* Get a configuration value by section and key
|
|
73
109
|
* @param section - The configuration section name
|
|
@@ -75,6 +111,7 @@ class HazoConfig {
|
|
|
75
111
|
* @returns The configuration value, or undefined if not found
|
|
76
112
|
*/
|
|
77
113
|
get(section, key) {
|
|
114
|
+
this.ensure_cache_fresh();
|
|
78
115
|
return this.config[section]?.[key];
|
|
79
116
|
}
|
|
80
117
|
/**
|
|
@@ -83,6 +120,7 @@ class HazoConfig {
|
|
|
83
120
|
* @returns A record of key-value pairs for the section, or undefined if section doesn't exist
|
|
84
121
|
*/
|
|
85
122
|
getSection(section) {
|
|
123
|
+
this.ensure_cache_fresh();
|
|
86
124
|
return this.config[section] ? { ...this.config[section] } : undefined;
|
|
87
125
|
}
|
|
88
126
|
/**
|
|
@@ -105,17 +143,17 @@ class HazoConfig {
|
|
|
105
143
|
save() {
|
|
106
144
|
try {
|
|
107
145
|
// Convert config object back to INI format
|
|
108
|
-
const iniContent =
|
|
146
|
+
const iniContent = ini.stringify(this.config, {
|
|
109
147
|
section: '[',
|
|
110
148
|
whitespace: true
|
|
111
149
|
});
|
|
112
150
|
// Write to file
|
|
113
|
-
|
|
151
|
+
fs.writeFileSync(this.filePath, iniContent, 'utf-8');
|
|
114
152
|
this.logger.info(`[HazoConfig] Configuration saved to: ${this.filePath}`);
|
|
115
153
|
}
|
|
116
154
|
catch (error) {
|
|
117
155
|
const configError = new Error(`Failed to save configuration: ${error.message || String(error)}`);
|
|
118
|
-
configError.code =
|
|
156
|
+
configError.code = EC.WRITE_ERROR;
|
|
119
157
|
configError.originalError = error;
|
|
120
158
|
this.logger.error(`[HazoConfig] Failed to save configuration: ${this.filePath}`, { error: String(error) });
|
|
121
159
|
throw configError;
|
|
@@ -128,9 +166,9 @@ class HazoConfig {
|
|
|
128
166
|
refresh() {
|
|
129
167
|
try {
|
|
130
168
|
// Read file content
|
|
131
|
-
const content =
|
|
169
|
+
const content = fs.readFileSync(this.filePath, 'utf-8');
|
|
132
170
|
// Parse INI content
|
|
133
|
-
const parsed =
|
|
171
|
+
const parsed = ini.parse(content);
|
|
134
172
|
// Convert to our internal format (ensure all values are strings)
|
|
135
173
|
this.config = {};
|
|
136
174
|
for (const [section, values] of Object.entries(parsed)) {
|
|
@@ -141,6 +179,8 @@ class HazoConfig {
|
|
|
141
179
|
}
|
|
142
180
|
}
|
|
143
181
|
}
|
|
182
|
+
// Update cache timestamp
|
|
183
|
+
this.last_cache_refresh = Date.now();
|
|
144
184
|
this.logger.info(`[HazoConfig] Configuration refreshed from: ${this.filePath}`, {
|
|
145
185
|
sections: Object.keys(this.config)
|
|
146
186
|
});
|
|
@@ -149,13 +189,13 @@ class HazoConfig {
|
|
|
149
189
|
// Handle file read errors
|
|
150
190
|
if (error.code === 'ENOENT') {
|
|
151
191
|
const configError = new Error(`Configuration file not found: ${this.filePath}`);
|
|
152
|
-
configError.code =
|
|
192
|
+
configError.code = EC.FILE_NOT_FOUND;
|
|
153
193
|
this.logger.error(`[HazoConfig] Configuration file not found: ${this.filePath}`);
|
|
154
194
|
throw configError;
|
|
155
195
|
}
|
|
156
196
|
// Handle parse errors
|
|
157
197
|
const configError = new Error(`Failed to parse configuration: ${error.message || String(error)}`);
|
|
158
|
-
configError.code =
|
|
198
|
+
configError.code = EC.PARSE_ERROR;
|
|
159
199
|
configError.originalError = error;
|
|
160
200
|
this.logger.error(`[HazoConfig] Failed to parse configuration: ${this.filePath}`, { error: String(error) });
|
|
161
201
|
throw configError;
|
|
@@ -173,6 +213,7 @@ class HazoConfig {
|
|
|
173
213
|
* @returns A record of all sections and their key-value pairs
|
|
174
214
|
*/
|
|
175
215
|
getAllSections() {
|
|
216
|
+
this.ensure_cache_fresh();
|
|
176
217
|
// Return a deep copy to prevent external modification
|
|
177
218
|
const result = {};
|
|
178
219
|
for (const [section, values] of Object.entries(this.config)) {
|
|
@@ -181,4 +222,3 @@ class HazoConfig {
|
|
|
181
222
|
return result;
|
|
182
223
|
}
|
|
183
224
|
}
|
|
184
|
-
exports.HazoConfig = HazoConfig;
|
package/dist/lib/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Purpose: Main export for the HazoConfig library.
|
|
4
3
|
*
|
|
@@ -6,11 +5,6 @@
|
|
|
6
5
|
* configuration management utility. It serves as the primary entry point
|
|
7
6
|
* for consumers of the library.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Object.defineProperty(exports, "HazoConfig", { enumerable: true, get: function () { return config_loader_js_1.HazoConfig; } });
|
|
13
|
-
var mock_config_provider_js_1 = require("./mock_config_provider.js");
|
|
14
|
-
Object.defineProperty(exports, "MockConfigProvider", { enumerable: true, get: function () { return mock_config_provider_js_1.MockConfigProvider; } });
|
|
15
|
-
var types_js_1 = require("./types.js");
|
|
16
|
-
Object.defineProperty(exports, "ConfigErrorCode", { enumerable: true, get: function () { return types_js_1.ConfigErrorCode; } });
|
|
8
|
+
export { HazoConfig } from './config-loader.js';
|
|
9
|
+
export { MockConfigProvider } from './mock_config_provider.js';
|
|
10
|
+
export { ConfigErrorCode } from './types.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock_config_provider.d.ts","sourceRoot":"","sources":["../../src/lib/mock_config_provider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"mock_config_provider.d.ts","sourceRoot":"","sources":["../../src/lib/mock_config_provider.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACvD,OAAO,CAAC,MAAM,CAA6C;IAE3D;;;OAGG;gBACS,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAMnE;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIrD;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS;IAI/D;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAOtD;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,OAAO,IAAI,IAAI;IAIf;;;OAGG;IACH,cAAc,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAQxD;;;OAGG;IACH,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI;CAOjE"}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Mock config provider for browser/Storybook testing
|
|
3
2
|
// Implements ConfigProvider interface using in-memory storage instead of file system
|
|
4
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
-
exports.MockConfigProvider = void 0;
|
|
6
3
|
/**
|
|
7
4
|
* Mock config provider that stores configuration in memory
|
|
8
5
|
* Useful for testing and Storybook demonstrations where file system is not available
|
|
9
6
|
*/
|
|
10
|
-
class MockConfigProvider {
|
|
7
|
+
export class MockConfigProvider {
|
|
11
8
|
/**
|
|
12
9
|
* Constructor
|
|
13
10
|
* @param initial_config - Optional initial configuration data
|
|
@@ -88,4 +85,3 @@ class MockConfigProvider {
|
|
|
88
85
|
}
|
|
89
86
|
}
|
|
90
87
|
}
|
|
91
|
-
exports.MockConfigProvider = MockConfigProvider;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -68,6 +68,18 @@ export interface HazoConfigOptions {
|
|
|
68
68
|
* Optional logger instance for logging operations
|
|
69
69
|
*/
|
|
70
70
|
logger?: Logger;
|
|
71
|
+
/**
|
|
72
|
+
* Cache time-to-live in milliseconds (default: 5000ms)
|
|
73
|
+
* When cache expires, configuration will be automatically refreshed from disk
|
|
74
|
+
* Set to 0 to disable automatic cache expiration (cache will only refresh on manual refresh() call)
|
|
75
|
+
*/
|
|
76
|
+
cache_ttl_ms?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Disable caching entirely (default: false)
|
|
79
|
+
* When true, every get() and getSection() call will read from disk
|
|
80
|
+
* When false, values are cached in memory and refreshed based on cache_ttl_ms
|
|
81
|
+
*/
|
|
82
|
+
disable_cache?: boolean;
|
|
71
83
|
}
|
|
72
84
|
/**
|
|
73
85
|
* Error codes for configuration operations
|
package/dist/lib/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACvC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;CACzC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAErD;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;IAE/D;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEtD;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAA;IAEZ;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACxC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;IACvC,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAA;CACzC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IAErD;;;;OAIG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAA;IAE/D;;;;;OAKG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IAEtD;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAA;IAEZ;;;OAGG;IACH,OAAO,IAAI,IAAI,CAAA;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;GAEG;AACH,oBAAY,eAAe;IACzB,cAAc,+BAA+B;IAC7C,UAAU,2BAA2B;IACrC,WAAW,4BAA4B;IACvC,WAAW,4BAA4B;IACvC,gBAAgB,iCAAiC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,GAAG,CAAA;CACpB"}
|
package/dist/lib/types.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Purpose: Core interfaces and types for the HazoConfig component.
|
|
4
3
|
*
|
|
@@ -7,16 +6,14 @@
|
|
|
7
6
|
* ConfigProvider pattern, configuration options, and error types, ensuring
|
|
8
7
|
* loose coupling and reusability across different projects.
|
|
9
8
|
*/
|
|
10
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.ConfigErrorCode = void 0;
|
|
12
9
|
/**
|
|
13
10
|
* Error codes for configuration operations
|
|
14
11
|
*/
|
|
15
|
-
var ConfigErrorCode;
|
|
12
|
+
export var ConfigErrorCode;
|
|
16
13
|
(function (ConfigErrorCode) {
|
|
17
14
|
ConfigErrorCode["FILE_NOT_FOUND"] = "HAZO_CONFIG_FILE_NOT_FOUND";
|
|
18
15
|
ConfigErrorCode["READ_ERROR"] = "HAZO_CONFIG_READ_ERROR";
|
|
19
16
|
ConfigErrorCode["WRITE_ERROR"] = "HAZO_CONFIG_WRITE_ERROR";
|
|
20
17
|
ConfigErrorCode["PARSE_ERROR"] = "HAZO_CONFIG_PARSE_ERROR";
|
|
21
18
|
ConfigErrorCode["VALIDATION_ERROR"] = "HAZO_CONFIG_VALIDATION_ERROR";
|
|
22
|
-
})(ConfigErrorCode || (
|
|
19
|
+
})(ConfigErrorCode || (ConfigErrorCode = {}));
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
// Utility functions for the component library
|
|
3
2
|
// Provides class name merging functionality using clsx and tailwind-merge
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const clsx_1 = require("clsx");
|
|
7
|
-
const tailwind_merge_1 = require("tailwind-merge");
|
|
3
|
+
import { clsx } from "clsx";
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
8
5
|
/**
|
|
9
6
|
* Merges class names using clsx and tailwind-merge
|
|
10
7
|
* @param inputs - Class values to merge
|
|
11
8
|
* @returns Merged class string
|
|
12
9
|
*/
|
|
13
|
-
function cn(...inputs) {
|
|
14
|
-
return
|
|
10
|
+
export function cn(...inputs) {
|
|
11
|
+
return twMerge(clsx(inputs));
|
|
15
12
|
}
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Demonstrates the config editor component with mock config provider
|
|
3
3
|
|
|
4
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
5
|
-
import { ConfigEditor } from './config_editor'
|
|
6
|
-
import { MockConfigProvider } from '../lib/mock_config_provider'
|
|
5
|
+
import { ConfigEditor } from './config_editor.js'
|
|
6
|
+
import { MockConfigProvider } from '../lib/mock_config_provider.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Sample configuration data for testing
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Provides a more advanced interface for editing configuration values
|
|
3
3
|
|
|
4
4
|
import React, { useState, useEffect } from 'react'
|
|
5
|
-
import { cn } from '../lib/utils'
|
|
6
|
-
import type { ConfigProvider } from '../lib/types'
|
|
5
|
+
import { cn } from '../lib/utils.js'
|
|
6
|
+
import type { ConfigProvider } from '../lib/types.js'
|
|
7
7
|
import { RefreshCw, Save } from 'lucide-react'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Demonstrates the config viewer component with mock config provider
|
|
3
3
|
|
|
4
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
5
|
-
import { ConfigViewer } from './config_viewer'
|
|
6
|
-
import { MockConfigProvider } from '../lib/mock_config_provider'
|
|
5
|
+
import { ConfigViewer } from './config_viewer.js'
|
|
6
|
+
import { MockConfigProvider } from '../lib/mock_config_provider.js'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Sample configuration data for testing
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// Displays configuration sections and values in a readable format
|
|
3
3
|
|
|
4
4
|
import React, { useState, useEffect } from 'react'
|
|
5
|
-
import { cn } from '../lib/utils'
|
|
6
|
-
import type { ConfigProvider } from '../lib/types'
|
|
5
|
+
import { cn } from '../lib/utils.js'
|
|
6
|
+
import type { ConfigProvider } from '../lib/types.js'
|
|
7
7
|
import { Pencil, CheckCircle2, XCircle } from 'lucide-react'
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Demonstrates how to create stories for components
|
|
3
3
|
|
|
4
4
|
import type { Meta, StoryObj } from '@storybook/react'
|
|
5
|
-
import { ExampleComponent } from './example_component'
|
|
5
|
+
import { ExampleComponent } from './example_component.js'
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Meta configuration for ExampleComponent stories
|
package/src/lib/config-loader.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import fs from 'fs'
|
|
11
11
|
import path from 'path'
|
|
12
12
|
import ini from 'ini'
|
|
13
|
-
import type { ConfigProvider, HazoConfigOptions, Logger } from './types'
|
|
14
|
-
import { ConfigErrorCode as EC } from './types'
|
|
13
|
+
import type { ConfigProvider, HazoConfigOptions, Logger } from './types.js'
|
|
14
|
+
import { ConfigErrorCode as EC } from './types.js'
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* No-op logger implementation (default when no logger provided)
|
|
@@ -27,21 +27,26 @@ const no_op_logger: Logger = {
|
|
|
27
27
|
* HazoConfig class
|
|
28
28
|
*
|
|
29
29
|
* Implements ConfigProvider interface for managing INI configuration files.
|
|
30
|
-
* Provides sync read/write operations with in-memory caching.
|
|
30
|
+
* Provides sync read/write operations with in-memory caching and automatic cache refresh.
|
|
31
31
|
*/
|
|
32
32
|
export class HazoConfig implements ConfigProvider {
|
|
33
33
|
private filePath: string
|
|
34
34
|
private logger: Logger
|
|
35
35
|
private config: Record<string, Record<string, string>> = {}
|
|
36
|
+
private cache_ttl_ms: number
|
|
37
|
+
private disable_cache: boolean
|
|
38
|
+
private last_cache_refresh: number = 0
|
|
36
39
|
|
|
37
40
|
/**
|
|
38
41
|
* Constructor
|
|
39
|
-
* @param options - Configuration options including filePath
|
|
42
|
+
* @param options - Configuration options including filePath, optional logger, cache settings
|
|
40
43
|
* @throws Error if file doesn't exist
|
|
41
44
|
*/
|
|
42
45
|
constructor(options: HazoConfigOptions) {
|
|
43
46
|
this.filePath = path.resolve(options.filePath)
|
|
44
47
|
this.logger = options.logger || no_op_logger
|
|
48
|
+
this.cache_ttl_ms = options.cache_ttl_ms ?? 5000 // Default 5 seconds
|
|
49
|
+
this.disable_cache = options.disable_cache ?? false // Default: caching enabled
|
|
45
50
|
|
|
46
51
|
// Validate file exists
|
|
47
52
|
if (!fs.existsSync(this.filePath)) {
|
|
@@ -55,6 +60,32 @@ export class HazoConfig implements ConfigProvider {
|
|
|
55
60
|
this.refresh()
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Check if cache needs to be refreshed and refresh if needed
|
|
65
|
+
* This method is called before read operations to ensure cache is up to date
|
|
66
|
+
*/
|
|
67
|
+
private ensure_cache_fresh(): void {
|
|
68
|
+
if (this.disable_cache) {
|
|
69
|
+
// If caching is disabled, always refresh from disk
|
|
70
|
+
this.refresh()
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.cache_ttl_ms === 0) {
|
|
75
|
+
// If TTL is 0, cache never expires (only manual refresh)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const now = Date.now()
|
|
80
|
+
const cache_age = now - this.last_cache_refresh
|
|
81
|
+
|
|
82
|
+
if (cache_age >= this.cache_ttl_ms) {
|
|
83
|
+
// Cache has expired, refresh from disk
|
|
84
|
+
this.logger.debug(`[HazoConfig] Cache expired (age: ${cache_age}ms, ttl: ${this.cache_ttl_ms}ms), refreshing from disk`)
|
|
85
|
+
this.refresh()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
58
89
|
/**
|
|
59
90
|
* Get a configuration value by section and key
|
|
60
91
|
* @param section - The configuration section name
|
|
@@ -62,6 +93,7 @@ export class HazoConfig implements ConfigProvider {
|
|
|
62
93
|
* @returns The configuration value, or undefined if not found
|
|
63
94
|
*/
|
|
64
95
|
get(section: string, key: string): string | undefined {
|
|
96
|
+
this.ensure_cache_fresh()
|
|
65
97
|
return this.config[section]?.[key]
|
|
66
98
|
}
|
|
67
99
|
|
|
@@ -71,6 +103,7 @@ export class HazoConfig implements ConfigProvider {
|
|
|
71
103
|
* @returns A record of key-value pairs for the section, or undefined if section doesn't exist
|
|
72
104
|
*/
|
|
73
105
|
getSection(section: string): Record<string, string> | undefined {
|
|
106
|
+
this.ensure_cache_fresh()
|
|
74
107
|
return this.config[section] ? { ...this.config[section] } : undefined
|
|
75
108
|
}
|
|
76
109
|
|
|
@@ -135,6 +168,9 @@ export class HazoConfig implements ConfigProvider {
|
|
|
135
168
|
}
|
|
136
169
|
}
|
|
137
170
|
|
|
171
|
+
// Update cache timestamp
|
|
172
|
+
this.last_cache_refresh = Date.now()
|
|
173
|
+
|
|
138
174
|
this.logger.info(`[HazoConfig] Configuration refreshed from: ${this.filePath}`, {
|
|
139
175
|
sections: Object.keys(this.config)
|
|
140
176
|
})
|
|
@@ -169,6 +205,7 @@ export class HazoConfig implements ConfigProvider {
|
|
|
169
205
|
* @returns A record of all sections and their key-value pairs
|
|
170
206
|
*/
|
|
171
207
|
getAllSections(): Record<string, Record<string, string>> {
|
|
208
|
+
this.ensure_cache_fresh()
|
|
172
209
|
// Return a deep copy to prevent external modification
|
|
173
210
|
const result: Record<string, Record<string, string>> = {}
|
|
174
211
|
for (const [section, values] of Object.entries(this.config)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Mock config provider for browser/Storybook testing
|
|
2
2
|
// Implements ConfigProvider interface using in-memory storage instead of file system
|
|
3
3
|
|
|
4
|
-
import type { ConfigProvider } from './types'
|
|
4
|
+
import type { ConfigProvider } from './types.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Mock config provider that stores configuration in memory
|
package/src/lib/types.ts
CHANGED
|
@@ -76,6 +76,20 @@ export interface HazoConfigOptions {
|
|
|
76
76
|
* Optional logger instance for logging operations
|
|
77
77
|
*/
|
|
78
78
|
logger?: Logger
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cache time-to-live in milliseconds (default: 5000ms)
|
|
82
|
+
* When cache expires, configuration will be automatically refreshed from disk
|
|
83
|
+
* Set to 0 to disable automatic cache expiration (cache will only refresh on manual refresh() call)
|
|
84
|
+
*/
|
|
85
|
+
cache_ttl_ms?: number
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Disable caching entirely (default: false)
|
|
89
|
+
* When true, every get() and getSection() call will read from disk
|
|
90
|
+
* When false, values are cached in memory and refreshed based on cache_ttl_ms
|
|
91
|
+
*/
|
|
92
|
+
disable_cache?: boolean
|
|
79
93
|
}
|
|
80
94
|
|
|
81
95
|
/**
|
package/tsconfig.build.json
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
"extends": "./tsconfig.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"noEmit": false,
|
|
5
|
-
"module": "
|
|
5
|
+
"module": "ESNext",
|
|
6
6
|
"declaration": true,
|
|
7
7
|
"declarationMap": true,
|
|
8
8
|
"outDir": "./dist",
|
|
9
9
|
"rootDir": "./src",
|
|
10
10
|
"allowImportingTsExtensions": false,
|
|
11
|
-
"moduleResolution": "
|
|
11
|
+
"moduleResolution": "node",
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"esModuleInterop": true
|
|
12
14
|
},
|
|
13
15
|
"include": ["src"],
|
|
14
16
|
"exclude": ["**/*.stories.tsx", "**/*.test.tsx", "**/*.test.ts"]
|