@biglogic/rgs 3.1.0 → 3.5.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 +24 -6
- package/SECURITY.md +1 -1
- package/advanced.js +1 -1
- package/core/hooks.d.ts +2 -3
- package/core/persistence.d.ts +23 -0
- package/core/plugins.d.ts +8 -0
- package/core/reactivity.d.ts +19 -0
- package/core/types.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +81 -77
- package/rgs-extension.vsix +0 -0
- package/core/advanced.js +0 -4
- package/core/async.js +0 -40
- package/core/hooks.js +0 -52
- package/core/security.js +0 -124
- package/core/store.js +0 -595
- package/core/types.js +0 -5
- package/core/utils.js +0 -72
- package/examples/README.md +0 -41
- package/examples/async-data-fetch/UserLoader.d.ts +0 -12
- package/examples/async-data-fetch/UserLoader.js +0 -10
- package/examples/async-data-fetch/UserLoader.ts +0 -30
- package/examples/basic-counter/CounterComponent.d.ts +0 -2
- package/examples/basic-counter/CounterComponent.js +0 -7
- package/examples/basic-counter/CounterComponent.tsx +0 -22
- package/examples/basic-counter/CounterStore.d.ts +0 -7
- package/examples/basic-counter/CounterStore.js +0 -13
- package/examples/basic-counter/CounterStore.ts +0 -25
- package/examples/big-data-indexeddb/BigDataStore.d.ts +0 -10
- package/examples/big-data-indexeddb/BigDataStore.js +0 -32
- package/examples/big-data-indexeddb/BigDataStore.ts +0 -60
- package/examples/global-theme/ThemeManager.d.ts +0 -7
- package/examples/global-theme/ThemeManager.js +0 -13
- package/examples/global-theme/ThemeManager.ts +0 -32
- package/examples/hybrid-cloud-sync/HybridStore.d.ts +0 -19
- package/examples/hybrid-cloud-sync/HybridStore.js +0 -44
- package/examples/hybrid-cloud-sync/HybridStore.ts +0 -78
- package/examples/persistent-cart/CartStore.d.ts +0 -13
- package/examples/persistent-cart/CartStore.js +0 -23
- package/examples/persistent-cart/CartStore.ts +0 -41
- package/examples/rbac-dashboard/DashboardStore.d.ts +0 -47
- package/examples/rbac-dashboard/DashboardStore.js +0 -31
- package/examples/rbac-dashboard/DashboardStore.ts +0 -46
- package/examples/secure-auth/AuthStore.d.ts +0 -14
- package/examples/secure-auth/AuthStore.js +0 -20
- package/examples/secure-auth/AuthStore.ts +0 -36
- package/examples/security-best-practices/SecurityStore.d.ts +0 -22
- package/examples/security-best-practices/SecurityStore.js +0 -30
- package/examples/security-best-practices/SecurityStore.ts +0 -75
- package/examples/stress-tests/StressStore.d.ts +0 -41
- package/examples/stress-tests/StressStore.js +0 -41
- package/examples/stress-tests/StressStore.ts +0 -61
- package/examples/super-easy/EasyStore.d.ts +0 -44
- package/examples/super-easy/EasyStore.js +0 -24
- package/examples/super-easy/EasyStore.ts +0 -61
- package/examples/undo-redo-editor/EditorStore.d.ts +0 -9
- package/examples/undo-redo-editor/EditorStore.js +0 -13
- package/examples/undo-redo-editor/EditorStore.ts +0 -28
- package/markdown/SUMMARY.md +0 -59
- package/markdown/api.md +0 -381
- package/markdown/chapters/01-philosophy.md +0 -54
- package/markdown/chapters/02-getting-started.md +0 -68
- package/markdown/chapters/03-the-magnetar-way.md +0 -69
- package/markdown/chapters/04-persistence-and-safety.md +0 -125
- package/markdown/chapters/05-plugin-sdk.md +0 -290
- package/markdown/chapters/05-plugins-and-extensibility.md +0 -190
- package/markdown/chapters/06-case-studies.md +0 -69
- package/markdown/chapters/07-faq.md +0 -53
- package/markdown/chapters/08-migration-guide.md +0 -253
- package/markdown/chapters/09-security-architecture.md +0 -40
- package/plugins/index.js +0 -34
- package/plugins/official/analytics.plugin.js +0 -19
- package/plugins/official/cloud-sync.plugin.js +0 -117
- package/plugins/official/debug.plugin.js +0 -62
- package/plugins/official/devtools.plugin.js +0 -28
- package/plugins/official/guard.plugin.js +0 -15
- package/plugins/official/immer.plugin.js +0 -10
- package/plugins/official/indexeddb.plugin.js +0 -102
- package/plugins/official/schema.plugin.js +0 -16
- package/plugins/official/snapshot.plugin.js +0 -27
- package/plugins/official/sync.plugin.js +0 -37
- package/plugins/official/undo-redo.plugin.js +0 -61
- package/tsconfig.tsbuildinfo +0 -1
|
@@ -1,253 +0,0 @@
|
|
|
1
|
-
# 🔄 Migration Guide
|
|
2
|
-
|
|
3
|
-
## Upgrading from Previous Versions
|
|
4
|
-
|
|
5
|
-
### Security: `secure` → `encoded`
|
|
6
|
-
|
|
7
|
-
**IMPORTANT:** The `secure` option has been deprecated in favor of `encoded` to clarify that it only applies **base64 encoding**, not encryption.
|
|
8
|
-
|
|
9
|
-
#### ❌ Old Code (Deprecated)
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
store.set('apiKey', 'secret123', {
|
|
13
|
-
persist: true,
|
|
14
|
-
secure: true // ⚠️ DEPRECATED: This is NOT encryption!
|
|
15
|
-
})
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
#### ✅ New Code (Recommended)
|
|
19
|
-
|
|
20
|
-
```typescript
|
|
21
|
-
store.set('apiKey', 'secret123', {
|
|
22
|
-
persist: true,
|
|
23
|
-
encoded: true // ✅ Clear: This is base64 encoding
|
|
24
|
-
})
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
#### Backward Compatibility
|
|
28
|
-
|
|
29
|
-
Your old code will **still work**, but you'll see a deprecation warning in TypeScript:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
/** @deprecated Use 'encoded' instead. 'secure' only applies base64 encoding, not encryption. */
|
|
33
|
-
secure?: boolean
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
#### Why This Change?
|
|
37
|
-
|
|
38
|
-
Base64 encoding is **NOT encryption**. It's trivial to decode:
|
|
39
|
-
|
|
40
|
-
```javascript
|
|
41
|
-
// Anyone can decode base64
|
|
42
|
-
const encoded = btoa(JSON.stringify({ secret: 'password123' }))
|
|
43
|
-
const decoded = JSON.parse(atob(encoded)) // { secret: 'password123' }
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**For real security**, use proper encryption libraries like:
|
|
47
|
-
|
|
48
|
-
- `crypto-js` (AES encryption)
|
|
49
|
-
- `tweetnacl` (NaCl encryption)
|
|
50
|
-
- Web Crypto API (`crypto.subtle`)
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
|
-
### Error Handling: `onError` Callback
|
|
55
|
-
|
|
56
|
-
**NEW:** You can now catch and handle errors from plugins, hydration, and other operations.
|
|
57
|
-
|
|
58
|
-
#### Example: Custom Error Logging
|
|
59
|
-
|
|
60
|
-
```typescript
|
|
61
|
-
import { initState, useStore } from '@biglogic/rgs'
|
|
62
|
-
|
|
63
|
-
const store = initState({
|
|
64
|
-
namespace: 'myapp',
|
|
65
|
-
onError: (error, context) => {
|
|
66
|
-
// Send to your error tracking service
|
|
67
|
-
console.error(`[gState Error] ${context.operation}:`, error)
|
|
68
|
-
|
|
69
|
-
if (context.operation === 'hydration') {
|
|
70
|
-
// Handle corrupted localStorage
|
|
71
|
-
localStorage.clear()
|
|
72
|
-
alert('Storage corrupted, resetting...')
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (context.operation.startsWith('plugin:')) {
|
|
76
|
-
// Handle plugin crashes
|
|
77
|
-
Sentry.captureException(error, { tags: { plugin: context.operation } })
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
})
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
#### Error Context
|
|
84
|
-
|
|
85
|
-
```typescript
|
|
86
|
-
interface ErrorContext {
|
|
87
|
-
operation: string // 'hydration', 'plugin:name:hook', 'set'
|
|
88
|
-
key?: string // State key (if applicable)
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
### Performance: `maxObjectSize` Warning
|
|
95
|
-
|
|
96
|
-
**NEW:** Get warned when storing objects larger than a configurable limit (default: 5MB).
|
|
97
|
-
|
|
98
|
-
#### Example: Custom Size Limit
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
import { createStore } from '@biglogic/rgs'
|
|
102
|
-
|
|
103
|
-
const store = createStore({
|
|
104
|
-
maxObjectSize: 10 * 1024 * 1024, // 10MB limit
|
|
105
|
-
onError: (error, context) => {
|
|
106
|
-
if (context.operation === 'set') {
|
|
107
|
-
console.warn(`Large object detected in key: ${context.key}`)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// This will trigger a warning if > 10MB
|
|
113
|
-
store.set('bigData', hugeArray)
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
#### Disable Size Checking
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const store = createStore({
|
|
120
|
-
maxObjectSize: 0 // Disable size warnings
|
|
121
|
-
})
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Upgrading to Latest Version (The Enterprise Update)
|
|
127
|
-
|
|
128
|
-
**IMPORTANT:** This release introduces a fundamental shift towards **Multi-Store Isolation**. Security rules and GDPR consents are now instance-bound rather than global.
|
|
129
|
-
|
|
130
|
-
### 1. Security: Global → Instance-Specific
|
|
131
|
-
|
|
132
|
-
In previous versions, security rules were shared globally. Now, each store instance maintains its own rules for better isolation in micro-frontend environments.
|
|
133
|
-
|
|
134
|
-
#### ❌ Deprecated Global Methods
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { addAccessRule, recordConsent } from '@biglogic/rgs'
|
|
138
|
-
|
|
139
|
-
// ⚠️ DEPRECATED: These affect the 'default' store only and are less isolated
|
|
140
|
-
addAccessRule('user_*', ['read', 'write'])
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
#### ✅ Recommended Instance Methods
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
const store = createStore({ namespace: 'my-isolated-app' })
|
|
147
|
-
|
|
148
|
-
// ✅ Use the instance methods
|
|
149
|
-
store.addAccessRule('user_*', ['read', 'write'])
|
|
150
|
-
store.recordConsent('user123', 'marketing', true)
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### 2. Operational Resilience: Ghost Stores
|
|
154
|
-
|
|
155
|
-
**NEW:** If you access a store that hasn't finished its initialization (e.g., during slow hydration), RGS now returns a **Ghost Store Proxy**.
|
|
156
|
-
|
|
157
|
-
- **Behavior:** It prevents application crashes by providing a safe fallback.
|
|
158
|
-
- **Developer Warning:** It logs a detailed warning in the console so you can fix the initialization sequence.
|
|
159
|
-
|
|
160
|
-
### 3. Performance: Regex Caching
|
|
161
|
-
|
|
162
|
-
**NEW:** Permission checks now use an internal **Regex Cache** per instance.
|
|
163
|
-
|
|
164
|
-
- **Why?** Avoids the overhead of re-compiling regex strings on every `.get()` or `.set()` call.
|
|
165
|
-
- **Impact:** Significant performance boost for applications with high-frequency state updates and complex RBAC rules.
|
|
166
|
-
|
|
167
|
-
### 4. Advanced Plugin Typing
|
|
168
|
-
|
|
169
|
-
**NEW:** Introducing `GStatePlugins` for Module Augmentation.
|
|
170
|
-
|
|
171
|
-
- You can now define types for your custom plugins to get full IDE autocomplete.
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## v2.9.5: The Architecture & Safety Update (2026-02-16)
|
|
176
|
-
|
|
177
|
-
This release focuses on improving developer ergonomics, security visibility, and complex dependency handling.
|
|
178
|
-
|
|
179
|
-
### 1. Nested Computed Dependencies
|
|
180
|
-
**NEW:** Computed values can now re-trigger based on other computed values.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
store.compute('tax', (get) => (get<number>('subtotal') || 0) * 0.2)
|
|
184
|
-
store.compute('total', (get) => (get<number>('subtotal') || 0) + (get<number>('tax') || 0))
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### 2. Direct Store Access: `getStore()`
|
|
188
|
-
**NEW:** A top-level utility to retrieve the default store without React hooks.
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
import { getStore } from '@biglogic/rgs'
|
|
192
|
-
|
|
193
|
-
export const toggleTheme = () => {
|
|
194
|
-
const store = getStore()
|
|
195
|
-
if (store) store.set('mode', 'dark')
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### 3. Exposed Metadata: `namespace` and `userId`
|
|
200
|
-
**NEW:** Store instances now expose their identifying properties as read-only getters.
|
|
201
|
-
|
|
202
|
-
```typescript
|
|
203
|
-
const store = createStore({ namespace: 'auth-vault', userId: 'user-001' })
|
|
204
|
-
console.log(store.namespace) // 'auth-vault'
|
|
205
|
-
console.log(store.userId) // 'user-001'
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
### 4. High-Volume & Hybrid Sync (Plugins)
|
|
209
|
-
**NEW:** Support for GB-scale storage and Remote Cloud Backups.
|
|
210
|
-
|
|
211
|
-
- **IndexedDB Plugin**: Replaces localStorage for massive browser datasets.
|
|
212
|
-
- **Cloud Sync Plugin**: Differential synchronization to MongoDB, Firebase, or any SQL backend.
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
// Example: Manual Cloud Sync
|
|
216
|
-
const result = await store.plugins.cloudSync.sync()
|
|
217
|
-
console.log('Stats:', store.plugins.cloudSync.getStats())
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
---
|
|
221
|
-
|
|
222
|
-
## Breaking Changes
|
|
223
|
-
|
|
224
|
-
### 🔒 Security Isolation
|
|
225
|
-
|
|
226
|
-
If you relied on `addAccessRule()` from the global export to affect a `createStore()` instance, you must now call `store.addAccessRule()` on that specific instance.
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
## Recommended Actions
|
|
231
|
-
|
|
232
|
-
### 1. Migrate Security Calls to Store Instances
|
|
233
|
-
|
|
234
|
-
**Priority:** High (if using multiple stores)
|
|
235
|
-
|
|
236
|
-
**Effort:** Low
|
|
237
|
-
|
|
238
|
-
### 2. Implement `GStatePlugins` for Custom Plugins
|
|
239
|
-
|
|
240
|
-
**Priority:** Medium (Developer Experience)
|
|
241
|
-
|
|
242
|
-
**Effort:** Low
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## Need Help?
|
|
247
|
-
|
|
248
|
-
- **Issues:** [GitHub Issues](https://github.com/dpassariello/rgs/issues)
|
|
249
|
-
- **Docs:** [Galaxy Documentation](../SUMMARY.md)
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Last updated: 2026-02-16
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Security Architecture & Hardening
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
React Globo State (RGS) is designed with a "Security-First" philosophy. Our architecture ensures that global state is not only reactive but protected against common web vulnerabilities and unauthorized access.
|
|
5
|
-
|
|
6
|
-
## 1. Data Sanitization (XSS Defense)
|
|
7
|
-
The `sanitizeValue` utility provides a robust baseline defense by stripping malicious content from strings and objects before they enter the store.
|
|
8
|
-
|
|
9
|
-
- **Scheme Blocking**: Specifically blocks `javascript:`, `vbscript:`, and `data:text/html` schemes.
|
|
10
|
-
- **Tag Removal**: Automatically removes dangerous HTML tags such as `<script>`, `<iframe>`, `<form>`, and `<meta>`.
|
|
11
|
-
- **Entity Removal**: Strips HTML entities (`&#...;`) to prevent obfuscation-based bypasses.
|
|
12
|
-
|
|
13
|
-
## 2. Advanced Deep Cloning
|
|
14
|
-
To ensure state immutability and prevent unintended side effects, RGS uses an intelligent cloning engine:
|
|
15
|
-
- **Native structuredClone**: Leverages the browser's native API for maximum performance.
|
|
16
|
-
- **Support for Collections**: Extends cloning capabilities to `Map` and `Set` objects.
|
|
17
|
-
- **Circular Reference Protection**: Uses `WeakMap` to handle complex nested structures safely.
|
|
18
|
-
|
|
19
|
-
## 3. Cryptography (AES-256-GCM)
|
|
20
|
-
The security module uses the Web Crypto API to provide high-performance, authenticated encryption:
|
|
21
|
-
- **AES-GCM**: Provides both confidentiality and integrity verification.
|
|
22
|
-
- **GCM (Galois/Counter Mode)**: Ensures that data has not been tampered with during storage.
|
|
23
|
-
|
|
24
|
-
## 4. RBAC (Role-Based Access Control)
|
|
25
|
-
RGS supports fine-grained access rules:
|
|
26
|
-
- **Fail-Closed Design**: Access is denied by default if any rules are defined.
|
|
27
|
-
- **Regex Caching**: Store instances cache compiled regular expressions for ultra-fast permission checks.
|
|
28
|
-
|
|
29
|
-
## 5. Security Best Practices
|
|
30
|
-
For real-world implementations, refer to the `examples/security-best-practices` directory, which covers:
|
|
31
|
-
- **Encryption Key Management**: Using `generateEncryptionKey()` for secure key generation.
|
|
32
|
-
- **Audit Logging**: Tracking all store modifications for compliance.
|
|
33
|
-
- **GDPR Compliance**: Managing user consent and data export/deletion.
|
|
34
|
-
|
|
35
|
-
## Summary of 2.9.5 Enhancements
|
|
36
|
-
- Robust regex patterns for `sanitizeValue`.
|
|
37
|
-
- Recursive sanitization for plain objects.
|
|
38
|
-
- `Map` and `Set` support in `deepClone`.
|
|
39
|
-
- **Exposed Metadata**: Store instances now expose read-only `namespace` and `userId`.
|
|
40
|
-
- **Direct Store Access**: Added `getStore()` utility for non-React contexts.
|
package/plugins/index.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export { immerPlugin } from "./official/immer.plugin";
|
|
2
|
-
export { undoRedoPlugin } from "./official/undo-redo.plugin";
|
|
3
|
-
export { schemaPlugin } from "./official/schema.plugin";
|
|
4
|
-
export { devToolsPlugin } from "./official/devtools.plugin";
|
|
5
|
-
export { snapshotPlugin } from "./official/snapshot.plugin";
|
|
6
|
-
export { guardPlugin } from "./official/guard.plugin";
|
|
7
|
-
export { analyticsPlugin } from "./official/analytics.plugin";
|
|
8
|
-
export { syncPlugin } from "./official/sync.plugin";
|
|
9
|
-
export { debugPlugin } from "./official/debug.plugin";
|
|
10
|
-
export { indexedDBPlugin } from "./official/indexeddb.plugin";
|
|
11
|
-
export { cloudSyncPlugin, createMongoAdapter, createFirestoreAdapter, createSqlRestAdapter } from "./official/cloud-sync.plugin";
|
|
12
|
-
export const loggerPlugin = (options) => ({
|
|
13
|
-
name: 'gstate-logger',
|
|
14
|
-
hooks: {
|
|
15
|
-
onSet: ({ key, value, version }) => {
|
|
16
|
-
const time = new Date().toLocaleTimeString(), groupLabel = `[gState] SET: ${key} (v${version}) @ ${time}`;
|
|
17
|
-
if (options?.collapsed)
|
|
18
|
-
console.groupCollapsed(groupLabel);
|
|
19
|
-
else
|
|
20
|
-
console.group(groupLabel);
|
|
21
|
-
console.info('%c Value:', 'color: #4CAF50; font-weight: bold;', value);
|
|
22
|
-
console.groupEnd();
|
|
23
|
-
},
|
|
24
|
-
onRemove: ({ key }) => {
|
|
25
|
-
console.warn(`[gState] REMOVED: ${key}`);
|
|
26
|
-
},
|
|
27
|
-
onTransaction: ({ key }) => {
|
|
28
|
-
if (key === 'START')
|
|
29
|
-
console.group('── TRANSACTION START ──');
|
|
30
|
-
else
|
|
31
|
-
console.groupEnd();
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
});
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export const analyticsPlugin = (options) => ({
|
|
2
|
-
name: 'gstate-analytics',
|
|
3
|
-
hooks: {
|
|
4
|
-
onSet: ({ key, value }) => {
|
|
5
|
-
if (!key)
|
|
6
|
-
return;
|
|
7
|
-
if (!options.keys || options.keys.includes(key)) {
|
|
8
|
-
options.provider({ key, value, action: 'SET' });
|
|
9
|
-
}
|
|
10
|
-
},
|
|
11
|
-
onRemove: ({ key }) => {
|
|
12
|
-
if (!key)
|
|
13
|
-
return;
|
|
14
|
-
if (!options.keys || options.keys.includes(key)) {
|
|
15
|
-
options.provider({ key, value: null, action: 'REMOVE' });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
export const cloudSyncPlugin = (options) => {
|
|
2
|
-
const { adapter, autoSyncInterval } = options;
|
|
3
|
-
const lastSyncedVersions = new Map();
|
|
4
|
-
const stats = {
|
|
5
|
-
lastSyncTimestamp: null,
|
|
6
|
-
totalKeysSynced: 0,
|
|
7
|
-
totalBytesSynced: 0,
|
|
8
|
-
syncCount: 0,
|
|
9
|
-
lastDuration: 0,
|
|
10
|
-
errors: 0
|
|
11
|
-
};
|
|
12
|
-
let timer = null;
|
|
13
|
-
return {
|
|
14
|
-
name: 'cloudSync',
|
|
15
|
-
hooks: {
|
|
16
|
-
onInstall: ({ store }) => {
|
|
17
|
-
store._registerMethod('cloudSync', 'sync', async () => {
|
|
18
|
-
const startTime = performance.now();
|
|
19
|
-
const dirtyData = {};
|
|
20
|
-
let bytesCount = 0;
|
|
21
|
-
try {
|
|
22
|
-
const allData = store.list();
|
|
23
|
-
const keys = Object.keys(allData);
|
|
24
|
-
for (const key of keys) {
|
|
25
|
-
const currentVersion = store._getVersion?.(key) || 0;
|
|
26
|
-
const lastVersion = lastSyncedVersions.get(key) || 0;
|
|
27
|
-
if (currentVersion > lastVersion) {
|
|
28
|
-
const val = allData[key];
|
|
29
|
-
dirtyData[key] = val;
|
|
30
|
-
bytesCount += JSON.stringify(val).length;
|
|
31
|
-
lastSyncedVersions.set(key, currentVersion);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
if (Object.keys(dirtyData).length === 0)
|
|
35
|
-
return { status: 'no-change', stats };
|
|
36
|
-
const success = await adapter.save(dirtyData);
|
|
37
|
-
if (success) {
|
|
38
|
-
stats.lastSyncTimestamp = Date.now();
|
|
39
|
-
stats.totalKeysSynced += Object.keys(dirtyData).length;
|
|
40
|
-
stats.totalBytesSynced += bytesCount;
|
|
41
|
-
stats.syncCount++;
|
|
42
|
-
stats.lastDuration = performance.now() - startTime;
|
|
43
|
-
if (options.onSync)
|
|
44
|
-
options.onSync(stats);
|
|
45
|
-
return { status: 'success', stats };
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
throw new Error(`Adapter ${adapter.name} failed to save.`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
stats.errors++;
|
|
53
|
-
console.error(`[gState] Cloud Sync Failed (${adapter.name}):`, err);
|
|
54
|
-
return { status: 'error', error: String(err), stats };
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
store._registerMethod('cloudSync', 'getStats', () => stats);
|
|
58
|
-
if (autoSyncInterval && autoSyncInterval > 0) {
|
|
59
|
-
timer = setInterval(() => {
|
|
60
|
-
const plugins = store.plugins;
|
|
61
|
-
const cs = plugins.cloudSync;
|
|
62
|
-
if (cs)
|
|
63
|
-
cs.sync();
|
|
64
|
-
}, autoSyncInterval);
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
onDestroy: () => {
|
|
68
|
-
if (timer)
|
|
69
|
-
clearInterval(timer);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
};
|
|
74
|
-
export const createMongoAdapter = (apiUrl, apiKey) => ({
|
|
75
|
-
name: 'MongoDB-Atlas',
|
|
76
|
-
save: async (data) => {
|
|
77
|
-
const response = await fetch(`${apiUrl}/action/updateOne`, {
|
|
78
|
-
method: 'POST',
|
|
79
|
-
headers: { 'Content-Type': 'application/json', 'api-key': apiKey },
|
|
80
|
-
body: JSON.stringify({
|
|
81
|
-
dataSource: 'Cluster0',
|
|
82
|
-
database: 'rgs_cloud',
|
|
83
|
-
collection: 'user_states',
|
|
84
|
-
filter: { id: 'global_state' },
|
|
85
|
-
update: { $set: { data, updatedAt: Date.now() } },
|
|
86
|
-
upsert: true
|
|
87
|
-
})
|
|
88
|
-
});
|
|
89
|
-
return response.ok;
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
export const createFirestoreAdapter = (db, docPath) => ({
|
|
93
|
-
name: 'Firebase-Firestore',
|
|
94
|
-
save: async (data) => {
|
|
95
|
-
try {
|
|
96
|
-
console.log('[Mock] Firestore Syncing:', data);
|
|
97
|
-
return true;
|
|
98
|
-
}
|
|
99
|
-
catch (e) {
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
export const createSqlRestAdapter = (endpoint, authToken) => ({
|
|
105
|
-
name: 'SQL-REST-API',
|
|
106
|
-
save: async (data) => {
|
|
107
|
-
const response = await fetch(endpoint, {
|
|
108
|
-
method: 'PATCH',
|
|
109
|
-
headers: {
|
|
110
|
-
'Content-Type': 'application/json',
|
|
111
|
-
'Authorization': `Bearer ${authToken}`
|
|
112
|
-
},
|
|
113
|
-
body: JSON.stringify(data)
|
|
114
|
-
});
|
|
115
|
-
return response.ok;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export const debugPlugin = () => {
|
|
2
|
-
if (process.env.NODE_ENV === 'production') {
|
|
3
|
-
return { name: 'gstate-debug-noop', hooks: {} };
|
|
4
|
-
}
|
|
5
|
-
return {
|
|
6
|
-
name: 'gstate-debug',
|
|
7
|
-
hooks: {
|
|
8
|
-
onInstall: ({ store }) => {
|
|
9
|
-
if (typeof window !== 'undefined') {
|
|
10
|
-
window.gstate = {
|
|
11
|
-
list: () => {
|
|
12
|
-
console.log('[gState] Current state:', store.list());
|
|
13
|
-
return store.list();
|
|
14
|
-
},
|
|
15
|
-
get: (key) => {
|
|
16
|
-
const val = store.get(key);
|
|
17
|
-
console.log(`[gState] get('${key}'):`, val);
|
|
18
|
-
return val;
|
|
19
|
-
},
|
|
20
|
-
set: (key, value) => {
|
|
21
|
-
const result = store.set(key, value);
|
|
22
|
-
console.log(`[gState] set('${key}', ${JSON.stringify(value)}):`, result);
|
|
23
|
-
return result;
|
|
24
|
-
},
|
|
25
|
-
watch: (key, callback) => {
|
|
26
|
-
const unwatch = store.watch(key, callback);
|
|
27
|
-
console.log(`[gState] watching '${key}'`);
|
|
28
|
-
return unwatch;
|
|
29
|
-
},
|
|
30
|
-
info: () => {
|
|
31
|
-
const info = {
|
|
32
|
-
namespace: store.namespace,
|
|
33
|
-
isReady: store.isReady,
|
|
34
|
-
keys: Object.keys(store.list()),
|
|
35
|
-
size: Object.keys(store.list()).length
|
|
36
|
-
};
|
|
37
|
-
console.log('[gState] Store Info:', info);
|
|
38
|
-
return info;
|
|
39
|
-
},
|
|
40
|
-
banner: () => {
|
|
41
|
-
console.log(`
|
|
42
|
-
╔═══════════════════════════════════════╗
|
|
43
|
-
║ 🧲 gState Debug ║
|
|
44
|
-
║ Type: gstate.list() ║
|
|
45
|
-
║ gstate.get(key) ║
|
|
46
|
-
║ gstate.set(key, value) ║
|
|
47
|
-
║ gstate.info() ║
|
|
48
|
-
╚═══════════════════════════════════════╝
|
|
49
|
-
`);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
console.log('[gState] Debug plugin installed. Type gstate.banner() for help.');
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
onDestroy: () => {
|
|
56
|
-
if (typeof window !== 'undefined') {
|
|
57
|
-
delete window.gstate;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export const devToolsPlugin = (options) => {
|
|
2
|
-
const ext = globalThis;
|
|
3
|
-
const global = ext;
|
|
4
|
-
const extension = global.__REDUX_DEVTOOLS_EXTENSION__;
|
|
5
|
-
if (!extension?.connect) {
|
|
6
|
-
return { name: 'gstate-devtools-noop', hooks: {} };
|
|
7
|
-
}
|
|
8
|
-
let _devTools = null;
|
|
9
|
-
return {
|
|
10
|
-
name: 'gstate-devtools',
|
|
11
|
-
hooks: {
|
|
12
|
-
onInstall: ({ store }) => {
|
|
13
|
-
_devTools = extension.connect({ name: options?.name || 'Magnetar Store' });
|
|
14
|
-
_devTools.init(store.list());
|
|
15
|
-
},
|
|
16
|
-
onSet: ({ key, store }) => {
|
|
17
|
-
if (!key || !_devTools)
|
|
18
|
-
return;
|
|
19
|
-
_devTools.send(`SET_${key.toUpperCase()}`, store.list());
|
|
20
|
-
},
|
|
21
|
-
onRemove: ({ key, store }) => {
|
|
22
|
-
if (!key || !_devTools)
|
|
23
|
-
return;
|
|
24
|
-
_devTools.send(`REMOVE_${key.toUpperCase()}`, store.list());
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export const guardPlugin = (guards) => ({
|
|
2
|
-
name: 'gstate-guard',
|
|
3
|
-
hooks: {
|
|
4
|
-
onBeforeSet: ({ key, value, store: _store }) => {
|
|
5
|
-
if (!key)
|
|
6
|
-
return;
|
|
7
|
-
const guard = guards[key];
|
|
8
|
-
if (guard) {
|
|
9
|
-
const transformed = guard(value);
|
|
10
|
-
if (transformed !== value) {
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
export const indexedDBPlugin = (options = {}) => {
|
|
2
|
-
const dbName = options.dbName || 'rgs-db';
|
|
3
|
-
const storeName = options.storeName || 'states';
|
|
4
|
-
const dbVersion = options.version || 1;
|
|
5
|
-
let db = null;
|
|
6
|
-
const getDB = () => {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
if (db)
|
|
9
|
-
return resolve(db);
|
|
10
|
-
const request = indexedDB.open(dbName, dbVersion);
|
|
11
|
-
request.onerror = () => reject(request.error);
|
|
12
|
-
request.onsuccess = () => {
|
|
13
|
-
db = request.result;
|
|
14
|
-
resolve(db);
|
|
15
|
-
};
|
|
16
|
-
request.onupgradeneeded = (event) => {
|
|
17
|
-
const database = event.target.result;
|
|
18
|
-
if (!database.objectStoreNames.contains(storeName)) {
|
|
19
|
-
database.createObjectStore(storeName);
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
});
|
|
23
|
-
};
|
|
24
|
-
const save = async (key, value) => {
|
|
25
|
-
const database = await getDB();
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const tx = database.transaction(storeName, 'readwrite');
|
|
28
|
-
const store = tx.objectStore(storeName);
|
|
29
|
-
const request = store.put(value, key);
|
|
30
|
-
request.onsuccess = () => resolve();
|
|
31
|
-
request.onerror = () => reject(request.error);
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
|
-
const load = async (key) => {
|
|
35
|
-
const database = await getDB();
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const tx = database.transaction(storeName, 'readonly');
|
|
38
|
-
const store = tx.objectStore(storeName);
|
|
39
|
-
const request = store.get(key);
|
|
40
|
-
request.onsuccess = () => resolve(request.result);
|
|
41
|
-
request.onerror = () => reject(request.error);
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
const remove = async (key) => {
|
|
45
|
-
const database = await getDB();
|
|
46
|
-
return new Promise((resolve, reject) => {
|
|
47
|
-
const tx = database.transaction(storeName, 'readwrite');
|
|
48
|
-
const store = tx.objectStore(storeName);
|
|
49
|
-
const request = store.delete(key);
|
|
50
|
-
request.onsuccess = () => resolve();
|
|
51
|
-
request.onerror = () => reject(request.error);
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
return {
|
|
55
|
-
name: 'indexedDB',
|
|
56
|
-
hooks: {
|
|
57
|
-
onInstall: ({ store }) => {
|
|
58
|
-
store._registerMethod('indexedDB', 'clear', async () => {
|
|
59
|
-
const database = await getDB();
|
|
60
|
-
const tx = database.transaction(storeName, 'readwrite');
|
|
61
|
-
tx.objectStore(storeName).clear();
|
|
62
|
-
});
|
|
63
|
-
},
|
|
64
|
-
onInit: async ({ store }) => {
|
|
65
|
-
const database = await getDB();
|
|
66
|
-
const tx = database.transaction(storeName, 'readonly');
|
|
67
|
-
const objectStore = tx.objectStore(storeName);
|
|
68
|
-
const request = objectStore.getAllKeys();
|
|
69
|
-
request.onsuccess = async () => {
|
|
70
|
-
const keys = request.result;
|
|
71
|
-
const prefix = store.namespace + '_';
|
|
72
|
-
for (const key of keys) {
|
|
73
|
-
if (key.startsWith(prefix)) {
|
|
74
|
-
const val = await load(key);
|
|
75
|
-
if (val) {
|
|
76
|
-
const storeKey = key.substring(prefix.length);
|
|
77
|
-
store._setSilently(storeKey, val.d);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
},
|
|
83
|
-
onSet: async ({ key, value, store }) => {
|
|
84
|
-
if (!key)
|
|
85
|
-
return;
|
|
86
|
-
const prefix = store.namespace + '_';
|
|
87
|
-
const data = {
|
|
88
|
-
d: value,
|
|
89
|
-
t: Date.now(),
|
|
90
|
-
v: store._getVersion?.(key) || 1
|
|
91
|
-
};
|
|
92
|
-
await save(`${prefix}${key}`, data);
|
|
93
|
-
},
|
|
94
|
-
onRemove: async ({ key, store }) => {
|
|
95
|
-
if (!key)
|
|
96
|
-
return;
|
|
97
|
-
const prefix = store.namespace + '_';
|
|
98
|
-
await remove(`${prefix}${key}`);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
};
|