memorio 4.9.7 → 4.9.10
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/AGENTS.md +126 -0
- package/README.md +18 -2
- package/examples/basic.ts +115 -0
- package/examples/browser-vanilla.html +358 -0
- package/examples/cache.ts +72 -0
- package/examples/cross-platform-guards.ts +57 -0
- package/examples/idb.ts +109 -0
- package/examples/multi-tenant-context.ts +44 -0
- package/examples/node-server.ts +308 -0
- package/examples/observer.ts +60 -0
- package/examples/platform.ts +115 -0
- package/examples/react-app.tsx +362 -0
- package/examples/react-observer.tsx +63 -0
- package/examples/semantic-memory.ts +60 -0
- package/examples/session-advanced.ts +91 -0
- package/examples/sqlite-batched-writes.ts +57 -0
- package/examples/state-advanced.ts +89 -0
- package/examples/store-advanced.ts +117 -0
- package/examples/typed-and-schema.ts +100 -0
- package/examples/useObserver.tsx +141 -0
- package/index.cjs +58 -32
- package/index.js +58 -32
- package/package.json +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Platform & Context Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates:
|
|
5
|
+
* - Platform detection
|
|
6
|
+
* - Checking storage persistence
|
|
7
|
+
* - Context isolation for server-side multi-tenancy
|
|
8
|
+
*
|
|
9
|
+
* Run: npx ts-node examples/platform.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import 'memorio'
|
|
13
|
+
|
|
14
|
+
// ============================================
|
|
15
|
+
// PLATFORM DETECTION
|
|
16
|
+
// ============================================
|
|
17
|
+
|
|
18
|
+
console.debug('=== Platform Detection ===')
|
|
19
|
+
console.debug('Memorio version:', memorio.version)
|
|
20
|
+
|
|
21
|
+
// Check which platform we're on
|
|
22
|
+
if (memorio.isBrowser()) {
|
|
23
|
+
console.debug('Running in: Browser')
|
|
24
|
+
console.debug('store.isPersistent:', store.isPersistent) // true
|
|
25
|
+
console.debug('session.isPersistent:', session.isPersistent) // true
|
|
26
|
+
} else if (memorio.isNode()) {
|
|
27
|
+
console.debug('Running in: Node.js')
|
|
28
|
+
console.debug('store.isPersistent:', store.isPersistent) // false (memory fallback)
|
|
29
|
+
console.debug('session.isPersistent:', session.isPersistent) // false (memory fallback)
|
|
30
|
+
} else if (memorio.isDeno()) {
|
|
31
|
+
console.debug('Running in: Deno')
|
|
32
|
+
console.debug('store.isPersistent:', store.isPersistent) // false (memory fallback)
|
|
33
|
+
console.debug('session.isPersistent:', session.isPersistent) // false (memory fallback)
|
|
34
|
+
} else if (memorio.isEdge()) {
|
|
35
|
+
console.debug('Running in: Edge Worker')
|
|
36
|
+
console.debug('store.isPersistent:', store.isPersistent) // true
|
|
37
|
+
console.debug('session.isPersistent:', session.isPersistent) // true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Get detailed capabilities
|
|
41
|
+
const caps = memorio.getCapabilities()
|
|
42
|
+
console.debug('\nCapabilities:')
|
|
43
|
+
console.debug(' Platform:', caps.platform)
|
|
44
|
+
console.debug(' localStorage:', caps.hasLocalStorage ? '✅' : '❌')
|
|
45
|
+
console.debug(' sessionStorage:', caps.hasSessionStorage ? '✅' : '❌')
|
|
46
|
+
console.debug(' IndexedDB:', caps.hasIndexedDB ? '✅' : '❌')
|
|
47
|
+
console.debug(' Session ID:', caps.sessionId)
|
|
48
|
+
|
|
49
|
+
// ============================================
|
|
50
|
+
// CONTEXT ISOLATION (Server-Side)
|
|
51
|
+
// ============================================
|
|
52
|
+
|
|
53
|
+
console.debug('\n=== Context Isolation ===')
|
|
54
|
+
console.debug('Use memorio.createContext() for multi-tenant server applications')
|
|
55
|
+
|
|
56
|
+
// Example: Simulating different users/requests
|
|
57
|
+
// In a real app, you'd create a context per HTTP request
|
|
58
|
+
|
|
59
|
+
// Create isolated context for User A
|
|
60
|
+
const userAContext = memorio.createContext('user-A')
|
|
61
|
+
userAContext.state.name = 'Mario'
|
|
62
|
+
userAContext.store.set('preferences', { theme: 'dark' })
|
|
63
|
+
userAContext.session.set('token', 'user-A-token')
|
|
64
|
+
|
|
65
|
+
// Create isolated context for User B
|
|
66
|
+
const userBContext = memorio.createContext('user-B')
|
|
67
|
+
userBContext.state.name = 'Luigi'
|
|
68
|
+
userBContext.store.set('preferences', { theme: 'light' })
|
|
69
|
+
userBContext.session.set('token', 'user-B-token')
|
|
70
|
+
|
|
71
|
+
// Verify isolation - each context has its own data
|
|
72
|
+
console.debug('\nUser A context:')
|
|
73
|
+
console.debug(' state.name:', userAContext.state.name) // 'Mario'
|
|
74
|
+
console.debug(' store.preferences:', userAContext.store.get('preferences')) // { theme: 'dark' }
|
|
75
|
+
console.debug(' session.token:', userAContext.session.get('token')) // 'user-A-token'
|
|
76
|
+
|
|
77
|
+
console.debug('\nUser B context:')
|
|
78
|
+
console.debug(' state.name:', userBContext.state.name) // 'Luigi'
|
|
79
|
+
console.debug(' store.preferences:', userBContext.store.get('preferences')) // { theme: 'light' }
|
|
80
|
+
console.debug(' session.token:', userBContext.session.get('token')) // 'user-B-token'
|
|
81
|
+
|
|
82
|
+
// Global state is separate from contexts
|
|
83
|
+
console.debug('\nGlobal state (not affected by contexts):')
|
|
84
|
+
console.debug(' state.name:', state.name) // undefined
|
|
85
|
+
|
|
86
|
+
// List all contexts
|
|
87
|
+
console.debug('\nActive contexts:', memorio.listContexts()) // ['user-A', 'user-B']
|
|
88
|
+
|
|
89
|
+
// Cleanup - delete a context when done
|
|
90
|
+
memorio.deleteContext('user-A')
|
|
91
|
+
console.debug('After deleting user-A:', memorio.listContexts()) // ['user-B']
|
|
92
|
+
|
|
93
|
+
// ============================================
|
|
94
|
+
// USE CASE: EXPRESS/FASTIFY MIDDLEWARE EXAMPLE
|
|
95
|
+
// ============================================
|
|
96
|
+
|
|
97
|
+
console.debug('\n=== Server Use Case ===')
|
|
98
|
+
console.debug(`
|
|
99
|
+
// Example: Express middleware for request isolation
|
|
100
|
+
app.use((req, res, next) => {
|
|
101
|
+
// Create isolated context per request
|
|
102
|
+
const ctx = memorio.createContext(\`req-\${req.id}\`)
|
|
103
|
+
req.memorio = ctx
|
|
104
|
+
next()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// In your route handler:
|
|
108
|
+
app.get('/user', (req, res) => {
|
|
109
|
+
// This state is isolated per request
|
|
110
|
+
req.memorio.state.user = getUserData()
|
|
111
|
+
req.memorio.store.set('cache', data)
|
|
112
|
+
})
|
|
113
|
+
`)
|
|
114
|
+
|
|
115
|
+
console.debug('\nPlatform & Context example complete!')
|
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio React App Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to use Memorio state in a React application.
|
|
5
|
+
*
|
|
6
|
+
* Run: npx ts-node --esm examples/react-app.tsx
|
|
7
|
+
* Or copy to your React project
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React, { useState, useEffect } from 'react'
|
|
11
|
+
import 'memorio'
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// 1. SETUP - Import once at app start
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
// In your main App.tsx or index.js:
|
|
18
|
+
// import 'memorio'
|
|
19
|
+
|
|
20
|
+
// ============================================
|
|
21
|
+
// 2. DEFINE YOUR STATE SHAPE
|
|
22
|
+
// ============================================
|
|
23
|
+
|
|
24
|
+
interface AppState {
|
|
25
|
+
user: {
|
|
26
|
+
name: string
|
|
27
|
+
email: string
|
|
28
|
+
avatar?: string
|
|
29
|
+
} | null
|
|
30
|
+
theme: 'light' | 'dark'
|
|
31
|
+
notifications: Notification[]
|
|
32
|
+
cart: CartItem[]
|
|
33
|
+
isLoading: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface Notification {
|
|
37
|
+
id: string
|
|
38
|
+
message: string
|
|
39
|
+
read: boolean
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CartItem {
|
|
43
|
+
id: number
|
|
44
|
+
name: string
|
|
45
|
+
price: number
|
|
46
|
+
quantity: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ============================================
|
|
50
|
+
// 3. USE STATE IN COMPONENTS
|
|
51
|
+
// ============================================
|
|
52
|
+
|
|
53
|
+
// ------------------------------
|
|
54
|
+
// Header Component
|
|
55
|
+
// ------------------------------
|
|
56
|
+
function Header() {
|
|
57
|
+
// Using useObserver to react to state changes
|
|
58
|
+
const [theme, setTheme] = useState(state.theme)
|
|
59
|
+
|
|
60
|
+
useObserver(() => {
|
|
61
|
+
setTheme(state.theme)
|
|
62
|
+
}, [state.theme])
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<header className={`header header--${theme}`}>
|
|
66
|
+
<h1>My App</h1>
|
|
67
|
+
<button onClick={() => {
|
|
68
|
+
state.theme = state.theme === 'light' ? 'dark' : 'light'
|
|
69
|
+
}}>
|
|
70
|
+
Toggle Theme
|
|
71
|
+
</button>
|
|
72
|
+
</header>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ------------------------------
|
|
77
|
+
// User Profile Component
|
|
78
|
+
// ------------------------------
|
|
79
|
+
function UserProfile() {
|
|
80
|
+
const [user, setUser] = useState(state.user)
|
|
81
|
+
|
|
82
|
+
// Auto-discover all state changes
|
|
83
|
+
useObserver(() => {
|
|
84
|
+
setUser(state.user)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
if (!user) {
|
|
88
|
+
return <div>Please log in</div>
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="profile">
|
|
93
|
+
<img src={user.avatar} alt={user.name} />
|
|
94
|
+
<h2>{user.name}</h2>
|
|
95
|
+
<p>{user.email}</p>
|
|
96
|
+
</div>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ------------------------------
|
|
101
|
+
// Login Form Component
|
|
102
|
+
// ------------------------------
|
|
103
|
+
function LoginForm() {
|
|
104
|
+
const [name, setName] = useState('')
|
|
105
|
+
const [email, setEmail] = useState('')
|
|
106
|
+
|
|
107
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
108
|
+
e.preventDefault()
|
|
109
|
+
|
|
110
|
+
// Set user state
|
|
111
|
+
state.user = {
|
|
112
|
+
name,
|
|
113
|
+
email,
|
|
114
|
+
avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${name}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Also persist to store
|
|
118
|
+
store.set('lastUser', name)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<form onSubmit={handleSubmit}>
|
|
123
|
+
<input
|
|
124
|
+
value={name}
|
|
125
|
+
onChange={e => setName(e.target.value)}
|
|
126
|
+
placeholder="Name"
|
|
127
|
+
/>
|
|
128
|
+
<input
|
|
129
|
+
value={email}
|
|
130
|
+
onChange={e => setEmail(e.target.value)}
|
|
131
|
+
placeholder="Email"
|
|
132
|
+
/>
|
|
133
|
+
<button type="submit">Login</button>
|
|
134
|
+
</form>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ------------------------------
|
|
139
|
+
// Shopping Cart Component
|
|
140
|
+
// ------------------------------
|
|
141
|
+
function Cart() {
|
|
142
|
+
const [items, setItems] = useState<CartItem[]>([])
|
|
143
|
+
|
|
144
|
+
useObserver(() => {
|
|
145
|
+
setItems(state.cart || [])
|
|
146
|
+
}, [state.cart])
|
|
147
|
+
|
|
148
|
+
const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className="cart">
|
|
152
|
+
<h2>Cart ({items.length})</h2>
|
|
153
|
+
{items.map(item => (
|
|
154
|
+
<div key={item.id}>
|
|
155
|
+
{item.name} - ${item.price} x {item.quantity}
|
|
156
|
+
</div>
|
|
157
|
+
))}
|
|
158
|
+
<strong>Total: ${total}</strong>
|
|
159
|
+
<div style={{ marginTop: '1rem' }}>
|
|
160
|
+
<AddToCartButton
|
|
161
|
+
product={{ id: 1, name: 'Sample Product', price: 9.99 }}
|
|
162
|
+
/>
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ------------------------------
|
|
169
|
+
// Add to Cart Button
|
|
170
|
+
// ------------------------------
|
|
171
|
+
function AddToCartButton({ product }: { product: { id: number, name: string, price: number } }) {
|
|
172
|
+
return (
|
|
173
|
+
<button onClick={() => {
|
|
174
|
+
// Initialize cart if not exists
|
|
175
|
+
if (!state.cart) {
|
|
176
|
+
state.cart = []
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Add item or increment quantity
|
|
180
|
+
const existing = state.cart.find(item => item.id === product.id)
|
|
181
|
+
if (existing) {
|
|
182
|
+
existing.quantity++
|
|
183
|
+
} else {
|
|
184
|
+
state.cart.push({
|
|
185
|
+
id: product.id,
|
|
186
|
+
name: product.name,
|
|
187
|
+
price: product.price,
|
|
188
|
+
quantity: 1
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
}}>
|
|
192
|
+
Add to Cart
|
|
193
|
+
</button>
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ------------------------------
|
|
198
|
+
// Notifications Component
|
|
199
|
+
// ------------------------------
|
|
200
|
+
function Notifications() {
|
|
201
|
+
const [notifications, setNotifications] = useState<Notification[]>([])
|
|
202
|
+
|
|
203
|
+
useObserver(() => {
|
|
204
|
+
setNotifications(state.notifications || [])
|
|
205
|
+
}, [state.notifications])
|
|
206
|
+
|
|
207
|
+
const unread = notifications.filter(n => !n.read).length
|
|
208
|
+
|
|
209
|
+
return (
|
|
210
|
+
<div className="notifications">
|
|
211
|
+
<span>🔔 {unread} unread</span>
|
|
212
|
+
{notifications.map(n => (
|
|
213
|
+
<div key={n.id} className={n.read ? 'read' : 'unread'}>
|
|
214
|
+
{n.message}
|
|
215
|
+
</div>
|
|
216
|
+
))}
|
|
217
|
+
</div>
|
|
218
|
+
)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ------------------------------
|
|
222
|
+
// Settings Component
|
|
223
|
+
// ------------------------------
|
|
224
|
+
function Settings() {
|
|
225
|
+
const [theme] = useState(state.theme)
|
|
226
|
+
const [savedSettings, setSavedSettings] = useState<any>(null)
|
|
227
|
+
|
|
228
|
+
// Load saved settings from store
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
const settings = store.get('appSettings')
|
|
231
|
+
if (settings) {
|
|
232
|
+
setSavedSettings(settings)
|
|
233
|
+
}
|
|
234
|
+
}, [])
|
|
235
|
+
|
|
236
|
+
const saveSettings = () => {
|
|
237
|
+
store.set('appSettings', { theme: state.theme })
|
|
238
|
+
alert('Settings saved!')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<div className="settings">
|
|
243
|
+
<h2>Settings</h2>
|
|
244
|
+
|
|
245
|
+
<label>
|
|
246
|
+
Theme:
|
|
247
|
+
<select
|
|
248
|
+
value={theme}
|
|
249
|
+
onChange={e => state.theme = e.target.value as 'light' | 'dark'}
|
|
250
|
+
>
|
|
251
|
+
<option value="light">Light</option>
|
|
252
|
+
<option value="dark">Dark</option>
|
|
253
|
+
</select>
|
|
254
|
+
</label>
|
|
255
|
+
|
|
256
|
+
<button onClick={saveSettings}>Save Settings</button>
|
|
257
|
+
|
|
258
|
+
{savedSettings && (
|
|
259
|
+
<p>Saved: {savedSettings.theme}</p>
|
|
260
|
+
)}
|
|
261
|
+
</div>
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ============================================
|
|
266
|
+
// 4. MAIN APP COMPONENT
|
|
267
|
+
// ============================================
|
|
268
|
+
|
|
269
|
+
function App() {
|
|
270
|
+
// Initialize default state
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
// Load theme from store
|
|
273
|
+
const savedTheme = store.get('appSettings')?.theme
|
|
274
|
+
if (savedTheme) {
|
|
275
|
+
state.theme = savedTheme
|
|
276
|
+
} else {
|
|
277
|
+
state.theme = 'light'
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Initialize notifications
|
|
281
|
+
state.notifications = [
|
|
282
|
+
{ id: '1', message: 'Welcome to Memorio!', read: false },
|
|
283
|
+
{ id: '2', message: 'Check out our new features', read: false }
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
// Check for returning user
|
|
287
|
+
const lastUser = store.get('lastUser')
|
|
288
|
+
if (lastUser) {
|
|
289
|
+
console.debug('Welcome back,', lastUser)
|
|
290
|
+
}
|
|
291
|
+
}, [])
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div className={`app app--${state.theme}`}>
|
|
295
|
+
<Header />
|
|
296
|
+
<main>
|
|
297
|
+
{state.user ? (
|
|
298
|
+
<>
|
|
299
|
+
<UserProfile />
|
|
300
|
+
<Cart />
|
|
301
|
+
<Notifications />
|
|
302
|
+
<Settings />
|
|
303
|
+
|
|
304
|
+
<button onClick={() => {
|
|
305
|
+
// Logout - clear user but keep settings
|
|
306
|
+
state.user = null
|
|
307
|
+
}}>
|
|
308
|
+
Logout
|
|
309
|
+
</button>
|
|
310
|
+
</>
|
|
311
|
+
) : (
|
|
312
|
+
<LoginForm />
|
|
313
|
+
)}
|
|
314
|
+
</main>
|
|
315
|
+
</div>
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export default App
|
|
320
|
+
|
|
321
|
+
// ============================================
|
|
322
|
+
// 5. USAGE SUMMARY
|
|
323
|
+
// ============================================
|
|
324
|
+
|
|
325
|
+
/*
|
|
326
|
+
MEMORIO REACT USAGE GUIDE:
|
|
327
|
+
|
|
328
|
+
1. IMPORT ONCE (index.js or App.tsx):
|
|
329
|
+
import 'memorio'
|
|
330
|
+
|
|
331
|
+
2. SET STATE:
|
|
332
|
+
state.user = { name: 'Mario', email: 'mario@example.com' }
|
|
333
|
+
state.theme = 'dark'
|
|
334
|
+
state.cart = []
|
|
335
|
+
|
|
336
|
+
3. READ STATE:
|
|
337
|
+
const value = state.user.name
|
|
338
|
+
const theme = state.theme
|
|
339
|
+
|
|
340
|
+
4. REACT TO CHANGES:
|
|
341
|
+
useObserver(() => {
|
|
342
|
+
console.debug('State changed!')
|
|
343
|
+
}, [state.user])
|
|
344
|
+
|
|
345
|
+
5. AUTO-DISCOVERY (watch all):
|
|
346
|
+
useObserver(() => {
|
|
347
|
+
console.debug(state.user, state.theme)
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
6. PERSIST DATA:
|
|
351
|
+
store.set('settings', { theme: 'dark' })
|
|
352
|
+
const settings = store.get('settings')
|
|
353
|
+
|
|
354
|
+
7. SESSION DATA (cleared on tab close):
|
|
355
|
+
session.set('token', 'abc123')
|
|
356
|
+
const token = session.get('token')
|
|
357
|
+
|
|
358
|
+
8. CLEAR DATA:
|
|
359
|
+
state.removeAll() // Clear all state
|
|
360
|
+
store.removeAll() // Clear all persisted
|
|
361
|
+
session.removeAll() // Clear all session
|
|
362
|
+
*/
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* react-observer.tsx
|
|
3
|
+
*
|
|
4
|
+
* Scenario: a React component reading reactive `state`. Shows both
|
|
5
|
+
* `useObserver` modes and when to prefer each, plus `typed<T>()` +
|
|
6
|
+
* `registerSchema()` for the paths you don't want to fail silently on a
|
|
7
|
+
* typo or a later rename.
|
|
8
|
+
*/
|
|
9
|
+
import 'memorio'
|
|
10
|
+
import { useReducer } from 'react'
|
|
11
|
+
|
|
12
|
+
interface AppState {
|
|
13
|
+
counter: number
|
|
14
|
+
user: { name: string; email: string }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Compile-time safety: same Proxy as the global `state`, no duplicated store.
|
|
18
|
+
const app = memorio.typed<AppState>()
|
|
19
|
+
|
|
20
|
+
// Runtime safety: reject writes that don't match the shape, independent of
|
|
21
|
+
// whatever calls it (including code you didn't write).
|
|
22
|
+
memorio.registerSchema('user', {
|
|
23
|
+
type: 'object',
|
|
24
|
+
required: ['name', 'email'],
|
|
25
|
+
properties: {
|
|
26
|
+
name: { type: 'string', min: 1 },
|
|
27
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export function Counter() {
|
|
32
|
+
const [, forceUpdate] = useReducer((x: number) => x + 1, 0)
|
|
33
|
+
|
|
34
|
+
// Auto-discovery mode: re-runs when any state path *touched inside the
|
|
35
|
+
// callback* changes. Convenient, but re-runs on anything you read there —
|
|
36
|
+
// don't reach for it in a callback that reads many unrelated paths.
|
|
37
|
+
useObserver(() => {
|
|
38
|
+
forceUpdate()
|
|
39
|
+
}, state.counter)
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div>
|
|
43
|
+
<span>Count: {app.counter}</span>
|
|
44
|
+
<button onClick={() => { app.counter += 1 }}>+1</button>
|
|
45
|
+
</div>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function UserBadge() {
|
|
50
|
+
const [, forceUpdate] = useReducer((x: number) => x + 1, 0)
|
|
51
|
+
|
|
52
|
+
// Explicit deps mode: only re-runs when the listed path(s) change — use
|
|
53
|
+
// this when you want precise control instead of auto-discovery.
|
|
54
|
+
useObserver(() => {
|
|
55
|
+
forceUpdate()
|
|
56
|
+
}, [state.user])
|
|
57
|
+
|
|
58
|
+
return <span>{app.user?.name ?? 'Guest'}</span>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Rejected at write time by the schema registered above:
|
|
62
|
+
// app.user = { name: 'Sara' } // ❌ missing "email"
|
|
63
|
+
// app.user = { name: 'Sara', email: 'x@y.com' } // ✅
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* semantic-memory.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: an LLM-backed app that needs to remember facts about a user
|
|
5
|
+
* with confidence, source, and expiry — and later retrieve only what's
|
|
6
|
+
* relevant to the current task.
|
|
7
|
+
*
|
|
8
|
+
* Important: `memory.context()` ranks by tags/type/confidence/recency.
|
|
9
|
+
* It is NOT embedding-based semantic search — don't build a feature that
|
|
10
|
+
* assumes free-text meaning-based retrieval on top of this alone.
|
|
11
|
+
*/
|
|
12
|
+
import { memorio } from 'memorio'
|
|
13
|
+
|
|
14
|
+
export async function rememberPreference(
|
|
15
|
+
key: string,
|
|
16
|
+
value: unknown,
|
|
17
|
+
opts: { confidence: number; source: string; tags: string[] }
|
|
18
|
+
) {
|
|
19
|
+
await memorio.memory.remember(key, value, {
|
|
20
|
+
type: 'preference',
|
|
21
|
+
scope: 'local',
|
|
22
|
+
...opts,
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function correctPreference(key: string, newValue: unknown, confidence: number) {
|
|
27
|
+
// update() supersedes, it doesn't overwrite — the old entry becomes
|
|
28
|
+
// status: 'superseded' and stays queryable for history/audit purposes.
|
|
29
|
+
await memorio.memory.update(key, newValue, { confidence })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function getRelevantContext(tags: string[]) {
|
|
33
|
+
// This is rule-based retrieval, not semantic similarity search. It's the
|
|
34
|
+
// right tool for "give me what's tagged/confident/recent enough," not for
|
|
35
|
+
// "find the memory that means the same thing as this new sentence."
|
|
36
|
+
return memorio.memory.context({
|
|
37
|
+
tags,
|
|
38
|
+
types: ['preference', 'decision'],
|
|
39
|
+
minConfidence: 0.7,
|
|
40
|
+
maxEntries: 10,
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If the task genuinely needs meaning-based retrieval over free text (e.g.
|
|
45
|
+
// "find prior notes about a similar problem" rather than "find notes tagged
|
|
46
|
+
// X with confidence > Y"), pair this with a dedicated embedding store for
|
|
47
|
+
// the similarity search, and keep using memorio.memory for the lifecycle
|
|
48
|
+
// metadata (confidence, TTL, supersession) on top of whatever it finds —
|
|
49
|
+
// don't simulate similarity search with context() alone.
|
|
50
|
+
|
|
51
|
+
// Usage:
|
|
52
|
+
// await rememberPreference('user.language', 'Italian', {
|
|
53
|
+
// confidence: 0.92,
|
|
54
|
+
// source: 'conversation',
|
|
55
|
+
// tags: ['user', 'ui'],
|
|
56
|
+
// })
|
|
57
|
+
//
|
|
58
|
+
// await correctPreference('user.language', 'English', 0.95)
|
|
59
|
+
//
|
|
60
|
+
// const ctx = await getRelevantContext(['user'])
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Session Advanced Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows advanced session (sessionStorage) operations.
|
|
5
|
+
* Session data is cleared when the browser tab closes.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx ts-node examples/session-advanced.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'memorio'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// CHECK PERSISTENCE
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
console.debug('=== Session Persistence Check ===')
|
|
17
|
+
console.debug('Is persistent (survives tab close):', session.isPersistent)
|
|
18
|
+
// In browser: true (sessionStorage)
|
|
19
|
+
// In Node.js/Deno: false (memory fallback)
|
|
20
|
+
|
|
21
|
+
if (!session.isPersistent) {
|
|
22
|
+
console.debug('⚠️ Warning: Using in-memory storage. Data will be lost on process restart!')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// AUTHENTICATION
|
|
27
|
+
// ============================================
|
|
28
|
+
|
|
29
|
+
// Store auth token (temporary - cleared when tab closes)
|
|
30
|
+
session.set('authToken', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...')
|
|
31
|
+
session.set('userId', 12345)
|
|
32
|
+
|
|
33
|
+
// Check if user is logged in
|
|
34
|
+
const token = session.get('authToken')
|
|
35
|
+
if (token) {
|
|
36
|
+
console.debug('User is logged in, token:', token.substring(0, 20) + '...')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================
|
|
40
|
+
// FORM PROGRESS
|
|
41
|
+
// ============================================
|
|
42
|
+
|
|
43
|
+
// Save form draft
|
|
44
|
+
session.set('formDraft', {
|
|
45
|
+
name: 'Mario',
|
|
46
|
+
email: 'mario@example.com',
|
|
47
|
+
message: 'Hello world!'
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Restore on page refresh
|
|
51
|
+
const draft = session.get('formDraft')
|
|
52
|
+
if (draft) {
|
|
53
|
+
console.debug('Restored draft:', draft)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// SHOPPING CART
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
// Store cart items (temporary)
|
|
61
|
+
session.set('cart', [
|
|
62
|
+
{ id: 1, name: 'Super Mushroom', price: 99, qty: 2 },
|
|
63
|
+
{ id: 2, name: 'Fire Flower', price: 199, qty: 1 }
|
|
64
|
+
])
|
|
65
|
+
|
|
66
|
+
// Calculate total
|
|
67
|
+
const cart = session.get('cart') || []
|
|
68
|
+
const total = cart.reduce((sum, item) => sum + (item.price * item.qty), 0)
|
|
69
|
+
console.debug('Cart total:', total)
|
|
70
|
+
|
|
71
|
+
// ============================================
|
|
72
|
+
// SESSION SIZE
|
|
73
|
+
// ============================================
|
|
74
|
+
|
|
75
|
+
// Get session storage size
|
|
76
|
+
console.debug('Session size:', session.size(), 'bytes')
|
|
77
|
+
|
|
78
|
+
// ============================================
|
|
79
|
+
// CLEANUP
|
|
80
|
+
// ============================================
|
|
81
|
+
|
|
82
|
+
// Remove specific item
|
|
83
|
+
session.remove('formDraft')
|
|
84
|
+
|
|
85
|
+
// Clear all session data (logout)
|
|
86
|
+
// session.removeAll()
|
|
87
|
+
|
|
88
|
+
// Or use alias
|
|
89
|
+
// session.clearAll()
|
|
90
|
+
|
|
91
|
+
console.debug('Session advanced example complete!')
|