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,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sqlite-batched-writes.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: importing or writing many rows into memorio's sqlite layer.
|
|
5
|
+
*
|
|
6
|
+
* Persistence there serializes the ENTIRE database on every flush — not
|
|
7
|
+
* incremental. Calling a persisting write inside a per-row loop is the
|
|
8
|
+
* single most common way to accidentally make this layer slow.
|
|
9
|
+
*/
|
|
10
|
+
import { memorio } from 'memorio'
|
|
11
|
+
|
|
12
|
+
interface UserRow {
|
|
13
|
+
id: number
|
|
14
|
+
name: string
|
|
15
|
+
role: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function importUsers(users: UserRow[]) {
|
|
19
|
+
await memorio.sqlite.ready
|
|
20
|
+
await memorio.sqlite.db.create('app', { persistence: true })
|
|
21
|
+
|
|
22
|
+
await memorio.sqlite.query.run(
|
|
23
|
+
'app',
|
|
24
|
+
`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
// Bad: this would serialize the whole DB after every single insert.
|
|
28
|
+
//
|
|
29
|
+
// for (const user of users) {
|
|
30
|
+
// await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
|
|
31
|
+
// await memorio.sqlite.db.flush('app') // <- don't do this per row
|
|
32
|
+
// }
|
|
33
|
+
|
|
34
|
+
// Good: run every insert first, persist once after the batch.
|
|
35
|
+
for (const user of users) {
|
|
36
|
+
await memorio.sqlite.query.run(
|
|
37
|
+
'app',
|
|
38
|
+
`INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
|
|
39
|
+
[user.id, user.name, user.role]
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Persist explicitly, once, after the whole batch — check the installed
|
|
44
|
+
// version's API for the exact flush/persist call name if it differs from
|
|
45
|
+
// automatic persistence-on-close.
|
|
46
|
+
const admins = await memorio.sqlite.query.select(
|
|
47
|
+
'app',
|
|
48
|
+
`SELECT * FROM users WHERE role = ?`,
|
|
49
|
+
['admin']
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return admins
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// For read-heavy or scratch-space use where durability doesn't matter,
|
|
56
|
+
// skip { persistence: true } entirely — sqlite runs in memory by default,
|
|
57
|
+
// which avoids the serialize cost altogether.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio State Advanced Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows advanced state features: locking, path tracking, and nesting.
|
|
5
|
+
*
|
|
6
|
+
* Run: npx ts-node examples/state-advanced.ts
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import 'memorio'
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// NESTED OBJECTS
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
// Create nested state
|
|
16
|
+
state.user = {
|
|
17
|
+
name: 'Mario',
|
|
18
|
+
profile: {
|
|
19
|
+
email: 'mario@example.com',
|
|
20
|
+
settings: {
|
|
21
|
+
theme: 'dark',
|
|
22
|
+
notifications: true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Access nested values
|
|
28
|
+
console.debug('User name:', state.user.name)
|
|
29
|
+
console.debug('Email:', state.user.profile.email)
|
|
30
|
+
console.debug('Theme:', state.user.profile.settings.theme)
|
|
31
|
+
|
|
32
|
+
// ============================================
|
|
33
|
+
// ARRAYS
|
|
34
|
+
// ============================================
|
|
35
|
+
|
|
36
|
+
// State arrays work like regular arrays
|
|
37
|
+
state.items = [1, 2, 3]
|
|
38
|
+
state.items.push(4)
|
|
39
|
+
console.debug('Items:', state.items) // [1, 2, 3, 4]
|
|
40
|
+
|
|
41
|
+
state.users = [
|
|
42
|
+
{ name: 'Mario', id: 1 },
|
|
43
|
+
{ name: 'Luigi', id: 2 }
|
|
44
|
+
]
|
|
45
|
+
state.users.push({ name: 'Peach', id: 3 })
|
|
46
|
+
console.debug('Users:', state.users)
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// LOCKING STATE
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
// Lock a value to prevent modifications
|
|
53
|
+
state.config = { maxUsers: 100, timeout: 30 }
|
|
54
|
+
state.config.lock()
|
|
55
|
+
|
|
56
|
+
// This will fail:
|
|
57
|
+
// state.config.maxUsers = 200 // Error: state 'config' is locked
|
|
58
|
+
|
|
59
|
+
console.debug('Config locked:', state.config)
|
|
60
|
+
|
|
61
|
+
// ============================================
|
|
62
|
+
// PATH TRACKING
|
|
63
|
+
// ============================================
|
|
64
|
+
|
|
65
|
+
// Get path information
|
|
66
|
+
console.debug('Path:', state.user.__path) // "state.user"
|
|
67
|
+
|
|
68
|
+
// Use path tracker for debugging
|
|
69
|
+
const path = state.user.profile
|
|
70
|
+
console.debug('Profile path:', path.email.toString()) // "state.user.profile.email"
|
|
71
|
+
|
|
72
|
+
// ============================================
|
|
73
|
+
// LIST ALL STATES
|
|
74
|
+
// ============================================
|
|
75
|
+
|
|
76
|
+
// Get all current state keys
|
|
77
|
+
console.debug('All states:', state.list)
|
|
78
|
+
|
|
79
|
+
// ============================================
|
|
80
|
+
// REMOVE STATE
|
|
81
|
+
// ============================================
|
|
82
|
+
|
|
83
|
+
// Remove specific state
|
|
84
|
+
state.remove('items')
|
|
85
|
+
|
|
86
|
+
// Remove all states
|
|
87
|
+
// state.removeAll()
|
|
88
|
+
|
|
89
|
+
console.debug('State advanced example complete!')
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Store Advanced Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows advanced store (localStorage) operations.
|
|
5
|
+
*
|
|
6
|
+
* Run: npx ts-node examples/store-advanced.ts
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import 'memorio'
|
|
10
|
+
|
|
11
|
+
// ============================================
|
|
12
|
+
// CHECK PERSISTENCE
|
|
13
|
+
// ============================================
|
|
14
|
+
|
|
15
|
+
console.debug('=== Store Persistence Check ===')
|
|
16
|
+
console.debug('Is persistent (survives restart):', store.isPersistent)
|
|
17
|
+
// In browser: true (localStorage)
|
|
18
|
+
// In Node.js/Deno: false (memory fallback)
|
|
19
|
+
|
|
20
|
+
if (!store.isPersistent) {
|
|
21
|
+
console.debug('⚠️ Warning: Using in-memory storage. Data will be lost on restart!')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ============================================
|
|
25
|
+
// PERSIST USER PREFERENCES
|
|
26
|
+
// ============================================
|
|
27
|
+
|
|
28
|
+
// Save user preferences
|
|
29
|
+
store.set('preferences', {
|
|
30
|
+
theme: 'dark',
|
|
31
|
+
language: 'en',
|
|
32
|
+
notifications: true,
|
|
33
|
+
fontSize: 16
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// CHECK AND LOAD PREFERENCES
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
const savedPrefs = store.get('preferences')
|
|
41
|
+
if (savedPrefs) {
|
|
42
|
+
console.debug('Loaded preferences:', savedPrefs)
|
|
43
|
+
} else {
|
|
44
|
+
console.debug('No preferences found, using defaults')
|
|
45
|
+
store.set('preferences', { theme: 'light', language: 'en' })
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================
|
|
49
|
+
// STORAGE QUOTA
|
|
50
|
+
// ============================================
|
|
51
|
+
|
|
52
|
+
// Get storage size
|
|
53
|
+
const currentSize = store.size()
|
|
54
|
+
console.debug('Current storage size:', currentSize, 'bytes')
|
|
55
|
+
|
|
56
|
+
// ============================================
|
|
57
|
+
// ALIAS METHODS
|
|
58
|
+
// ============================================
|
|
59
|
+
|
|
60
|
+
// store.delete() is alias for store.remove()
|
|
61
|
+
store.set('temp', 'value')
|
|
62
|
+
store.delete('temp')
|
|
63
|
+
|
|
64
|
+
// store.clearAll() is alias for store.removeAll()
|
|
65
|
+
// store.clearAll()
|
|
66
|
+
|
|
67
|
+
// ============================================
|
|
68
|
+
// ERROR HANDLING
|
|
69
|
+
// ============================================
|
|
70
|
+
|
|
71
|
+
// Try-catch for large data
|
|
72
|
+
try {
|
|
73
|
+
// Store large data
|
|
74
|
+
const largeData = {
|
|
75
|
+
items: Array(1000).fill(null).map((_, i) => ({ id: i, data: 'x'.repeat(100) }))
|
|
76
|
+
}
|
|
77
|
+
store.set('largeData', largeData)
|
|
78
|
+
console.debug('Large data stored successfully')
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('Storage full:', error)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============================================
|
|
84
|
+
// DATA SERIALIZATION
|
|
85
|
+
// ============================================
|
|
86
|
+
|
|
87
|
+
// Store supports all JSON-serializable types
|
|
88
|
+
store.set('string', 'hello')
|
|
89
|
+
store.set('number', 42)
|
|
90
|
+
store.set('boolean', true)
|
|
91
|
+
store.set('array', [1, 2, 3])
|
|
92
|
+
store.set('object', { nested: { value: 'deep' } })
|
|
93
|
+
store.set('null', null)
|
|
94
|
+
|
|
95
|
+
// Functions are not supported (logged as error)
|
|
96
|
+
store.set('function', () => { }) // logs: "It's not secure to store functions."
|
|
97
|
+
|
|
98
|
+
// ============================================
|
|
99
|
+
// PRACTICAL EXAMPLE: APP STATE
|
|
100
|
+
// ============================================
|
|
101
|
+
|
|
102
|
+
// Save app state
|
|
103
|
+
const appState = {
|
|
104
|
+
lastPage: '/dashboard',
|
|
105
|
+
sidebarOpen: true,
|
|
106
|
+
recentFiles: ['file1.txt', 'file2.pdf'],
|
|
107
|
+
lastSaved: Date.now()
|
|
108
|
+
}
|
|
109
|
+
store.set('appState', appState)
|
|
110
|
+
|
|
111
|
+
// Load on next visit
|
|
112
|
+
const restored = store.get('appState')
|
|
113
|
+
if (restored) {
|
|
114
|
+
console.debug('Restored app state:', restored.lastPage)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.debug('Store advanced example complete!')
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MEMORIO TYPED STORES + SCHEMA VALIDATION - LIVE EXAMPLES
|
|
3
|
+
*
|
|
4
|
+
* This file demonstrates every code snippet used in docs/markdown/SCHEMA.md
|
|
5
|
+
* and docs/markdown/TYPED.md. It is real, compiling TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* Primary usage: `import 'memorio'` (side-effect import) makes all globals
|
|
8
|
+
* available, including the `memorio` namespace object.
|
|
9
|
+
*
|
|
10
|
+
* @module memorio/examples/typed-and-schema
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import 'memorio'
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// TYPED STORES - compile-time type safety on the global `state` proxy
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
interface AppState {
|
|
20
|
+
user: { name: string; age: number; email: string }
|
|
21
|
+
theme: 'light' | 'dark'
|
|
22
|
+
items: string[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const app = memorio.typed<AppState>()
|
|
26
|
+
|
|
27
|
+
app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
28
|
+
app.theme = 'dark'
|
|
29
|
+
app.items = ['apple', 'banana']
|
|
30
|
+
|
|
31
|
+
state.user.name = 'Luigi'
|
|
32
|
+
state.counter = 100
|
|
33
|
+
const currentTheme: 'light' | 'dark' = app.theme
|
|
34
|
+
void currentTheme
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// SCHEMA VALIDATION - runtime guards that reject invalid state writes
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
memorio.registerSchema('user', {
|
|
41
|
+
type: 'object',
|
|
42
|
+
required: ['name', 'email'],
|
|
43
|
+
properties: {
|
|
44
|
+
name: { type: 'string', min: 1 },
|
|
45
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
46
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
memorio.registerSchema('theme', { enum: ['light', 'dark'] })
|
|
51
|
+
|
|
52
|
+
memorio.registerSchema('items', {
|
|
53
|
+
type: 'array',
|
|
54
|
+
validator: (val: any) => Array.isArray(val) && val.every((i: any) => typeof i === 'string')
|
|
55
|
+
? true
|
|
56
|
+
: 'items must be an array of strings'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
memorio.registerSchema('counter', (val: any) => {
|
|
60
|
+
if (typeof val !== 'number') return 'counter must be a number'
|
|
61
|
+
if (val < 0) return 'counter must be >= 0'
|
|
62
|
+
return true
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// These writes are rejected at runtime:
|
|
66
|
+
state.user = { name: 'Sara' } // missing 'email' - rejected
|
|
67
|
+
state.theme = 'purple' // not in enum - rejected
|
|
68
|
+
state.counter = -5 // fails custom validator - rejected
|
|
69
|
+
|
|
70
|
+
// These pass:
|
|
71
|
+
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 }
|
|
72
|
+
state.theme = 'light'
|
|
73
|
+
state.counter = 5
|
|
74
|
+
|
|
75
|
+
// Manual validation:
|
|
76
|
+
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
77
|
+
memorio.validate('theme', 'dark')
|
|
78
|
+
|
|
79
|
+
memorio.listSchemas()
|
|
80
|
+
memorio.unregisterSchema('counter')
|
|
81
|
+
|
|
82
|
+
// =============================================================================
|
|
83
|
+
// TYPED + SCHEMA - combine both for full safety
|
|
84
|
+
// =============================================================================
|
|
85
|
+
|
|
86
|
+
memorio.registerSchema('profile', {
|
|
87
|
+
type: 'object',
|
|
88
|
+
required: ['bio'],
|
|
89
|
+
properties: {
|
|
90
|
+
bio: { type: 'string', min: 1 },
|
|
91
|
+
avatar: { type: 'string' }
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
interface ProfileState {
|
|
96
|
+
profile: { bio: string; avatar?: string }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const typedProfile = memorio.typed<ProfileState>()
|
|
100
|
+
typedProfile.profile = { bio: 'Developer', avatar: 'pic.png' }
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio useObserver Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the useObserver hook for React.
|
|
5
|
+
* useObserver automatically tracks state changes and triggers re-renders.
|
|
6
|
+
*
|
|
7
|
+
* Note: This example requires React to be present in the environment.
|
|
8
|
+
* Run in a React environment.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import 'memorio'
|
|
12
|
+
|
|
13
|
+
// ============================================
|
|
14
|
+
// BASIC USEOBSERVER
|
|
15
|
+
// ============================================
|
|
16
|
+
|
|
17
|
+
// Example 1: Watch single value
|
|
18
|
+
useObserver(callback, [state.value])
|
|
19
|
+
|
|
20
|
+
function Counter() {
|
|
21
|
+
useObserver(() => {
|
|
22
|
+
console.debug('Count changed:', state.count)
|
|
23
|
+
}, [state.count])
|
|
24
|
+
|
|
25
|
+
return <div>{state.count} </div>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ============================================
|
|
29
|
+
// MULTIPLE DEPENDENCIES
|
|
30
|
+
// ============================================
|
|
31
|
+
|
|
32
|
+
// Example 2: Watch multiple values
|
|
33
|
+
function UserProfile() {
|
|
34
|
+
useObserver(() => {
|
|
35
|
+
console.debug('User or theme changed')
|
|
36
|
+
}, [state.user, state.theme])
|
|
37
|
+
|
|
38
|
+
return <div>{state.user.name} </div>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================
|
|
42
|
+
// AUTO-DISCOVERY
|
|
43
|
+
// ============================================
|
|
44
|
+
|
|
45
|
+
// Example 3: No dependencies - auto-discovers all state access
|
|
46
|
+
function AutoDiscovery() {
|
|
47
|
+
useObserver(() => {
|
|
48
|
+
// Automatically tracks state.user, state.items, state.count
|
|
49
|
+
console.debug('Changed:', state.user.name, state.items.length, state.count)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return <div>{state.user.name} </div>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================
|
|
56
|
+
// SYNC WITH USESTATE
|
|
57
|
+
// ============================================
|
|
58
|
+
|
|
59
|
+
// Example 4: Sync with useState
|
|
60
|
+
function SyncedComponent() {
|
|
61
|
+
const [localData, setLocalData] = useState(null)
|
|
62
|
+
|
|
63
|
+
useObserver((newValue) => {
|
|
64
|
+
setLocalData(newValue)
|
|
65
|
+
}, [state.data])
|
|
66
|
+
|
|
67
|
+
return <div>{localData} </div>
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ============================================
|
|
71
|
+
// PRACTICAL EXAMPLE
|
|
72
|
+
// ============================================
|
|
73
|
+
|
|
74
|
+
// Real-world React component example:
|
|
75
|
+
import 'memorio'
|
|
76
|
+
|
|
77
|
+
function TodoApp() {
|
|
78
|
+
// Track todos
|
|
79
|
+
useObserver(() => {
|
|
80
|
+
console.debug('Todos updated:', state.todos)
|
|
81
|
+
// Optionally save to store
|
|
82
|
+
store.set('todos', state.todos)
|
|
83
|
+
}, [state.todos])
|
|
84
|
+
|
|
85
|
+
// Track filter
|
|
86
|
+
useObserver(() => {
|
|
87
|
+
console.debug('Filter changed:', state.filter)
|
|
88
|
+
}, [state.filter])
|
|
89
|
+
|
|
90
|
+
const addTodo = (text) => {
|
|
91
|
+
state.todos = [...(state.todos || []), { text, done: false }]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const toggleTodo = (index) => {
|
|
95
|
+
const todos = [...state.todos]
|
|
96
|
+
todos[index].done = !todos[index].done
|
|
97
|
+
state.todos = todos
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const filteredTodos = (state.todos || []).filter(t =>
|
|
101
|
+
state.filter === 'all' ||
|
|
102
|
+
(state.filter === 'done' && t.done) ||
|
|
103
|
+
(state.filter === 'todo' && !t.done)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div>
|
|
108
|
+
<select
|
|
109
|
+
value={state.filter}
|
|
110
|
+
onChange={e => state.filter = e.target.value}
|
|
111
|
+
>
|
|
112
|
+
<option value="all" > All </option>
|
|
113
|
+
< option value="done" > Done </option>
|
|
114
|
+
< option value="todo" > Todo </option>
|
|
115
|
+
</select>
|
|
116
|
+
<ul>
|
|
117
|
+
{
|
|
118
|
+
filteredTodos.map((todo, i) => (
|
|
119
|
+
<li
|
|
120
|
+
key={i}
|
|
121
|
+
onClick={() => toggleTodo(i)}
|
|
122
|
+
style={{ textDecoration: todo.done ? 'line-through' : 'none' }
|
|
123
|
+
}
|
|
124
|
+
>
|
|
125
|
+
{todo.text}
|
|
126
|
+
</li>
|
|
127
|
+
))}
|
|
128
|
+
</ul>
|
|
129
|
+
< input
|
|
130
|
+
onKeyDown={e => {
|
|
131
|
+
if (e.key === 'Enter') {
|
|
132
|
+
addTodo(e.target.value)
|
|
133
|
+
e.target.value = ''
|
|
134
|
+
}
|
|
135
|
+
}}
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
console.debug('useObserver example - run in React environment!')
|