@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.
Files changed (49) hide show
  1. package/README.md +84 -3
  2. package/package.json +91 -85
  3. package/COPYRIGHT.md +0 -4
  4. package/FUNDING.yml +0 -12
  5. package/SECURITY.md +0 -13
  6. package/advanced.d.ts +0 -9
  7. package/core/advanced.d.ts +0 -5
  8. package/core/async.d.ts +0 -8
  9. package/core/hooks.d.ts +0 -17
  10. package/core/persistence.d.ts +0 -23
  11. package/core/plugins.d.ts +0 -8
  12. package/core/reactivity.d.ts +0 -19
  13. package/core/security.d.ts +0 -56
  14. package/core/store.d.ts +0 -7
  15. package/core/sync.d.ts +0 -76
  16. package/core/types.d.ts +0 -163
  17. package/core/utils.d.ts +0 -2
  18. package/docs/README.md +0 -389
  19. package/docs/SUMMARY.md +0 -64
  20. package/docs/_config.yml +0 -1
  21. package/docs/api.md +0 -381
  22. package/docs/chapters/01-philosophy.md +0 -54
  23. package/docs/chapters/02-getting-started.md +0 -68
  24. package/docs/chapters/03-the-magnetar-way.md +0 -69
  25. package/docs/chapters/04-persistence-and-safety.md +0 -125
  26. package/docs/chapters/05-plugin-sdk.md +0 -290
  27. package/docs/chapters/05-plugins-and-extensibility.md +0 -190
  28. package/docs/chapters/06-case-studies.md +0 -69
  29. package/docs/chapters/07-faq.md +0 -53
  30. package/docs/chapters/08-migration-guide.md +0 -284
  31. package/docs/chapters/09-security-architecture.md +0 -50
  32. package/docs/chapters/10-local-first-sync.md +0 -146
  33. package/docs/qa.md +0 -47
  34. package/index.d.ts +0 -41
  35. package/index.js +0 -2
  36. package/index.js.map +0 -7
  37. package/plugins/index.d.ts +0 -15
  38. package/plugins/official/analytics.plugin.d.ts +0 -9
  39. package/plugins/official/cloud-sync.plugin.d.ts +0 -22
  40. package/plugins/official/debug.plugin.d.ts +0 -2
  41. package/plugins/official/devtools.plugin.d.ts +0 -4
  42. package/plugins/official/guard.plugin.d.ts +0 -2
  43. package/plugins/official/immer.plugin.d.ts +0 -2
  44. package/plugins/official/indexeddb.plugin.d.ts +0 -7
  45. package/plugins/official/schema.plugin.d.ts +0 -2
  46. package/plugins/official/snapshot.plugin.d.ts +0 -2
  47. package/plugins/official/sync.plugin.d.ts +0 -4
  48. package/plugins/official/undo-redo.plugin.d.ts +0 -4
  49. 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(/&amp;#x([0-9a-fA-F]+);?/i);if(s&&s[1])return String.fromCharCode(parseInt(s[1],16));let i=r.match(/&amp;#([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