memorio 4.7.1 → 4.8.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/README.md +447 -34
- package/SECURITY.md +10 -10
- package/SUMMARY.md +21 -0
- package/index.cjs +170 -31
- package/index.d.ts +4 -0
- package/index.js +125 -8
- package/llms.txt +119 -28
- package/markdown/CHANGELOG.md +8 -8
- package/markdown/HISTORY.md +192 -0
- package/markdown/INSPECT.md +116 -0
- package/markdown/MEMORY-ATTACHMENT.md +95 -0
- package/markdown/MEMORY.md +155 -0
- package/markdown/SCHEMA.md +169 -0
- package/markdown/SECURITY.md +1 -1
- package/markdown/SQLITE.md +181 -0
- package/markdown/SYNC.md +170 -0
- package/markdown/TYPED.md +158 -0
- package/package.json +15 -9
- package/types/exports.d.ts +27 -0
- package/types/history.d.ts +27 -0
- package/types/inspect.d.ts +14 -0
- package/types/memorio.d.ts +77 -2
- package/types/memory.d.ts +127 -0
- package/types/schema.d.ts +53 -0
- package/types/sqlite.d.ts +35 -0
package/markdown/SYNC.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Synchronization & Cloud (optional)
|
|
2
|
+
|
|
3
|
+
`memorio.memory` is **local-first**. Data is created and served from the device;
|
|
4
|
+
the cloud is only ever a **transport/persistence provider**, never the source of
|
|
5
|
+
truth. Enabling sync does not replace local storage — it *mirrors* it.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
memorio
|
|
9
|
+
│
|
|
10
|
+
┌────────┴────────┐
|
|
11
|
+
│ Memory Engine │
|
|
12
|
+
└────────┬────────┘
|
|
13
|
+
┌────────────┼────────────┐
|
|
14
|
+
▼ ▼ ▼
|
|
15
|
+
local SQLite cloud
|
|
16
|
+
memory durable sync
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 1. The rule: the data is born local
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
memorio.memory.remember('user.language', 'Italian', { scope: 'local' })
|
|
23
|
+
// ↓ local first
|
|
24
|
+
// store / sessionStorage / IndexedDB / sql.js
|
|
25
|
+
// ↓ sync / push (when online)
|
|
26
|
+
// cloud provider
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The cloud therefore does not **replace** memory: it **replicates** it. This
|
|
30
|
+
gives you: offline-first, lowest latency, data available immediately,
|
|
31
|
+
synchronization when online, multi-device, multi-user, centralized persistence.
|
|
32
|
+
|
|
33
|
+
We deliberately do **not** provide:
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
// ❌ two mental models
|
|
37
|
+
memory.cloud.save(...)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Instead:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
memorio.memory.remember('user.language', 'Italian')
|
|
44
|
+
// and a single configuration point:
|
|
45
|
+
memorio.memory.configure({ sync: { provider: myCloudProvider, namespace: '…' } })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 2. Scopes (isolation, not a security boundary)
|
|
49
|
+
|
|
50
|
+
| Scope | Lifetime | Syncs by default |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `'device'` | this browser/device only | no (sticky) |
|
|
53
|
+
| `'user'` | follows the user across devices | yes (requires provider + namespace) |
|
|
54
|
+
| `'shared'` | shared across users / tenant | yes (requires provider + namespace) |
|
|
55
|
+
|
|
56
|
+
> As with `memorio.createContext`, **scoping is a naming convention, not a
|
|
57
|
+
> security boundary.** Enforce real isolation server-side.
|
|
58
|
+
|
|
59
|
+
## 3. SQLite as the local durable store
|
|
60
|
+
|
|
61
|
+
SQLite (`sql.js`) is **in-memory by default** (volatie per page load). It becomes
|
|
62
|
+
the durable journal/value store when you opt in:
|
|
63
|
+
|
|
64
|
+
- `sqlite.config({ persistence: true })` / `sqlite.db.create('app', { persistence: true })`
|
|
65
|
+
snapshot the database to `store` (localStorage) and restore it on reopen.
|
|
66
|
+
- Writes are snapshotted via sql.js `updateHook` (debounced).
|
|
67
|
+
- `sqlite.db.persist(name)` forces an immediate save; `sqlite.db.close(name)`
|
|
68
|
+
flushes + closes; `sqlite.db.download(name, file?)` triggers a browser
|
|
69
|
+
`.sqlite` download (dev convenience).
|
|
70
|
+
|
|
71
|
+
See `docs/markdown/SQLITE.md` for the full SQLite reference.
|
|
72
|
+
|
|
73
|
+
## 4. The local operation journal
|
|
74
|
+
|
|
75
|
+
The **sync journal** is the durable op log that drives cloud reconciliation.
|
|
76
|
+
It is persisted on `store` (localStorage) — **not** on an in-memory sql.js db,
|
|
77
|
+
because pending operations must survive a refresh for offline-first to work.
|
|
78
|
+
|
|
79
|
+
| Method | Returns | Notes |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `memory.journal.append(entry, operation)` | `Promise<MemoryEntry>` | records `remember\|update\|forget\|expire\|confirm\|supersede` with `sync:'pending'` |
|
|
82
|
+
| `memory.journal.pending()` | `Promise<MemoryEntry[]>` | rows where `sync != 'synced'`, for the current namespace |
|
|
83
|
+
| `memory.journal.markSynced(ids)` | `Promise<number>` | advances rows to `synced` (namespace-scoped) |
|
|
84
|
+
| `memory.journal.get(id)` | `Promise<MemoryEntry \| null>` | single entry, namespace-scoped |
|
|
85
|
+
| `memory.journal.clear()` | `Promise<void>` | wipes the current namespace's journal |
|
|
86
|
+
| `memory.journal.replay()` | `Promise<SyncAck>` | pushes `pending()` to the provider, marks synced, optional `pull` |
|
|
87
|
+
| `memory.journal.status()` | `Promise<'store'>` | the substrate in use |
|
|
88
|
+
|
|
89
|
+
We sync **operations of memory**, never a raw database dump:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
user A device A
|
|
93
|
+
remember X ─────► local ─────► sync ─────► cloud
|
|
94
|
+
forget Z ──────► local ─────► sync ─────► cloud
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 5. Conflict resolution
|
|
98
|
+
|
|
99
|
+
The cloud must not simply say "last write wins." Memorio tags every entry with:
|
|
100
|
+
|
|
101
|
+
- `confidence` (0–1, user/system trust in the value)
|
|
102
|
+
- `lastConfirmedAt` / `updatedAt` (epoch ms)
|
|
103
|
+
- `version` (monotonic per-key counter)
|
|
104
|
+
- `source` / `scope`
|
|
105
|
+
|
|
106
|
+
Remote conflicts are surfaced as `sync:'conflict'` rows via
|
|
107
|
+
`journal.pending()`; the provider's `resolve(op)` hint decides locally. Example:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
Laptop: language=Italian, confidence=0.92
|
|
111
|
+
Phone: language=English, confidence=0.61
|
|
112
|
+
→ higher-confidence entry wins locally; the provider decides for shared scope.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 6. Configuring a backend
|
|
116
|
+
|
|
117
|
+
Sync is **opt-in**. You supply an application-owned `provider` that knows how to
|
|
118
|
+
talk to your backend (REST, WebSocket, Supabase, a custom agent server, …).
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
memorio.memory.configure({
|
|
122
|
+
namespace: 'user:123:device:abc', // tenant/user/device — partitions the journal
|
|
123
|
+
provider: {
|
|
124
|
+
push(ops) { return fetch('/api/sync', { method: 'POST', body: JSON.stringify(ops), headers: authHeaders }) }
|
|
125
|
+
pull(since) { return fetch(`/api/sync?since=${since}`).then(r => r.json()) }
|
|
126
|
+
resolve(op) { return op.confidence >= 0.8 ? 'local' : 'remote' }
|
|
127
|
+
},
|
|
128
|
+
auto: true // auto-replay on focus/online (default true)
|
|
129
|
+
})
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
interface SyncProvider {
|
|
134
|
+
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
135
|
+
pull?(since?: number): Promise<MemoryEntry[]>
|
|
136
|
+
resolve?(op: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`memorio.memory.ready` resolves once the local journal substrate is chosen.
|
|
141
|
+
|
|
142
|
+
## 7. Security (NIST / OWASP / NSA posture)
|
|
143
|
+
|
|
144
|
+
- **Memorio never handles credentials.** No passwords, tokens, or API keys are
|
|
145
|
+
read from or stored by memorio. Authentication/authorization live in your
|
|
146
|
+
`provider`/backend (OWASP A01: Broken Access Control).
|
|
147
|
+
- **Namespace isolation.** The journal is keyed by `namespace:id` at the storage
|
|
148
|
+
layer; there is **no API** to enumerate or open another namespace's journal. A
|
|
149
|
+
client holding a forged/fake namespace simply sees its own (empty) journal.
|
|
150
|
+
- **No dynamic code.** Journal entries are strictly JSON-round-tripped,
|
|
151
|
+
size-capped (10 MB/entry), and never `eval`'d. The sql.js loader never
|
|
152
|
+
`import()`s a bare specifier that could be hijacked at build time.
|
|
153
|
+
- **Trust boundary:** memorio owns the local durable copy + operation log; the
|
|
154
|
+
provider/backend owns remote-side auth and conflict resolution. Memorio
|
|
155
|
+
surfaces `conflict`/`error` rows; it does not fabricate a winner.
|
|
156
|
+
- **Data-at-rest (NSA/CISA).** memorio's `store`/`idb`/`sqlite` snapshots are
|
|
157
|
+
**not encrypted**. If you persist user data server-side or ship it through your
|
|
158
|
+
backend, encrypt it server-side with keys you manage — memorio treats the local
|
|
159
|
+
store as untrusted-from-the-browser and does not attest its own integrity.
|
|
160
|
+
|
|
161
|
+
## 8. Where data lives
|
|
162
|
+
|
|
163
|
+
| Substrate | API | Volatile? | Persistent? |
|
|
164
|
+
|---|---|---|---|
|
|
165
|
+
| in-memory `Proxy` | `state` | yes (per tab) | no |
|
|
166
|
+
| `localStorage` / Map | `store` | no | yes (browser) |
|
|
167
|
+
| `sessionStorage` / Map | `session` | no | per-tab (browser) |
|
|
168
|
+
| IndexedDB | `idb`, `memory` durable | no | yes |
|
|
169
|
+
| sql.js (WASM heap) | `sqlite` | **yes** | only with `persistence: true` (snapshot → `store`) |
|
|
170
|
+
| sync journal | `memory.journal` | no | yes (`store`) |
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Typed Stores - Memorio
|
|
2
|
+
|
|
3
|
+
> ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
|
|
4
|
+
|
|
5
|
+
`memorio.typed<T>()` returns the global `state` proxy cast to a TypeScript type `T`, giving you **compile-time** type safety on every access and mutation.
|
|
6
|
+
|
|
7
|
+
It's a **zero-runtime-cost** wrapper: the returned object is the *exact same* Proxy as `globalThis.state`, just with TypeScript types applied via a generic.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import 'memorio'
|
|
15
|
+
|
|
16
|
+
interface AppState {
|
|
17
|
+
user: { name: string; age: number; email: string }
|
|
18
|
+
theme: 'light' | 'dark'
|
|
19
|
+
items: string[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const app = memorio.typed<AppState>()
|
|
23
|
+
|
|
24
|
+
// Type-checked at compile time:
|
|
25
|
+
app.user = { name: 'Sara', age: 30, email: 'sara@test.com' }
|
|
26
|
+
app.theme = 'dark'
|
|
27
|
+
|
|
28
|
+
// TypeScript errors:
|
|
29
|
+
// app.user = { name: 42 } // age missing, name wrong type
|
|
30
|
+
// app.theme = 'purple' // not a valid literal
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Why use typed stores?
|
|
36
|
+
|
|
37
|
+
| Without typed | With `memorio.typed<T>()` |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `state.user = { name: 42 }` — runs silently, bug at runtime | `app.user = { name: 42 }` — TypeScript error at compile time |
|
|
40
|
+
| No autocomplete on `state.user.email` | Full IntelliSense: properties, types, method suggestions |
|
|
41
|
+
| Rename `user` to `profile` — no compiler warning anywhere | Every `app.user` access flagged as an error |
|
|
42
|
+
| AI-generated code lacks guardrails | AI gets autocomplete and type feedback inline |
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Combine with Schema Validation
|
|
47
|
+
|
|
48
|
+
Typed stores catch type errors at compile time; schema validation catches invalid values at runtime. Together they form a **defense-in-depth** strategy:
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import 'memorio'
|
|
52
|
+
|
|
53
|
+
interface ProfileState {
|
|
54
|
+
profile: { bio: string; avatar?: string }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const app = memorio.typed<ProfileState>()
|
|
58
|
+
|
|
59
|
+
memorio.registerSchema('profile', {
|
|
60
|
+
type: 'object',
|
|
61
|
+
required: ['bio'],
|
|
62
|
+
properties: {
|
|
63
|
+
bio: { type: 'string', min: 1 },
|
|
64
|
+
avatar: { type: 'string' }
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
app.profile = { bio: 'Developer', avatar: 'pic.png' } // ✅ type + schema pass
|
|
69
|
+
app.profile = { avatar: 'pic.png' } // ❌ TypeScript: bio missing
|
|
70
|
+
// ❌ Runtime: bio required
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
See [Schema Validation](SCHEMA.md) for runtime validation details.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Named import variant
|
|
78
|
+
|
|
79
|
+
`typed` is also available as a named export if you prefer explicit dependencies:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { typed } from 'memorio'
|
|
83
|
+
|
|
84
|
+
const app = typed<AppState>()
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The `memorio` namespace object is the same — `import 'memorio'` is the recommended entry, named exports are an alternative.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Full API
|
|
92
|
+
|
|
93
|
+
| Method | Parameters | Returns | Description |
|
|
94
|
+
|--------|-----------|---------|-------------|
|
|
95
|
+
| `memorio.typed<T>()` | Generic type `T` | `T` | Returns the global `state` proxy cast to `T` |
|
|
96
|
+
|
|
97
|
+
The returned object shares the same identity as `globalThis.state`:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const app = memorio.typed<AppState>()
|
|
101
|
+
console.debug(app === state) // true — same Proxy instance
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## React + typed stores
|
|
107
|
+
|
|
108
|
+
Pair with the `useObserver` hook for type-safe, reactive React components:
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import 'memorio'
|
|
112
|
+
import { useReducer } from 'react'
|
|
113
|
+
|
|
114
|
+
interface AppState {
|
|
115
|
+
user: { name: string; age: number }
|
|
116
|
+
theme: 'light' | 'dark'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const app = memorio.typed<AppState>()
|
|
120
|
+
|
|
121
|
+
function UserProfile() {
|
|
122
|
+
const [, forceUpdate] = useReducer(x => x + 1, 0)
|
|
123
|
+
|
|
124
|
+
useObserver(forceUpdate, [state.user.name])
|
|
125
|
+
|
|
126
|
+
return (
|
|
127
|
+
<div>
|
|
128
|
+
<h1>{app.user.name}</h1>
|
|
129
|
+
<span>Theme: {app.theme}</span>
|
|
130
|
+
</div>
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Best Practices
|
|
138
|
+
|
|
139
|
+
1. **Define your AppState at the root** of your app and import it everywhere:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// types/app-state.ts
|
|
143
|
+
export interface AppState {
|
|
144
|
+
user: { name: string; email: string }
|
|
145
|
+
theme: 'light' | 'dark'
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// anywhere in your app
|
|
151
|
+
import 'memorio'
|
|
152
|
+
import type { AppState } from '../types/app-state'
|
|
153
|
+
const app = memorio.typed<AppState>()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
2. **Layer schema validation on top** for runtime safety, especially for data coming from APIs or user input.
|
|
157
|
+
|
|
158
|
+
3. **Use alongside `memorio.help()`** to list available globals during development.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memorio",
|
|
3
3
|
"codeName": "memorio",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.8.0",
|
|
5
5
|
"description": "Memorio, State + Observer, Store and iDB for an easy life - Cross-platform compatible",
|
|
6
6
|
"main": "./index.cjs",
|
|
7
7
|
"browser": "./index.js",
|
|
@@ -34,8 +34,9 @@
|
|
|
34
34
|
"reactive",
|
|
35
35
|
"universal",
|
|
36
36
|
"cross-platform",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
37
|
+
"sqlite",
|
|
38
|
+
"sql.js",
|
|
39
|
+
"wasm",
|
|
39
40
|
"browser",
|
|
40
41
|
"frontend"
|
|
41
42
|
],
|
|
@@ -52,15 +53,15 @@
|
|
|
52
53
|
"markdown/**/*",
|
|
53
54
|
"types/**/*",
|
|
54
55
|
"COPYRIGHT.md",
|
|
55
|
-
"index.d.ts",
|
|
56
|
-
"index.cjs",
|
|
57
|
-
"index.js",
|
|
58
|
-
"LICENSE.md",
|
|
59
|
-
"llms.txt",
|
|
60
56
|
"README.md",
|
|
61
57
|
"FUNDING.yml",
|
|
62
58
|
"SECURITY.md",
|
|
63
|
-
"SUMMARY.md"
|
|
59
|
+
"SUMMARY.md",
|
|
60
|
+
"LICENSE.md",
|
|
61
|
+
"index.d.ts",
|
|
62
|
+
"index.cjs",
|
|
63
|
+
"index.js",
|
|
64
|
+
"llms.txt"
|
|
64
65
|
],
|
|
65
66
|
"publishConfig": {
|
|
66
67
|
"access": "public"
|
|
@@ -77,6 +78,11 @@
|
|
|
77
78
|
"overrides": {
|
|
78
79
|
"esbuild": "^0.25.0"
|
|
79
80
|
},
|
|
81
|
+
"peerDependenciesMeta": {
|
|
82
|
+
"sql.js": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
80
86
|
"exports": {
|
|
81
87
|
".": {
|
|
82
88
|
"import": "./index.js",
|
package/types/exports.d.ts
CHANGED
|
@@ -8,12 +8,16 @@
|
|
|
8
8
|
* @see index.ts (runtime re-export)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
/// <reference path="./schema.d.ts" />
|
|
12
|
+
/// <reference path="./history.d.ts" />
|
|
13
|
+
/// <reference path="./inspect.d.ts" />
|
|
11
14
|
/// <reference path="./memorio.d.ts" />
|
|
12
15
|
/// <reference path="./state.d.ts" />
|
|
13
16
|
/// <reference path="./store.d.ts" />
|
|
14
17
|
/// <reference path="./session.d.ts" />
|
|
15
18
|
/// <reference path="./cache.d.ts" />
|
|
16
19
|
/// <reference path="./idb.d.ts" />
|
|
20
|
+
/// <reference path="./sqlite.d.ts" />
|
|
17
21
|
/// <reference path="./observer.d.ts" />
|
|
18
22
|
/// <reference path="./useObserver.d.ts" />
|
|
19
23
|
|
|
@@ -23,6 +27,7 @@ export const store: _store
|
|
|
23
27
|
export const session: _session
|
|
24
28
|
export const cache: _cache
|
|
25
29
|
export const idb: _idb
|
|
30
|
+
export const sqlite: _sqlite
|
|
26
31
|
export const observer: _observer
|
|
27
32
|
export const useObserver: _useObserver
|
|
28
33
|
export const dispatch: _dispatch
|
|
@@ -40,6 +45,28 @@ export const isolate: _memorio['isolate']
|
|
|
40
45
|
export const message: (...args: unknown[]) => void
|
|
41
46
|
export const help: () => void
|
|
42
47
|
|
|
48
|
+
export const registerSchema: (path: string, schema: any) => void
|
|
49
|
+
export const validate: (path: string, value: any) => { valid: boolean; errors?: string[] }
|
|
50
|
+
export const unregisterSchema: (path: string) => boolean
|
|
51
|
+
export const listSchemas: () => string[]
|
|
52
|
+
export const typed: <T extends Record<string, any>>() => T
|
|
53
|
+
export const snapshot: () => Record<string, any>
|
|
54
|
+
export const diff: (snap: Record<string, any>) => Array<{ path: string; oldValue: any; newValue: any }>
|
|
55
|
+
export const undo: () => any
|
|
56
|
+
export const redo: () => any
|
|
57
|
+
export const canUndo: () => boolean
|
|
58
|
+
export const canRedo: () => boolean
|
|
59
|
+
export const rollback: (snap: Record<string, any>) => void
|
|
60
|
+
export const trace: () => any[]
|
|
61
|
+
export const enableHistory: (enabled?: boolean) => void
|
|
62
|
+
export const clearHistory: () => void
|
|
63
|
+
export const clearRedo: () => void
|
|
64
|
+
export const stateKeys: () => string[]
|
|
65
|
+
export const pathExists: (path: string) => boolean
|
|
66
|
+
export const stateType: (path: string) => string
|
|
67
|
+
export const stateGet: (path: string) => any
|
|
68
|
+
export const stateSchema: () => Array<{ path: string; type: string; defined: boolean }>
|
|
69
|
+
|
|
43
70
|
export const propertyName: (container: any, object: any) => string | null
|
|
44
71
|
export const internal: {
|
|
45
72
|
debug: boolean
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/// MEMORIO HISTORY TYPES
|
|
2
|
+
/// Ambient declarations for the snapshot / undo / redo / trace API.
|
|
3
|
+
|
|
4
|
+
///
|
|
5
|
+
// A single recorded state mutation.
|
|
6
|
+
///
|
|
7
|
+
interface _mutationRecord {
|
|
8
|
+
/** Dotted state path (e.g. 'user.name'). */
|
|
9
|
+
path: string
|
|
10
|
+
/** 'set' or 'delete'. */
|
|
11
|
+
action: 'set' | 'delete'
|
|
12
|
+
/** The new value written (for 'set'), undefined for 'delete'. */
|
|
13
|
+
newValue: any
|
|
14
|
+
/** The previous value before the mutation. */
|
|
15
|
+
previousValue: any
|
|
16
|
+
/** Epoch timestamp (ms) when the mutation occurred. */
|
|
17
|
+
timestamp: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
///
|
|
21
|
+
// Result of diffing a snapshot against current state.
|
|
22
|
+
///
|
|
23
|
+
interface _diffEntry {
|
|
24
|
+
path: string
|
|
25
|
+
oldValue: any
|
|
26
|
+
newValue: any
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/// MEMORIO INSPECT TYPES
|
|
2
|
+
/// Ambient declarations for the state introspection API.
|
|
3
|
+
|
|
4
|
+
///
|
|
5
|
+
// Schema report entry describing a single state path.
|
|
6
|
+
///
|
|
7
|
+
interface _stateSchemaEntry {
|
|
8
|
+
/** Dotted path relative to `state`. */
|
|
9
|
+
path: string
|
|
10
|
+
/** Runtime type: 'string', 'number', 'boolean', 'object', 'array', 'undefined'. */
|
|
11
|
+
type: string
|
|
12
|
+
/** Whether the path resolves to a defined value. */
|
|
13
|
+
defined: boolean
|
|
14
|
+
}
|
package/types/memorio.d.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MemoryEntry,
|
|
3
|
+
MemorySync,
|
|
4
|
+
MemoryOperation,
|
|
5
|
+
SyncProvider,
|
|
6
|
+
SyncConfig,
|
|
7
|
+
SyncAck,
|
|
8
|
+
SyncDirection,
|
|
9
|
+
RememberOptions,
|
|
10
|
+
RecallOptions,
|
|
11
|
+
ContextOptions,
|
|
12
|
+
ContextEntry,
|
|
13
|
+
MemoryStats
|
|
14
|
+
} from './memory'
|
|
15
|
+
|
|
1
16
|
/**
|
|
2
17
|
* Environment capabilities
|
|
3
18
|
*/
|
|
@@ -60,10 +75,70 @@ interface _memorio {
|
|
|
60
75
|
session: any
|
|
61
76
|
cache: any
|
|
62
77
|
}
|
|
63
|
-
|
|
78
|
+
help?: () => void
|
|
79
|
+
// Schema validation
|
|
80
|
+
registerSchema?: (path: string, schema: any) => void
|
|
81
|
+
validate?: (path: string, value: any) => { valid: boolean; errors?: string[] }
|
|
82
|
+
unregisterSchema?: (path: string) => boolean
|
|
83
|
+
listSchemas?: () => string[]
|
|
84
|
+
// Typed store
|
|
85
|
+
typed?: <T extends Record<string, any>>() => T
|
|
86
|
+
// History / snapshot / undo / redo / trace
|
|
87
|
+
snapshot?: () => Record<string, any>
|
|
88
|
+
diff?: (snap: Record<string, any>) => Array<{ path: string; oldValue: any; newValue: any }>
|
|
89
|
+
undo?: () => _mutationRecord | undefined
|
|
90
|
+
redo?: () => _mutationRecord | undefined
|
|
91
|
+
canUndo?: () => boolean
|
|
92
|
+
canRedo?: () => boolean
|
|
93
|
+
rollback?: (snap: Record<string, any>) => void
|
|
94
|
+
trace?: () => _mutationRecord[]
|
|
95
|
+
enableHistory?: (enabled?: boolean) => void
|
|
96
|
+
clearHistory?: () => void
|
|
97
|
+
clearRedo?: () => void
|
|
98
|
+
// Introspection
|
|
99
|
+
stateKeys?: () => string[]
|
|
100
|
+
pathExists?: (path: string) => boolean
|
|
101
|
+
stateType?: (path: string) => string
|
|
102
|
+
stateGet?: (path: string) => any
|
|
103
|
+
stateSchema?: () => Array<{ path: string; type: string; defined: boolean }>
|
|
104
|
+
memory?: MemoryAPI
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface MemoryAPI {
|
|
108
|
+
remember<T>(key: string, value: T, opts?: RememberOptions<T>): Promise<void>
|
|
109
|
+
recall<T>(query: string, opts?: RecallOptions): Promise<T | null>
|
|
110
|
+
update(key: string, value: any, opts?: Partial<RememberOptions>): Promise<void>
|
|
111
|
+
forget(key: string): Promise<void>
|
|
112
|
+
context(opts?: ContextOptions): Promise<ContextEntry[]>
|
|
113
|
+
stats(): Promise<MemoryStats>
|
|
114
|
+
forgetExpired(): Promise<number>
|
|
115
|
+
clear(): Promise<void>
|
|
116
|
+
/**
|
|
117
|
+
* Configure the optional cloud-sync layer. Supplying a `provider` (an
|
|
118
|
+
* application-owned object that knows how to talk to your backend) enables
|
|
119
|
+
* the local operation journal. The `namespace` (tenant/user/device)
|
|
120
|
+
* partitions the journal so contexts/tenants cannot read each other's
|
|
121
|
+
* operations. Memorio never handles credentials — auth lives in the
|
|
122
|
+
* provider/backend.
|
|
123
|
+
*/
|
|
124
|
+
configure(opts: SyncConfig): MemoryAPI
|
|
125
|
+
/** Local operation journal. Writes are namespaced and durable (SQLite when
|
|
126
|
+
* available, else localStorage). Use `replay()` to push `pending()` entries
|
|
127
|
+
* to the configured provider. */
|
|
128
|
+
journal: MemoryJournal
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface MemoryJournal {
|
|
132
|
+
append(entry: MemoryEntry, operation: MemoryOperation): Promise<MemoryEntry>
|
|
133
|
+
pending(): Promise<MemoryEntry[]>
|
|
134
|
+
markSynced(ids: string[]): Promise<number>
|
|
135
|
+
get(id: string): Promise<MemoryEntry | null>
|
|
136
|
+
clear(): Promise<void>
|
|
137
|
+
replay(): Promise<SyncAck>
|
|
138
|
+
status(): Promise<'store'>
|
|
64
139
|
}
|
|
65
140
|
|
|
66
|
-
type memorio = _memorio
|
|
141
|
+
type memorio = _memorio
|
|
67
142
|
declare var memorio: _memorio
|
|
68
143
|
|
|
69
144
|
interface GlobalMemorio {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export type MemoryScope = 'hot' | 'session' | 'local' | 'durable'
|
|
2
|
+
|
|
3
|
+
export type MemoryType =
|
|
4
|
+
| 'fact'
|
|
5
|
+
| 'preference'
|
|
6
|
+
| 'decision'
|
|
7
|
+
| 'task'
|
|
8
|
+
| 'context'
|
|
9
|
+
|
|
10
|
+
export type MemoryStatus = 'active' | 'obsolete' | 'superseded'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Logical operations recorded in the local sync journal.
|
|
14
|
+
* The cloud side only ever sees operations — never a raw dump of a substrate.
|
|
15
|
+
*/
|
|
16
|
+
export type MemoryOperation =
|
|
17
|
+
| 'remember'
|
|
18
|
+
| 'update'
|
|
19
|
+
| 'forget'
|
|
20
|
+
| 'expire'
|
|
21
|
+
| 'confirm'
|
|
22
|
+
| 'supersede'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Synchronization state of a journal entry against a remote/cloud backend.
|
|
26
|
+
*/
|
|
27
|
+
export type MemorySync = 'pending' | 'synced' | 'conflict' | 'error'
|
|
28
|
+
|
|
29
|
+
export type SyncDirection = 'up' | 'down' | 'both'
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Pluggable cloud/sync transport. Memorio never ships a credential flow:
|
|
33
|
+
* the application supplies a `provider` that knows how to talk to its backend
|
|
34
|
+
* (REST, WebSocket, Supabase, a custom agent server, ...). Memorio owns the
|
|
35
|
+
* local journal, the provider only moves operations.
|
|
36
|
+
*/
|
|
37
|
+
export interface SyncProvider {
|
|
38
|
+
/** Pushes pending local operations; returns ids that were accepted */
|
|
39
|
+
push(ops: MemoryEntry[]): Promise<{ synced: string[]; conflicts?: string[]; error?: string }>
|
|
40
|
+
/** Optional pull of remote operations newer than `since` (epoch ms) */
|
|
41
|
+
pull?(since?: number): Promise<MemoryEntry[]>
|
|
42
|
+
/** Optional conflict resolution hint: 'local' | 'remote' | 'merge' */
|
|
43
|
+
resolve?(op: MemoryEntry): Promise<'local' | 'remote' | 'merge'>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface SyncConfig {
|
|
47
|
+
/** Sync provider implementation (application supplied) */
|
|
48
|
+
provider?: SyncProvider
|
|
49
|
+
/** Which direction to sync; 'both' is the default */
|
|
50
|
+
direction?: SyncDirection
|
|
51
|
+
/** Auto-sync on every local write? defaults to true */
|
|
52
|
+
auto?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SyncAck {
|
|
56
|
+
synced: string[]
|
|
57
|
+
conflicts?: string[]
|
|
58
|
+
error?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface MemoryEntry<T = any> {
|
|
62
|
+
id: string
|
|
63
|
+
key: string
|
|
64
|
+
value: T
|
|
65
|
+
type: MemoryType
|
|
66
|
+
confidence: number
|
|
67
|
+
scope: MemoryScope
|
|
68
|
+
ttl?: number | null
|
|
69
|
+
tags: string[]
|
|
70
|
+
source?: string
|
|
71
|
+
status: MemoryStatus
|
|
72
|
+
createdAt: number
|
|
73
|
+
lastConfirmedAt: number
|
|
74
|
+
supersededId?: string | null
|
|
75
|
+
/** Monotonic per-key write counter, used for last-writer / conflict resolution */
|
|
76
|
+
version?: number
|
|
77
|
+
/** Epoch ms of the last mutation that touched this entry */
|
|
78
|
+
updatedAt?: number
|
|
79
|
+
/** Last logical operation that produced this entry */
|
|
80
|
+
operation?: MemoryOperation
|
|
81
|
+
/** Local-cloud sync state of the entry (undefined => not tracked for sync) */
|
|
82
|
+
sync?: MemorySync
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface RememberOptions<T = any> {
|
|
86
|
+
id?: string
|
|
87
|
+
type?: MemoryType
|
|
88
|
+
confidence?: number
|
|
89
|
+
scope?: MemoryScope
|
|
90
|
+
ttl?: number | null
|
|
91
|
+
tags?: string | string[]
|
|
92
|
+
source?: string
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface RecallOptions {
|
|
96
|
+
type?: MemoryType | MemoryType[]
|
|
97
|
+
tags?: string | string[]
|
|
98
|
+
minConfidence?: number
|
|
99
|
+
includeObsolete?: boolean
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ContextOptions {
|
|
103
|
+
tags?: string | string[]
|
|
104
|
+
types?: MemoryType | MemoryType[]
|
|
105
|
+
minConfidence?: number
|
|
106
|
+
maxEntries?: number
|
|
107
|
+
scopes?: MemoryScope[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ContextEntry {
|
|
111
|
+
key: string
|
|
112
|
+
value: any
|
|
113
|
+
type: MemoryType
|
|
114
|
+
confidence: number
|
|
115
|
+
scope: MemoryScope
|
|
116
|
+
tags: string[]
|
|
117
|
+
age: number
|
|
118
|
+
accessCount: number
|
|
119
|
+
lastAccessedAt: number
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface MemoryStats {
|
|
123
|
+
total: number
|
|
124
|
+
byScope: Record<MemoryScope, number>
|
|
125
|
+
byType: Partial<Record<MemoryType, number>>
|
|
126
|
+
expired: number
|
|
127
|
+
}
|