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.
@@ -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!')