@biglogic/rgs 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/CODEOWNERS +1 -0
- package/dist/CONTRIBUTING.md +65 -0
- package/dist/COPYRIGHT.md +4 -0
- package/dist/LICENSE +9 -0
- package/dist/README.md +267 -0
- package/dist/SECURITY.md +3 -0
- package/dist/advanced.d.ts +9 -0
- package/dist/advanced.js +1 -0
- package/dist/core/advanced.d.ts +5 -0
- package/dist/core/async.d.ts +8 -0
- package/dist/core/hooks.d.ts +8 -0
- package/dist/core/security.d.ts +54 -0
- package/dist/core/store.d.ts +7 -0
- package/dist/core/types.d.ts +134 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +1 -0
- package/dist/markdown/SUMMARY.md +55 -0
- package/dist/markdown/api.md +342 -0
- package/dist/markdown/chapters/01-philosophy.md +54 -0
- package/dist/markdown/chapters/02-getting-started.md +68 -0
- package/dist/markdown/chapters/03-the-magnetar-way.md +62 -0
- package/dist/markdown/chapters/04-persistence-and-safety.md +84 -0
- package/dist/markdown/chapters/05-plugin-sdk.md +290 -0
- package/dist/markdown/chapters/05-plugins-and-extensibility.md +174 -0
- package/dist/markdown/chapters/06-case-studies.md +69 -0
- package/dist/markdown/chapters/07-faq.md +53 -0
- package/dist/markdown/chapters/08-migration-guide.md +206 -0
- package/dist/package.json +81 -0
- package/dist/plugins/index.d.ts +13 -0
- package/dist/plugins/official/analytics.plugin.d.ts +9 -0
- package/dist/plugins/official/debug.plugin.d.ts +2 -0
- package/dist/plugins/official/devtools.plugin.d.ts +4 -0
- package/dist/plugins/official/guard.plugin.d.ts +2 -0
- package/dist/plugins/official/immer.plugin.d.ts +2 -0
- package/dist/plugins/official/schema.plugin.d.ts +2 -0
- package/dist/plugins/official/snapshot.plugin.d.ts +2 -0
- package/dist/plugins/official/sync.plugin.d.ts +4 -0
- package/dist/plugins/official/undo-redo.plugin.d.ts +4 -0
- package/package.json +81 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# 🔧 Plugin SDK: Build Your Own Extensions
|
|
2
|
+
|
|
3
|
+
This guide shows you how to create custom plugins for RGS (Argis) - React Globo State. Plugins are the way to extend the store with custom functionality.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📦 What is a Plugin?
|
|
8
|
+
|
|
9
|
+
A plugin is a module that:
|
|
10
|
+
|
|
11
|
+
1. Adds **lifecycle hooks** to react to store events
|
|
12
|
+
2. Exposes **custom methods** via `store.plugins.yourPlugin.method()`
|
|
13
|
+
3. Can **validate**, **transform**, or **track** data
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 🏗️ Plugin Structure
|
|
18
|
+
|
|
19
|
+
Every plugin implements the `IPlugin` interface:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import type { IPlugin, PluginContext } from 'argis'
|
|
23
|
+
|
|
24
|
+
interface IPlugin {
|
|
25
|
+
name: string
|
|
26
|
+
hooks: {
|
|
27
|
+
onInit?: (ctx: PluginContext) => void | Promise<void>
|
|
28
|
+
onInstall?: (ctx: PluginContext) => void | Promise<void>
|
|
29
|
+
onSet?: (ctx: PluginContext) => void | Promise<void>
|
|
30
|
+
onGet?: (ctx: PluginContext) => void | Promise<void>
|
|
31
|
+
onRemove?: (ctx: PluginContext) => void | Promise<void>
|
|
32
|
+
onDestroy?: (ctx: PluginContext) => void | Promise<void>
|
|
33
|
+
onTransaction?: (ctx: PluginContext) => void | Promise<void>
|
|
34
|
+
onBeforeSet?: (ctx: PluginContext) => void | Promise<void>
|
|
35
|
+
onAfterSet?: (ctx: PluginContext) => void | Promise<void>
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 🎯 Creating Your First Plugin
|
|
43
|
+
|
|
44
|
+
### Example: A Simple Logger Plugin
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import type { IPlugin, PluginContext } from 'argis'
|
|
48
|
+
|
|
49
|
+
export const loggerPlugin = (): IPlugin => {
|
|
50
|
+
return {
|
|
51
|
+
name: 'my-logger',
|
|
52
|
+
hooks: {
|
|
53
|
+
onSet: ({ key, value }: PluginContext) => {
|
|
54
|
+
console.log(`[Logger] Set "${key}" to:`, value)
|
|
55
|
+
},
|
|
56
|
+
onGet: ({ key, value }: PluginContext) => {
|
|
57
|
+
console.log(`[Logger] Got "${key}":`, value)
|
|
58
|
+
},
|
|
59
|
+
onRemove: ({ key }: PluginContext) => {
|
|
60
|
+
console.log(`[Logger] Removed "${key}"`)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Usage
|
|
67
|
+
store._addPlugin(loggerPlugin())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🔌 Registering Custom Methods
|
|
73
|
+
|
|
74
|
+
Plugins can expose methods accessible via `store.plugins.pluginName.methodName()`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import type { IPlugin, PluginContext } from 'argis'
|
|
78
|
+
|
|
79
|
+
export const counterPlugin = (): IPlugin => {
|
|
80
|
+
let _count = 0
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
name: 'counter',
|
|
84
|
+
hooks: {
|
|
85
|
+
onInstall: ({ store }) => {
|
|
86
|
+
// Register methods with explicit plugin namespace
|
|
87
|
+
store._registerMethod('counter', 'increment', () => {
|
|
88
|
+
_count++
|
|
89
|
+
return _count
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
store._registerMethod('counter', 'decrement', () => {
|
|
93
|
+
_count--
|
|
94
|
+
return _count
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
store._registerMethod('counter', 'getCount', () => _count)
|
|
98
|
+
|
|
99
|
+
store._registerMethod('counter', 'reset', () => {
|
|
100
|
+
_count = 0
|
|
101
|
+
return _count
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Usage
|
|
109
|
+
store._addPlugin(counterPlugin())
|
|
110
|
+
|
|
111
|
+
store.plugins.counter.increment() // returns 1
|
|
112
|
+
store.plugins.counter.increment() // returns 2
|
|
113
|
+
store.plugins.counter.getCount() // returns 2
|
|
114
|
+
store.plugins.counter.decrement() // returns 1
|
|
115
|
+
store.plugins.counter.reset() // returns 0
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## ⚙️ Plugin Configuration
|
|
121
|
+
|
|
122
|
+
Plugins can accept configuration options:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
interface ValidationConfig {
|
|
126
|
+
email?: (value: string) => boolean | string
|
|
127
|
+
username?: (value: string) => boolean | string
|
|
128
|
+
maxLength?: number
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export const validationPlugin = (config: ValidationConfig): IPlugin => {
|
|
132
|
+
return {
|
|
133
|
+
name: 'validation',
|
|
134
|
+
hooks: {
|
|
135
|
+
onBeforeSet: ({ key, value }) => {
|
|
136
|
+
const validator = config[key as keyof ValidationConfig]
|
|
137
|
+
if (validator && typeof value === 'string') {
|
|
138
|
+
const result = validator(value)
|
|
139
|
+
if (result !== true) {
|
|
140
|
+
throw new Error(`Validation failed for "${key}": ${result}`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Check maxLength
|
|
145
|
+
if (config.maxLength && typeof value === 'string' && value.length > config.maxLength) {
|
|
146
|
+
throw new Error(`"${key}" exceeds max length of ${config.maxLength}`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Usage
|
|
154
|
+
store._addPlugin(validationPlugin({
|
|
155
|
+
email: (v) => v.includes('@') ? true : 'Invalid email',
|
|
156
|
+
username: (v) => v.length >= 3 ? true : 'Too short',
|
|
157
|
+
maxLength: 100
|
|
158
|
+
}))
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🔄 Using Store Internals
|
|
164
|
+
|
|
165
|
+
Plugins have access to internal store methods:
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
export const auditPlugin = (): IPlugin => {
|
|
169
|
+
return {
|
|
170
|
+
name: 'audit',
|
|
171
|
+
hooks: {
|
|
172
|
+
onSet: ({ store, key, value, version }) => {
|
|
173
|
+
// Read other keys
|
|
174
|
+
const currentUser = store.get('currentUser')
|
|
175
|
+
|
|
176
|
+
// Set silently (without triggering hooks)
|
|
177
|
+
store._setSilently('_auditLog', [
|
|
178
|
+
...(store.get('_auditLog') || []),
|
|
179
|
+
{ action: 'set', key, timestamp: Date.now() }
|
|
180
|
+
])
|
|
181
|
+
|
|
182
|
+
// Get version
|
|
183
|
+
const ver = store._getVersion(key)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Available Internal Methods
|
|
191
|
+
|
|
192
|
+
| Method | Description |
|
|
193
|
+
|--------|-------------|
|
|
194
|
+
| `store.get(key)` | Get a value |
|
|
195
|
+
| `store.set(key, value)` | Set a value |
|
|
196
|
+
| `store._setSilently(key, value)` | Set without triggering hooks |
|
|
197
|
+
| `store.list()` | Get all key-value pairs |
|
|
198
|
+
| `store._getVersion(key)` | Get version number |
|
|
199
|
+
| `store._subscribe(callback)` | Subscribe to changes |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 🧪 Full Example: Persistence Plugin
|
|
204
|
+
|
|
205
|
+
Here's a complete plugin that auto-saves to a custom backend:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import type { IPlugin, PluginContext } from 'argis'
|
|
209
|
+
|
|
210
|
+
interface AutoSaveConfig {
|
|
211
|
+
endpoint: string
|
|
212
|
+
debounceMs?: number
|
|
213
|
+
keys?: string[] // Which keys to save
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export const autoSavePlugin = (config: AutoSaveConfig): IPlugin => {
|
|
217
|
+
const { endpoint, debounceMs = 1000, keys } = config
|
|
218
|
+
let _saveTimeout: ReturnType<typeof setTimeout> | null = null
|
|
219
|
+
|
|
220
|
+
const save = async (store: any) => {
|
|
221
|
+
const data = keys
|
|
222
|
+
? Object.fromEntries(keys.map(k => [k, store.get(k)]))
|
|
223
|
+
: store.list()
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await fetch(endpoint, {
|
|
227
|
+
method: 'POST',
|
|
228
|
+
headers: { 'Content-Type': 'application/json' },
|
|
229
|
+
body: JSON.stringify(data)
|
|
230
|
+
})
|
|
231
|
+
console.log('[AutoSave] Saved to', endpoint)
|
|
232
|
+
} catch (err) {
|
|
233
|
+
console.error('[AutoSave] Failed:', err)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
name: 'auto-save',
|
|
239
|
+
hooks: {
|
|
240
|
+
onInstall: async ({ store }) => {
|
|
241
|
+
// Load initial data
|
|
242
|
+
try {
|
|
243
|
+
const res = await fetch(endpoint)
|
|
244
|
+
const data = await res.json()
|
|
245
|
+
Object.entries(data).forEach(([k, v]) => {
|
|
246
|
+
store._setSilently(k, v)
|
|
247
|
+
})
|
|
248
|
+
} catch {
|
|
249
|
+
// Ignore load errors
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
onSet: ({ store }) => {
|
|
253
|
+
// Debounced save
|
|
254
|
+
if (_saveTimeout) clearTimeout(_saveTimeout)
|
|
255
|
+
_saveTimeout = setTimeout(() => save(store), debounceMs)
|
|
256
|
+
},
|
|
257
|
+
onDestroy: ({ store }) => {
|
|
258
|
+
// Save immediately on destroy
|
|
259
|
+
if (_saveTimeout) clearTimeout(_saveTimeout)
|
|
260
|
+
save(store)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Usage
|
|
267
|
+
store._addPlugin(autoSavePlugin({
|
|
268
|
+
endpoint: '/api/save',
|
|
269
|
+
debounceMs: 2000,
|
|
270
|
+
keys: ['user', 'settings', 'preferences']
|
|
271
|
+
}))
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## 📋 Plugin Best Practices
|
|
277
|
+
|
|
278
|
+
1. **Use Namespaced Methods** - Always use `store._registerMethod('pluginName', 'method', fn)`
|
|
279
|
+
2. **Clean Up Resources** - Use `onDestroy` to clean up timers, listeners, etc.
|
|
280
|
+
3. **Handle Errors** - Wrap async operations in try/catch
|
|
281
|
+
4. **Document Configuration** - Provide clear TypeScript interfaces
|
|
282
|
+
5. **Test Hooks** - Test each hook independently
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## 📚 Related
|
|
287
|
+
|
|
288
|
+
- [Plugin API Reference](../api/plugins.md)
|
|
289
|
+
- [Chapter 5: Ecosystem and Plugins](05-plugins-and-extensibility.md)
|
|
290
|
+
- [Case Studies](06-case-studies.md)
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# 🔌 Chapter 5: Ecosystem and Plugins - Become a Power User
|
|
2
|
+
|
|
3
|
+
RGS is not a closed box. It's a modular engine that you can extend to cover every business need. All plugins are designed to be "Plug & Play".
|
|
4
|
+
|
|
5
|
+
## 🔌 Available Plugins
|
|
6
|
+
|
|
7
|
+
RGS includes 8 official plugins:
|
|
8
|
+
|
|
9
|
+
| Plugin | Purpose | Import |
|
|
10
|
+
|--------|---------|--------|
|
|
11
|
+
| `devToolsPlugin` | Redux DevTools integration | `rgs` |
|
|
12
|
+
| `debugPlugin` | Console debug access (DEV only) | `rgs` |
|
|
13
|
+
| `syncPlugin` | Cross-tab synchronization | `rgs/advanced` |
|
|
14
|
+
| `immerPlugin` | Immer for mutable-style updates | `rgs` |
|
|
15
|
+
| `snapshotPlugin` | Save/restore state snapshots | `rgs` |
|
|
16
|
+
| `undoRedoPlugin` | History management | `rgs` |
|
|
17
|
+
| `schemaPlugin` | Schema validation | `rgs` |
|
|
18
|
+
| `guardPlugin` | Pre-set value transformation | `rgs` |
|
|
19
|
+
| `analyticsPlugin` | Track state changes | `rgs` |
|
|
20
|
+
|
|
21
|
+
## 🔎 1. DevTools: See Under the Hood
|
|
22
|
+
|
|
23
|
+
Import the official plugin and you'll see every state change, transaction, and execution time in the console or dev tools (Redux DevTools support included!).
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { devToolsPlugin } from 'argis';
|
|
27
|
+
|
|
28
|
+
store._addPlugin(devToolsPlugin({ name: 'My Store' }));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 🐛 2. Debug: Console Access (DEV ONLY)
|
|
32
|
+
|
|
33
|
+
⚠️ **FOR DEVELOPMENT ONLY** - This plugin is automatically disabled in production.
|
|
34
|
+
|
|
35
|
+
Access your store directly from the browser console:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { debugPlugin } from 'argis';
|
|
39
|
+
|
|
40
|
+
// Always wrap in dev check
|
|
41
|
+
if (process.env.NODE_ENV === 'development') {
|
|
42
|
+
store._addPlugin(debugPlugin())
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then in the browser console:
|
|
47
|
+
```javascript
|
|
48
|
+
gstate.list() // View all state
|
|
49
|
+
gstate.get('key') // Get a value
|
|
50
|
+
gstate.set('key', val) // Set a value
|
|
51
|
+
gstate.info() // Store info
|
|
52
|
+
gstate.banner() // Show help
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 2. Cross-Tab Sync: Multi-Tab Magic
|
|
56
|
+
|
|
57
|
+
Have your app open in three browser tabs? With the `syncPlugin`, if a user changes the theme in one tab, all other tabs update instantly. **Without hitting the server.**
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { syncPlugin } from 'rgs/advanced';
|
|
61
|
+
|
|
62
|
+
store._addPlugin(syncPlugin({ channelName: 'my_app_sync' }));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 🕐 3. TTL (Time To Live): Expiring Data
|
|
66
|
+
|
|
67
|
+
Use the `ttl` option in persist to make data expire automatically:
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
store.set('session_token', tokenValue, {
|
|
71
|
+
persist: true,
|
|
72
|
+
ttl: 3600000 // Expires in 1 hour
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 🎲 4. Undo/Redo: History Management
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { undoRedoPlugin } from 'argis';
|
|
80
|
+
|
|
81
|
+
store._addPlugin(undoRedoPlugin({ limit: 50 }));
|
|
82
|
+
|
|
83
|
+
// Later...
|
|
84
|
+
store.undo();
|
|
85
|
+
store.redo();
|
|
86
|
+
store.canUndo(); // boolean
|
|
87
|
+
store.canRedo(); // boolean
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## 📸 5. Snapshots: Save & Restore State
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { snapshotPlugin } from 'argis';
|
|
94
|
+
|
|
95
|
+
store._addPlugin(snapshotPlugin());
|
|
96
|
+
|
|
97
|
+
// Save current state
|
|
98
|
+
store.takeSnapshot('backup_1');
|
|
99
|
+
|
|
100
|
+
// Restore
|
|
101
|
+
store.restoreSnapshot('backup_1');
|
|
102
|
+
|
|
103
|
+
// List all snapshots
|
|
104
|
+
store.listSnapshots(); // ['backup_1', ...]
|
|
105
|
+
|
|
106
|
+
// Delete
|
|
107
|
+
store.deleteSnapshot('backup_1');
|
|
108
|
+
store.clearSnapshots();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## 🛡️ 6. Guard: Pre-Set Transformation
|
|
112
|
+
|
|
113
|
+
Transform values before they hit the store:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { guardPlugin } from 'argis';
|
|
117
|
+
|
|
118
|
+
store._addPlugin(guardPlugin({
|
|
119
|
+
'user_input': (val) => val.trim().toLowerCase()
|
|
120
|
+
}));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## ✅ 7. Schema: Validation
|
|
124
|
+
|
|
125
|
+
Validate values before setting:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { schemaPlugin } from 'argis';
|
|
129
|
+
|
|
130
|
+
store._addPlugin(schemaPlugin({
|
|
131
|
+
'email': (val) => {
|
|
132
|
+
if (typeof val !== 'string') return 'Must be a string';
|
|
133
|
+
return val.includes('@') ? true : 'Invalid email';
|
|
134
|
+
}
|
|
135
|
+
}));
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 📊 8. Analytics: Track Changes
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { analyticsPlugin } from 'argis';
|
|
142
|
+
|
|
143
|
+
store._addPlugin(analyticsPlugin({
|
|
144
|
+
provider: (event) => {
|
|
145
|
+
console.log('State changed:', event);
|
|
146
|
+
// Send to analytics service
|
|
147
|
+
},
|
|
148
|
+
keys: ['user', 'cart'] // Only track these keys
|
|
149
|
+
}));
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 🔄 9. Immer Integration
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { immerPlugin } from 'argis';
|
|
156
|
+
|
|
157
|
+
store._addPlugin(immerPlugin());
|
|
158
|
+
|
|
159
|
+
// Update nested state with Immer
|
|
160
|
+
store.setWithProduce('user', (draft) => {
|
|
161
|
+
draft.name = 'New Name';
|
|
162
|
+
draft.address.city = 'New City';
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## 💡 A Word of Wisdom for Easy & Advanced Scenarios
|
|
169
|
+
|
|
170
|
+
Plugins are used to **abstract the boring logic**. If you find yourself writing the same `useEffect` to sync two things in 5 different parts of your app... **Stop.** Create a plugin or use an existing one.
|
|
171
|
+
|
|
172
|
+
The best code is the code you write once and forget about.
|
|
173
|
+
|
|
174
|
+
**Next step:** [Case Studies: Real-World Production Strategies](06-case-studies.md)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# 🛒 Chapter 6: Case Studies - Real Strategies for Real People
|
|
2
|
+
|
|
3
|
+
In this chapter, we put theory aside and see how RGS solves the problems that keep you up at night (or at least frustrated in the office).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 🍎 Case 1: High-Performance E-commerce
|
|
8
|
+
|
|
9
|
+
**The Problem**: A shopping cart with 50 items, complex sidebar filters, and a product list that must update without "jumping" the whole page.
|
|
10
|
+
|
|
11
|
+
**The RGS Strategy**:
|
|
12
|
+
|
|
13
|
+
1. **Atomic Filters**: Don't save the entire filters object. Use separate keys (`category`, `priceRange`, `search`). This way, if the user only changes the price, the search bar doesn't re-render.
|
|
14
|
+
2. **Persistent Cart**: Use `gstate` with a `cart` namespace.
|
|
15
|
+
3. **Computed State for Totals**:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
cartStore.compute('totalAmount', ['items'], (s) =>
|
|
19
|
+
s.items.reduce((acc, curr) => acc + curr.price, 0)
|
|
20
|
+
);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Why do this?** Because the component displaying the total price updates *only* when the `items` array changes, not when the user's name or shipping address changes. **Zero waste.**
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 📊 Case 2: Real-time Dashboard (Sockets/Events)
|
|
28
|
+
|
|
29
|
+
**The Problem**: You receive thousands of updates via WebSocket (e.g., crypto prices or server notifications), and React can't keep up.
|
|
30
|
+
|
|
31
|
+
**The RGS Strategy**:
|
|
32
|
+
|
|
33
|
+
1. **Atomic Transactions**: In RGS, you can group updates.
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
socket.on('bulk_update', (data) => {
|
|
37
|
+
store.transaction(() => {
|
|
38
|
+
data.forEach(item => store.set(`price_${item.id}`, item.price));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Why do this?** Instead of triggering 100 React updates, the `transaction` triggers **only one** at the end. Your dashboard's performance will go from "tractor" to "Ferrari".
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## 🏦 Case 3: Multi-Step Forms (User Onboarding)
|
|
48
|
+
|
|
49
|
+
**The Problem**: A signup form with 5 steps. If the user hits "Back" or refreshes the page, they lose everything.
|
|
50
|
+
|
|
51
|
+
**The RGS Strategy**:
|
|
52
|
+
|
|
53
|
+
1. Use a dedicated `gstate` called `onboarding`.
|
|
54
|
+
2. Enable `persist: true`.
|
|
55
|
+
3. At each step, just call `set('step1', values)`.
|
|
56
|
+
**Why do this?** Because you don't have to manage manual saving logic. When the user returns, the fields are already populated. At the very end (Step 5), call `store.destroy()` to clean up. Clean and elegant.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🛡️ Message for Advanced Architects
|
|
61
|
+
|
|
62
|
+
*"But I could do all this with a custom cache and an event bus..."*
|
|
63
|
+
Sure you could. You could also walk to work instead of driving. But RGS is the car: it's tested, it handles edge cases (closed tabs, full storage, corrupted types), and it lets you focus on **business logic**, not infrastructure.
|
|
64
|
+
|
|
65
|
+
Stop reinventing the wheel. Use RGS.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
**Next step:** [FAQ: For the Skeptics and the Curious](07-faq.md)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ❓ Chapter 7: FAQ - Architectural Insights
|
|
2
|
+
|
|
3
|
+
This section provides technical context for the design decisions behind RGS (Argis) - React Globo State.
|
|
4
|
+
|
|
5
|
+
## 1. "Why integrate Security and GDPR into the State layer?"
|
|
6
|
+
|
|
7
|
+
**The Rationale:** In enterprise environments, ensuring that every data access is authorized is critical. By integrating **RBAC** and **Auditing** directly into the store instance, we provide a "Secure-by-Default" architecture. This prevents common oversights where developers might forget to apply permission checks in custom middleware or component logic.
|
|
8
|
+
|
|
9
|
+
## 2. "How does RGS compare to the React Context API?"
|
|
10
|
+
|
|
11
|
+
**The Technical Difference:** Context is a dependency injection tool. When values change, React often triggers broad re-renders across the consumer tree. RGS uses a **Surgical Subscription** model. Only components observing a specific key are notified of changes, ensuring optimal performance even in data-heavy applications.
|
|
12
|
+
|
|
13
|
+
## 3. "Is there a performance overhead for Safety Features?"
|
|
14
|
+
|
|
15
|
+
**The Trade-off:** Capabilities like deep freezing (immutability) and sanitization do introduce a small computational cost (typically 1-2ms). We believe this is a worthwhile investment to prevent accidental state mutations and security vulnerabilities. For performance-critical scenarios (like high-frequency animations), these features can be selectively disabled.
|
|
16
|
+
|
|
17
|
+
## 4. "How is Type Safety handled with string keys?"
|
|
18
|
+
|
|
19
|
+
**The Approach:** String keys provide the flexibility needed for dynamic and runtime-generated state namespaces. For developers requiring strict type safety, RGS offers the `gstate` factory and `GStatePlugins` augmentation, allowing you to define a fully typed interface for your store and its plugins.
|
|
20
|
+
|
|
21
|
+
## 5. "What logic dictates the use of 'Ghost Stores'?"
|
|
22
|
+
|
|
23
|
+
**Operational Resilience:** Many applications experience race conditions during hydration or initialization. Instead of allowing the application to crash due to an uninitialized reference, RGS returns a protective Proxy. This Proxy logs a developer warning while providing a safe fallback, ensuring the user interface remains functional while the developer addresses the initialization sequence.
|
|
24
|
+
|
|
25
|
+
## 6. "Is it compatible with modern React patterns (SSR/Next.js)?"
|
|
26
|
+
|
|
27
|
+
**Yes.** RGS is built on top of `useSyncExternalStore` and is fully compatible with Concurrent Rendering and Server-Side Rendering (SSR). It works seamlessly with Next.js, Remix, and other modern frameworks without hydration mismatch issues.
|
|
28
|
+
|
|
29
|
+
## 7. "Where do the best practices and improvements come from?"
|
|
30
|
+
|
|
31
|
+
**The Process:** All improvements and best practices are based on:
|
|
32
|
+
|
|
33
|
+
- **Official React Documentation** - useSyncExternalStore for SSR, hooks rules, React 18/19 features
|
|
34
|
+
- **TypeScript Best Practices** - Type safety patterns, generics, strict mode
|
|
35
|
+
- **Security Standards** - OWASP for XSS prevention, AES-256-GCM encryption, RBAC patterns
|
|
36
|
+
- **Community Libraries** - Patterns from Zustand, Redux, Jotai for plugin architecture
|
|
37
|
+
- **Enterprise Patterns** - Error handling, multi-store isolation, GDPR compliance
|
|
38
|
+
|
|
39
|
+
Key fixes (like security isolation per-store, Immer optional loading) come from common issues in similar libraries.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🛑 Best Practices: Maximizing Reliability
|
|
44
|
+
|
|
45
|
+
1. **State Granularity**: Use RGS for global, persistent, or secured data. For transient UI state (like toggle transitions), standard `useState` is more appropriate.
|
|
46
|
+
2. **Namespace Management**: Always define a unique namespace for your store to prevent data collisions in shared domain environments.
|
|
47
|
+
3. **Rule Validation**: Ensure your RBAC rules are tested against your expected key patterns to maintain a robust security posture.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 👋 Conclusion
|
|
52
|
+
|
|
53
|
+
RGS is designed for teams that prioritize long-term maintainability and system stability. By handling the complexities of security and persistence at the architectural level, we allow developers to focus on building features with confidence.
|