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.
@@ -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
- ```
@@ -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'`.
@@ -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.