memorio 5.1.3 → 5.2.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.
- package/AGENTS.md +22 -13
- package/CHANGELOG.md +24 -9
- package/README.md +61 -19
- package/SECURITY-HARDENING.md +258 -0
- package/SECURITY.md +67 -3
- package/SUMMARY.md +55 -59
- package/adr/002-observer-semantics.md +1 -1
- package/adr/003-deep-mutation-semantics.md +1 -1
- package/adr/004-array-mutation-semantics.md +1 -1
- package/adr/010-logic-phase-0.md +42 -0
- package/adr/README.md +16 -11
- package/bin/cli.js +82 -60
- package/examples/acquired-knowledge.ts +174 -0
- package/examples/agent-memory-demo.ts +140 -0
- package/examples/sqlite-batched-writes.ts +60 -60
- package/examples/sync.ts +90 -90
- package/examples/useObserver.tsx +2 -2
- package/global.cjs +2026 -115
- package/global.js +2021 -116
- package/index.cjs +2026 -115
- package/index.d.ts +1 -0
- package/index.js +2021 -116
- package/llms.txt +135 -4
- package/markdown/EXTENSION_VSCODE_DESIGN.md +410 -0
- package/markdown/LOGIC.md +100 -0
- package/markdown/MEMORY-ATTACHMENT.md +17 -10
- package/markdown/MEMORY.md +404 -14
- package/markdown/MEM_FORMAT.md +313 -0
- package/markdown/SQLITE.md +2 -2
- package/markdown/STATE.md +27 -3
- package/markdown/SYNC.md +19 -15
- package/markdown/TEMPORAL.md +297 -0
- package/markdown/USEOBSERVER.md +7 -4
- package/modules/redux.cjs +1188 -38
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +1187 -38
- package/modules/redux.js.map +1 -1
- package/package.json +14 -5
- package/types/exports.d.ts +47 -3
- package/types/logic.d.ts +79 -0
- package/types/memorio.d.ts +60 -16
- package/types/memory.d.ts +118 -0
- package/types/session.d.ts +1 -4
- package/types/state.d.ts +19 -5
- package/types/store.d.ts +1 -4
- package/types/temporal.d.ts +95 -0
- package/types/useObserver.d.ts +6 -10
- package/vsix/memorio.vsix +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent-memory-demo.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: An AI agent that remembers user preferences across conversations.
|
|
5
|
+
*
|
|
6
|
+
* Demonstrates:
|
|
7
|
+
* - Conversation 1: agent remembers a user preference
|
|
8
|
+
* - Conversation 2: agent recalls the preference
|
|
9
|
+
* - Context generation: agent gets relevant context for a query
|
|
10
|
+
* - Temporal history: agent queries how state evolved
|
|
11
|
+
* - Provenance: agent explains why it knows what it knows
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createAgentMemory } from '../../agent'
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const agent = createAgentMemory('support-agent')
|
|
18
|
+
|
|
19
|
+
// Clear any prior state for a clean demo
|
|
20
|
+
await agent.clear()
|
|
21
|
+
|
|
22
|
+
// ─── Conversation 1 ───────────────────────────────────────────
|
|
23
|
+
console.log('=== Conversation 1 ===')
|
|
24
|
+
console.log('User: "I prefer PostgreSQL for databases."')
|
|
25
|
+
|
|
26
|
+
// Agent remembers the preference with metadata
|
|
27
|
+
await agent.remember('user.pref.database', 'PostgreSQL', {
|
|
28
|
+
type: 'preference',
|
|
29
|
+
confidence: 0.91,
|
|
30
|
+
source: 'conversation-1',
|
|
31
|
+
tags: ['database', 'infrastructure'],
|
|
32
|
+
ttl: null // persistent memory
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
console.log('Agent: "Got it. PostgreSQL it is."')
|
|
36
|
+
console.log('')
|
|
37
|
+
|
|
38
|
+
// ─── Conversation 2 ─────────────────────────────────────────
|
|
39
|
+
console.log('=== Conversation 2 (later) ===')
|
|
40
|
+
console.log('User: "What database did I say I preferred?"')
|
|
41
|
+
|
|
42
|
+
// Agent recalls the memory
|
|
43
|
+
const preference = await agent.recall('user.pref.database')
|
|
44
|
+
console.log(`Agent: "You said ${preference?.value}."`)
|
|
45
|
+
console.log('')
|
|
46
|
+
|
|
47
|
+
// ─── Context Generation ─────────────────────────────────────
|
|
48
|
+
console.log('=== Context for Agent Query ===')
|
|
49
|
+
console.log('User: "What databases do you know about for this project?"')
|
|
50
|
+
|
|
51
|
+
const context = await agent.context({
|
|
52
|
+
tags: ['database'],
|
|
53
|
+
types: ['preference', 'decision', 'fact'],
|
|
54
|
+
minConfidence: 0.5,
|
|
55
|
+
maxEntries: 10
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
console.log('Agent Context (relevant memories):')
|
|
59
|
+
for (const entry of context) {
|
|
60
|
+
console.log(` - ${entry.key}: ${entry.value}`)
|
|
61
|
+
console.log(` type: ${entry.type}, confidence: ${entry.confidence}, age: ${Math.round(entry.age / 1000)}s`)
|
|
62
|
+
console.log(` tags: ${entry.tags.join(', ')}`)
|
|
63
|
+
}
|
|
64
|
+
console.log('')
|
|
65
|
+
|
|
66
|
+
// ─── Memory Evolution (supersedence) ─────────────────────────
|
|
67
|
+
console.log('=== Memory Evolution ===')
|
|
68
|
+
|
|
69
|
+
// Update the preference (creates superseded entries)
|
|
70
|
+
await agent.remember('user.pref.database', 'MySQL', {
|
|
71
|
+
type: 'preference',
|
|
72
|
+
confidence: 0.85,
|
|
73
|
+
source: 'conversation-3',
|
|
74
|
+
tags: ['database', 'infrastructure']
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
// Small delay to ensure unique timestamps for superseded entries
|
|
78
|
+
await new Promise(resolve => setTimeout(resolve, 5))
|
|
79
|
+
|
|
80
|
+
await agent.remember('user.pref.database', 'SQLite', {
|
|
81
|
+
type: 'preference',
|
|
82
|
+
confidence: 0.92,
|
|
83
|
+
source: 'conversation-4',
|
|
84
|
+
tags: ['database', 'infrastructure']
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Query the latest state
|
|
88
|
+
const latest = await agent.recall('user.pref.database')
|
|
89
|
+
console.log(`Latest preference: ${latest?.value} (confidence: ${latest?.confidence})`)
|
|
90
|
+
|
|
91
|
+
// Query all entries including superseded
|
|
92
|
+
const mem = (globalThis as any).memorio.memory
|
|
93
|
+
const allEntries = await mem._loadAll()
|
|
94
|
+
const keyHistory = allEntries
|
|
95
|
+
.filter((e: any) => e.key === `agent:support-agent:user.pref.database`)
|
|
96
|
+
.sort((a: any, b: any) => a.createdAt - b.createdAt)
|
|
97
|
+
|
|
98
|
+
console.log('History of this decision:')
|
|
99
|
+
for (const entry of keyHistory) {
|
|
100
|
+
const statusIcon = entry.status === 'superseded' ? ' (superseded)' : ' (current)'
|
|
101
|
+
console.log(` - ${new Date(entry.createdAt).toISOString()}: ${entry.value}${statusIcon}`)
|
|
102
|
+
console.log(` source: ${entry.source}, confidence: ${entry.confidence}`)
|
|
103
|
+
}
|
|
104
|
+
console.log('')
|
|
105
|
+
|
|
106
|
+
// ─── Provenance / Explain ───────────────────────────────────
|
|
107
|
+
console.log('=== Provenance ===')
|
|
108
|
+
console.log('User: "Why do you know SQLite?"')
|
|
109
|
+
|
|
110
|
+
const current = await agent.recall('user.pref.database')
|
|
111
|
+
console.log(`Agent: "I know SQLite because:"`)
|
|
112
|
+
console.log(` - Source: ${current?.source}`)
|
|
113
|
+
console.log(` - Confidence: ${current?.confidence}`)
|
|
114
|
+
console.log(` - Stored at: ${new Date(current!.createdAt).toISOString()}`)
|
|
115
|
+
console.log(` - Tags: ${current?.tags.join(', ')}`)
|
|
116
|
+
console.log('')
|
|
117
|
+
|
|
118
|
+
// Show why we know about PostgreSQL (even though it was superseded)
|
|
119
|
+
const postgresqlEntry = keyHistory.find((e: any) => e.value === 'PostgreSQL')
|
|
120
|
+
if (postgresqlEntry) {
|
|
121
|
+
console.log(`Agent: "PostgreSQL was previously known because:"`)
|
|
122
|
+
console.log(` - Source: ${postgresqlEntry.source}`)
|
|
123
|
+
console.log(` - Confidence: ${postgresqlEntry.confidence}`)
|
|
124
|
+
console.log(` - Status: ${postgresqlEntry.status}`)
|
|
125
|
+
console.log(` - Stored at: ${new Date(postgresqlEntry.createdAt).toISOString()}`)
|
|
126
|
+
console.log(` - Tags: ${postgresqlEntry.tags.join(', ')}`)
|
|
127
|
+
}
|
|
128
|
+
console.log('')
|
|
129
|
+
|
|
130
|
+
// ─── Stats ─────────────────────────────────────────────────
|
|
131
|
+
console.log('=== Memory Stats ===')
|
|
132
|
+
const stats = await agent.stats()
|
|
133
|
+
console.log(`Total memories: ${stats.total}`)
|
|
134
|
+
console.log(`By scope:`, stats.byScope)
|
|
135
|
+
console.log(`By type:`, stats.byType)
|
|
136
|
+
|
|
137
|
+
console.log('\n=== Demo Complete ===')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main().catch(console.error)
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sqlite-batched-writes.ts
|
|
3
|
-
*
|
|
4
|
-
* Scenario: importing or writing many rows into memorio's sqlite layer.
|
|
5
|
-
*
|
|
6
|
-
* With persistence enabled, each write is recorded in an append-only journal
|
|
7
|
-
* (incremental). Periodic checkpoints
|
|
8
|
-
* write-idle
|
|
9
|
-
* per-row loop triggers a checkpoint on every iteration, which is expensive.
|
|
10
|
-
*/
|
|
11
|
-
import { memorio } from 'memorio'
|
|
12
|
-
|
|
13
|
-
interface UserRow {
|
|
14
|
-
id: number
|
|
15
|
-
name: string
|
|
16
|
-
role: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function importUsers(users: UserRow[]) {
|
|
20
|
-
await memorio.sqlite.ready
|
|
21
|
-
await memorio.sqlite.db.create('app', { persistence: true })
|
|
22
|
-
|
|
23
|
-
await memorio.sqlite.query.run(
|
|
24
|
-
'app',
|
|
25
|
-
`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
|
|
26
|
-
)
|
|
27
|
-
|
|
28
|
-
// Bad: this would trigger a full checkpoint after every single insert.
|
|
29
|
-
//
|
|
30
|
-
// for (const user of users) {
|
|
31
|
-
// await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
|
|
32
|
-
// await memorio.sqlite.db.flush('app') // <- don't do this per row
|
|
33
|
-
// }
|
|
34
|
-
|
|
35
|
-
// Good: run every insert first, flush once after the batch. Writes are
|
|
36
|
-
// journaled incrementally (cheap); the checkpoint (full snapshot) happens
|
|
37
|
-
// once at the end.
|
|
38
|
-
for (const user of users) {
|
|
39
|
-
await memorio.sqlite.query.run(
|
|
40
|
-
'app',
|
|
41
|
-
`INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
|
|
42
|
-
[user.id, user.name, user.role]
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Flush explicitly, once, after the whole batch
|
|
47
|
-
// version's API for the exact flush/persist call name if it differs from
|
|
48
|
-
// automatic persistence-on-close.
|
|
49
|
-
const admins = await memorio.sqlite.query.select(
|
|
50
|
-
'app',
|
|
51
|
-
`SELECT * FROM users WHERE role = ?`,
|
|
52
|
-
['admin']
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
return admins
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// For read-heavy or scratch-space use where durability doesn't matter,
|
|
59
|
-
// skip { persistence: true } entirely - sqlite runs in memory by default,
|
|
60
|
-
// which avoids journal and checkpoint overhead altogether.
|
|
1
|
+
/**
|
|
2
|
+
* sqlite-batched-writes.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: importing or writing many rows into memorio's sqlite layer.
|
|
5
|
+
*
|
|
6
|
+
* With persistence enabled, each write is recorded in an append-only journal
|
|
7
|
+
* (incremental). Periodic checkpoints - every 50 operations or 250 ms of
|
|
8
|
+
* write-idle - take a full database snapshot. Calling `flush()` inside a
|
|
9
|
+
* per-row loop triggers a checkpoint on every iteration, which is expensive.
|
|
10
|
+
*/
|
|
11
|
+
import { memorio } from 'memorio'
|
|
12
|
+
|
|
13
|
+
interface UserRow {
|
|
14
|
+
id: number
|
|
15
|
+
name: string
|
|
16
|
+
role: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function importUsers(users: UserRow[]) {
|
|
20
|
+
await memorio.sqlite.ready
|
|
21
|
+
await memorio.sqlite.db.create('app', { persistence: true })
|
|
22
|
+
|
|
23
|
+
await memorio.sqlite.query.run(
|
|
24
|
+
'app',
|
|
25
|
+
`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, role TEXT)`
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// Bad: this would trigger a full checkpoint after every single insert.
|
|
29
|
+
//
|
|
30
|
+
// for (const user of users) {
|
|
31
|
+
// await memorio.sqlite.query.run('app', 'INSERT INTO users ...', [...])
|
|
32
|
+
// await memorio.sqlite.db.flush('app') // <- don't do this per row
|
|
33
|
+
// }
|
|
34
|
+
|
|
35
|
+
// Good: run every insert first, flush once after the batch. Writes are
|
|
36
|
+
// journaled incrementally (cheap); the checkpoint (full snapshot) happens
|
|
37
|
+
// once at the end.
|
|
38
|
+
for (const user of users) {
|
|
39
|
+
await memorio.sqlite.query.run(
|
|
40
|
+
'app',
|
|
41
|
+
`INSERT INTO users (id, name, role) VALUES (?, ?, ?)`,
|
|
42
|
+
[user.id, user.name, user.role]
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Flush explicitly, once, after the whole batch - check the installed
|
|
47
|
+
// version's API for the exact flush/persist call name if it differs from
|
|
48
|
+
// automatic persistence-on-close.
|
|
49
|
+
const admins = await memorio.sqlite.query.select(
|
|
50
|
+
'app',
|
|
51
|
+
`SELECT * FROM users WHERE role = ?`,
|
|
52
|
+
['admin']
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
return admins
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// For read-heavy or scratch-space use where durability doesn't matter,
|
|
59
|
+
// skip { persistence: true } entirely - sqlite runs in memory by default,
|
|
60
|
+
// which avoids journal and checkpoint overhead altogether.
|
package/examples/sync.ts
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* sync.ts
|
|
3
|
-
*
|
|
4
|
-
* Scenario: an app using `memorio.memory` that wants to survive being
|
|
5
|
-
* offline (data is born local, in the journal, and always readable) and
|
|
6
|
-
* mirror itself to a backend when a connection is available.
|
|
7
|
-
*
|
|
8
|
-
* The cloud is a transport/persistence provider here, never the source of
|
|
9
|
-
* truth - sync mirrors the local journal, it doesn't replace it. Provide
|
|
10
|
-
* your own `SyncProvider`; memorio doesn't ship one.
|
|
11
|
-
*/
|
|
12
|
-
import { memorio } from 'memorio'
|
|
13
|
-
|
|
14
|
-
interface MemoryEntry {
|
|
15
|
-
key: string
|
|
16
|
-
value: unknown
|
|
17
|
-
confidence?: number
|
|
18
|
-
source?: string
|
|
19
|
-
scope?: '
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface SyncProvider {
|
|
23
|
-
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
24
|
-
pull?(since?: number): Promise<MemoryEntry[]>
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const backendProvider: SyncProvider = {
|
|
29
|
-
push: ops =>
|
|
30
|
-
fetch('/api/sync', {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
body: JSON.stringify(ops),
|
|
33
|
-
headers: { 'Content-Type': 'application/json' }
|
|
34
|
-
}).then(r => r.json()),
|
|
35
|
-
|
|
36
|
-
pull: since => fetch(`/api/sync?since=${since ?? 0}`).then(r => r.json()),
|
|
37
|
-
|
|
38
|
-
// Higher-confidence entry wins locally; for 'shared' scope the provider
|
|
39
|
-
// (i.e. your backend) makes the final call instead.
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function configureSync(namespace: string) {
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
memorio.memory.configure({
|
|
47
|
-
namespace, // e.g. `user:123:device:abc` - partitions the local journal
|
|
48
|
-
provider: backendProvider,
|
|
49
|
-
auto: true // auto-replay the journal on focus/online (default: true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
await memorio.memory.ready
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export async function rememberAndSync(key: string, value: unknown) {
|
|
56
|
-
// Local-first: this resolves immediately from the local store, no
|
|
57
|
-
// network round-trip required.
|
|
58
|
-
await memorio.memory.remember(key, value, { scope: '
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
// The journal: the durable op log that drives cloud reconciliation
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
export async function inspectPendingOps() {
|
|
66
|
-
const pending = await memorio.memory.journal.pending()
|
|
67
|
-
console.debug('Operations waiting to sync:', pending)
|
|
68
|
-
return pending
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export async function forceReplay() {
|
|
72
|
-
// Pushes pending() to the provider, marks synced entries, and pulls if
|
|
73
|
-
// the provider implements it. Normally triggered automatically (`auto:
|
|
74
|
-
// true`) on focus/online - call this manually to sync on demand instead.
|
|
75
|
-
const ack = await memorio.memory.journal.replay()
|
|
76
|
-
console.debug('Sync result:', ack)
|
|
77
|
-
return ack
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function resetJournal() {
|
|
81
|
-
// Wipes the current namespace's journal only - does not touch the
|
|
82
|
-
// memory entries themselves, just the pending sync log.
|
|
83
|
-
await memorio.memory.journal.clear()
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Usage:
|
|
87
|
-
// await configureSync('user:123:device:abc')
|
|
88
|
-
// await rememberAndSync('user.language', 'Italian')
|
|
89
|
-
// await inspectPendingOps()
|
|
90
|
-
// await forceReplay()
|
|
1
|
+
/**
|
|
2
|
+
* sync.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: an app using `memorio.memory` that wants to survive being
|
|
5
|
+
* offline (data is born local, in the journal, and always readable) and
|
|
6
|
+
* mirror itself to a backend when a connection is available.
|
|
7
|
+
*
|
|
8
|
+
* The cloud is a transport/persistence provider here, never the source of
|
|
9
|
+
* truth - sync mirrors the local journal, it doesn't replace it. Provide
|
|
10
|
+
* your own `SyncProvider`; memorio doesn't ship one.
|
|
11
|
+
*/
|
|
12
|
+
import { memorio } from 'memorio'
|
|
13
|
+
|
|
14
|
+
interface MemoryEntry {
|
|
15
|
+
key: string
|
|
16
|
+
value: unknown
|
|
17
|
+
confidence?: number
|
|
18
|
+
source?: string
|
|
19
|
+
scope?: 'hot' | 'session' | 'local' | 'durable'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface SyncProvider {
|
|
23
|
+
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
24
|
+
pull?(since?: number): Promise<MemoryEntry[]>
|
|
25
|
+
resolveConflict?(local: MemoryEntry, remote: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const backendProvider: SyncProvider = {
|
|
29
|
+
push: ops =>
|
|
30
|
+
fetch('/api/sync', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
body: JSON.stringify(ops),
|
|
33
|
+
headers: { 'Content-Type': 'application/json' }
|
|
34
|
+
}).then(r => r.json()),
|
|
35
|
+
|
|
36
|
+
pull: since => fetch(`/api/sync?since=${since ?? 0}`).then(r => r.json()),
|
|
37
|
+
|
|
38
|
+
// Higher-confidence entry wins locally; for 'shared' scope the provider
|
|
39
|
+
// (i.e. your backend) makes the final call instead.
|
|
40
|
+
resolveConflict: async (local, remote) => (local.confidence ?? 0) >= 0.8 ? 'local' : 'remote'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function configureSync(namespace: string) {
|
|
44
|
+
// Namespace partitions the journal; only entries written while sync is
|
|
45
|
+
// configured (via configure()) flow through the provider below.
|
|
46
|
+
memorio.memory.configure({
|
|
47
|
+
namespace, // e.g. `user:123:device:abc` - partitions the local journal
|
|
48
|
+
provider: backendProvider,
|
|
49
|
+
auto: true // auto-replay the journal on focus/online (default: true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await memorio.memory.journal.status() // substrate is 'store'; journal is ready
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function rememberAndSync(key: string, value: unknown) {
|
|
56
|
+
// Local-first: this resolves immediately from the local store, no
|
|
57
|
+
// network round-trip required.
|
|
58
|
+
await memorio.memory.remember(key, value, { scope: 'local', source: 'app' })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// The journal: the durable op log that drives cloud reconciliation
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
export async function inspectPendingOps() {
|
|
66
|
+
const pending = await memorio.memory.journal.pending()
|
|
67
|
+
console.debug('Operations waiting to sync:', pending)
|
|
68
|
+
return pending
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function forceReplay() {
|
|
72
|
+
// Pushes pending() to the provider, marks synced entries, and pulls if
|
|
73
|
+
// the provider implements it. Normally triggered automatically (`auto:
|
|
74
|
+
// true`) on focus/online - call this manually to sync on demand instead.
|
|
75
|
+
const ack = await memorio.memory.journal.replay()
|
|
76
|
+
console.debug('Sync result:', ack)
|
|
77
|
+
return ack
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function resetJournal() {
|
|
81
|
+
// Wipes the current namespace's journal only - does not touch the
|
|
82
|
+
// memory entries themselves, just the pending sync log.
|
|
83
|
+
await memorio.memory.journal.clear()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Usage:
|
|
87
|
+
// await configureSync('user:123:device:abc')
|
|
88
|
+
// await rememberAndSync('user.language', 'Italian')
|
|
89
|
+
// await inspectPendingOps()
|
|
90
|
+
// await forceReplay()
|
package/examples/useObserver.tsx
CHANGED
|
@@ -58,8 +58,8 @@ function AutoDiscovery() {
|
|
|
58
58
|
function SyncedComponent() {
|
|
59
59
|
const [localData, setLocalData] = useState(null)
|
|
60
60
|
|
|
61
|
-
useObserver((
|
|
62
|
-
setLocalData(
|
|
61
|
+
useObserver(() => {
|
|
62
|
+
setLocalData(state.data)
|
|
63
63
|
}, [state.data])
|
|
64
64
|
|
|
65
65
|
return <div>{localData}</div>
|