memorio 4.9.35 → 5.1.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.
Files changed (82) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +95 -516
  3. package/SECURITY.md +159 -33
  4. package/SUMMARY.md +59 -45
  5. package/adr/001-state-proxy-model.md +95 -0
  6. package/adr/002-observer-semantics.md +179 -0
  7. package/adr/003-deep-mutation-semantics.md +128 -0
  8. package/adr/004-array-mutation-semantics.md +127 -0
  9. package/adr/005-scheduler-contract.md +148 -0
  10. package/adr/006-context-isolation.md +91 -0
  11. package/adr/007-mutation-records.md +117 -0
  12. package/adr/008-transactions.md +105 -0
  13. package/adr/009-history-model.md +109 -0
  14. package/adr/README.md +46 -0
  15. package/adr/template.md +48 -0
  16. package/bin/cli.js +68 -0
  17. package/examples/basic.ts +115 -115
  18. package/examples/browser-vanilla.html +358 -358
  19. package/examples/cache.ts +72 -72
  20. package/examples/cross-platform-guards.ts +57 -57
  21. package/examples/history.ts +104 -0
  22. package/examples/idb.ts +109 -109
  23. package/examples/multi-tenant-context.ts +44 -44
  24. package/examples/node-server.ts +308 -308
  25. package/examples/observer.ts +60 -60
  26. package/examples/platform.ts +115 -115
  27. package/examples/react-app.tsx +362 -362
  28. package/examples/react-observer.tsx +63 -63
  29. package/examples/semantic-memory.ts +60 -60
  30. package/examples/session-advanced.ts +91 -91
  31. package/examples/sqlite-batched-writes.ts +57 -57
  32. package/examples/state-advanced.ts +89 -89
  33. package/examples/store-advanced.ts +117 -117
  34. package/examples/sync.ts +90 -0
  35. package/examples/typed-and-schema.ts +102 -100
  36. package/examples/useObserver.tsx +140 -141
  37. package/global.cjs +5733 -0
  38. package/global.d.ts +8 -0
  39. package/global.js +5667 -0
  40. package/index.cjs +2202 -1041
  41. package/index.d.ts +2 -0
  42. package/index.js +2178 -1040
  43. package/llms.txt +113 -8
  44. package/markdown/AUDIT-REPORT.md +134 -0
  45. package/markdown/CACHE.md +191 -0
  46. package/markdown/DEVTOOLS.md +128 -0
  47. package/markdown/DISPATCH.md +176 -0
  48. package/markdown/HISTORY.md +198 -0
  49. package/markdown/IDB.md +177 -0
  50. package/markdown/IMPORT.md +152 -0
  51. package/markdown/INSPECT.md +122 -0
  52. package/markdown/LOGGER.md +153 -0
  53. package/markdown/MEMORY-ATTACHMENT.md +95 -0
  54. package/markdown/MEMORY.md +161 -0
  55. package/markdown/OBSERVER.md +208 -0
  56. package/markdown/PLATFORM.md +277 -0
  57. package/markdown/REDUX.md +54 -0
  58. package/markdown/SCHEMA.md +175 -0
  59. package/markdown/SESSION.md +164 -0
  60. package/markdown/SQLITE.md +189 -0
  61. package/markdown/STATE.md +159 -0
  62. package/markdown/STORE.md +170 -0
  63. package/markdown/SYNC.md +318 -0
  64. package/markdown/TYPED.md +164 -0
  65. package/markdown/USEOBSERVER.md +256 -0
  66. package/modules/redux.cjs +701 -177
  67. package/modules/redux.cjs.map +1 -1
  68. package/modules/redux.js +701 -177
  69. package/modules/redux.js.map +1 -1
  70. package/package.json +26 -4
  71. package/types/broadcast.d.ts +61 -0
  72. package/types/computed.d.ts +96 -0
  73. package/types/encryption.d.ts +129 -0
  74. package/types/env.d.ts +19 -9
  75. package/types/exports.d.ts +29 -0
  76. package/types/history.d.ts +13 -1
  77. package/types/memorio.d.ts +25 -6
  78. package/types/mutation.d.ts +75 -0
  79. package/types/security.d.ts +67 -0
  80. package/types/session.d.ts +23 -5
  81. package/types/store.d.ts +19 -3
  82. package/vsix/memorio.vsix +0 -0
@@ -0,0 +1,208 @@
1
+ > **Status:** Published
2
+ > **Date:** 2026-09-12
3
+ > **Scope**: API Reference
4
+ > **Standard**: Memorio API Specification v5
5
+ >
6
+ ---
7
+ # Observer - Memorio
8
+
9
+ Observer lets you react to state changes. When a state key changes, your callback function runs.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install memorio
15
+ ```
16
+
17
+ ```javascript
18
+ import { observer } from 'memorio';
19
+ ```
20
+
21
+ > **Global API**: `import 'memorio/global'` is the opt-in entrypoint that also exposes `state`, `store`, `session`, `cache`, `idb`, `sqlite`, `observer`, and `useObserver` on `globalThis`.
22
+
23
+ ---
24
+
25
+ ## Quick Examples
26
+
27
+ ### Example 1: Basic Usage
28
+
29
+ ```javascript
30
+ observer('state.counter', (newValue) => {
31
+ console.debug('Counter is now:', newValue);
32
+ });
33
+
34
+ state.counter = 1;
35
+ // Output: "Counter is now: 1"
36
+
37
+ state.counter = 5;
38
+ // Output: "Counter is now: 5"
39
+ ```
40
+
41
+ ### Example 2: Intermediate
42
+
43
+ ```javascript
44
+ // Observer with old value
45
+ observer('state.user', (newValue, oldValue) => {
46
+ console.debug(`User changed from ${oldValue?.name} to ${newValue?.name}`);
47
+ });
48
+
49
+ state.user = { name: 'Mario' };
50
+ // Output: "User changed from undefined to Mario"
51
+
52
+ state.user = { name: 'Luigi' };
53
+ // Output: "User changed from Mario to Luigi"
54
+ ```
55
+
56
+ ### Example 3: Advanced
57
+
58
+ ```javascript
59
+ // Multiple callbacks for same path
60
+ observer('state.data', [handler1, handler2]);
61
+
62
+ // List all observers
63
+ console.debug(observer.list);
64
+ // Output: [{ name: 'state.data', id: '...' }, ...]
65
+
66
+ // Remove specific observer
67
+ observer.remove('state.data');
68
+
69
+ // Remove all observers
70
+ observer.removeAll();
71
+
72
+ // Check if observer exists
73
+ if (observer.has('state.counter')) {
74
+ console.debug('Observer exists for counter');
75
+ }
76
+ ```
77
+
78
+ ---
79
+
80
+ ## Direct Values (Vanilla JS)
81
+
82
+ Observer supports direct state values, not just string paths:
83
+
84
+ ```javascript
85
+ // Direct value - no string needed!
86
+ observer(state.counter, (newValue) => {
87
+ console.debug('Counter:', newValue);
88
+ });
89
+
90
+ // Works with objects too
91
+ observer(state.user, (newValue) => {
92
+ console.debug('User:', newValue?.name);
93
+ });
94
+
95
+ // Multiple callbacks as array
96
+ observer('state.data', [cb1, cb2, cb3]);
97
+ ```
98
+
99
+ ---
100
+
101
+ ## API Reference
102
+
103
+ ### observer(path, callback, option)
104
+
105
+ | Parameter | Type | Description |
106
+ |-----------|------|-------------|
107
+ | `path` | `string \| object` | State path (e.g., `'state.counter'`) or direct state value |
108
+ | `callback` | `function \| array` | Function(s) called on change. Can be a single function or array of functions |
109
+ | `option` | `boolean` | Listen continuously (default: `true`) |
110
+
111
+ ### Callback Parameters
112
+
113
+ ```javascript
114
+ observer('state.key', (newValue, oldValue) => {
115
+ // newValue: the new value
116
+ // oldValue: the previous value
117
+ });
118
+ ```
119
+
120
+ ### Properties
121
+
122
+ | Property | Type | Description |
123
+ |----------|------|-------------|
124
+ | `observer.list` | `Array` | Get all active observers |
125
+
126
+ ### Methods
127
+
128
+ | Method | Parameters | Description |
129
+ |--------|------------|-------------|
130
+ | `observer.remove(name)` | `string` | Remove observer for specific path |
131
+ | `observer.removeAll()` | none | Remove all observers |
132
+ | `observer.has(name)` | `string` | Check if observer exists for path (returns boolean) |
133
+
134
+ ---
135
+
136
+ ## React Integration
137
+
138
+ ### With useState
139
+
140
+ ```javascript
141
+ const [count, setCount] = useState(0);
142
+
143
+ observer('state.counter', () => {
144
+ setCount(state.counter);
145
+ });
146
+ ```
147
+
148
+ ### With useEffect
149
+
150
+ ```javascript
151
+ useEffect(() => {
152
+ const handleChange = (newVal) => {
153
+ console.debug('Changed:', newVal);
154
+ };
155
+
156
+ observer('state.data', handleChange);
157
+
158
+ // Cleanup
159
+ return () => observer.remove('state.data');
160
+ }, []);
161
+ ```
162
+
163
+ ---
164
+
165
+ ## How It Works
166
+
167
+ Observer subscribes to state changes via the Proxy's set trap. When `state.key = value` is called:
168
+ 1. The Proxy intercepts the set
169
+ 2. Fires all callbacks registered for that path
170
+ 3. Callbacks receive newValue and oldValue
171
+
172
+ ---
173
+
174
+ ## Best Practices
175
+
176
+ 1. Clean up observers in React `useEffect` return
177
+ 2. Use specific paths: `'state.user.name'` not `'state'`
178
+ 3. Remove observers when components unmount
179
+ 4. Use `observer.removeAll()` on page navigation
180
+
181
+ ---
182
+
183
+ ## Common Patterns
184
+
185
+ ### Form Validation
186
+
187
+ ```javascript
188
+ observer('state.form.email', (email) => {
189
+ const isValid = email.includes('@');
190
+ state.form.isValid = isValid;
191
+ });
192
+ ```
193
+
194
+ ### Analytics
195
+
196
+ ```javascript
197
+ observer('state.page', (page) => {
198
+ analytics.track('page_view', { page });
199
+ });
200
+ ```
201
+
202
+ ### Auto-save
203
+
204
+ ```javascript
205
+ observer('state.draft', (content) => {
206
+ store.set('autosave', content);
207
+ });
208
+ ```
@@ -0,0 +1,277 @@
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 | Bun | Deno | Edge |
25
+ |--------|--------|---------|-----|------|------|
26
+ | `state` | ✅ | ✅ | ✅ | ✅ | ✅ |
27
+ | `cache` | ✅ | ✅ | ✅ | ✅ | ✅ |
28
+ | `store` | ✅ (localStorage) | ⚠️ (memory) | ✅* | ⚠️ (memory) | ✅ |
29
+ | `session` | ✅ (sessionStorage) | ⚠️ (memory) | ⚠️ (memory) | ⚠️ (memory) | ✅ |
30
+ | `idb` | ✅ | ❌ | ❌ | ❌ | ⚠️ |
31
+ | `sqlite` | ✅ (sql.js) | ❌ | ✅ (native `bun:sqlite`) | ❌ | ⚠️ |
32
+ | `useObserver` | ✅ | ⚠️ | ⚠️ | ⚠️ | ✅ |
33
+ | `devtools` | ✅ | ❌ | ❌ | ❌ | ❌ |
34
+
35
+ *Bun: `store` uses `localStorage` when available (Bun 1.x+), falls back to memory otherwise.
36
+
37
+ > ⚠️ **Bun row added from project notes, not verified against source for this document.** Confirm against `core/platform.ts` before publishing.
38
+
39
+ ---
40
+
41
+ ## Platform Detection
42
+
43
+ Memorio automatically detects the environment on import:
44
+
45
+ ```javascript
46
+ import { memorio, store } from 'memorio';
47
+
48
+ // Check current platform
49
+ console.debug(memorio.getCapabilities().platform); // 'browser' | 'node' | 'bun' | 'deno' | 'edge'
50
+ console.debug(store.isPersistent); // true if using real localStorage
51
+ ```
52
+
53
+ ### Available Platform APIs
54
+
55
+ ```javascript
56
+ // Check platform
57
+ memorio.isBrowser() // true in browser
58
+ memorio.isNode() // true in Node.js
59
+ memorio.isBun() // true in Bun
60
+ memorio.isDeno() // true in Deno
61
+ memorio.isEdge() // true in Edge Workers
62
+
63
+ // Get capabilities
64
+ const caps = memorio.getCapabilities();
65
+ // caps.platform, caps.hasSessionStorage, caps.hasLocalStorage, caps.hasBunSqlite, etc.
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Platform Compatibility Matrix
71
+
72
+ | Feature | Browser | Node.js | Deno | Edge Workers |
73
+ |---------|---------|---------|------|--------------|
74
+ | `state` | ✅ | ✅ | ✅ | ✅ |
75
+ | `observer` | ✅ | ✅ | ✅ | ✅ |
76
+ | `useObserver` | ✅ | ⚠️ React only | ⚠️ React only | ✅ |
77
+ | `cache` | ✅ | ✅ | ✅ | ✅ |
78
+ | `store` | ✅ (localStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ (localStorage) |
79
+ | `session` | ✅ (sessionStorage) | ⚠️ (memory) | ⚠️ (memory) | ✅ (sessionStorage) |
80
+ | `idb` | ✅ | ❌ | ❌ | ⚠️ |
81
+
82
+ - ✅ Full support
83
+ - ⚠️ Partial support (fallback to in-memory)
84
+ - ❌ Not available
85
+
86
+ ---
87
+
88
+ ## Client vs Server Usage
89
+
90
+ ### 🖥️ Client-Side (Browser)
91
+
92
+ All features work with real browser storage:
93
+
94
+ ```javascript
95
+ import { store, session, idb } from 'memorio'
96
+
97
+ // Store - persistent localStorage
98
+ store.set('preferences', { theme: 'dark' });
99
+ store.isPersistent; // true
100
+
101
+ // Session - temporary sessionStorage
102
+ session.set('token', 'jwt-token');
103
+ session.isPersistent; // true (survives refresh)
104
+
105
+ // IDB - large data storage
106
+ idb.db.create('myApp');
107
+ ```
108
+
109
+ ### 🖥️ Server-Side (Node.js/Deno)
110
+
111
+ Use `state` and `cache` for in-memory data. Store/session fall back to memory:
112
+
113
+ ```javascript
114
+ import { state, store, session, cache } from 'memorio'
115
+
116
+ // State - in-memory global state
117
+ state.user = { name: 'Server User' };
118
+
119
+ // Cache - in-memory temporary cache
120
+ cache.set('apiResponse', data);
121
+
122
+ // Store - in-memory fallback (not persistent)
123
+ store.set('temp', data);
124
+ store.isPersistent; // false - data lost on restart
125
+
126
+ // Session - in-memory fallback
127
+ session.set('requestData', data);
128
+ session.isPersistent; // false - data lost on restart
129
+ ```
130
+
131
+ ---
132
+
133
+ ## Session Isolation
134
+
135
+ Each instance/session gets unique storage keys to prevent conflicts:
136
+
137
+ ```javascript
138
+ // Without context: "memorio_store_[sessionId]_key"
139
+ // With context: "[contextName]-key"
140
+ ```
141
+
142
+ This ensures:
143
+ - Multiple browser tabs don't share session data
144
+ - Server-side requests are isolated
145
+
146
+ ---
147
+
148
+ ## Context Isolation (Server-Side Multi-Tenancy)
149
+
150
+ > ⚠️ **Server-Side Only**: This feature is designed for multi-tenant server environments (Node.js, Deno). Not needed for client-side applications.
151
+
152
+ For server-side applications handling multiple tenants (e.g., different users/requests), use **contexts** to isolate data:
153
+
154
+ ### Creating a Context
155
+
156
+ ```javascript
157
+ import { memorio, state } from 'memorio'
158
+
159
+ // Create isolated context for a user/session
160
+ const ctx = memorio.isolate('user-123');
161
+
162
+ // Keys in store/session are prefixed with context name
163
+ // store: "user-123-key"
164
+ // session: "user-123-key"
165
+
166
+ // Use context's isolated storage
167
+ ctx.state.user = { name: 'Isolated User' };
168
+ ctx.store.set('settings', { theme: 'dark' });
169
+ ctx.session.set('token', 'abc123');
170
+ ctx.cache.set('temp', data);
171
+
172
+ // Context is completely isolated from global state
173
+ console.debug(state.user); // undefined - global state is separate
174
+ ```
175
+
176
+ ### Managing Contexts
177
+
178
+ ```javascript
179
+ // List all contexts
180
+ const contexts = memorio.listContexts();
181
+ console.debug(contexts); // ['user-123', 'user-456', ...]
182
+
183
+ // Delete a context (cleanup)
184
+ memorio.deleteContext('user-123');
185
+ ```
186
+
187
+ > **Note**: `memorio.isolate('name')` is a shorthand alias for creating isolated contexts.
188
+
189
+ ### Context Use Cases
190
+
191
+ #### 1. Per-Request Isolation (Express/Fastify)
192
+
193
+ ```javascript
194
+ // Middleware to isolate each request
195
+ app.use((req, res, next) => {
196
+ const ctx = memorio.createContext(`req-${req.id}`);
197
+ req.memorioContext = ctx;
198
+ next();
199
+ });
200
+
201
+ // In route handler
202
+ app.get('/user', (req, res) => {
203
+ const ctx = req.memorioContext;
204
+ ctx.state.user = getUserData();
205
+ // Each request has isolated state
206
+ });
207
+ ```
208
+
209
+ #### 2. Multi-Tenant SaaS
210
+
211
+ ```javascript
212
+ // Each tenant gets isolated storage
213
+ function handleTenant(tenantId) {
214
+ const ctx = memorio.createContext(tenantId);
215
+
216
+ ctx.state.config = getTenantConfig(tenantId);
217
+ ctx.store.set('data', tenantData);
218
+
219
+ return ctx;
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Best Practices
226
+
227
+ ### Client-Side (Browser)
228
+
229
+ 1. Use `store` for persistent data (preferences, user settings)
230
+ 2. Use `session` for temporary data (auth tokens)
231
+ 3. Use `cache` for computed values
232
+ 4. Use `state` for reactive UI state
233
+
234
+ ### Server-Side (Node.js/Deno)
235
+
236
+ 1. Use `memorio.createContext()` for each request/tenant
237
+ 2. Don't use global `state`/`store`/`session` across requests
238
+ 3. Use `cache` for request-scoped caching
239
+ 4. Check `store.isPersistent` / `session.isPersistent` if persistence matters
240
+
241
+ ### Edge Workers
242
+
243
+ Same as browser - localStorage and sessionStorage are available.
244
+
245
+ ---
246
+
247
+ ## API Reference
248
+
249
+ ### Global Functions
250
+
251
+ | Function | Returns | Description |
252
+ |----------|---------|-------------|
253
+ | `memorio.isBrowser()` | `boolean` | Check if running in browser |
254
+ | `memorio.isNode()` | `boolean` | Check if running in Node.js |
255
+ | `memorio.isDeno()` | `boolean` | Check if running in Deno |
256
+ | `memorio.isEdge()` | `boolean` | Check if running in Edge |
257
+ | `memorio.getCapabilities()` | `object` | Get platform capabilities |
258
+
259
+ ### Context Management
260
+
261
+ | Function | Returns | Description |
262
+ |----------|---------|-------------|
263
+ | `memorio.isolate(name?)` | `Context` | Create isolated context |
264
+ | `memorio.listContexts()` | `string[]` | List all context IDs |
265
+ | `memorio.deleteContext(id)` | `boolean` | Delete a context |
266
+
267
+ ### Properties
268
+
269
+ | Property | Type | Description |
270
+ |----------|------|-------------|
271
+ | `memorio.version` | `string` | Memorio version |
272
+ | `memorio.getCapabilities().platform` | `string` | Current platform |
273
+ | `memorio.isBrowser()` / `isNode()` / `isDeno()` / `isEdge()` | `boolean` | Platform checks |
274
+ | `memorio._sessionId` | `string` | Unique session identifier |
275
+
276
+ > **Classic `import`**: context APIs are also named exports.
277
+ > `import { createContext, listContexts, deleteContext, isolate } from 'memorio'`.
@@ -0,0 +1,54 @@
1
+ > **Status:** Published
2
+ > **Date:** 2026-09-12
3
+ > **Scope**: API Reference
4
+ > **Standard**: Memorio API Specification v5
5
+ >
6
+ ---
7
+ # Redux and other state managers - Memorio
8
+
9
+ > Skip this unless you already use Redux.
10
+
11
+ > **Your state manager owns application state. Memorio owns memory.**
12
+
13
+ ```ts
14
+ import { createMemorioReduxBridge } from 'memorio/redux'
15
+
16
+ const memorioRedux = createMemorioReduxBridge<AppState>({
17
+ mappings: {
18
+ 'user.preferences': {
19
+ selector: state => state.user.preferences,
20
+ type: 'preference',
21
+ scope: 'local',
22
+ tags: ['app', 'user']
23
+ },
24
+ 'user.name': state => state.user.name
25
+ },
26
+ whitelist: ['user/preferencesChanged', 'user/nameChanged'],
27
+ debug: true
28
+ })
29
+
30
+ const store = createStore(reducer, applyMiddleware(memorioRedux.middleware))
31
+
32
+ await memorioRedux.hydrate(store, {
33
+ onHydration(values) { store.dispatch(restorePreferences(values)) }
34
+ })
35
+ ```
36
+
37
+ ```text
38
+ Redux ─────────────→ memorio memorio ───────────→ Redux
39
+ application state durable memory bootstrap hydration (one-shot)
40
+ ```
41
+
42
+ The bridge persists only mapped selectors, never mirrors the full tree, coalesces bursts, and never breaks Redux dispatch if Memorio fails.
43
+
44
+ | | Redux | Memorio |
45
+ |---|---|---|
46
+ | Core model | single store | independent runtime layers |
47
+ | State changes | actions + reducers | direct reactive mutation |
48
+ | Persistence | external integration | built in |
49
+ | Structured memory | - | native |
50
+ | Offline memory sync | - | supported |
51
+ | Time-travel debugging | mature | State Intelligence (in progress) |
52
+ | Production dependencies | ecosystem-dependent | zero in core |
53
+
54
+ Use Redux when you need its action-pipeline discipline and ecosystem. Use Memorio when you want state, persistence, and structured memory under one runtime. You can use both.
@@ -0,0 +1,175 @@
1
+ > **Status:** Published
2
+ > **Date:** 2026-09-12
3
+ > **Scope**: API Reference
4
+ > **Standard**: Memorio API Specification v5
5
+ >
6
+ ---
7
+ # Schema Validation - Memorio
8
+
9
+ > ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
10
+
11
+ 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.
12
+
13
+ Schema validation is **opt-in** and **zero-dependency**.
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```javascript
20
+ import { memorio, state } from 'memorio'
21
+
22
+ // Register a validator for a top-level state key
23
+ memorio.registerSchema('user', {
24
+ type: 'object',
25
+ required: ['name', 'email'],
26
+ properties: {
27
+ name: { type: 'string', min: 1 },
28
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
29
+ age: { type: 'number', min: 0, max: 150 }
30
+ }
31
+ })
32
+
33
+ // Valid write - accepted
34
+ state.user = { name: 'Sara', email: 'sara@test.com', age: 30 }
35
+
36
+ // Invalid write - rejected, returns false
37
+ state.user = { name: 'Sara' } // missing 'email'
38
+ state.user = { name: 42, email: 'x' } // wrong type for 'name'
39
+ state.user = { age: -5 } // out of range
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Schema Definition
45
+
46
+ A `Schema` object supports the following fields:
47
+
48
+ | Field | Type | Description |
49
+ |-------|------|-------------|
50
+ | `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array' \| 'any'` | Runtime type check |
51
+ | `required` | `string[]` | Property names that must exist (objects only) |
52
+ | `properties` | `Record<string, Schema>` | Nested property schemas (validated recursively) |
53
+ | `min` | `number` | Number: minimum value. String: minimum length |
54
+ | `max` | `number` | Number: maximum value. String: maximum length |
55
+ | `pattern` | `RegExp` | Regex the string value must match |
56
+ | `enum` | `any[]` | Whitelist of allowed values |
57
+ | `validator` | `(value) => boolean \| string` | Custom validator function |
58
+
59
+ ### Custom validator functions
60
+
61
+ For logic that's hard to express declaratively, pass a function instead of a schema object:
62
+
63
+ ```javascript
64
+ memorio.registerSchema('counter', (value) => {
65
+ if (typeof value !== 'number') return 'counter must be a number'
66
+ if (value < 0) return 'counter must be >= 0'
67
+ return true
68
+ })
69
+ ```
70
+
71
+ A custom validator receives the raw value. Return `true` to accept, or a **string** describing the error to reject.
72
+
73
+ ---
74
+
75
+ ## Path-based registration
76
+
77
+ Schemas are keyed by their **state path**, relative to `state`:
78
+
79
+ | API call | Catches |
80
+ |----------|---------|
81
+ | `registerSchema('user', schema)` | `state.user = value` |
82
+ | `registerSchema('user.age', schema)` | `state.user.age = value` |
83
+ | `registerSchema('items', schema)` | `state.items = value` |
84
+
85
+ The full dotted path is constructed from the proxy's tree depth. Nested sets propagate the full path automatically.
86
+
87
+ ---
88
+
89
+ ## Manual validation
90
+
91
+ You can validate a value without writing it to state:
92
+
93
+ ```javascript
94
+ memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
95
+ // { valid: true }
96
+
97
+ memorio.validate('user', { name: 'Sara' })
98
+ // { valid: false, errors: ["user: missing required property 'email'"] }
99
+ ```
100
+
101
+ When no schema is registered for a path, `validate` returns `{ valid: true }`.
102
+
103
+ ---
104
+
105
+ ## Schema management
106
+
107
+ ```javascript
108
+ memorio.listSchemas() // ['user', 'theme', 'items', 'counter']
109
+ memorio.unregisterSchema('counter') // removes the schema
110
+ ```
111
+
112
+ ---
113
+
114
+ ## Full API
115
+
116
+ | Method | Parameters | Returns | Description |
117
+ |--------|-----------|---------|-------------|
118
+ | `memorio.registerSchema(path, schema)` | `string`, `Schema \| fn` | `void` | Register a validator |
119
+ | `memorio.validate(path, value)` | `string`, `any` | `{ valid, errors? }` | Manually validate a value |
120
+ | `memorio.unregisterSchema(path)` | `string` | `boolean` | Remove a registered schema |
121
+ | `memorio.listSchemas()` | none | `string[]` | List all registered paths |
122
+ | `memorio.registerSchema()` is also importable | `registerSchema` | named export | same function |
123
+
124
+ ---
125
+
126
+ ## Combine with Typed Stores
127
+
128
+ Schema validation gives you **runtime** safety; typed stores give you **compile-time** safety. Use both for full coverage:
129
+
130
+ ```typescript
131
+ import { memorio, state } from 'memorio'
132
+
133
+ interface AppState {
134
+ user: { name: string; email: string; age: number }
135
+ theme: 'light' | 'dark'
136
+ }
137
+
138
+ const app = memorio.typed<AppState>()
139
+
140
+ memorio.registerSchema('user', {
141
+ type: 'object',
142
+ required: ['name', 'email'],
143
+ properties: {
144
+ name: { type: 'string', min: 1 },
145
+ email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
146
+ age: { type: 'number', min: 0, max: 150 }
147
+ }
148
+ })
149
+
150
+ app.user = { name: '', email: 'bad' } // ❌ TypeScript: age missing
151
+ // ❌ Runtime: missing required fields
152
+ app.user = { name: 'Sara', email: 'ok', age: 30 } // ✅ both checks pass
153
+ ```
154
+
155
+ See [Typed Stores](TYPED.md) for compile-time type safety.
156
+
157
+ ---
158
+
159
+ ## How it works
160
+
161
+ 1. When you call `registerSchema(path, schema)`, the schema is stored in an internal `Map`.
162
+ 2. On every `state.set` operation, the proxy's `set` trap computes the full path (e.g. `'user.name'`).
163
+ 3. If a schema is registered for that path, the value is validated.
164
+ 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).
165
+ 5. If no schema is registered, the write proceeds normally.
166
+
167
+ The validation adds negligible overhead when no schemas are registered (a single `Map` lookup that returns `undefined`).
168
+
169
+ ---
170
+
171
+ ## Limitations
172
+
173
+ - 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.
174
+ - 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.
175
+ - The schema system is not a replacement for server-side validation. It protects against accidental misuse and provides defense-in-depth in the browser.