@biglogic/rgs 3.7.0 → 3.7.3
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 +84 -3
- package/package.json +91 -85
- package/COPYRIGHT.md +0 -4
- package/FUNDING.yml +0 -12
- package/SECURITY.md +0 -13
- package/advanced.d.ts +0 -9
- package/core/advanced.d.ts +0 -5
- package/core/async.d.ts +0 -8
- package/core/hooks.d.ts +0 -17
- package/core/persistence.d.ts +0 -23
- package/core/plugins.d.ts +0 -8
- package/core/reactivity.d.ts +0 -19
- package/core/security.d.ts +0 -56
- package/core/store.d.ts +0 -7
- package/core/sync.d.ts +0 -76
- package/core/types.d.ts +0 -163
- package/core/utils.d.ts +0 -2
- package/docs/README.md +0 -389
- package/docs/SUMMARY.md +0 -64
- package/docs/_config.yml +0 -1
- package/docs/api.md +0 -381
- package/docs/chapters/01-philosophy.md +0 -54
- package/docs/chapters/02-getting-started.md +0 -68
- package/docs/chapters/03-the-magnetar-way.md +0 -69
- package/docs/chapters/04-persistence-and-safety.md +0 -125
- package/docs/chapters/05-plugin-sdk.md +0 -290
- package/docs/chapters/05-plugins-and-extensibility.md +0 -190
- package/docs/chapters/06-case-studies.md +0 -69
- package/docs/chapters/07-faq.md +0 -53
- package/docs/chapters/08-migration-guide.md +0 -284
- package/docs/chapters/09-security-architecture.md +0 -50
- package/docs/chapters/10-local-first-sync.md +0 -146
- package/docs/qa.md +0 -47
- package/index.d.ts +0 -41
- package/index.js +0 -2
- package/index.js.map +0 -7
- package/plugins/index.d.ts +0 -15
- package/plugins/official/analytics.plugin.d.ts +0 -9
- package/plugins/official/cloud-sync.plugin.d.ts +0 -22
- package/plugins/official/debug.plugin.d.ts +0 -2
- package/plugins/official/devtools.plugin.d.ts +0 -4
- package/plugins/official/guard.plugin.d.ts +0 -2
- package/plugins/official/immer.plugin.d.ts +0 -2
- package/plugins/official/indexeddb.plugin.d.ts +0 -7
- package/plugins/official/schema.plugin.d.ts +0 -2
- package/plugins/official/snapshot.plugin.d.ts +0 -2
- package/plugins/official/sync.plugin.d.ts +0 -4
- package/plugins/official/undo-redo.plugin.d.ts +0 -4
- package/rgs-extension.vsix +0 -0
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
# 8. Migration Guide
|
|
2
|
-
|
|
3
|
-
## Upgrading to v3.5.0 (The Type-Safe Era)
|
|
4
|
-
|
|
5
|
-
### New Feature: Selectors
|
|
6
|
-
v3.5.0 introduces **Function Selectors** for `useStore`. This is fully backward compatible, but we recommend migrating new code to this pattern for better type safety.
|
|
7
|
-
|
|
8
|
-
#### ✅ Recommended Pattern
|
|
9
|
-
```tsx
|
|
10
|
-
// Type-safe, autocomplete friendly
|
|
11
|
-
const userName = useStore(state => state.user.name)
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
#### 🟡 Legacy Pattern (Still Supported)
|
|
15
|
-
```tsx
|
|
16
|
-
// String-based, prone to typos
|
|
17
|
-
const [userName] = useStore('user_name')
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
## Upgrading to v3.4.0
|
|
23
|
-
|
|
24
|
-
### Performance Defaults
|
|
25
|
-
- **Size Limits**: `maxObjectSize` and `maxTotalSize` now default to `0` (disabled). If you relied on these warnings during development, explicitly enable them in `initState` or `gstate` options.
|
|
26
|
-
|
|
27
|
-
### Deprecations Removed
|
|
28
|
-
- `useGState` and `useSimpleState` have been removed. Replace all instances with `useStore`.
|
|
29
|
-
- `_registerMethod(name, fn)` support is removed from types (runtime warning remains). Update plugins to use `_registerMethod(pluginName, methodName, fn)`.
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## Upgrading from Previous Versions
|
|
34
|
-
|
|
35
|
-
### Security: `secure` → `encoded`
|
|
36
|
-
|
|
37
|
-
**IMPORTANT:** The `secure` option has been deprecated in favor of `encoded` to clarify that it only applies **base64 encoding**, not encryption.
|
|
38
|
-
|
|
39
|
-
#### ❌ Old Code (Deprecated)
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
store.set('apiKey', 'secret123', {
|
|
43
|
-
persist: true,
|
|
44
|
-
secure: true // ⚠️ DEPRECATED: This is NOT encryption!
|
|
45
|
-
})
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
#### ✅ New Code (Recommended)
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
store.set('apiKey', 'secret123', {
|
|
52
|
-
persist: true,
|
|
53
|
-
encoded: true // ✅ Clear: This is base64 encoding
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
#### Backward Compatibility
|
|
58
|
-
|
|
59
|
-
Your old code will **still work**, but you'll see a deprecation warning in TypeScript:
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
/** @deprecated Use 'encoded' instead. 'secure' only applies base64 encoding, not encryption. */
|
|
63
|
-
secure?: boolean
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
#### Why This Change?
|
|
67
|
-
|
|
68
|
-
Base64 encoding is **NOT encryption**. It's trivial to decode:
|
|
69
|
-
|
|
70
|
-
```javascript
|
|
71
|
-
// Anyone can decode base64
|
|
72
|
-
const encoded = btoa(JSON.stringify({ secret: 'password123' }))
|
|
73
|
-
const decoded = JSON.parse(atob(encoded)) // { secret: 'password123' }
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**For real security**, use proper encryption libraries like:
|
|
77
|
-
|
|
78
|
-
- `crypto-js` (AES encryption)
|
|
79
|
-
- `tweetnacl` (NaCl encryption)
|
|
80
|
-
- Web Crypto API (`crypto.subtle`)
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
### Error Handling: `onError` Callback
|
|
85
|
-
|
|
86
|
-
**NEW:** You can now catch and handle errors from plugins, hydration, and other operations.
|
|
87
|
-
|
|
88
|
-
#### Example: Custom Error Logging
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
import { initState, useStore } from '@biglogic/rgs'
|
|
92
|
-
|
|
93
|
-
const store = initState({
|
|
94
|
-
namespace: 'myapp',
|
|
95
|
-
onError: (error, context) => {
|
|
96
|
-
// Send to your error tracking service
|
|
97
|
-
console.error(`[gState Error] ${context.operation}:`, error)
|
|
98
|
-
|
|
99
|
-
if (context.operation === 'hydration') {
|
|
100
|
-
// Handle corrupted localStorage
|
|
101
|
-
localStorage.clear()
|
|
102
|
-
alert('Storage corrupted, resetting...')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (context.operation.startsWith('plugin:')) {
|
|
106
|
-
// Handle plugin crashes
|
|
107
|
-
Sentry.captureException(error, { tags: { plugin: context.operation } })
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
#### Error Context
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
interface ErrorContext {
|
|
117
|
-
operation: string // 'hydration', 'plugin:name:hook', 'set'
|
|
118
|
-
key?: string // State key (if applicable)
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
### Performance: `maxObjectSize` Warning
|
|
125
|
-
|
|
126
|
-
**NEW:** Get warned when storing objects larger than a configurable limit (default: 5MB).
|
|
127
|
-
|
|
128
|
-
#### Example: Custom Size Limit
|
|
129
|
-
|
|
130
|
-
```typescript
|
|
131
|
-
import { createStore } from '@biglogic/rgs'
|
|
132
|
-
|
|
133
|
-
const store = createStore({
|
|
134
|
-
maxObjectSize: 10 * 1024 * 1024, // 10MB limit
|
|
135
|
-
onError: (error, context) => {
|
|
136
|
-
if (context.operation === 'set') {
|
|
137
|
-
console.warn(`Large object detected in key: ${context.key}`)
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
// This will trigger a warning if > 10MB
|
|
143
|
-
store.set('bigData', hugeArray)
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
#### Disable Size Checking
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
const store = createStore({
|
|
150
|
-
maxObjectSize: 0 // Disable size warnings
|
|
151
|
-
})
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
---
|
|
155
|
-
|
|
156
|
-
## Upgrading to Latest Version (The Enterprise Update)
|
|
157
|
-
|
|
158
|
-
**IMPORTANT:** This release introduces a fundamental shift towards **Multi-Store Isolation**. Security rules and GDPR consents are now instance-bound rather than global.
|
|
159
|
-
|
|
160
|
-
### 1. Security: Global → Instance-Specific
|
|
161
|
-
|
|
162
|
-
In previous versions, security rules were shared globally. Now, each store instance maintains its own rules for better isolation in micro-frontend environments.
|
|
163
|
-
|
|
164
|
-
#### ❌ Deprecated Global Methods
|
|
165
|
-
|
|
166
|
-
```typescript
|
|
167
|
-
import { addAccessRule, recordConsent } from '@biglogic/rgs'
|
|
168
|
-
|
|
169
|
-
// ⚠️ DEPRECATED: These affect the 'default' store only and are less isolated
|
|
170
|
-
addAccessRule('user_*', ['read', 'write'])
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
#### ✅ Recommended Instance Methods
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
const store = createStore({ namespace: 'my-isolated-app' })
|
|
177
|
-
|
|
178
|
-
// ✅ Use the instance methods
|
|
179
|
-
store.addAccessRule('user_*', ['read', 'write'])
|
|
180
|
-
store.recordConsent('user123', 'marketing', true)
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### 2. Operational Resilience: Ghost Stores
|
|
184
|
-
|
|
185
|
-
**NEW:** If you access a store that hasn't finished its initialization (e.g., during slow hydration), RGS now returns a **Ghost Store Proxy**.
|
|
186
|
-
|
|
187
|
-
- **Behavior:** It prevents application crashes by providing a safe fallback.
|
|
188
|
-
- **Developer Warning:** It logs a detailed warning in the console so you can fix the initialization sequence.
|
|
189
|
-
|
|
190
|
-
### 3. Performance: Regex Caching
|
|
191
|
-
|
|
192
|
-
**NEW:** Permission checks now use an internal **Regex Cache** per instance.
|
|
193
|
-
|
|
194
|
-
- **Why?** Avoids the overhead of re-compiling regex strings on every `.get()` or `.set()` call.
|
|
195
|
-
- **Impact:** Significant performance boost for applications with high-frequency state updates and complex RBAC rules.
|
|
196
|
-
|
|
197
|
-
### 4. Advanced Plugin Typing
|
|
198
|
-
|
|
199
|
-
**NEW:** Introducing `GStatePlugins` for Module Augmentation.
|
|
200
|
-
|
|
201
|
-
- You can now define types for your custom plugins to get full IDE autocomplete.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## v2.9.5: The Architecture & Safety Update (2026-02-16)
|
|
206
|
-
|
|
207
|
-
This release focuses on improving developer ergonomics, security visibility, and complex dependency handling.
|
|
208
|
-
|
|
209
|
-
### 1. Nested Computed Dependencies
|
|
210
|
-
|
|
211
|
-
**NEW:** Computed values can now re-trigger based on other computed values.
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
store.compute('tax', (get) => (get<number>('subtotal') || 0) * 0.2)
|
|
215
|
-
store.compute('total', (get) => (get<number>('subtotal') || 0) + (get<number>('tax') || 0))
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### 2. Direct Store Access: `getStore()`
|
|
219
|
-
**NEW:** A top-level utility to retrieve the default store without React hooks.
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
import { getStore } from '@biglogic/rgs'
|
|
223
|
-
|
|
224
|
-
export const toggleTheme = () => {
|
|
225
|
-
const store = getStore()
|
|
226
|
-
if (store) store.set('mode', 'dark')
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### 3. Exposed Metadata: `namespace` and `userId`
|
|
231
|
-
**NEW:** Store instances now expose their identifying properties as read-only getters.
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
const store = createStore({ namespace: 'auth-vault', userId: 'user-001' })
|
|
235
|
-
console.log(store.namespace) // 'auth-vault'
|
|
236
|
-
console.log(store.userId) // 'user-001'
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### 4. High-Volume & Hybrid Sync (Plugins)
|
|
240
|
-
**NEW:** Support for GB-scale storage and Remote Cloud Backups.
|
|
241
|
-
|
|
242
|
-
- **IndexedDB Plugin**: Replaces localStorage for massive browser datasets.
|
|
243
|
-
- **Cloud Sync Plugin**: Differential synchronization to MongoDB, Firebase, or any SQL backend.
|
|
244
|
-
|
|
245
|
-
```typescript
|
|
246
|
-
// Example: Manual Cloud Sync
|
|
247
|
-
const result = await store.plugins.cloudSync.sync()
|
|
248
|
-
console.log('Stats:', store.plugins.cloudSync.getStats())
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
---
|
|
252
|
-
|
|
253
|
-
## Breaking Changes
|
|
254
|
-
|
|
255
|
-
### 🔒 Security Isolation
|
|
256
|
-
|
|
257
|
-
If you relied on `addAccessRule()` from the global export to affect a `createStore()` instance, you must now call `store.addAccessRule()` on that specific instance.
|
|
258
|
-
|
|
259
|
-
---
|
|
260
|
-
|
|
261
|
-
## Recommended Actions
|
|
262
|
-
|
|
263
|
-
### 1. Migrate Security Calls to Store Instances
|
|
264
|
-
|
|
265
|
-
**Priority:** High (if using multiple stores)
|
|
266
|
-
|
|
267
|
-
**Effort:** Low
|
|
268
|
-
|
|
269
|
-
### 2. Implement `GStatePlugins` for Custom Plugins
|
|
270
|
-
|
|
271
|
-
**Priority:** Medium (Developer Experience)
|
|
272
|
-
|
|
273
|
-
**Effort:** Low
|
|
274
|
-
|
|
275
|
-
---
|
|
276
|
-
|
|
277
|
-
## Need Help?
|
|
278
|
-
|
|
279
|
-
- **Issues:** [GitHub Issues](https://github.com/dpassariello/rgs/issues)
|
|
280
|
-
- **Docs:** [Galaxy Documentation](../SUMMARY.md)
|
|
281
|
-
|
|
282
|
-
---
|
|
283
|
-
|
|
284
|
-
## Last updated: 2026-02-16
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# Security Architecture & Hardening
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
Reactive Global 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
|
-
- **Safe Random UUID**: Fallback generation for environments without Web Crypto API.
|
|
24
|
-
|
|
25
|
-
## 4. RBAC (Role-Based Access Control)
|
|
26
|
-
RGS supports fine-grained access rules:
|
|
27
|
-
- **Fail-Closed Design**: Access is denied by default if any rules are defined.
|
|
28
|
-
- **Regex Caching**: Store instances cache compiled regular expressions for ultra-fast permission checks.
|
|
29
|
-
- **ReDoS Protection**: Regex patterns have a 100ms timeout to prevent denial-of-service attacks.
|
|
30
|
-
- **Storage Key Validation**: All persisted keys are validated against a strict pattern before storage.
|
|
31
|
-
|
|
32
|
-
## 5. Security Best Practices
|
|
33
|
-
For real-world implementations, refer to the `examples/security-best-practices` directory, which covers:
|
|
34
|
-
- **Encryption Key Management**: Using `generateEncryptionKey()` for secure key generation.
|
|
35
|
-
- **Audit Logging**: Tracking all store modifications for compliance.
|
|
36
|
-
- **GDPR Compliance**: Managing user consent and data export/deletion.
|
|
37
|
-
|
|
38
|
-
## Summary of 2.9.5 Enhancements
|
|
39
|
-
- Robust regex patterns for `sanitizeValue`.
|
|
40
|
-
- Recursive sanitization for plain objects.
|
|
41
|
-
- `Map` and `Set` support in `deepClone`.
|
|
42
|
-
- **Exposed Metadata**: Store instances now expose read-only `namespace` and `userId`.
|
|
43
|
-
- **Direct Store Access**: Added `getStore()` utility for non-React contexts.
|
|
44
|
-
|
|
45
|
-
## Summary of 3.7.0 Enhancements
|
|
46
|
-
- **ReDoS Protection**: Regex patterns timeout after 100ms to prevent malicious patterns.
|
|
47
|
-
- **Safe UUID**: Fallback UUID generation for environments without Web Crypto API.
|
|
48
|
-
- **Storage Key Validation**: Keys validated before persistence to prevent injection.
|
|
49
|
-
- **Production Safety**: Global window access only enabled in development mode (`NODE_ENV !== 'production'`).
|
|
50
|
-
- **PBKDF2 Key Derivation**: New `deriveKeyFromPassword()` and `generateSalt()` functions for secure password-based encryption (NIST SC-12 compliant).
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# 🚀 Chapter 10: Local-First Sync Engine
|
|
2
|
-
|
|
3
|
-
RGS now includes a powerful **Local-First Sync Engine** that makes your app work offline by default and automatically synchronize when connectivity is restored.
|
|
4
|
-
|
|
5
|
-
## Why Local-First?
|
|
6
|
-
|
|
7
|
-
Traditional apps require an internet connection to work. Local-First apps work immediately with local data and sync in the background when possible.
|
|
8
|
-
|
|
9
|
-
### Benefits
|
|
10
|
-
|
|
11
|
-
- **Instant Load** - No waiting for server responses
|
|
12
|
-
- **Works Offline** - App functions without internet
|
|
13
|
-
- **Better UX** - No loading spinners for data
|
|
14
|
-
- **Conflict Resolution** - Smart merge strategies
|
|
15
|
-
|
|
16
|
-
## Quick Start
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { gstate, useSyncedState } from '@biglogic/rgs'
|
|
20
|
-
|
|
21
|
-
// Create store with sync enabled
|
|
22
|
-
const store = gstate({
|
|
23
|
-
todos: [],
|
|
24
|
-
user: null
|
|
25
|
-
}, {
|
|
26
|
-
namespace: 'myapp',
|
|
27
|
-
sync: {
|
|
28
|
-
endpoint: 'https://api.example.com/sync',
|
|
29
|
-
// Use a getter function for secure token retrieval
|
|
30
|
-
authToken: () => localStorage.getItem('auth_token'),
|
|
31
|
-
autoSyncInterval: 30000, // Sync every 30s
|
|
32
|
-
syncOnReconnect: true // Auto-sync when back online
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
// Use in React components
|
|
37
|
-
function TodoList() {
|
|
38
|
-
const [todos, setTodos] = useSyncedState('todos')
|
|
39
|
-
|
|
40
|
-
// Add todo - automatically queued for sync
|
|
41
|
-
const addTodo = (text) => {
|
|
42
|
-
setTodos([...todos, { id: Date.now(), text }])
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return <div>{/* ... */}</div>
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## Configuration Options
|
|
50
|
-
|
|
51
|
-
| Option | Type | Default | Description |
|
|
52
|
-
|--------|------|---------|-------------|
|
|
53
|
-
| `endpoint` | `string` | required | Remote sync server URL |
|
|
54
|
-
| `authToken` | `string` or `() => string \| null` | - | Authentication token or getter function for secure retrieval |
|
|
55
|
-
| `strategy` | `string` | `'last-write-wins'` | Conflict resolution |
|
|
56
|
-
| `autoSyncInterval` | `number` | `30000` | Auto-sync interval (ms) |
|
|
57
|
-
| `syncOnReconnect` | `boolean` | `true` | Sync on network restore |
|
|
58
|
-
| `debounceTime` | `number` | `1000` | Batch changes (ms) |
|
|
59
|
-
| `maxRetries` | `number` | `3` | Failed sync retries |
|
|
60
|
-
| `onConflict` | `function` | - | Custom conflict handler |
|
|
61
|
-
| `onSync` | `function` | - | Sync completion callback |
|
|
62
|
-
|
|
63
|
-
## Conflict Resolution Strategies
|
|
64
|
-
|
|
65
|
-
### 1. Last-Write-Wins (Default)
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
sync: { strategy: 'last-write-wins' }
|
|
69
|
-
```
|
|
70
|
-
Latest timestamp wins - simplest strategy.
|
|
71
|
-
|
|
72
|
-
### 2. Server-Wins
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
sync: { strategy: 'server-wins' }
|
|
76
|
-
```
|
|
77
|
-
Always prefer remote values - useful for read-heavy apps.
|
|
78
|
-
|
|
79
|
-
### 3. Client-Wins
|
|
80
|
-
|
|
81
|
-
```typescript
|
|
82
|
-
sync: { strategy: 'client-wins' }
|
|
83
|
-
```
|
|
84
|
-
Always prefer local values - useful for write-heavy apps.
|
|
85
|
-
|
|
86
|
-
### 4. Custom Merge
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
sync: {
|
|
90
|
-
strategy: 'merge',
|
|
91
|
-
onConflict: (conflict) => {
|
|
92
|
-
// Custom logic for merging
|
|
93
|
-
return {
|
|
94
|
-
action: 'merge',
|
|
95
|
-
value: { /* merged result */ }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Hook API
|
|
102
|
-
|
|
103
|
-
### useSyncedState
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
const [value, setValue, syncState] = useSyncedState('key')
|
|
107
|
-
|
|
108
|
-
// syncState contains:
|
|
109
|
-
// - isOnline: boolean
|
|
110
|
-
// - isSyncing: boolean
|
|
111
|
-
// - pendingChanges: number
|
|
112
|
-
// - conflicts: number
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### useSyncStatus
|
|
116
|
-
|
|
117
|
-
```typescript
|
|
118
|
-
const status = useSyncStatus()
|
|
119
|
-
// Global sync status across all stores
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Manual Sync Control
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
// Force sync
|
|
126
|
-
await store.plugins.sync.flush()
|
|
127
|
-
|
|
128
|
-
// Get sync state
|
|
129
|
-
const state = store.plugins.sync.getState()
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Integration with Persistence
|
|
133
|
-
|
|
134
|
-
The Sync Engine works seamlessly with RGS's existing persistence layer:
|
|
135
|
-
|
|
136
|
-
- **Local Storage** - Data persists locally first
|
|
137
|
-
- **IndexedDB** - For larger datasets
|
|
138
|
-
- **Cloud Sync** - Optional remote backup
|
|
139
|
-
|
|
140
|
-
Your data survives browser refresh, works offline, and stays synchronized across devices.
|
|
141
|
-
|
|
142
|
-
## Next Steps
|
|
143
|
-
|
|
144
|
-
- Learn about [Security Architecture](09-security-architecture.md)
|
|
145
|
-
- Explore [Plugin SDK](05-plugin-sdk.md)
|
|
146
|
-
- Check [Migration Guide](08-migration-guide.md)
|
package/docs/qa.md
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# 🚀 QA Release Checklist: Massive Change
|
|
2
|
-
|
|
3
|
-
> **Release ID:** [Insert ID]
|
|
4
|
-
> **Date:** 2026-02-19
|
|
5
|
-
> **QA Lead:** [Name]
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
### 1. 🔍 Impact Analysis & Setup
|
|
10
|
-
- [ ] **Traceability Matrix:** Mapping new requirements to existing test cases.
|
|
11
|
-
- [ ] **Impact Analysis:** Identifying "High-Risk" modules (e.g., DB, API, Payment Gateway).
|
|
12
|
-
- [ ] **Staging Environment:** Verify alignment of config/data with Production.
|
|
13
|
-
- [ ] **Smoke Test Suite:** Selection of 10-15 fundamental tests to validate build stability.
|
|
14
|
-
|
|
15
|
-
### 2. 🛡️ Regression & Functional Testing
|
|
16
|
-
- [ ] **Critical Regression:** Execution of automated tests on core flows (Business Critical).
|
|
17
|
-
- [ ] **New Features:** Detailed validation according to acceptance criteria (AC).
|
|
18
|
-
- [ ] **Edge Cases:** Testing on invalid inputs and boundary scenarios.
|
|
19
|
-
- [ ] **Compatibility:** Testing on Browsers (Chrome, Safari, Firefox) and Mobile (iOS, Android).
|
|
20
|
-
|
|
21
|
-
### 3. ⚙️ Technical Integrity & Performance
|
|
22
|
-
- [ ] **Data Migration:** Verify that DB changes haven't corrupted existing records.
|
|
23
|
-
- [ ] **API Contracts:** Verify that endpoints haven't introduced breaking changes.
|
|
24
|
-
- [ ] **Performance Baseline:** Check response times compared to the previous version.
|
|
25
|
-
- [ ] **Security Scan:** Basic check on permissions and OWASP vulnerabilities.
|
|
26
|
-
|
|
27
|
-
### 4. 🏁 Release Readiness (Go/No-Go)
|
|
28
|
-
- [ ] **UAT Sign-off:** Final approval from stakeholders.
|
|
29
|
-
- [ ] **Rollback Plan:** Documented and ready recovery procedure.
|
|
30
|
-
- [ ] **Feature Flags:** Verify that toggles are correctly configured on [LaunchDarkly](https://launchdarkly.com) or similar.
|
|
31
|
-
- [ ] **Monitoring:** [Sentry](https://sentry.io) or [Datadog](https://www.datadoghq.com) dashboards ready for post-live monitoring.
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
### 📊 Execution Report
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
| Category | Total Tests | Passed | Failed | Blockers |
|
|
39
|
-
| :--- | :---: | :---: | :---: | :---: |
|
|
40
|
-
| **Smoke Test** | 0 | 0 | 0 | 0 |
|
|
41
|
-
| **Regression** | 0 | 0 | 0 | 0 |
|
|
42
|
-
| **New Features**| 0 | 0 | 0 | 0 |
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
**Final Notes:**
|
|
47
|
-
*Add any critical bugs found or observations on stability here.*
|
package/index.d.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { createStore as baseCreateStore } from "./core/store";
|
|
2
|
-
import { useStore as baseUseStore } from "./core/hooks";
|
|
3
|
-
import * as Security from "./core/security";
|
|
4
|
-
import type { IStore, StoreConfig } from "./core/types";
|
|
5
|
-
export declare const gstate: <S extends Record<string, unknown>>(initialState: S, configOrNamespace?: string | StoreConfig<S>) => IStore<S> & (<K extends keyof S>(key: K) => readonly [S[K] | undefined, (val: S[K] | ((draft: S[K]) => S[K]), options?: unknown) => boolean]);
|
|
6
|
-
export { baseCreateStore as createStore };
|
|
7
|
-
export { useStore, useIsStoreReady, initState, getStore, destroyState, useStore as useGState, useStore as useSimpleState } from "./core/hooks";
|
|
8
|
-
export { createAsyncStore } from "./core/async";
|
|
9
|
-
export { SyncEngine, createSyncEngine } from "./core/sync";
|
|
10
|
-
export type { SyncConfig, SyncState, SyncResult, SyncStrategy, ConflictInfo, ConflictResolution } from "./core/sync";
|
|
11
|
-
export { initSync, destroySync, useSyncedState, useSyncStatus, triggerSync } from "./core/hooks";
|
|
12
|
-
export * from "./plugins/index";
|
|
13
|
-
export { generateEncryptionKey, exportKey, importKey, isCryptoAvailable, setAuditLogger, logAudit, validateKey, sanitizeValue, deriveKeyFromPassword, generateSalt } from "./core/security";
|
|
14
|
-
export declare const addAccessRule: (pattern: string | ((key: string, userId?: string) => boolean), perms: Security.Permission[]) => void | undefined;
|
|
15
|
-
export declare const hasPermission: (key: string, action: Security.Permission, uid?: string) => boolean;
|
|
16
|
-
export declare const recordConsent: (uid: string, p: string, g: boolean) => Security.ConsentRecord;
|
|
17
|
-
export declare const hasConsent: (uid: string, p: string) => boolean;
|
|
18
|
-
export declare const getConsents: (uid: string) => Security.ConsentRecord[];
|
|
19
|
-
export declare const revokeConsent: (uid: string, p: string) => Security.ConsentRecord | null | undefined;
|
|
20
|
-
export declare const exportUserData: (uid: string) => {
|
|
21
|
-
userId: string;
|
|
22
|
-
exportedAt: number;
|
|
23
|
-
consents: import("./core/security").ConsentRecord[];
|
|
24
|
-
};
|
|
25
|
-
export declare const deleteUserData: (uid: string) => {
|
|
26
|
-
success: boolean;
|
|
27
|
-
deletedConsents: number;
|
|
28
|
-
};
|
|
29
|
-
export declare const clearAccessRules: () => void;
|
|
30
|
-
export declare const clearAllConsents: () => void;
|
|
31
|
-
export type { EncryptionKey, AuditEntry, Permission, AccessRule, ConsentRecord } from "./core/security";
|
|
32
|
-
export type { IStore, StoreConfig, PersistOptions, StateUpdater, ComputedSelector, WatcherCallback } from "./core/types";
|
|
33
|
-
declare global {
|
|
34
|
-
var createStore: typeof baseCreateStore;
|
|
35
|
-
var gstate: <S extends Record<string, unknown>>(initialState: S, configOrNamespace?: string | StoreConfig<S>) => IStore<S> & ((key: string) => unknown);
|
|
36
|
-
var initState: typeof import("./core/hooks").initState;
|
|
37
|
-
var destroyState: typeof import("./core/hooks").destroyState;
|
|
38
|
-
var gState: IStore<Record<string, unknown>>;
|
|
39
|
-
var rgs: IStore<Record<string, unknown>>;
|
|
40
|
-
var useStore: typeof baseUseStore;
|
|
41
|
-
}
|
package/index.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{produce as Ct,freeze as Le}from"immer";var ht=(t,e)=>{let n=Date.now();if(/\(\.*\+\?\)\+/.test(t)||/\(\.*\?\)\*/.test(t))return console.warn(`[gstate] Potentially dangerous regex pattern blocked: ${t}`),!1;if(t.length>500)return console.warn("[gstate] Regex pattern exceeds maximum length limit"),!1;try{let s=new RegExp(t).test(e),i=Date.now()-n;return i>100&&console.warn(`[gstate] Slow regex detected (${i}ms) for pattern: ${t}`),s}catch{return!1}},wt=()=>{if(typeof crypto<"u"&&typeof crypto.randomUUID=="function")try{return crypto.randomUUID()}catch{}throw new Error("Cryptographically secure random UUID generation is required but crypto.randomUUID is unavailable. Please use a browser or environment with Web Crypto API support.")},A=typeof crypto<"u"&&typeof crypto.subtle<"u"&&typeof crypto.subtle.generateKey=="function",M=async(t,e,n=1e5)=>{if(!A)throw new Error("Web Crypto API not available");let r=await crypto.subtle.importKey("raw",new TextEncoder().encode(t),"PBKDF2",!1,["deriveKey"]),s=await crypto.subtle.deriveKey({name:"PBKDF2",salt:new Uint8Array(e),iterations:n,hash:"SHA-256"},r,{name:"AES-GCM",length:256},!0,["encrypt","decrypt"]),i=crypto.getRandomValues(new Uint8Array(12));return{key:s,iv:i}},V=(t=16)=>crypto.getRandomValues(new Uint8Array(t)),j=async()=>{if(!A)throw new Error("Web Crypto API not available");let t=await crypto.subtle.generateKey({name:"AES-GCM",length:256},!0,["encrypt","decrypt"]),e=crypto.getRandomValues(new Uint8Array(12));return{key:t,iv:e}},$=async t=>{let e=await crypto.subtle.exportKey("raw",t.key);return{key:btoa(String.fromCharCode(...new Uint8Array(e))),iv:btoa(String.fromCharCode(...t.iv))}},U=async(t,e)=>{let n=Uint8Array.from(atob(t),i=>i.charCodeAt(0)),r=Uint8Array.from(atob(e),i=>i.charCodeAt(0));return{key:await crypto.subtle.importKey("raw",n,{name:"AES-GCM",length:256},!0,["encrypt","decrypt"]),iv:r}},He=async(t,e)=>{let n=new TextEncoder,r=n.encode(JSON.stringify(t)),s=await crypto.subtle.encrypt({name:"AES-GCM",iv:e.iv},e.key,r),i=new Uint8Array(e.iv.length+s.byteLength);return i.set(e.iv),i.set(new Uint8Array(s),e.iv.length),btoa(String.fromCharCode(...i))},Xe=async(t,e)=>{let n=Uint8Array.from(atob(t),a=>a.charCodeAt(0)),r=n.slice(0,12),s=n.slice(12),i=await crypto.subtle.decrypt({name:"AES-GCM",iv:r},e.key,s);return JSON.parse(new TextDecoder().decode(i))},De=null,N=t=>{De=t},Ze=()=>De!==null,P=t=>{De&&De(t)},Ne=(t,e,n)=>{t.set(e instanceof RegExp?e.source:e,n)},_e=(t,e,n,r)=>{if(t.size===0)return!0;for(let[s,i]of t){let a;if(typeof s=="function"?a=s(e,r):a=ht(s,e),a)return i.includes(n)||i.includes("admin")}return!1},R=t=>{if(typeof t=="string"){let e=t.replace(/&#[xX]?[0-9a-fA-F]+;?/g,r=>{let s=r.match(/&#x([0-9a-fA-F]+);?/i);if(s&&s[1])return String.fromCharCode(parseInt(s[1],16));let i=r.match(/&#([0-9]+);?/);return i&&i[1]?String.fromCharCode(parseInt(i[1],10)):""});try{e=decodeURIComponent(e)}catch{}return e.replace(/\b(javascript|vbscript|data:text\/html|about:blank|chrome:)/gi,"[SEC-REMOVED]").replace(/<script\b[^>]*>[\s\S]*?<\s*\/\s*script\b[^>]*>/gi,"[SEC-REMOVED]").replace(/on\w+\s*=/gi,"[SEC-REMOVED]=").replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi,"[SEC-REMOVED]").replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi,"[SEC-REMOVED]").replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi,"[SEC-REMOVED]").replace(/<svg\b[^<]*(?:(?!<\/svg>)<[^<]*)*<\/svg>/gi,"[SEC-REMOVED]").replace(/<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form>/gi,"[SEC-REMOVED]").replace(/<base\b[^<]*(?:(?!<\/base>)<[^<]*)*<\/base>/gi,"[SEC-REMOVED]").replace(/<link\b[^<]*(?:(?!<\/link>)<[^<]*)*<\/link>/gi,"[SEC-REMOVED]").replace(/<meta\b[^<]*(?:(?!<\/meta>)<[^<]*)*<\/meta>/gi,"[SEC-REMOVED]").replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi,"[SEC-REMOVED]")}if(t&&typeof t=="object"&&!Array.isArray(t)){if(Object.getPrototypeOf(t)===Object.prototype){let e={};for(let[n,r]of Object.entries(t))e[n]=R(r);return e}return t}return Array.isArray(t)?t.map(e=>R(e)):t},O=t=>/^[a-zA-Z0-9_.-]+$/.test(t)&&t.length<=256,ze=(t,e,n,r)=>{let s={id:wt(),purpose:n,granted:r,timestamp:Date.now()},i=t.get(e)||[];return i.push(s),t.set(e,i),P({timestamp:Date.now(),action:"set",key:`consent:${n}`,userId:e,success:!0}),s},Ye=(t,e,n)=>{let r=t.get(e);if(!r)return!1;for(let s=r.length-1;s>=0;s--){let i=r[s];if(i&&i.purpose===n)return i.granted}return!1},et=(t,e,n)=>ze(t,e,n,!1),tt=(t,e)=>t.get(e)||[],nt=(t,e)=>({userId:e,exportedAt:Date.now(),consents:t.get(e)||[]}),rt=(t,e)=>{let n=t.get(e)?.length||0;return t.delete(e),{success:!0,deletedConsents:n}};var me=t=>{if(t===null||typeof t!="object")return t;if(typeof structuredClone=="function")try{return structuredClone(t)}catch{}let e=new WeakMap,n=r=>{if(r===null||typeof r!="object"||typeof r=="function")return r;if(e.has(r))return e.get(r);if(r instanceof Date)return new Date(r.getTime());if(r instanceof RegExp)return new RegExp(r.source,r.flags);if(r instanceof Map){let a=new Map;return e.set(r,a),r.forEach((u,f)=>a.set(n(f),n(u))),a}if(r instanceof Set){let a=new Set;return e.set(r,a),r.forEach(u=>a.add(n(u))),a}let s=Array.isArray(r)?[]:Object.create(Object.getPrototypeOf(r));e.set(r,s);let i=[...Object.keys(r),...Object.getOwnPropertySymbols(r)];for(let a of i)s[a]=n(r[a]);return s};return n(t)},Re=(t,e)=>{if(t===e)return!0;if(t===null||e===null||typeof t!="object"||typeof e!="object")return t===e;if(Array.isArray(t)&&Array.isArray(e)){if(t.length!==e.length)return!1;for(let s=0;s<t.length;s++)if(!Re(t[s],e[s]))return!1;return!0}let n=Object.keys(t),r=Object.keys(e);if(n.length!==r.length)return!1;for(let s=0;s<n.length;s++){let i=n[s];if(!(i in e)||!Re(t[i],e[i]))return!1}return!0};import{freeze as bt}from"immer";var ot=t=>`${t}_`,it=async t=>{if(!t.storage)return;let{store:e,config:n,diskQueue:r,storage:s,encryptionKey:i,audit:a,onError:u,silent:f,currentVersion:p}=t,b=ot(n.namespace||"gstate");try{let h={};e.forEach((S,v)=>{h[v]=S});let l,y=n?.encoded;y?l=btoa(JSON.stringify(h)):l=JSON.stringify(h),s.setItem(b.replace("_",""),JSON.stringify({v:1,t:Date.now(),e:null,d:l,_sys_v:p,_b64:y?!0:void 0})),a("set","FULL_STATE",!0)}catch(h){let l=h instanceof Error?h:new Error(String(h));u?u(l,{operation:"persist",key:"FULL_STATE"}):f||console.error("[gstate] Persist failed: ",l)}let m=Array.from(r.entries());r.clear();for(let[h,l]of m)try{if(!h||!/^[a-zA-Z0-9_.-]+$/.test(h)||h.length>256){console.warn(`[gstate] Invalid storage key: ${h}`);continue}let y=l.value,S=l.options.encoded||l.options.encrypted;if(l.options.encrypted){if(!i)throw new Error(`Encryption key missing for "${h}"`);y=await He(l.value,i)}else S?y=btoa(JSON.stringify(l.value)):typeof l.value=="object"&&l.value!==null&&(y=JSON.stringify(l.value));s.setItem(`${b}${h}`,JSON.stringify({v:t.versions.get(h)||1,t:Date.now(),e:l.options.ttl?Date.now()+l.options.ttl:null,d:y,_sys_v:p,_enc:l.options.encrypted?!0:void 0,_b64:S?!0:void 0})),a("set",h,!0)}catch(y){let S=y instanceof Error?y:new Error(String(y));u?u(S,{operation:"persist",key:h}):f||console.error("[gstate] Persist failed: ",S)}},at=async(t,e,n)=>{let{storage:r,config:s,encryptionKey:i,audit:a,onError:u,silent:f,currentVersion:p,store:b,sizes:m,versions:h}=t,l=ot(s.namespace||"gstate"),y=s.immer??!0;if(r)try{let S={},v=0;for(let ue=0;ue<(r.length||0);ue++){let I=r.key(ue);if(!I||!I.startsWith(l))continue;let D=r.getItem(I);if(D)try{let _=JSON.parse(D),le=I.substring(l.length);if(v=Math.max(v,_._sys_v!==void 0?_._sys_v:_.v||0),_.e&&Date.now()>_.e){r.removeItem(I),ue--;continue}let de=_.d;if(_._enc&&i)de=await Xe(de,i);else if(typeof de=="string"){if(_._b64)try{de=JSON.parse(atob(de))}catch{}else if(de.startsWith("{")||de.startsWith("["))try{de=JSON.parse(de)}catch{}}S[le]=de,a("hydrate",le,!0)}catch(_){a("hydrate",I,!1,String(_));let le=_ instanceof Error?_:new Error(String(_));u?u(le,{operation:"hydration",key:I}):f||console.error(`[gstate] Hydration failed for "${I}": `,_)}}let he=v<p&&s.migrate?s.migrate(S,v):S;Object.entries(he).forEach(([ue,I])=>{let D=y&&I!==null&&typeof I=="object"?bt(me(I),!0):I,_=e(D),le=m.get(ue)||0;t.totalSize=t.totalSize-le+_,m.set(ue,_),b.set(ue,D),h.set(ue,1)}),n()}catch(S){let v=S instanceof Error?S:new Error(String(S));u?u(v,{operation:"hydration"}):f||console.error("[gstate] Hydration failed: ",v)}};var ct=(t,e,n)=>{if(t.plugins.size!==0)for(let r of t.plugins.values()){let s=r.hooks?.[e];if(s)try{s(n)}catch(i){let a=i instanceof Error?i:new Error(String(i));t.onError?t.onError(a,{operation:`plugin:${r.name}:${e}`,key:n.key}):t.silent||console.error(`[gstate] Plugin "${r.name}" error:`,i)}}},ut=(t,e,n)=>{try{t.plugins.set(e.name,e),e.hooks?.onInstall?.({store:n})}catch(r){let s=r instanceof Error?r:new Error(String(r));t.onError?t.onError(s,{operation:"plugin:install",key:e.name}):t.silent||console.error(`[gstate] Failed to install plugin "${e.name}": `,r)}};var E=class{store;config;pendingQueue=new Map;remoteVersions=new Map;syncTimer=null;onlineStatusListeners=new Set;syncStateListeners=new Set;_isOnline=!0;_isSyncing=!1;constructor(e,n){this.store=e,this.config={endpoint:n.endpoint,authToken:n.authToken||"",strategy:n.strategy||"last-write-wins",autoSyncInterval:n.autoSyncInterval??3e4,syncOnReconnect:n.syncOnReconnect??!0,debounceTime:n.debounceTime??1e3,fetch:n.fetch||fetch,onSync:n.onSync||(()=>{}),onConflict:n.onConflict||(()=>({action:"accept-local"})),maxRetries:n.maxRetries??3},this._isOnline=typeof navigator<"u"?navigator.onLine:!0,this._setupOnlineListener(),this._setupStoreListener(),this.config.autoSyncInterval>0&&this._startAutoSync()}_getAuthToken(){let e=this.config.authToken;return typeof e=="function"?e()||"":e||""}_setupOnlineListener(){typeof window>"u"||(window.addEventListener("online",()=>{this._isOnline=!0,this._notifyOnlineChange(!0),this.config.syncOnReconnect&&this.sync()}),window.addEventListener("offline",()=>{this._isOnline=!1,this._notifyOnlineChange(!1)}))}_setupStoreListener(){this.store._subscribe(()=>{})}_startAutoSync(){setInterval(()=>{this._isOnline&&!this._isSyncing&&this.pendingQueue.size>0&&this.sync()},this.config.autoSyncInterval)}_notifyOnlineChange(e){this.onlineStatusListeners.forEach(n=>n(e)),this._notifyStateChange()}_notifyStateChange(){let e=this.getState();this.syncStateListeners.forEach(n=>n(e))}queueChange(e,n){let r=this.store._getVersion(e)||1;this.pendingQueue.set(e,{key:e,value:me(n),timestamp:Date.now(),version:r}),this._notifyStateChange(),this.syncTimer&&clearTimeout(this.syncTimer),this.syncTimer=setTimeout(()=>{this._isOnline&&this.sync()},this.config.debounceTime)}async sync(){if(this._isSyncing)return{success:!1,syncedKeys:[],conflicts:[],errors:["Sync already in progress"],timestamp:Date.now(),duration:0};this._isSyncing=!0,this._notifyStateChange();let e=Date.now(),n=[],r=[],s=[];try{let i=Array.from(this.pendingQueue.values());if(i.length===0)return this._isSyncing=!1,this._notifyStateChange(),{success:!0,syncedKeys:[],conflicts:[],errors:[],timestamp:Date.now(),duration:Date.now()-e};await this._fetchRemoteVersions(i.map(u=>u.key));for(let u of i)try{let f=this.remoteVersions.get(u.key);if(!f)await this._pushChange(u),n.push(u.key),this.pendingQueue.delete(u.key);else if(f.version>=u.version){let p={key:u.key,localValue:u.value,remoteValue:f.value,localVersion:u.version,remoteVersion:f.version,timestamp:u.timestamp};r.push(p);let b=this.config.onConflict(p);await this._resolveConflict(u,f,b),n.push(u.key),this.pendingQueue.delete(u.key)}else await this._pushChange(u),n.push(u.key),this.pendingQueue.delete(u.key)}catch(f){s.push(`Failed to sync "${u.key}": ${f}`)}let a={success:s.length===0,syncedKeys:n,conflicts:r,errors:s,timestamp:Date.now(),duration:Date.now()-e};return this.config.onSync(a),a}catch(i){let a=`Sync failed: ${i}`;return s.push(a),{success:!1,syncedKeys:n,conflicts:r,errors:s,timestamp:Date.now(),duration:Date.now()-e}}finally{this._isSyncing=!1,this._notifyStateChange()}}async _fetchRemoteVersions(e){try{let n=this._getAuthToken(),r=await this.config.fetch(`${this.config.endpoint}/versions`,{method:"POST",headers:{"Content-Type":"application/json",...n&&{Authorization:`Bearer ${n}`}},body:JSON.stringify({keys:e})});if(r.ok){let s=await r.json();if(s.versions)for(let[i,a]of Object.entries(s.versions))this.remoteVersions.set(i,a)}}catch(n){console.warn("[SyncEngine] Failed to fetch remote versions:",n)}}async _pushChange(e){let n=0;for(;n<this.config.maxRetries;)try{let r=this._getAuthToken(),s=await this.config.fetch(`${this.config.endpoint}/sync`,{method:"POST",headers:{"Content-Type":"application/json",...r&&{Authorization:`Bearer ${r}`}},body:JSON.stringify({key:e.key,value:e.value,version:e.version,timestamp:e.timestamp})});if(s.ok){let i=await s.json();i.version&&this.remoteVersions.set(e.key,{version:i.version,timestamp:i.timestamp||Date.now(),value:e.value});return}n++}catch(r){if(n++,n>=this.config.maxRetries)throw r}}async _resolveConflict(e,n,r){switch(r.action){case"accept-local":await this._pushChange({...e,version:n.version+1,timestamp:Date.now()});break;case"accept-remote":this.store.set(e.key,n.value);break;case"merge":this.store.set(e.key,r.value),await this._pushChange({key:e.key,value:r.value,version:Math.max(e.version,n.version)+1,timestamp:Date.now()});break;case"discard":break}}getState(){return{isOnline:this._isOnline,isSyncing:this._isSyncing,lastSyncTimestamp:null,pendingChanges:this.pendingQueue.size,conflicts:0}}onOnlineChange(e){return this.onlineStatusListeners.add(e),()=>this.onlineStatusListeners.delete(e)}onStateChange(e){return this.syncStateListeners.add(e),()=>this.syncStateListeners.delete(e)}async flush(){return this.sync()}destroy(){this.syncTimer&&clearTimeout(this.syncTimer),this.pendingQueue.clear(),this.onlineStatusListeners.clear(),this.syncStateListeners.clear()}},z=(t,e)=>new E(t,e);var xt={local:()=>typeof window<"u"?window.localStorage:null,session:()=>typeof window<"u"?window.sessionStorage:null,memory:()=>{let t=new Map;return{getItem:e=>t.get(e)||null,setItem:(e,n)=>t.set(e,n),removeItem:e=>t.delete(e),key:e=>Array.from(t.keys())[e]||null,get length(){return t.size}}}},T=t=>{let e=new Map,n=new Map,r=new Map,s=new Set,i=new Map,a=new Set,u=new Map,f=new Map,p=new Map,b=new Map,m=new Map,h=new Map,l=new Map,y=new Map,S=t?.namespace||"gstate",v=t?.silent??!1,he=t?.debounceTime??150,ue=t?.version??0,I=t?.storage||xt.local(),D=t?.onError,_=t?.maxObjectSize??0,le=t?.maxTotalSize??0,de=t?.encryptionKey??null,Te=t?.validateInput??!0,pt=t?.auditEnabled??!0,ke=t?.userId,Ie=t?.immer??!0,ft=t?.persistByDefault??t?.persistence??t?.persist??!1;t?.accessRules&&t.accessRules.forEach(o=>Ne(l,o.pattern,o.permissions));let Me=!1,Ve=!1,je=!1,fe=0,ve=null,Se=null,$e,yt=new Promise(o=>{$e=o}),St=()=>`${S}_`,Be=()=>({store:e,versions:n,sizes:r,totalSize:fe,storage:I,config:t||{},diskQueue:m,encryptionKey:de,audit:be,onError:D,silent:v,debounceTime:he,currentVersion:ue}),Fe=()=>({plugins:b,onError:D,silent:v}),Ue=o=>{if(o==null)return 0;let c=typeof o;if(c==="boolean")return 4;if(c==="number")return 8;if(c==="string")return o.length*2;if(c!=="object")return 0;let g=0,C=[o],x=new WeakSet;for(;C.length>0;){let w=C.pop();if(typeof w=="boolean")g+=4;else if(typeof w=="number")g+=8;else if(typeof w=="string")g+=w.length*2;else if(typeof w=="object"&&w!==null){let ee=w;if(x.has(ee))continue;if(x.add(ee),Array.isArray(ee))for(let ge=0;ge<ee.length;ge++)C.push(ee[ge]);else for(let ge of Object.keys(ee))g+=ge.length*2,C.push(ee[ge])}}return g},we=(o,c)=>{ct(Fe(),o,c)},be=(o,c,g,C)=>{pt&&Ze()&&P&&P({timestamp:Date.now(),action:o,key:c,userId:ke,success:g,error:C})},Je=o=>{let c=f.get(o);if(!c)return;let g=new Set,C=w=>(g.add(w),f.has(w)?f.get(w).lastValue:Y.get(w)),x=c.selector(C);c.deps.forEach(w=>{if(!g.has(w)){let ee=p.get(w);ee&&(ee.delete(o),ee.size===0&&p.delete(w))}}),g.forEach(w=>{c.deps.has(w)||(p.has(w)||p.set(w,new Set),p.get(w).add(o))}),c.deps=g,Re(c.lastValue,x)||(c.lastValue=Ie&&x!==null&&typeof x=="object"?Le(me(x),!0):x,n.set(o,(n.get(o)||0)+1),Ce(o))},Ce=o=>{if(o){if(p.has(o)){let C=p.get(o);for(let x of C)Je(x)}let c=u.get(o);if(c){let C=Y.get(o);for(let x of c)try{x(C)}catch(w){let ee=w instanceof Error?w:new Error(String(w));D?D(ee,{operation:"watcher",key:o}):v||console.error(`[gstate] Watcher error for "${o}":`,w)}}let g=i.get(o);if(g)for(let C of g)try{C()}catch(x){let w=x instanceof Error?x:new Error(String(x));D?D(w,{operation:"keyListener",key:o}):v||console.error(`[gstate] Listener error for "${o}":`,x)}}if(Me){Ve=!0;return}for(let c of s)try{c()}catch(g){let C=g instanceof Error?g:new Error(String(g));D?D(C,{operation:"listener"}):v||console.error("[gstate] Global listener error: ",g)}},We=async()=>{it(Be())},Ae={},Y={_setSilently:(o,c)=>{let g=r.get(o)||0,C=Ie&&c!==null&&typeof c=="object"?Le(me(c),!0):c,x=typeof process<"u"&&!0,ee=(_>0||le>0)&&!x?Ue(C):0;fe=fe-g+ee,r.set(o,ee),e.set(o,C),n.set(o,(n.get(o)||0)+1),Se=null},_registerMethod:(o,c,g)=>{let C=x=>x==="__proto__"||x==="constructor"||x==="prototype";if(C(o)||C(c)){console.warn("[gstate] Refusing to register method with unsafe key:",o,c);return}Ae[o]||(Ae[o]={}),Ae[o][c]=g},set:(o,c,g={})=>{let C=e.get(o),x=Ie&&typeof c=="function"?Ct(C,c):c;if(Te&&!O(o))return v||console.warn(`[gstate] Invalid key: ${o}`),!1;if(!_e(l,o,"write",ke))return be("set",o,!1,"RBAC Denied"),v||console.error(`[gstate] RBAC Denied for "${o}"`),!1;let w=Te?R(x):x,ee=r.get(o)||0;we("onBeforeSet",{key:o,value:w,store:Y,version:n.get(o)||0});let ge=Ie&&w!==null&&typeof w=="object"?Le(me(w),!0):w;if(!Re(C,ge)){let mt=typeof process<"u"&&!0,xe=(_>0||le>0)&&!mt?Ue(ge):0;if(_>0&&xe>_){let Ee=new Error(`Object size (${xe} bytes) exceeds maxObjectSize (${_} bytes)`);D?D(Ee,{operation:"set",key:o}):v||console.warn(`[gstate] ${Ee.message} for "${o}"`)}if(le>0){let Ee=fe-ee+xe;if(Ee>le){let Ge=new Error(`Total store size (${Ee} bytes) exceeds limit (${le} bytes)`);D?D(Ge,{operation:"set"}):v||console.warn(`[gstate] ${Ge.message}`)}}fe=fe-ee+xe,r.set(o,xe),e.set(o,ge),n.set(o,(n.get(o)||0)+1),Se=null;let Qe=g.persist??ft;return Qe&&(m.set(o,{value:ge,options:{...g,persist:Qe,encoded:g.encoded||t?.encoded}}),ve&&clearTimeout(ve),ve=setTimeout(We,he)),we("onSet",{key:o,value:ge,store:Y,version:n.get(o)}),be("set",o,!0),Ce(o),!0}return!1},get:o=>{if(!_e(l,o,"read",ke))return be("get",o,!1,"RBAC Denied"),null;let c=e.get(o);return we("onGet",{store:Y,key:o,value:c}),be("get",o,!0),c},compute:(o,c)=>{try{return f.has(o)||(f.set(o,{selector:c,lastValue:null,deps:new Set}),Je(o)),f.get(o).lastValue}catch(g){let C=g instanceof Error?g:new Error(String(g));return D?D(C,{operation:"compute",key:o}):v||console.error(`[gstate] Compute error for "${o}": `,g),null}},watch:(o,c)=>{u.has(o)||u.set(o,new Set);let g=u.get(o);return g.add(c),()=>{g.delete(c),g.size===0&&u.delete(o)}},remove:o=>{if(!_e(l,o,"delete",ke))return be("delete",o,!1,"RBAC Denied"),!1;let c=e.get(o),g=e.delete(o);return g&&(fe-=r.get(o)||0,r.delete(o),we("onRemove",{store:Y,key:o,value:c}),Se=null),n.set(o,(n.get(o)||0)+1),I&&I.removeItem(`${St()}${o}`),be("delete",o,!0),Ce(o),g},delete:o=>Y.remove(o),deleteAll:()=>{if(Array.from(e.keys()).forEach(o=>Y.remove(o)),I){let o=S+"_";for(let c=0;c<(I.length||0);c++){let g=I.key(c);g?.startsWith(o)&&(I.removeItem(g),c--)}}return fe=0,r.clear(),Se=null,!0},list:()=>Object.fromEntries(e.entries()),use:o=>{a.add(o)},transaction:o=>{Me=!0,we("onTransaction",{store:Y,key:"START"});try{o()}finally{Me=!1,we("onTransaction",{store:Y,key:"END"}),Ve&&(Ve=!1,Ce())}},destroy:()=>{ve&&(clearTimeout(ve),ve=null),m.clear(),typeof window<"u"&&window.removeEventListener("beforeunload",qe),we("onDestroy",{store:Y}),s.clear(),i.clear(),u.clear(),f.clear(),p.clear(),b.clear(),e.clear(),r.clear(),fe=0,l.clear(),y.clear(),n.clear(),h.clear(),a.clear()},_addPlugin:o=>{ut(Fe(),o,Y)},_removePlugin:o=>{b.delete(o)},_subscribe:(o,c)=>{if(c){i.has(c)||i.set(c,new Set);let g=i.get(c);return g.add(o),()=>{g.delete(o),g.size===0&&i.delete(c)}}return s.add(o),()=>s.delete(o)},_getVersion:o=>n.get(o)??0,addAccessRule:(o,c)=>Ne(l,o,c),hasPermission:(o,c,g)=>_e(l,o,c,g),recordConsent:(o,c,g)=>ze(y,o,c,g),hasConsent:(o,c)=>Ye(y,o,c),getConsents:o=>tt(y,o),revokeConsent:(o,c)=>et(y,o,c),exportUserData:o=>nt(y,o),deleteUserData:o=>rt(y,o),getSnapshot:()=>(Se||(Se=Object.fromEntries(e.entries())),Se),get plugins(){return Ae},get isReady(){return je},get namespace(){return S},get userId(){return ke},whenReady:()=>yt};["addAccessRule","recordConsent","hasConsent","getConsents","revokeConsent","exportUserData","deleteUserData"].forEach(o=>{let c=Y[o];c&&Y._registerMethod("security",o,c)});let qe=()=>{m.size>0&&We()};typeof window<"u"&&window.addEventListener("beforeunload",qe),I?at(Be(),o=>{let c=typeof process<"u"&&!0;return(_>0||le>0)&&!c?Ue(o):0},()=>{je=!0,Se=null,$e(),Ce()}).then(()=>{}):(je=!0,$e());let Oe=null;return t?.sync&&(Oe=new E(Y,t.sync),Y._registerMethod("sync","flush",()=>Oe?.flush()),Y._registerMethod("sync","getState",()=>Oe?.getState()),Y._registerMethod("sync","onStateChange",o=>Oe?.onStateChange(o))),Y};import{useSyncExternalStore as lt,useDebugValue as Et,useMemo as Ke,useCallback as Pe,useEffect as dt,useState as gt}from"react";var pe=null,L=t=>{pe&&!t?.namespace&&(t?.silent||console.warn("[gstate] Store already exists. Pass a unique namespace to create additional stores."));let e=T(t);return pe=e,e},K=()=>{pe&&(pe.destroy(),pe=null)},B=t=>{let e=t||pe,n=Ke(()=>r=>e?e._subscribe(r):()=>{},[e]);return lt(n,()=>e?e.isReady:!1,()=>!0)},k=()=>pe;function d(t,e){let n=Ke(()=>e||pe,[e]),r=Ke(()=>{let l=()=>{},y=()=>!1,S=()=>null;return{set:y,get:S,remove:y,delete:y,deleteAll:y,list:()=>({}),compute:S,watch:()=>()=>{},use:l,transaction:l,destroy:l,_subscribe:()=>()=>{},_setSilently:l,_registerMethod:l,_addPlugin:l,_removePlugin:l,_getVersion:()=>0,get isReady(){return!1},whenReady:()=>Promise.resolve(),get plugins(){return{}},getSnapshot:()=>({}),get namespace(){return"ghost"},get userId(){}}},[]),s=n||r,i=typeof t=="function",a=i?null:t,u=i?t:null,f=Pe(l=>i?s._subscribe(l):s._subscribe(l,a),[s,i,a]),p=Pe(()=>i?u(s.getSnapshot()):s.get(a)??void 0,[s,i,a,u]),b=Pe(()=>{if(i)try{return u({})}catch{return}else return},[u,i]),m=lt(f,p,b),h=Pe((l,y)=>i?(typeof process<"u"||console.warn("[gstate] Cannot set value when using a selector."),!1):s.set(a,l,y),[s,i,a]);return Et(m,l=>i?`Selector: ${JSON.stringify(l)}`:`${a}: ${JSON.stringify(l)}`),i?m:[m,h]}var ye=new Map,F=(t,e)=>{let n=t.namespace;if(ye.has(n))return console.warn(`[gstate] Sync engine already exists for namespace "${n}". Call destroySync first.`),ye.get(n);let r=new E(t,e);return ye.set(n,r),r},J=t=>{let e=ye.get(t);e&&(e.destroy(),ye.delete(t))};function W(t,e){let n=e||pe,r=n?.namespace||"default",s=ye.get(r),i=d(t,n),a=i[0],u=i[1],[f,p]=gt(()=>s?.getState()||{isOnline:!0,isSyncing:!1,lastSyncTimestamp:null,pendingChanges:0,conflicts:0});dt(()=>s?s.onStateChange(p):void 0,[s]);let b=Pe((m,h)=>{let l=u(m,h);if(l&&s){let y=n?.get(t);s.queueChange(t,y)}return l},[u,s,t,n]);return[a,b,f]}var q=()=>{let[t,e]=gt({isOnline:!0,isSyncing:!1,lastSyncTimestamp:null,pendingChanges:0,conflicts:0});return dt(()=>{let n=()=>{let s=!0,i=!1,a=0,u=0;ye.forEach(f=>{let p=f.getState();s=s&&p.isOnline,i=i||p.isSyncing,a+=p.pendingChanges,u+=p.conflicts}),e({isOnline:s,isSyncing:i,lastSyncTimestamp:null,pendingChanges:a,conflicts:u})};n();let r=Array.from(ye.values()).map(s=>s.onStateChange(n));return()=>r.forEach(s=>s())},[]),t},Q=async t=>{let e=t||pe?.namespace;if(!e)return;let n=ye.get(e);n&&await n.flush()};var G=(t,e)=>{let n=e?.key||"async_data",r=e?.store||T({namespace:`async_${n}`,silent:!0});return r.get(n)==null&&r.set(n,{data:null,loading:!1,error:null,updatedAt:null}),Object.assign(r,{execute:async()=>{let i=r.get(n);r.set(n,{...i||{data:null,loading:!1,error:null,updatedAt:null},loading:!0,error:null}),"whenReady"in r&&!r.isReady&&await r.whenReady();try{let a=await t(),u=r.get(n);r.set(n,{...u||{data:null,loading:!1,error:null,updatedAt:null},data:a,loading:!1,updatedAt:Date.now()},{persist:e?.persist})}catch(a){let u=r.get(n);r.set(n,{...u||{data:null,loading:!1,error:null,updatedAt:null},error:a instanceof Error?a:new Error(String(a)),loading:!1})}}})};var _t=()=>({name:"gstate-immer",hooks:{onInstall:({store:t})=>{t._registerMethod("immer","setWithProduce",((e,n)=>t.set(e,n)))}}});var Rt=t=>{let e=[],n=-1,r=!1,s=t?.limit||50;return{name:"gstate-undo-redo",hooks:{onInstall:({store:i})=>{e.push(i.list()),n=0,i._registerMethod("undoRedo","undo",()=>{if(n>0){r=!0,n--;let a=e[n];return a?(Object.entries(a).forEach(([u,f])=>{i._setSilently(u,f)}),r=!1,!0):!1}return!1}),i._registerMethod("undoRedo","redo",()=>{if(n<e.length-1){r=!0,n++;let a=e[n];return a?(Object.entries(a).forEach(([u,f])=>{i._setSilently(u,f)}),r=!1,!0):!1}return!1}),i._registerMethod("undoRedo","canUndo",()=>n>0),i._registerMethod("undoRedo","canRedo",()=>n<e.length-1)},onSet:({store:i})=>{r||(n<e.length-1&&(e=e.slice(0,n+1)),e.push(i.list()),e.length>s?e.shift():n++)}}}};var Pt=t=>({name:"gstate-schema",hooks:{onSet:({key:e,value:n})=>{if(!e)return;let r=t[e];if(r){let s=r(n);if(s!==!0)throw new Error(`[Schema Error] Validation failed for key "${e}": ${s===!1?"Invalid type":s}`)}}}});var Tt=t=>{let r=globalThis.__REDUX_DEVTOOLS_EXTENSION__;if(!r?.connect)return{name:"gstate-devtools-noop",hooks:{}};let s=null;return{name:"gstate-devtools",hooks:{onInstall:({store:i})=>{s=r.connect({name:t?.name||"Magnetar Store"}),s.init(i.list())},onSet:({key:i,store:a})=>{!i||!s||s.send(`SET_${i.toUpperCase()}`,a.list())},onRemove:({key:i,store:a})=>{!i||!s||s.send(`REMOVE_${i.toUpperCase()}`,a.list())}}}};var It=()=>{let t=new Map;return{name:"gstate-snapshot",hooks:{onInstall:({store:e})=>{e._registerMethod("snapshot","takeSnapshot",(n=>{t.set(n,e.list())})),e._registerMethod("snapshot","restoreSnapshot",(n=>{let r=t.get(n);return r?(e.transaction(()=>{Object.entries(r).forEach(([s,i])=>{e.set(s,i)})}),!0):!1})),e._registerMethod("snapshot","listSnapshots",(()=>Array.from(t.keys()))),e._registerMethod("snapshot","deleteSnapshot",(n=>t.delete(n))),e._registerMethod("snapshot","clearSnapshots",(()=>t.clear()))}}}};var At=t=>({name:"gstate-guard",hooks:{onBeforeSet:({key:e,value:n,store:r})=>{if(!e)return;let s=t[e];if(s){let i=s(n)}}}});var Ot=t=>({name:"gstate-analytics",hooks:{onSet:({key:e,value:n})=>{e&&(!t.keys||t.keys.includes(e))&&t.provider({key:e,value:n,action:"SET"})},onRemove:({key:e})=>{e&&(!t.keys||t.keys.includes(e))&&t.provider({key:e,value:null,action:"REMOVE"})}}});var Dt=t=>{let e=new BroadcastChannel(t?.channelName||"gstate_sync"),n=!1;return{name:"gstate-sync",hooks:{onInstall:({store:r})=>{e.onmessage=s=>{let{key:i,value:a,action:u}=s.data;i&&(n=!0,u==="REMOVE"?r.remove(i):r.set(i,a),n=!1)}},onSet:({key:r,value:s})=>{!r||n||e.postMessage({key:r,value:s,action:"SET"})},onRemove:({key:r})=>{!r||n||e.postMessage({key:r,action:"REMOVE"})},onDestroy:()=>{e.close()}}}};var Mt=()=>({name:"gstate-debug-noop",hooks:{}});var Vt=(t={})=>{let e=t.dbName||"rgs-db",n=t.storeName||"states",r=t.version||1,s=null,i=()=>new Promise((p,b)=>{if(s)return p(s);let m=indexedDB.open(e,r);m.onerror=()=>b(m.error),m.onsuccess=()=>{s=m.result,p(s)},m.onupgradeneeded=h=>{let l=h.target.result;l.objectStoreNames.contains(n)||l.createObjectStore(n)}}),a=async(p,b)=>{let m=await i();return new Promise((h,l)=>{let v=m.transaction(n,"readwrite").objectStore(n).put(b,p);v.onsuccess=()=>h(),v.onerror=()=>l(v.error)})},u=async p=>{let b=await i();return new Promise((m,h)=>{let S=b.transaction(n,"readonly").objectStore(n).get(p);S.onsuccess=()=>m(S.result),S.onerror=()=>h(S.error)})},f=async p=>{let b=await i();return new Promise((m,h)=>{let S=b.transaction(n,"readwrite").objectStore(n).delete(p);S.onsuccess=()=>m(),S.onerror=()=>h(S.error)})};return{name:"indexedDB",hooks:{onInstall:({store:p})=>{p._registerMethod("indexedDB","clear",async()=>{(await i()).transaction(n,"readwrite").objectStore(n).clear()})},onInit:async({store:p})=>{let l=(await i()).transaction(n,"readonly").objectStore(n).getAllKeys();l.onsuccess=async()=>{let y=l.result,S=p.namespace+"_";for(let v of y)if(v.startsWith(S)){let he=await u(v);if(he){let ue=v.substring(S.length);p._setSilently(ue,he.d)}}}},onSet:async({key:p,value:b,store:m})=>{if(!p)return;let h=m.namespace+"_",l={d:b,t:Date.now(),v:m._getVersion?.(p)||1};await a(`${h}${p}`,l)},onRemove:async({key:p,store:b})=>{if(!p)return;let m=b.namespace+"_";await f(`${m}${p}`)}}}};var jt=t=>{let{adapter:e,autoSyncInterval:n}=t,r=new Map,s={lastSyncTimestamp:null,totalKeysSynced:0,totalBytesSynced:0,syncCount:0,lastDuration:0,errors:0},i=null;return{name:"cloudSync",hooks:{onInstall:({store:a})=>{a._registerMethod("cloudSync","sync",async()=>{let u=performance.now(),f={},p=0;try{let b=a.list(),m=Object.keys(b);for(let l of m){let y=a._getVersion?.(l)||0,S=r.get(l)||0;if(y>S){let v=b[l];f[l]=v,p+=JSON.stringify(v).length,r.set(l,y)}}if(Object.keys(f).length===0)return{status:"no-change",stats:s};if(await e.save(f))return s.lastSyncTimestamp=Date.now(),s.totalKeysSynced+=Object.keys(f).length,s.totalBytesSynced+=p,s.syncCount++,s.lastDuration=performance.now()-u,t.onSync&&t.onSync(s),{status:"success",stats:s};throw new Error(`Adapter ${e.name} failed to save.`)}catch(b){return s.errors++,console.error(`[gstate] Cloud Sync Failed (${e.name}):`,b),{status:"error",error:String(b),stats:s}}}),a._registerMethod("cloudSync","getStats",()=>s),n&&n>0&&(i=setInterval(()=>{let f=a.plugins.cloudSync;f&&f.sync()},n))},onDestroy:()=>{i&&clearInterval(i)}}}},$t=(t,e)=>({name:"MongoDB-Atlas",save:async n=>(await fetch(`${t}/action/updateOne`,{method:"POST",headers:{"Content-Type":"application/json","api-key":e},body:JSON.stringify({dataSource:"Cluster0",database:"rgs_cloud",collection:"user_states",filter:{id:"global_state"},update:{$set:{data:n,updatedAt:Date.now()}},upsert:!0})})).ok}),Ut=(t,e)=>({name:"Firebase-Firestore",save:async n=>{try{return((...i)=>{})("[Mock] Firestore Syncing:",n),!0}catch{return!1}}}),Nt=(t,e)=>({name:"SQL-REST-API",save:async n=>{let r=e();return r?(await fetch(t,{method:"PATCH",headers:{"Content-Type":"application/json",Authorization:`Bearer ${r}`},body:JSON.stringify(n),credentials:"same-origin"})).ok:(console.warn("[gstate] No auth token available for SQL-REST sync"),!1)}});var $n=t=>({name:"gstate-logger",hooks:{onSet:({key:e,value:n,version:r})=>{let s=new Date().toLocaleTimeString(),i=`[gstate] SET: ${e} (v${r}) @ ${s}`;t?.collapsed?console.groupCollapsed(i):console.group(i),console.info("%c Value:","color: #4CAF50; font-weight: bold;",n),console.groupEnd()},onRemove:({key:e})=>{console.warn(`[gstate] REMOVED: ${e}`)},onTransaction:({key:e})=>{e==="START"?console.group("\u2500\u2500 TRANSACTION START \u2500\u2500"):console.groupEnd()}}});var H=(t,e)=>{let r=T(typeof e=="string"?{namespace:e}:e);t&&Object.entries(t).forEach(([a,u])=>{r.get(a)===null&&r._setSilently(a,u)});let s=a=>d(a,r);return typeof window<"u"&&typeof process>"u"&&(window.gstate=r,window.gState=r,window.rgs=r),Object.assign(s,r)};var te=(t,e)=>k()?.addAccessRule(t,e),ne=(t,e,n)=>k()?.hasPermission(t,e,n)??!0,re=(t,e,n)=>{let r=k();if(!r)throw new Error("[gstate] recordConsent failed: No store found. call initState() first.");return r.recordConsent(t,e,n)},se=(t,e)=>k()?.hasConsent(t,e)??!1,oe=t=>k()?.getConsents(t)??[],ie=(t,e)=>k()?.revokeConsent(t,e),ae=t=>{let e=k();if(!e)throw new Error("[gstate] exportUserData failed: No store found.");return e.exportUserData(t)},ce=t=>{let e=k();if(!e)throw new Error("[gstate] deleteUserData failed: No store found.");return e.deleteUserData(t)},X=()=>{},Z=()=>{};export{E as SyncEngine,te as addAccessRule,Ot as analyticsPlugin,X as clearAccessRules,Z as clearAllConsents,jt as cloudSyncPlugin,G as createAsyncStore,Ut as createFirestoreAdapter,$t as createMongoAdapter,Nt as createSqlRestAdapter,T as createStore,z as createSyncEngine,Mt as debugPlugin,ce as deleteUserData,M as deriveKeyFromPassword,K as destroyState,J as destroySync,Tt as devToolsPlugin,$ as exportKey,ae as exportUserData,j as generateEncryptionKey,V as generateSalt,oe as getConsents,k as getStore,H as gstate,At as guardPlugin,se as hasConsent,ne as hasPermission,_t as immerPlugin,U as importKey,Vt as indexedDBPlugin,L as initState,F as initSync,A as isCryptoAvailable,P as logAudit,$n as loggerPlugin,re as recordConsent,ie as revokeConsent,R as sanitizeValue,Pt as schemaPlugin,N as setAuditLogger,It as snapshotPlugin,Dt as syncPlugin,Q as triggerSync,Rt as undoRedoPlugin,d as useGState,B as useIsStoreReady,d as useSimpleState,d as useStore,q as useSyncStatus,W as useSyncedState,O as validateKey};
|
|
2
|
-
//# sourceMappingURL=index.js.map
|