vasuzex 2.2.4 → 2.3.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/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to Vasuzex will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [2.3.0] - 2026-02-18
|
|
6
|
+
|
|
7
|
+
### 🚀 Standalone Script Support
|
|
8
|
+
|
|
9
|
+
This release enables Config and Log facades to work in standalone scripts without Application instance, following Laravel's graceful degradation pattern.
|
|
10
|
+
|
|
11
|
+
### ✨ Added
|
|
12
|
+
|
|
13
|
+
#### Config Facade Standalone Mode
|
|
14
|
+
- **Standalone script support** - Config.get() now works without Application instance
|
|
15
|
+
- **Auto-discovery** - Walks up directory tree to find project root with `config/` directory
|
|
16
|
+
- **Config loading** - Loads all .cjs files from `config/` directory using require()
|
|
17
|
+
- **Dot notation** - Full support for nested keys (`database.connections.default.host`)
|
|
18
|
+
- **Caching** - Loaded config cached for performance
|
|
19
|
+
- **Graceful fallback** - Tries Application-bound service first, falls back if unavailable
|
|
20
|
+
```javascript
|
|
21
|
+
// Now works in standalone scripts!
|
|
22
|
+
import { Config } from 'vasuzex';
|
|
23
|
+
const dbHost = Config.get('database.connections.default.host', 'localhost');
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
#### Log Facade Standalone Mode
|
|
27
|
+
- **Standalone script support** - Log methods now work without Application instance
|
|
28
|
+
- **Console fallback** - Falls back to console.log with structured formatting
|
|
29
|
+
- **ISO timestamps** - Production-quality log output: `[2026-02-18T05:21:16.107Z] [INFO] message`
|
|
30
|
+
- **Respects LOG_LEVEL** - Debug logs only show if `LOG_LEVEL=debug` or `DEBUG=true`
|
|
31
|
+
- **All log levels** - debug(), info(), warn(), error() all supported
|
|
32
|
+
- **Graceful fallback** - Tries Application-bound logger first, falls back if unavailable
|
|
33
|
+
```javascript
|
|
34
|
+
// Now works in standalone scripts!
|
|
35
|
+
import { Log } from 'vasuzex';
|
|
36
|
+
Log.info('Processing data', { records: 100 });
|
|
37
|
+
// Output: [2026-02-18T05:21:16.107Z] [INFO] Processing data {"records":100}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 🔧 Technical Details
|
|
41
|
+
|
|
42
|
+
- **Service container priority** - Always tries Application instance first (backward compatible)
|
|
43
|
+
- **Zero breaking changes** - All existing apps continue working unchanged
|
|
44
|
+
- **Laravel pattern** - Follows Laravel's facade graceful degradation pattern
|
|
45
|
+
- **Production-ready** - Proper error handling, silent failures, performance optimized
|
|
46
|
+
|
|
47
|
+
### 🎯 Use Cases
|
|
48
|
+
|
|
49
|
+
This enables:
|
|
50
|
+
- ✅ Database migration scripts using Config.get()
|
|
51
|
+
- ✅ Cron jobs and workers using Log.info()
|
|
52
|
+
- ✅ CLI tools without full Application bootstrap
|
|
53
|
+
- ✅ Test scripts accessing configuration
|
|
54
|
+
- ✅ Utility scripts with proper logging
|
|
55
|
+
|
|
4
56
|
## [2.2.0] - 2026-02-06
|
|
5
57
|
|
|
6
58
|
### 🚀 Major Pro-Level Enhancements
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# vasuzex v2.3.0 Release Notes
|
|
2
|
+
|
|
3
|
+
## Laravel-Style Standalone Script Support
|
|
4
|
+
|
|
5
|
+
### Overview
|
|
6
|
+
Added standalone script support to Config and Log facades, following Laravel's best practices for graceful facade degradation. This enables vasuzex facades to work in **both** application contexts (with service container) and standalone scripts (without Application instance).
|
|
7
|
+
|
|
8
|
+
### Why This is v2.3.0 (MINOR), Not a Patch
|
|
9
|
+
This is a **new feature** that maintains backward compatibility:
|
|
10
|
+
- ✅ Existing apps continue to work exactly as before (no breaking changes)
|
|
11
|
+
- ✅ Adds new capability: facades now work in standalone scripts
|
|
12
|
+
- ✅ Follows Laravel patterns (service container first, graceful fallback)
|
|
13
|
+
- ✅ Production-ready implementation with proper error handling
|
|
14
|
+
|
|
15
|
+
Per semver: `MAJOR.MINOR.PATCH` - this is a MINOR version (new backward-compatible functionality).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Changes Required
|
|
20
|
+
|
|
21
|
+
### 1. `/framework/Support/Facades/Config.js`
|
|
22
|
+
|
|
23
|
+
**Purpose**: Enable Config.get() in standalone scripts by loading .cjs config files from `config/` directory.
|
|
24
|
+
|
|
25
|
+
**Implementation**:
|
|
26
|
+
- Tries Application-bound config service first (normal app behavior)
|
|
27
|
+
- Falls back to direct config file loading if no Application
|
|
28
|
+
- Discovers project root by walking up directory tree
|
|
29
|
+
- Loads all .cjs files from `config/` directory using `require()`
|
|
30
|
+
- Supports dot notation (`database.connections.default.host`)
|
|
31
|
+
- Caches loaded config for performance
|
|
32
|
+
|
|
33
|
+
**File location**: `framework/Support/Facades/Config.js`
|
|
34
|
+
|
|
35
|
+
**Full file content**: See `/Users/rishi/Desktop/work/neasto/node_modules/.pnpm/vasuzex@2.2.4_@azure+core-client@1.10.1_@types+node@25.2.3_react-redux@9.2.0_@types+rea_6b288229c8b08a609ae395ef19989414/node_modules/vasuzex/framework/Support/Facades/Config.js`
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### 2. `/framework/Support/Facades/Log.js`
|
|
40
|
+
|
|
41
|
+
**Purpose**: Enable Log.info/error/warn/debug in standalone scripts with console fallback.
|
|
42
|
+
|
|
43
|
+
**Implementation**:
|
|
44
|
+
- Tries Application-bound logger first (normal app behavior)
|
|
45
|
+
- Falls back to console.log with structured formatting
|
|
46
|
+
- ISO timestamps for production-quality logs
|
|
47
|
+
- Respects LOG_LEVEL environment variable (debug only shows if LOG_LEVEL=debug)
|
|
48
|
+
- All log levels supported: debug, info, warn, error
|
|
49
|
+
|
|
50
|
+
**File location**: `framework/Support/Facades/Log.js`
|
|
51
|
+
|
|
52
|
+
**Full file content**: See `/Users/rishi/Desktop/work/neasto/node_modules/.pnpm/vasuzex@2.2.4_@azure+core-client@1.10.1_@types+node@25.2.3_react-redux@9.2.0_@types+rea_6b288229c8b08a609ae395ef19989414/node_modules/vasuzex/framework/Support/Facades/Log.js`
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Testing Done
|
|
57
|
+
|
|
58
|
+
### Standalone Script Tests ✅
|
|
59
|
+
1. ✅ Config.get() - basic values (app.name, app.env)
|
|
60
|
+
2. ✅ Config.get() - nested dot notation (database.connections.default.host)
|
|
61
|
+
3. ✅ Config.get() - default values for missing keys
|
|
62
|
+
4. ✅ Log.info() - with structured data
|
|
63
|
+
5. ✅ Log.error() - with error data
|
|
64
|
+
6. ✅ Log.warn() - with warning data
|
|
65
|
+
7. ✅ Log.debug() - respects LOG_LEVEL
|
|
66
|
+
|
|
67
|
+
### Real-World Script Tests ✅
|
|
68
|
+
1. ✅ Elasticsearch setup scripts (setup-indices.js)
|
|
69
|
+
2. ✅ Elasticsearch client connection (ElasticsearchClient.js)
|
|
70
|
+
3. ✅ Dispatch test scripts (full-dispatch-test.cjs)
|
|
71
|
+
4. ✅ Database bootstrap scripts (work without Application)
|
|
72
|
+
|
|
73
|
+
### Backward Compatibility ✅
|
|
74
|
+
- Apps with Application instance continue using service container
|
|
75
|
+
- No changes required to existing application code
|
|
76
|
+
- Facades try App context first, fall back only if needed
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Laravel Pattern Compliance
|
|
81
|
+
|
|
82
|
+
### Config Facade Pattern:
|
|
83
|
+
- ✓ Loads from `config/` directory (like Laravel's config path)
|
|
84
|
+
- ✓ Supports dot notation (like Laravel's `config('database.default')`)
|
|
85
|
+
- ✓ Returns default if key not found
|
|
86
|
+
- ✓ Caches loaded config (like Laravel's config cache)
|
|
87
|
+
|
|
88
|
+
### Log Facade Pattern:
|
|
89
|
+
- ✓ Service container first (facade root)
|
|
90
|
+
- ✓ Graceful degradation to console
|
|
91
|
+
- ✓ Standard log levels (debug/info/warn/error)
|
|
92
|
+
- ✓ Respects environment settings
|
|
93
|
+
|
|
94
|
+
### Facade Pattern:
|
|
95
|
+
- ✓ Service container priority (tries Application first)
|
|
96
|
+
- ✓ Graceful fallback (standalone mode)
|
|
97
|
+
- ✓ No breaking changes (backward compatible)
|
|
98
|
+
- ✓ Production-ready error handling
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Publishing Instructions
|
|
103
|
+
|
|
104
|
+
### 1. Copy Changes to vasuzex Repo
|
|
105
|
+
Copy updated files from neasto's node_modules:
|
|
106
|
+
```bash
|
|
107
|
+
# Config.js
|
|
108
|
+
cp /Users/rishi/Desktop/work/neasto/node_modules/.pnpm/vasuzex@2.2.4_*/node_modules/vasuzex/framework/Support/Facades/Config.js \
|
|
109
|
+
<vasuzex-repo>/framework/Support/Facades/Config.js
|
|
110
|
+
|
|
111
|
+
# Log.js
|
|
112
|
+
cp /Users/rishi/Desktop/work/neasto/node_modules/.pnpm/vasuzex@2.2.4_*/node_modules/vasuzex/framework/Support/Facades/Log.js \
|
|
113
|
+
<vasuzex-repo>/framework/Support/Facades/Log.js
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 2. Update package.json
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"version": "2.3.0"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### 3. Update CHANGELOG.md
|
|
124
|
+
```markdown
|
|
125
|
+
## [2.3.0] - 2026-02-18
|
|
126
|
+
|
|
127
|
+
### Added
|
|
128
|
+
- **Standalone Script Support**: Config and Log facades now work without Application instance
|
|
129
|
+
- Config facade loads .cjs files from config/ directory in standalone mode
|
|
130
|
+
- Log facade falls back to console.log with ISO timestamps in standalone mode
|
|
131
|
+
- Graceful degradation pattern: tries service container first, falls back if no Application
|
|
132
|
+
|
|
133
|
+
### Changed
|
|
134
|
+
- Config.js: Enhanced with standalone config loading (backward compatible)
|
|
135
|
+
- Log.js: Enhanced with console fallback logging (backward compatible)
|
|
136
|
+
|
|
137
|
+
### Technical
|
|
138
|
+
- Follows Laravel service container pattern with graceful fallback
|
|
139
|
+
- Production-ready error handling
|
|
140
|
+
- Zero breaking changes - all existing apps continue working unchanged
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 4. Git Workflow
|
|
144
|
+
```bash
|
|
145
|
+
git checkout -b feature/standalone-facade-support
|
|
146
|
+
git add framework/Support/Facades/Config.js
|
|
147
|
+
git add framework/Support/Facades/Log.js
|
|
148
|
+
git add package.json
|
|
149
|
+
git add CHANGELOG.md
|
|
150
|
+
git commit -m "feat: Add standalone script support to Config and Log facades (v2.3.0)
|
|
151
|
+
|
|
152
|
+
- Enable facades to work without Application instance
|
|
153
|
+
- Load config from files in standalone mode
|
|
154
|
+
- Fallback logging for standalone scripts
|
|
155
|
+
- Maintains backward compatibility
|
|
156
|
+
- Follows Laravel graceful degradation pattern"
|
|
157
|
+
git push origin feature/standalone-facade-support
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 5. Create PR & Merge
|
|
161
|
+
Review changes, merge to main
|
|
162
|
+
|
|
163
|
+
### 6. Publish to npm
|
|
164
|
+
```bash
|
|
165
|
+
npm version 2.3.0
|
|
166
|
+
npm publish
|
|
167
|
+
git push --tags
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 7. Update neasto
|
|
171
|
+
```bash
|
|
172
|
+
cd /Users/rishi/Desktop/work/neasto
|
|
173
|
+
pnpm update vasuzex
|
|
174
|
+
# Should install vasuzex@2.3.0
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Summary
|
|
180
|
+
|
|
181
|
+
This is a **proper Laravel-standard implementation**, not a quick patch:
|
|
182
|
+
|
|
183
|
+
✅ **Service container priority** - Uses Application-bound services when available
|
|
184
|
+
✅ **Graceful degradation** - Falls back intelligently when no Application
|
|
185
|
+
✅ **Production-ready** - Proper error handling, caching, performance
|
|
186
|
+
✅ **Zero breaking changes** - All existing code continues working
|
|
187
|
+
✅ **Laravel patterns** - Follows Laravel's facade and config patterns
|
|
188
|
+
|
|
189
|
+
**Version: 2.3.0** (minor version - new backward-compatible feature)
|
|
@@ -1,13 +1,147 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Config Facade
|
|
3
|
+
* Enhanced to support standalone scripts without Application instance
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { Facade, createFacade } from './Facade.js';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import { existsSync, readdirSync } from 'fs';
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
// Create require for loading .cjs files
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
let standaloneConfig = null;
|
|
16
|
+
let standaloneConfigLoaded = false;
|
|
6
17
|
|
|
7
18
|
class ConfigFacade extends Facade {
|
|
8
19
|
static getFacadeAccessor() {
|
|
9
20
|
return 'config';
|
|
10
21
|
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get config value with standalone fallback
|
|
25
|
+
* Works in both app context and standalone scripts
|
|
26
|
+
*/
|
|
27
|
+
static get(key, defaultValue = null) {
|
|
28
|
+
// Try app context first (if Application exists)
|
|
29
|
+
if (this.app) {
|
|
30
|
+
try {
|
|
31
|
+
const instance = this.getFacadeRoot();
|
|
32
|
+
return instance.get(key, defaultValue);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Fall through to standalone mode
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Standalone mode: Load config directly
|
|
39
|
+
return this.getStandalone(key, defaultValue);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Load config in standalone mode (no Application)
|
|
44
|
+
* Discovers project root and loads config files
|
|
45
|
+
*/
|
|
46
|
+
static getStandalone(key, defaultValue = null) {
|
|
47
|
+
if (!standaloneConfigLoaded) {
|
|
48
|
+
this.loadStandaloneConfig();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!standaloneConfig) {
|
|
52
|
+
return defaultValue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Navigate nested keys (e.g., 'database.connections.default')
|
|
56
|
+
const keys = key.split('.');
|
|
57
|
+
let value = standaloneConfig;
|
|
58
|
+
|
|
59
|
+
for (const k of keys) {
|
|
60
|
+
if (value && typeof value === 'object' && k in value) {
|
|
61
|
+
value = value[k];
|
|
62
|
+
} else {
|
|
63
|
+
return defaultValue;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return value !== undefined ? value : defaultValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Load all config files from config/ directory
|
|
72
|
+
*/
|
|
73
|
+
static loadStandaloneConfig() {
|
|
74
|
+
try {
|
|
75
|
+
// Find project root (look for config/ directory)
|
|
76
|
+
const rootDir = this.findProjectRoot();
|
|
77
|
+
if (!rootDir) {
|
|
78
|
+
standaloneConfig = {};
|
|
79
|
+
standaloneConfigLoaded = true;
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const configDir = path.join(rootDir, 'config');
|
|
84
|
+
if (!existsSync(configDir)) {
|
|
85
|
+
standaloneConfig = {};
|
|
86
|
+
standaloneConfigLoaded = true;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Load all .cjs config files
|
|
91
|
+
const files = readdirSync(configDir);
|
|
92
|
+
standaloneConfig = {};
|
|
93
|
+
|
|
94
|
+
for (const file of files) {
|
|
95
|
+
if (file.endsWith('.cjs')) {
|
|
96
|
+
const configName = file.replace(/\.cjs$/, '');
|
|
97
|
+
const configPath = path.join(configDir, file);
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Clear require cache to get fresh config
|
|
101
|
+
const resolvedPath = require.resolve(configPath);
|
|
102
|
+
delete require.cache[resolvedPath];
|
|
103
|
+
|
|
104
|
+
// Load config file
|
|
105
|
+
const configData = require(configPath);
|
|
106
|
+
standaloneConfig[configName] = configData;
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Silently skip files that can't be loaded
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
standaloneConfigLoaded = true;
|
|
114
|
+
} catch (error) {
|
|
115
|
+
standaloneConfig = {};
|
|
116
|
+
standaloneConfigLoaded = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Find project root by looking for config/ directory
|
|
122
|
+
*/
|
|
123
|
+
static findProjectRoot() {
|
|
124
|
+
try {
|
|
125
|
+
// Start from current working directory
|
|
126
|
+
let dir = process.cwd();
|
|
127
|
+
|
|
128
|
+
// Walk up until we find config/
|
|
129
|
+
for (let i = 0; i < 10; i++) {
|
|
130
|
+
const configPath = path.join(dir, 'config');
|
|
131
|
+
if (existsSync(configPath)) {
|
|
132
|
+
return dir;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const parent = path.dirname(dir);
|
|
136
|
+
if (parent === dir) break; // Reached filesystem root
|
|
137
|
+
dir = parent;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return null;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
11
145
|
}
|
|
12
146
|
|
|
13
147
|
export default createFacade(ConfigFacade);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Log Facade
|
|
3
|
+
* Enhanced to support standalone scripts without Application instance
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { Facade, createFacade } from './Facade.js';
|
|
@@ -8,6 +9,66 @@ class LogFacade extends Facade {
|
|
|
8
9
|
static getFacadeAccessor() {
|
|
9
10
|
return 'log';
|
|
10
11
|
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Fallback logging methods for standalone scripts
|
|
15
|
+
*/
|
|
16
|
+
static log(level, message, data = null) {
|
|
17
|
+
const timestamp = new Date().toISOString();
|
|
18
|
+
const dataStr = data ? ' ' + JSON.stringify(data) : '';
|
|
19
|
+
console.log(`[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static debug(message, data = null) {
|
|
23
|
+
if (this.app) {
|
|
24
|
+
try {
|
|
25
|
+
const instance = this.getFacadeRoot();
|
|
26
|
+
return instance.debug(message, data);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// Fall through to standalone mode
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Standalone: Only log debug in verbose mode
|
|
32
|
+
if (process.env.LOG_LEVEL === 'debug' || process.env.DEBUG) {
|
|
33
|
+
this.log('debug', message, data);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static info(message, data = null) {
|
|
38
|
+
if (this.app) {
|
|
39
|
+
try {
|
|
40
|
+
const instance = this.getFacadeRoot();
|
|
41
|
+
return instance.info(message, data);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// Fall through to standalone mode
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
this.log('info', message, data);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static warn(message, data = null) {
|
|
50
|
+
if (this.app) {
|
|
51
|
+
try {
|
|
52
|
+
const instance = this.getFacadeRoot();
|
|
53
|
+
return instance.warn(message, data);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Fall through to standalone mode
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
this.log('warn', message, data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static error(message, data = null) {
|
|
62
|
+
if (this.app) {
|
|
63
|
+
try {
|
|
64
|
+
const instance = this.getFacadeRoot();
|
|
65
|
+
return instance.error(message, data);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Fall through to standalone mode
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.log('error', message, data);
|
|
71
|
+
}
|
|
11
72
|
}
|
|
12
73
|
|
|
13
74
|
export default createFacade(LogFacade);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - Rows per page selector
|
|
11
11
|
* - Action buttons (edit/view/delete/switch)
|
|
12
12
|
* - Loading and empty states
|
|
13
|
+
* - State persistence (restores page/filters when navigating back)
|
|
13
14
|
*
|
|
14
15
|
* @module components/DataTable
|
|
15
16
|
*/
|
|
@@ -38,6 +39,8 @@ import { Pagination } from "./Pagination.jsx";
|
|
|
38
39
|
* @param {string} props.initialStatusFilter - Initial status filter (all/true/false)
|
|
39
40
|
* @param {number} props.initialLimit - Initial rows per page
|
|
40
41
|
* @param {string} props.emptyText - Text to show when no data
|
|
42
|
+
* @param {boolean} props.persistState - Enable state persistence (default: true)
|
|
43
|
+
* @param {string} props.stateKey - Custom key for state storage (default: derived from apiUrl)
|
|
41
44
|
*/
|
|
42
45
|
export function DataTable(props) {
|
|
43
46
|
// Internal refresh key for self-refresh
|
|
@@ -60,6 +63,8 @@ export function DataTable(props) {
|
|
|
60
63
|
onDelete,
|
|
61
64
|
onToggle,
|
|
62
65
|
api, // API client instance passed as prop
|
|
66
|
+
persistState = true, // Enable state persistence by default
|
|
67
|
+
stateKey, // Optional custom state key
|
|
63
68
|
} = props;
|
|
64
69
|
|
|
65
70
|
// Validate that api client is provided
|
|
@@ -67,6 +72,66 @@ export function DataTable(props) {
|
|
|
67
72
|
throw new Error('DataTable requires "api" prop - pass your API client instance');
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
// Generate unique storage key based on apiUrl or custom stateKey
|
|
76
|
+
const storageKey = React.useMemo(() => {
|
|
77
|
+
if (stateKey) return `datatable_${stateKey}`;
|
|
78
|
+
// Use apiUrl as key (remove query params for consistency)
|
|
79
|
+
const cleanUrl = apiUrl.split('?')[0];
|
|
80
|
+
return `datatable_${cleanUrl.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
81
|
+
}, [apiUrl, stateKey]);
|
|
82
|
+
|
|
83
|
+
// Helper to load persisted state
|
|
84
|
+
const loadPersistedState = React.useCallback(() => {
|
|
85
|
+
if (!persistState) return null;
|
|
86
|
+
try {
|
|
87
|
+
const stored = sessionStorage.getItem(storageKey);
|
|
88
|
+
return stored ? JSON.parse(stored) : null;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}, [persistState, storageKey]);
|
|
93
|
+
|
|
94
|
+
// Helper to save state
|
|
95
|
+
const saveState = React.useCallback((state) => {
|
|
96
|
+
if (!persistState) return;
|
|
97
|
+
try {
|
|
98
|
+
sessionStorage.setItem(storageKey, JSON.stringify(state));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Silently fail if sessionStorage is not available
|
|
101
|
+
}
|
|
102
|
+
}, [persistState, storageKey]);
|
|
103
|
+
|
|
104
|
+
// Initialize state from persisted data or props
|
|
105
|
+
const persistedState = loadPersistedState();
|
|
106
|
+
|
|
107
|
+
const [page, setPage] = React.useState(persistedState?.page || initialPage);
|
|
108
|
+
const [sortBy, setSortBy] = React.useState(
|
|
109
|
+
persistedState?.sortBy || initialSortBy || (columns.find((c) => c.sortable)?.field) || "",
|
|
110
|
+
);
|
|
111
|
+
const [sortOrder, setSortOrder] = React.useState(persistedState?.sortOrder || initialSortOrder);
|
|
112
|
+
const [search, setSearch] = React.useState(persistedState?.search || initialSearch || "");
|
|
113
|
+
const [statusFilter, setStatusFilter] = React.useState(persistedState?.statusFilter || initialStatusFilter || "all");
|
|
114
|
+
const [limit, setLimit] = React.useState(persistedState?.limit || initialLimit || 10);
|
|
115
|
+
const [data, setData] = React.useState([]);
|
|
116
|
+
const [loading, setLoading] = React.useState(false);
|
|
117
|
+
const [totalPages, setTotalPages] = React.useState(1);
|
|
118
|
+
const [totalItems, setTotalItems] = React.useState(0);
|
|
119
|
+
// Column search state
|
|
120
|
+
const [columnSearch, setColumnSearch] = React.useState(persistedState?.columnSearch || {});
|
|
121
|
+
|
|
122
|
+
// Save state whenever it changes
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
saveState({
|
|
125
|
+
page,
|
|
126
|
+
sortBy,
|
|
127
|
+
sortOrder,
|
|
128
|
+
search,
|
|
129
|
+
statusFilter,
|
|
130
|
+
limit,
|
|
131
|
+
columnSearch,
|
|
132
|
+
});
|
|
133
|
+
}, [page, sortBy, sortOrder, search, statusFilter, limit, columnSearch, saveState]);
|
|
134
|
+
|
|
70
135
|
const handleStatusToggle = async (row) => {
|
|
71
136
|
if (!toggleLink) return;
|
|
72
137
|
try {
|
|
@@ -81,21 +146,6 @@ export function DataTable(props) {
|
|
|
81
146
|
toast.error(error.message || "Failed to update status");
|
|
82
147
|
}
|
|
83
148
|
};
|
|
84
|
-
|
|
85
|
-
const [page, setPage] = React.useState(initialPage);
|
|
86
|
-
const [sortBy, setSortBy] = React.useState(
|
|
87
|
-
initialSortBy || (columns.find((c) => c.sortable)?.field) || "",
|
|
88
|
-
);
|
|
89
|
-
const [sortOrder, setSortOrder] = React.useState(initialSortOrder);
|
|
90
|
-
const [search, setSearch] = React.useState(initialSearch || "");
|
|
91
|
-
const [statusFilter, setStatusFilter] = React.useState(initialStatusFilter || "all");
|
|
92
|
-
const [limit, setLimit] = React.useState(initialLimit || 10);
|
|
93
|
-
const [data, setData] = React.useState([]);
|
|
94
|
-
const [loading, setLoading] = React.useState(false);
|
|
95
|
-
const [totalPages, setTotalPages] = React.useState(1);
|
|
96
|
-
const [totalItems, setTotalItems] = React.useState(0);
|
|
97
|
-
// Column search state
|
|
98
|
-
const [columnSearch, setColumnSearch] = React.useState({});
|
|
99
149
|
|
|
100
150
|
// Reset page to 1 when columnSearch changes
|
|
101
151
|
React.useEffect(() => {
|