memorio 4.7.3 → 4.9.5
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 +242 -331
- package/SUMMARY.md +3 -0
- package/index.cjs +87 -147
- package/index.d.ts +1 -0
- package/index.js +46 -100
- package/llms.txt +1 -1
- package/package.json +9 -3
- package/types/env.d.ts +9 -0
- package/types/exports.d.ts +52 -1
- package/types/memorio.d.ts +43 -10
- package/types/memory.d.ts +95 -0
- package/types/schema.d.ts +53 -53
- package/types/sqlite.d.ts +35 -0
- package/markdown/CACHE.md +0 -90
- package/markdown/CHANGELOG.md +0 -161
- package/markdown/DEVTOOLS.md +0 -122
- package/markdown/DISPATCH.md +0 -168
- package/markdown/HISTORY.md +0 -192
- package/markdown/IDB.md +0 -169
- package/markdown/IMPORT.md +0 -139
- package/markdown/INSPECT.md +0 -116
- package/markdown/LOGGER.md +0 -147
- package/markdown/MEMORY.md +0 -155
- package/markdown/OBSERVER.md +0 -200
- package/markdown/PLATFORM.md +0 -265
- package/markdown/SCHEMA.md +0 -169
- package/markdown/SECURITY.md +0 -323
- package/markdown/SESSION.md +0 -154
- package/markdown/STATE.md +0 -153
- package/markdown/STORE.md +0 -164
- package/markdown/TYPED.md +0 -158
- package/markdown/USEOBSERVER.md +0 -259
package/markdown/OBSERVER.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
# Observer - Memorio
|
|
2
|
-
|
|
3
|
-
Observer lets you react to state changes. When a state key changes, your callback function runs.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install memorio
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```javascript
|
|
12
|
-
import 'memorio';
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Quick Examples
|
|
18
|
-
|
|
19
|
-
### Example 1: Basic Usage
|
|
20
|
-
|
|
21
|
-
```javascript
|
|
22
|
-
observer('state.counter', (newValue) => {
|
|
23
|
-
console.debug('Counter is now:', newValue);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
state.counter = 1;
|
|
27
|
-
// Output: "Counter is now: 1"
|
|
28
|
-
|
|
29
|
-
state.counter = 5;
|
|
30
|
-
// Output: "Counter is now: 5"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Example 2: Intermediate
|
|
34
|
-
|
|
35
|
-
```javascript
|
|
36
|
-
// Observer with old value
|
|
37
|
-
observer('state.user', (newValue, oldValue) => {
|
|
38
|
-
console.debug(`User changed from ${oldValue?.name} to ${newValue?.name}`);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
state.user = { name: 'Mario' };
|
|
42
|
-
// Output: "User changed from undefined to Mario"
|
|
43
|
-
|
|
44
|
-
state.user = { name: 'Luigi' };
|
|
45
|
-
// Output: "User changed from Mario to Luigi"
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Example 3: Advanced
|
|
49
|
-
|
|
50
|
-
```javascript
|
|
51
|
-
// Multiple callbacks for same path
|
|
52
|
-
observer('state.data', [handler1, handler2]);
|
|
53
|
-
|
|
54
|
-
// List all observers
|
|
55
|
-
console.debug(observer.list);
|
|
56
|
-
// Output: [{ name: 'state.data', id: '...' }, ...]
|
|
57
|
-
|
|
58
|
-
// Remove specific observer
|
|
59
|
-
observer.remove('state.data');
|
|
60
|
-
|
|
61
|
-
// Remove all observers
|
|
62
|
-
observer.removeAll();
|
|
63
|
-
|
|
64
|
-
// Check if observer exists
|
|
65
|
-
if (observer.has('state.counter')) {
|
|
66
|
-
console.debug('Observer exists for counter');
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
## Direct Values (Vanilla JS)
|
|
73
|
-
|
|
74
|
-
Observer supports direct state values, not just string paths:
|
|
75
|
-
|
|
76
|
-
```javascript
|
|
77
|
-
// Direct value - no string needed!
|
|
78
|
-
observer(state.counter, (newValue) => {
|
|
79
|
-
console.debug('Counter:', newValue);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Works with objects too
|
|
83
|
-
observer(state.user, (newValue) => {
|
|
84
|
-
console.debug('User:', newValue?.name);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
// Multiple callbacks as array
|
|
88
|
-
observer('state.data', [cb1, cb2, cb3]);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
---
|
|
92
|
-
|
|
93
|
-
## API Reference
|
|
94
|
-
|
|
95
|
-
### observer(path, callback, option)
|
|
96
|
-
|
|
97
|
-
| Parameter | Type | Description |
|
|
98
|
-
|-----------|------|-------------|
|
|
99
|
-
| `path` | `string \| object` | State path (e.g., `'state.counter'`) or direct state value |
|
|
100
|
-
| `callback` | `function \| array` | Function(s) called on change. Can be a single function or array of functions |
|
|
101
|
-
| `option` | `boolean` | Listen continuously (default: `true`) |
|
|
102
|
-
|
|
103
|
-
### Callback Parameters
|
|
104
|
-
|
|
105
|
-
```javascript
|
|
106
|
-
observer('state.key', (newValue, oldValue) => {
|
|
107
|
-
// newValue: the new value
|
|
108
|
-
// oldValue: the previous value
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Properties
|
|
113
|
-
|
|
114
|
-
| Property | Type | Description |
|
|
115
|
-
|----------|------|-------------|
|
|
116
|
-
| `observer.list` | `Array` | Get all active observers |
|
|
117
|
-
|
|
118
|
-
### Methods
|
|
119
|
-
|
|
120
|
-
| Method | Parameters | Description |
|
|
121
|
-
|--------|------------|-------------|
|
|
122
|
-
| `observer.remove(name)` | `string` | Remove observer for specific path |
|
|
123
|
-
| `observer.removeAll()` | none | Remove all observers |
|
|
124
|
-
| `observer.has(name)` | `string` | Check if observer exists for path (returns boolean) |
|
|
125
|
-
|
|
126
|
-
---
|
|
127
|
-
|
|
128
|
-
## React Integration
|
|
129
|
-
|
|
130
|
-
### With useState
|
|
131
|
-
|
|
132
|
-
```javascript
|
|
133
|
-
const [count, setCount] = useState(0);
|
|
134
|
-
|
|
135
|
-
observer('state.counter', () => {
|
|
136
|
-
setCount(state.counter);
|
|
137
|
-
});
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### With useEffect
|
|
141
|
-
|
|
142
|
-
```javascript
|
|
143
|
-
useEffect(() => {
|
|
144
|
-
const handleChange = (newVal) => {
|
|
145
|
-
console.debug('Changed:', newVal);
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
observer('state.data', handleChange);
|
|
149
|
-
|
|
150
|
-
// Cleanup
|
|
151
|
-
return () => observer.remove('state.data');
|
|
152
|
-
}, []);
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
## How It Works
|
|
158
|
-
|
|
159
|
-
Observer subscribes to state changes via the Proxy's set trap. When `state.key = value` is called:
|
|
160
|
-
1. The Proxy intercepts the set
|
|
161
|
-
2. Fires all callbacks registered for that path
|
|
162
|
-
3. Callbacks receive newValue and oldValue
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Best Practices
|
|
167
|
-
|
|
168
|
-
1. Clean up observers in React `useEffect` return
|
|
169
|
-
2. Use specific paths: `'state.user.name'` not `'state'`
|
|
170
|
-
3. Remove observers when components unmount
|
|
171
|
-
4. Use `observer.removeAll()` on page navigation
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## Common Patterns
|
|
176
|
-
|
|
177
|
-
### Form Validation
|
|
178
|
-
|
|
179
|
-
```javascript
|
|
180
|
-
observer('state.form.email', (email) => {
|
|
181
|
-
const isValid = email.includes('@');
|
|
182
|
-
state.form.isValid = isValid;
|
|
183
|
-
});
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
### Analytics
|
|
187
|
-
|
|
188
|
-
```javascript
|
|
189
|
-
observer('state.page', (page) => {
|
|
190
|
-
analytics.track('page_view', { page });
|
|
191
|
-
});
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
### Auto-save
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
observer('state.draft', (content) => {
|
|
198
|
-
store.set('autosave', content);
|
|
199
|
-
});
|
|
200
|
-
```
|
package/markdown/PLATFORM.md
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
# Platform & Context Isolation - Memorio
|
|
2
|
-
|
|
3
|
-
> ℹ️ **New in v2.7.0, expanded in v3.0.2**: Context isolation system for multi-tenant server-side applications
|
|
4
|
-
|
|
5
|
-
Memorio automatically detects the runtime environment and adapts its behavior accordingly. This document explains platform compatibility, session isolation, and the new context system.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Quick Reference: Client vs Server
|
|
10
|
-
|
|
11
|
-
### Which Module to Use?
|
|
12
|
-
|
|
13
|
-
| Scenario | Recommended Module | Persistence |
|
|
14
|
-
|----------|-------------------|-------------|
|
|
15
|
-
| UI State (React/components) | `state` | Memory |
|
|
16
|
-
| Temporary computed data | `cache` | Memory |
|
|
17
|
-
| User preferences | `store` | Browser localStorage |
|
|
18
|
-
| Auth tokens | `session` | Browser sessionStorage |
|
|
19
|
-
| Large offline data | `idb` | IndexedDB |
|
|
20
|
-
| Server request isolation | `memorio.createContext()` | Memory |
|
|
21
|
-
|
|
22
|
-
### Module Availability
|
|
23
|
-
|
|
24
|
-
| Module | Browser | Node.js | Deno | Edge |
|
|
25
|
-
|--------|--------|---------|------|------|
|
|
26
|
-
| `state` | ✅ | ✅ | ✅ | ✅ |
|
|
27
|
-
| `cache` | ✅ | ✅ | ✅ | ✅ |
|
|
28
|
-
| `store` | ✅ (localStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ |
|
|
29
|
-
| `session` | ✅ (sessionStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ |
|
|
30
|
-
| `idb` | ✅ | ❌ | ❌ | ⚠️ |
|
|
31
|
-
| `useObserver` | ✅ | ⚠️ | ⚠️ | ✅ |
|
|
32
|
-
| `devtools` | ✅ | ❌ | ❌ | ❌ |
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## Platform Detection
|
|
37
|
-
|
|
38
|
-
Memorio automatically detects the environment on import:
|
|
39
|
-
|
|
40
|
-
```javascript
|
|
41
|
-
import 'memorio';
|
|
42
|
-
|
|
43
|
-
// Check current platform
|
|
44
|
-
console.debug(memorio.getCapabilities().platform); // 'browser' | 'node' | 'deno' | 'edge'
|
|
45
|
-
console.debug(store.isPersistent); // true if using real localStorage
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
### Available Platform APIs
|
|
49
|
-
|
|
50
|
-
```javascript
|
|
51
|
-
// Check platform
|
|
52
|
-
memorio.isBrowser() // true in browser
|
|
53
|
-
memorio.isNode() // true in Node.js
|
|
54
|
-
memorio.isDeno() // true in Deno
|
|
55
|
-
memorio.isEdge() // true in Edge Workers
|
|
56
|
-
|
|
57
|
-
// Get capabilities
|
|
58
|
-
const caps = memorio.getCapabilities();
|
|
59
|
-
// caps.platform, caps.hasSessionStorage, caps.hasLocalStorage, etc.
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
---
|
|
63
|
-
|
|
64
|
-
## Platform Compatibility Matrix
|
|
65
|
-
|
|
66
|
-
| Feature | Browser | Node.js | Deno | Edge Workers |
|
|
67
|
-
|---------|---------|---------|------|--------------|
|
|
68
|
-
| `state` | ✅ | ✅ | ✅ | ✅ |
|
|
69
|
-
| `observer` | ✅ | ✅ | ✅ | ✅ |
|
|
70
|
-
| `useObserver` | ✅ | ⚠️ React only | ⚠️ React only | ✅ |
|
|
71
|
-
| `cache` | ✅ | ✅ | ✅ | ✅ |
|
|
72
|
-
| `store` | ✅ (localStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ (localStorage) |
|
|
73
|
-
| `session` | ✅ (sessionStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ (sessionStorage) |
|
|
74
|
-
| `idb` | ✅ | ❌ | ❌ | ⚠️ |
|
|
75
|
-
|
|
76
|
-
- ✅ Full support
|
|
77
|
-
- ⚠️ Partial support (fallback to in-memory)
|
|
78
|
-
- ❌ Not available
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Client vs Server Usage
|
|
83
|
-
|
|
84
|
-
### 🖥️ Client-Side (Browser)
|
|
85
|
-
|
|
86
|
-
All features work with real browser storage:
|
|
87
|
-
|
|
88
|
-
```javascript
|
|
89
|
-
// Store - persistent localStorage
|
|
90
|
-
store.set('preferences', { theme: 'dark' });
|
|
91
|
-
store.isPersistent; // true
|
|
92
|
-
|
|
93
|
-
// Session - temporary sessionStorage
|
|
94
|
-
session.set('token', 'jwt-token');
|
|
95
|
-
session.isPersistent; // true (survives refresh)
|
|
96
|
-
|
|
97
|
-
// IDB - large data storage
|
|
98
|
-
idb.db.create('myApp');
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### 🖥️ Server-Side (Node.js/Deno)
|
|
102
|
-
|
|
103
|
-
Use `state` and `cache` for in-memory data. Store/session fall back to memory:
|
|
104
|
-
|
|
105
|
-
```javascript
|
|
106
|
-
// State - in-memory global state
|
|
107
|
-
state.user = { name: 'Server User' };
|
|
108
|
-
|
|
109
|
-
// Cache - in-memory temporary cache
|
|
110
|
-
cache.set('apiResponse', data);
|
|
111
|
-
|
|
112
|
-
// Store - in-memory fallback (not persistent)
|
|
113
|
-
store.set('temp', data);
|
|
114
|
-
store.isPersistent; // false - data lost on restart
|
|
115
|
-
|
|
116
|
-
// Session - in-memory fallback
|
|
117
|
-
session.set('requestData', data);
|
|
118
|
-
session.isPersistent; // false - data lost on restart
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
---
|
|
122
|
-
|
|
123
|
-
## Session Isolation
|
|
124
|
-
|
|
125
|
-
Each instance/session gets unique storage keys to prevent conflicts:
|
|
126
|
-
|
|
127
|
-
```javascript
|
|
128
|
-
// Without context: "memorio_store_[sessionId]_key"
|
|
129
|
-
// With context: "[contextName]-key"
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
This ensures:
|
|
133
|
-
- Multiple browser tabs don't share session data
|
|
134
|
-
- Server-side requests are isolated
|
|
135
|
-
|
|
136
|
-
---
|
|
137
|
-
|
|
138
|
-
## Context Isolation (Server-Side Multi-Tenancy)
|
|
139
|
-
|
|
140
|
-
> ⚠️ **Server-Side Only**: This feature is designed for multi-tenant server environments (Node.js, Deno). Not needed for client-side applications.
|
|
141
|
-
|
|
142
|
-
For server-side applications handling multiple tenants (e.g., different users/requests), use **contexts** to isolate data:
|
|
143
|
-
|
|
144
|
-
### Creating a Context
|
|
145
|
-
|
|
146
|
-
```javascript
|
|
147
|
-
// Create isolated context for a user/session
|
|
148
|
-
const ctx = memorio.isolate('user-123');
|
|
149
|
-
|
|
150
|
-
// Keys in store/session are prefixed with context name
|
|
151
|
-
// store: "user-123-key"
|
|
152
|
-
// session: "user-123-key"
|
|
153
|
-
|
|
154
|
-
// Use context's isolated storage
|
|
155
|
-
ctx.state.user = { name: 'Isolated User' };
|
|
156
|
-
ctx.store.set('settings', { theme: 'dark' });
|
|
157
|
-
ctx.session.set('token', 'abc123');
|
|
158
|
-
ctx.cache.set('temp', data);
|
|
159
|
-
|
|
160
|
-
// Context is completely isolated from global state
|
|
161
|
-
console.debug(state.user); // undefined - global state is separate
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### Managing Contexts
|
|
165
|
-
|
|
166
|
-
```javascript
|
|
167
|
-
// List all contexts
|
|
168
|
-
const contexts = memorio.listContexts();
|
|
169
|
-
console.debug(contexts); // ['user-123', 'user-456', ...]
|
|
170
|
-
|
|
171
|
-
// Delete a context (cleanup)
|
|
172
|
-
memorio.deleteContext('user-123');
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
> **Note**: `memorio.isolate('name')` is a shorthand alias for creating isolated contexts.
|
|
176
|
-
|
|
177
|
-
### Context Use Cases
|
|
178
|
-
|
|
179
|
-
#### 1. Per-Request Isolation (Express/Fastify)
|
|
180
|
-
|
|
181
|
-
```javascript
|
|
182
|
-
// Middleware to isolate each request
|
|
183
|
-
app.use((req, res, next) => {
|
|
184
|
-
const ctx = memorio.createContext(`req-${req.id}`);
|
|
185
|
-
req.memorioContext = ctx;
|
|
186
|
-
next();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
// In route handler
|
|
190
|
-
app.get('/user', (req, res) => {
|
|
191
|
-
const ctx = req.memorioContext;
|
|
192
|
-
ctx.state.user = getUserData();
|
|
193
|
-
// Each request has isolated state
|
|
194
|
-
});
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
#### 2. Multi-Tenant SaaS
|
|
198
|
-
|
|
199
|
-
```javascript
|
|
200
|
-
// Each tenant gets isolated storage
|
|
201
|
-
function handleTenant(tenantId) {
|
|
202
|
-
const ctx = memorio.createContext(tenantId);
|
|
203
|
-
|
|
204
|
-
ctx.state.config = getTenantConfig(tenantId);
|
|
205
|
-
ctx.store.set('data', tenantData);
|
|
206
|
-
|
|
207
|
-
return ctx;
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
## Best Practices
|
|
214
|
-
|
|
215
|
-
### Client-Side (Browser)
|
|
216
|
-
|
|
217
|
-
1. Use `store` for persistent data (preferences, user settings)
|
|
218
|
-
2. Use `session` for temporary data (auth tokens)
|
|
219
|
-
3. Use `cache` for computed values
|
|
220
|
-
4. Use `state` for reactive UI state
|
|
221
|
-
|
|
222
|
-
### Server-Side (Node.js/Deno)
|
|
223
|
-
|
|
224
|
-
1. Use `memorio.createContext()` for each request/tenant
|
|
225
|
-
2. Don't use global `state`/`store`/`session` across requests
|
|
226
|
-
3. Use `cache` for request-scoped caching
|
|
227
|
-
4. Check `store.isPersistent` / `session.isPersistent` if persistence matters
|
|
228
|
-
|
|
229
|
-
### Edge Workers
|
|
230
|
-
|
|
231
|
-
Same as browser - localStorage and sessionStorage are available.
|
|
232
|
-
|
|
233
|
-
---
|
|
234
|
-
|
|
235
|
-
## API Reference
|
|
236
|
-
|
|
237
|
-
### Global Functions
|
|
238
|
-
|
|
239
|
-
| Function | Returns | Description |
|
|
240
|
-
|----------|---------|-------------|
|
|
241
|
-
| `memorio.isBrowser()` | `boolean` | Check if running in browser |
|
|
242
|
-
| `memorio.isNode()` | `boolean` | Check if running in Node.js |
|
|
243
|
-
| `memorio.isDeno()` | `boolean` | Check if running in Deno |
|
|
244
|
-
| `memorio.isEdge()` | `boolean` | Check if running in Edge |
|
|
245
|
-
| `memorio.getCapabilities()` | `object` | Get platform capabilities |
|
|
246
|
-
|
|
247
|
-
### Context Management
|
|
248
|
-
|
|
249
|
-
| Function | Returns | Description |
|
|
250
|
-
|----------|---------|-------------|
|
|
251
|
-
| `memorio.isolate(name?)` | `Context` | Create isolated context |
|
|
252
|
-
| `memorio.listContexts()` | `string[]` | List all context IDs |
|
|
253
|
-
| `memorio.deleteContext(id)` | `boolean` | Delete a context |
|
|
254
|
-
|
|
255
|
-
### Properties
|
|
256
|
-
|
|
257
|
-
| Property | Type | Description |
|
|
258
|
-
|----------|------|-------------|
|
|
259
|
-
| `memorio.version` | `string` | Memorio version |
|
|
260
|
-
| `memorio.getCapabilities().platform` | `string` | Current platform |
|
|
261
|
-
| `memorio.isBrowser()` / `isNode()` / `isDeno()` / `isEdge()` | `boolean` | Platform checks |
|
|
262
|
-
| `memorio._sessionId` | `string` | Unique session identifier |
|
|
263
|
-
|
|
264
|
-
> **Classic `import`**: context APIs are also named exports.
|
|
265
|
-
> `import { createContext, listContexts, deleteContext, isolate } from 'memorio'`.
|
package/markdown/SCHEMA.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# Schema Validation - Memorio
|
|
2
|
-
|
|
3
|
-
> ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
|
|
4
|
-
|
|
5
|
-
Schema validation guards your `state` against invalid writes. It runs inside the state proxy's `set` trap, so any `state.somePath = value` that violates a registered schema is rejected at runtime — before the value is ever stored.
|
|
6
|
-
|
|
7
|
-
Schema validation is **opt-in** and **zero-dependency**.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Quick Start
|
|
12
|
-
|
|
13
|
-
```javascript
|
|
14
|
-
import 'memorio'
|
|
15
|
-
|
|
16
|
-
// Register a validator for a top-level state key
|
|
17
|
-
memorio.registerSchema('user', {
|
|
18
|
-
type: 'object',
|
|
19
|
-
required: ['name', 'email'],
|
|
20
|
-
properties: {
|
|
21
|
-
name: { type: 'string', min: 1 },
|
|
22
|
-
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
23
|
-
age: { type: 'number', min: 0, max: 150 }
|
|
24
|
-
}
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
// Valid write — accepted
|
|
28
|
-
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 }
|
|
29
|
-
|
|
30
|
-
// Invalid write — rejected, returns false
|
|
31
|
-
state.user = { name: 'Sara' } // missing 'email'
|
|
32
|
-
state.user = { name: 42, email: 'x' } // wrong type for 'name'
|
|
33
|
-
state.user = { age: -5 } // out of range
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Schema Definition
|
|
39
|
-
|
|
40
|
-
A `Schema` object supports the following fields:
|
|
41
|
-
|
|
42
|
-
| Field | Type | Description |
|
|
43
|
-
|-------|------|-------------|
|
|
44
|
-
| `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array' \| 'any'` | Runtime type check |
|
|
45
|
-
| `required` | `string[]` | Property names that must exist (objects only) |
|
|
46
|
-
| `properties` | `Record<string, Schema>` | Nested property schemas (validated recursively) |
|
|
47
|
-
| `min` | `number` | Number: minimum value. String: minimum length |
|
|
48
|
-
| `max` | `number` | Number: maximum value. String: maximum length |
|
|
49
|
-
| `pattern` | `RegExp` | Regex the string value must match |
|
|
50
|
-
| `enum` | `any[]` | Whitelist of allowed values |
|
|
51
|
-
| `validator` | `(value) => boolean \| string` | Custom validator function |
|
|
52
|
-
|
|
53
|
-
### Custom validator functions
|
|
54
|
-
|
|
55
|
-
For logic that's hard to express declaratively, pass a function instead of a schema object:
|
|
56
|
-
|
|
57
|
-
```javascript
|
|
58
|
-
memorio.registerSchema('counter', (value) => {
|
|
59
|
-
if (typeof value !== 'number') return 'counter must be a number'
|
|
60
|
-
if (value < 0) return 'counter must be >= 0'
|
|
61
|
-
return true
|
|
62
|
-
})
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
A custom validator receives the raw value. Return `true` to accept, or a **string** describing the error to reject.
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Path-based registration
|
|
70
|
-
|
|
71
|
-
Schemas are keyed by their **state path**, relative to `state`:
|
|
72
|
-
|
|
73
|
-
| API call | Catches |
|
|
74
|
-
|----------|---------|
|
|
75
|
-
| `registerSchema('user', schema)` | `state.user = value` |
|
|
76
|
-
| `registerSchema('user.age', schema)` | `state.user.age = value` |
|
|
77
|
-
| `registerSchema('items', schema)` | `state.items = value` |
|
|
78
|
-
|
|
79
|
-
The full dotted path is constructed from the proxy's tree depth. Nested sets propagate the full path automatically.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Manual validation
|
|
84
|
-
|
|
85
|
-
You can validate a value without writing it to state:
|
|
86
|
-
|
|
87
|
-
```javascript
|
|
88
|
-
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
89
|
-
// { valid: true }
|
|
90
|
-
|
|
91
|
-
memorio.validate('user', { name: 'Sara' })
|
|
92
|
-
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
When no schema is registered for a path, `validate` returns `{ valid: true }`.
|
|
96
|
-
|
|
97
|
-
---
|
|
98
|
-
|
|
99
|
-
## Schema management
|
|
100
|
-
|
|
101
|
-
```javascript
|
|
102
|
-
memorio.listSchemas() // ['user', 'theme', 'items', 'counter']
|
|
103
|
-
memorio.unregisterSchema('counter') // removes the schema
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Full API
|
|
109
|
-
|
|
110
|
-
| Method | Parameters | Returns | Description |
|
|
111
|
-
|--------|-----------|---------|-------------|
|
|
112
|
-
| `memorio.registerSchema(path, schema)` | `string`, `Schema \| fn` | `void` | Register a validator |
|
|
113
|
-
| `memorio.validate(path, value)` | `string`, `any` | `{ valid, errors? }` | Manually validate a value |
|
|
114
|
-
| `memorio.unregisterSchema(path)` | `string` | `boolean` | Remove a registered schema |
|
|
115
|
-
| `memorio.listSchemas()` | none | `string[]` | List all registered paths |
|
|
116
|
-
| `memorio.registerSchema()` is also importable | `registerSchema` | named export | same function |
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Combine with Typed Stores
|
|
121
|
-
|
|
122
|
-
Schema validation gives you **runtime** safety; typed stores give you **compile-time** safety. Use both for full coverage:
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
import 'memorio'
|
|
126
|
-
|
|
127
|
-
interface AppState {
|
|
128
|
-
user: { name: string; email: string; age: number }
|
|
129
|
-
theme: 'light' | 'dark'
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const app = memorio.typed<AppState>()
|
|
133
|
-
|
|
134
|
-
memorio.registerSchema('user', {
|
|
135
|
-
type: 'object',
|
|
136
|
-
required: ['name', 'email'],
|
|
137
|
-
properties: {
|
|
138
|
-
name: { type: 'string', min: 1 },
|
|
139
|
-
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
140
|
-
age: { type: 'number', min: 0, max: 150 }
|
|
141
|
-
}
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
app.user = { name: '', email: 'bad' } // ❌ TypeScript: age missing
|
|
145
|
-
// ❌ Runtime: missing required fields
|
|
146
|
-
app.user = { name: 'Sara', email: 'ok', age: 30 } // ✅ both checks pass
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
See [Typed Stores](TYPED.md) for compile-time type safety.
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## How it works
|
|
154
|
-
|
|
155
|
-
1. When you call `registerSchema(path, schema)`, the schema is stored in an internal `Map`.
|
|
156
|
-
2. On every `state.set` operation, the proxy's `set` trap computes the full path (e.g. `'user.name'`).
|
|
157
|
-
3. If a schema is registered for that path, the value is validated.
|
|
158
|
-
4. If validation fails, the write is rejected (`return false`), and an error is logged via `console.error` (when `memorio.debug = true`) or `console.debug` (via the internal `message` helper).
|
|
159
|
-
5. If no schema is registered, the write proceeds normally.
|
|
160
|
-
|
|
161
|
-
The validation adds negligible overhead when no schemas are registered (a single `Map` lookup that returns `undefined`).
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## Limitations
|
|
166
|
-
|
|
167
|
-
- Schema validation hooks into the `state` proxy only. `store`, `session`, and `cache` are not validated (they use separate storage). Use `validate()` before writing to other modules.
|
|
168
|
-
- Path matching is **exact**: `registerSchema('user')` guards `state.user = ...`, but does **not** recursively validate `state.user.name = 'new'`. Register schemas at each path you need to guard.
|
|
169
|
-
- The schema system is not a replacement for server-side validation. It protects against accidental misuse and provides defense-in-depth in the browser.
|