memorio 4.7.3 → 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.
@@ -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`) |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "memorio",
3
3
  "codeName": "memorio",
4
- "version": "4.7.3",
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
- "node",
38
- "deno",
37
+ "sqlite",
38
+ "sql.js",
39
+ "wasm",
39
40
  "browser",
40
41
  "frontend"
41
42
  ],
@@ -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",
@@ -17,6 +17,7 @@
17
17
  /// <reference path="./session.d.ts" />
18
18
  /// <reference path="./cache.d.ts" />
19
19
  /// <reference path="./idb.d.ts" />
20
+ /// <reference path="./sqlite.d.ts" />
20
21
  /// <reference path="./observer.d.ts" />
21
22
  /// <reference path="./useObserver.d.ts" />
22
23
 
@@ -26,6 +27,7 @@ export const store: _store
26
27
  export const session: _session
27
28
  export const cache: _cache
28
29
  export const idb: _idb
30
+ export const sqlite: _sqlite
29
31
  export const observer: _observer
30
32
  export const useObserver: _useObserver
31
33
  export const dispatch: _dispatch
@@ -1,5 +1,11 @@
1
1
  import type {
2
2
  MemoryEntry,
3
+ MemorySync,
4
+ MemoryOperation,
5
+ SyncProvider,
6
+ SyncConfig,
7
+ SyncAck,
8
+ SyncDirection,
3
9
  RememberOptions,
4
10
  RecallOptions,
5
11
  ContextOptions,
@@ -107,6 +113,29 @@ interface MemoryAPI {
107
113
  stats(): Promise<MemoryStats>
108
114
  forgetExpired(): Promise<number>
109
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'>
110
139
  }
111
140
 
112
141
  type memorio = _memorio
package/types/memory.d.ts CHANGED
@@ -9,6 +9,55 @@ export type MemoryType =
9
9
 
10
10
  export type MemoryStatus = 'active' | 'obsolete' | 'superseded'
11
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
+
12
61
  export interface MemoryEntry<T = any> {
13
62
  id: string
14
63
  key: string
@@ -23,6 +72,14 @@ export interface MemoryEntry<T = any> {
23
72
  createdAt: number
24
73
  lastConfirmedAt: number
25
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
26
83
  }
27
84
 
28
85
  export interface RememberOptions<T = any> {
@@ -0,0 +1,35 @@
1
+ /*!
2
+ memorio
3
+ Copyright (c) 2019 Dario Passariello <dariopassariello@gmail.com>
4
+ Licensed under MIT License, see
5
+ dario.passariello.ca
6
+ */
7
+
8
+ /**
9
+ * Memorio SQLite module.
10
+ *
11
+ * Backed by the optional `sql.js` package (SQLite compiled to WebAssembly).
12
+ * Lazily loaded in the browser; disabled in non-browser environments.
13
+ */
14
+ interface _sqlite {
15
+ /** Database connection tools (create, get, delete, list, size, export, import). */
16
+ db: any
17
+ /** SQL execution tools (run, select). */
18
+ query: any
19
+ /** CRUD shortcut helpers (set, get). */
20
+ data: any
21
+ /** Lazy-load / configure the sql.js engine (wasm loader, locateFile, etc.). */
22
+ config: (opts: any) => any
23
+ /** Promise that resolves once the sql.js engine has been initialized. */
24
+ ready: Promise<void> | null
25
+ /** True when running outside a supported browser environment. */
26
+ _disabled?: boolean
27
+ /** Human readable reason the module is disabled, when applicable. */
28
+ _warning?: string
29
+ [key: string]: any
30
+ }
31
+
32
+ declare var sqlite: _sqlite
33
+ type sqlite = _sqlite
34
+
35
+ declare var sqlbases: any[]