@biglogic/rgs 3.8.0 → 3.8.4
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 +313 -300
- package/docs/README.md +313 -300
- package/docs/SUMMARY.md +10 -19
- package/index.js +1868 -43
- package/package.json +2 -2
- package/docs/api.md +0 -381
- package/docs/chapters/05-plugin-sdk.md +0 -290
- package/docs/chapters/05-plugins-and-extensibility.md +0 -190
- package/docs/qa.md +0 -47
- /package/docs/chapters/{06-case-studies.md → case-studies.md} +0 -0
- /package/docs/chapters/{07-faq.md → faq.md} +0 -0
- /package/docs/chapters/{02-getting-started.md → getting-started.md} +0 -0
- /package/docs/chapters/{10-local-first-sync.md → local-first-sync.md} +0 -0
- /package/docs/chapters/{08-migration-guide.md → migration-guide.md} +0 -0
- /package/docs/chapters/{04-persistence-and-safety.md → persistence-and-safety.md} +0 -0
- /package/docs/chapters/{01-philosophy.md → philosophy.md} +0 -0
- /package/docs/chapters/{09-security-architecture.md → security-architecture.md} +0 -0
- /package/docs/chapters/{03-the-magnetar-way.md → the-magnetar-way.md} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@biglogic/rgs",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Argis (RGS) - Reactive Global State: A react state everywhere made easy",
|
|
6
6
|
"type": "module",
|
|
@@ -86,4 +86,4 @@
|
|
|
86
86
|
"tslib": "^2.8.1",
|
|
87
87
|
"typescript": "^5.9.3"
|
|
88
88
|
}
|
|
89
|
-
}
|
|
89
|
+
}
|
package/docs/api.md
DELETED
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
# 📚 API Reference
|
|
2
|
-
|
|
3
|
-
Complete API reference for RGS (Argis) - Reactive Global State.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Core Functions
|
|
8
|
-
|
|
9
|
-
### `initState`
|
|
10
|
-
|
|
11
|
-
Initializes a global store instance.
|
|
12
|
-
|
|
13
|
-
```typescript
|
|
14
|
-
function initState<S extends Record<string, unknown>>(
|
|
15
|
-
config?: StoreConfig<S>
|
|
16
|
-
): IStore<S>
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
**Parameters:**
|
|
20
|
-
- `config` - Optional store configuration
|
|
21
|
-
|
|
22
|
-
**Returns:** `IStore<S>`
|
|
23
|
-
|
|
24
|
-
**Example:**
|
|
25
|
-
```typescript
|
|
26
|
-
const store = initState({
|
|
27
|
-
namespace: 'myApp',
|
|
28
|
-
version: 1,
|
|
29
|
-
persistByDefault: true,
|
|
30
|
-
onError: (error, context) => {
|
|
31
|
-
console.error(`Error in ${context.operation}:`, error.message)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
### `useStore`
|
|
39
|
-
|
|
40
|
-
React hook for reactive state.
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
function useStore<T = unknown, S extends Record<string, unknown> = Record<string, unknown>>(
|
|
44
|
-
key: string,
|
|
45
|
-
store?: IStore<S>
|
|
46
|
-
): readonly [T | undefined, (val: T | StateUpdater<T>, options?: PersistOptions) => boolean]
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
**Parameters:**
|
|
50
|
-
- `key` - State key to subscribe to
|
|
51
|
-
- `store` - Optional store instance
|
|
52
|
-
|
|
53
|
-
**Returns:** Tuple of `[value, setter]`
|
|
54
|
-
|
|
55
|
-
---
|
|
56
|
-
|
|
57
|
-
### `createStore`
|
|
58
|
-
|
|
59
|
-
Creates a new store instance.
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
function createStore<S extends Record<string, unknown>>(
|
|
63
|
-
config?: StoreConfig<S>
|
|
64
|
-
): IStore<S>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
### `getStore`
|
|
70
|
-
|
|
71
|
-
Retrieves the currently active default store instance. Useful for accessing the store outside of React components or in utility functions.
|
|
72
|
-
|
|
73
|
-
```typescript
|
|
74
|
-
function getStore(): IStore<Record<string, unknown>> | null
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
**Returns:** The active `IStore` or `null` if no store was initialized via `initState`.
|
|
78
|
-
|
|
79
|
-
---
|
|
80
|
-
|
|
81
|
-
## Store Interface (`IStore`)
|
|
82
|
-
|
|
83
|
-
### State Operations
|
|
84
|
-
|
|
85
|
-
#### `set`
|
|
86
|
-
|
|
87
|
-
Sets a value in the store.
|
|
88
|
-
|
|
89
|
-
```typescript
|
|
90
|
-
store.set<T>(key: string, value: T | StateUpdater<T>, options?: PersistOptions): boolean
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
#### `get`
|
|
94
|
-
|
|
95
|
-
Gets a value from the store.
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
store.get<T>(key: string): T | null
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
#### `remove` / `delete`
|
|
102
|
-
|
|
103
|
-
Removes a value from the store.
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
store.remove(key: string): boolean
|
|
107
|
-
store.delete(key: string): boolean
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
#### `deleteAll`
|
|
111
|
-
|
|
112
|
-
Removes all values from the store.
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
store.deleteAll(): void
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
#### `list`
|
|
119
|
-
|
|
120
|
-
Returns all key-value pairs.
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
store.list(): Record<string, unknown>
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
### Metadata Properties
|
|
129
|
-
|
|
130
|
-
#### `namespace`
|
|
131
|
-
|
|
132
|
-
The unique namespace of the store (read-only).
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
store.namespace: string
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### `userId`
|
|
139
|
-
|
|
140
|
-
The current user ID associated with the store for RBAC and audit logs (read-only).
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
store.userId?: string
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
### Computed Values
|
|
149
|
-
|
|
150
|
-
#### `compute`
|
|
151
|
-
|
|
152
|
-
Creates or retrieves a computed (derived) value.
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
store.compute<T>(key: string, selector: ComputedSelector<T>): T
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**Example:**
|
|
159
|
-
```typescript
|
|
160
|
-
const fullName = store.compute('fullName', (get) => {
|
|
161
|
-
const first = get<string>('firstName')
|
|
162
|
-
const last = get<string>('lastName')
|
|
163
|
-
return `${first} ${last}`
|
|
164
|
-
})
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
> **Note:** RGS supports **nested computed dependencies**. A computed value can reactively depend on other computed values in the same store.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
### Watching Changes
|
|
172
|
-
|
|
173
|
-
#### `watch`
|
|
174
|
-
|
|
175
|
-
Watches for changes on a specific key.
|
|
176
|
-
|
|
177
|
-
```typescript
|
|
178
|
-
store.watch<T>(key: string, callback: WatcherCallback<T>): () => void
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
**Returns:** Unsubscribe function
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
### Transactions
|
|
186
|
-
|
|
187
|
-
#### `transaction`
|
|
188
|
-
|
|
189
|
-
Groups multiple operations into a single transaction.
|
|
190
|
-
|
|
191
|
-
```typescript
|
|
192
|
-
store.transaction(fn: () => void): void
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
---
|
|
196
|
-
|
|
197
|
-
### Middleware
|
|
198
|
-
|
|
199
|
-
#### `use`
|
|
200
|
-
|
|
201
|
-
Adds a middleware function.
|
|
202
|
-
|
|
203
|
-
```typescript
|
|
204
|
-
store.use(middleware: Middleware): void
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
### Lifecycle
|
|
210
|
-
|
|
211
|
-
#### `destroy`
|
|
212
|
-
|
|
213
|
-
Destroys the store and cleans up resources.
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
store.destroy(): void
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
---
|
|
220
|
-
|
|
221
|
-
## Plugin API
|
|
222
|
-
|
|
223
|
-
### `_addPlugin`
|
|
224
|
-
|
|
225
|
-
Adds a plugin to the store.
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
store._addPlugin(plugin: IPlugin): void
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### `_removePlugin`
|
|
232
|
-
|
|
233
|
-
Removes a plugin from the store.
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
store._removePlugin(name: string): void
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### `_registerMethod`
|
|
240
|
-
|
|
241
|
-
Registers a custom method on the store.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
// New signature (recommended)
|
|
245
|
-
store._registerMethod(pluginName: string, methodName: string, fn: (...args) => unknown): void
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
---
|
|
249
|
-
|
|
250
|
-
## Plugins Property
|
|
251
|
-
|
|
252
|
-
Access plugin methods via `store.plugins`:
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
store.plugins.undoRedo.undo()
|
|
256
|
-
store.plugins.undoRedo.redo()
|
|
257
|
-
store.plugins.counter.increment()
|
|
258
|
-
store.plugins.cloudSync.sync()
|
|
259
|
-
store.plugins.cloudSync.getStats()
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
---
|
|
263
|
-
|
|
264
|
-
## Configuration (`StoreConfig`)
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
interface StoreConfig<S> {
|
|
268
|
-
/** Unique namespace for this store */
|
|
269
|
-
namespace?: string
|
|
270
|
-
/** Schema version */
|
|
271
|
-
version?: number
|
|
272
|
-
/** Suppress console warnings */
|
|
273
|
-
silent?: boolean
|
|
274
|
-
/** Debounce time for disk flush (default: 150ms) */
|
|
275
|
-
debounceTime?: number
|
|
276
|
-
/** Custom storage adapter */
|
|
277
|
-
storage?: CustomStorage | Storage
|
|
278
|
-
/** Migration function */
|
|
279
|
-
migrate?: (oldState: Record<string, unknown>, oldVersion: number) => S
|
|
280
|
-
/** Error handler */
|
|
281
|
-
onError?: (error: Error, context: { operation: string; key?: string }) => void
|
|
282
|
-
/** Max object size in bytes (default: 5MB) */
|
|
283
|
-
maxObjectSize?: number
|
|
284
|
-
/** Max total store size in bytes (default: 50MB) */
|
|
285
|
-
maxTotalSize?: number
|
|
286
|
-
/** AES-256-GCM encryption key */
|
|
287
|
-
encryptionKey?: EncryptionKey
|
|
288
|
-
/** Enable audit logging */
|
|
289
|
-
auditEnabled?: boolean
|
|
290
|
-
/** Current user ID for audit */
|
|
291
|
-
userId?: string
|
|
292
|
-
/** Enable input validation */
|
|
293
|
-
validateInput?: boolean
|
|
294
|
-
/** Access control rules */
|
|
295
|
-
accessRules?: Array<{
|
|
296
|
-
pattern: string | ((key: string, userId?: string) => boolean)
|
|
297
|
-
permissions: Permission[]
|
|
298
|
-
}>
|
|
299
|
-
/** Enable Immer (default: true) */
|
|
300
|
-
immer?: boolean
|
|
301
|
-
}
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
## Persistence Options (`PersistOptions`)
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
interface PersistOptions {
|
|
310
|
-
/** Persist to storage (default: localStorage) */
|
|
311
|
-
persist?: boolean
|
|
312
|
-
/** Base64 encode the value */
|
|
313
|
-
encoded?: boolean
|
|
314
|
-
/** AES-256-GCM encryption */
|
|
315
|
-
encrypted?: boolean
|
|
316
|
-
/** Time-to-live in milliseconds */
|
|
317
|
-
ttl?: number
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
---
|
|
322
|
-
|
|
323
|
-
## Types
|
|
324
|
-
|
|
325
|
-
### `StateUpdater`
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
type StateUpdater<T> = (draft: T) => void | T
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### `ComputedSelector`
|
|
332
|
-
|
|
333
|
-
```typescript
|
|
334
|
-
type ComputedSelector<T> = (get: <V>(key: string) => V | null) => T
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
### `WatcherCallback`
|
|
338
|
-
|
|
339
|
-
```typescript
|
|
340
|
-
type WatcherCallback<T> = (value: T | null) => void
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### `Middleware`
|
|
344
|
-
|
|
345
|
-
```typescript
|
|
346
|
-
type Middleware<T = unknown> = (key: string, value: T, meta: StoreMetadata) => void
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
---
|
|
350
|
-
|
|
351
|
-
## Security Types
|
|
352
|
-
|
|
353
|
-
### `Permission`
|
|
354
|
-
|
|
355
|
-
```typescript
|
|
356
|
-
type Permission = 'read' | 'write' | 'delete' | 'admin'
|
|
357
|
-
```
|
|
358
|
-
|
|
359
|
-
### `AccessRule`
|
|
360
|
-
|
|
361
|
-
```typescript
|
|
362
|
-
interface AccessRule {
|
|
363
|
-
pattern: string | ((key: string, userId?: string) => boolean)
|
|
364
|
-
permissions: Permission[]
|
|
365
|
-
}
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
---
|
|
369
|
-
|
|
370
|
-
## Plugin Hooks
|
|
371
|
-
|
|
372
|
-
| Hook | Description |
|
|
373
|
-
|------|-------------|
|
|
374
|
-
| `onInit` | Called when plugin is first initialized |
|
|
375
|
-
| `onInstall` | Called when plugin is added to store |
|
|
376
|
-
| `onBeforeSet` | Called before a value is set |
|
|
377
|
-
| `onSet` | Called after a value is set |
|
|
378
|
-
| `onGet` | Called when a value is retrieved |
|
|
379
|
-
| `onRemove` | Called when a value is removed |
|
|
380
|
-
| `onDestroy` | Called when store is destroyed |
|
|
381
|
-
| `onTransaction` | Called during a transaction |
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
# 🔧 Plugin SDK: Build Your Own Extensions
|
|
2
|
-
|
|
3
|
-
This guide shows you how to create custom plugins for RGS (Argis) - Reactive Global 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 '@biglogic/rgs'
|
|
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 '@biglogic/rgs'
|
|
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 '@biglogic/rgs'
|
|
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 '@biglogic/rgs'
|
|
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', 'methodName', 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)
|