memorio 4.9.35 → 5.0.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.
Files changed (76) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +307 -359
  3. package/SECURITY.md +17 -1
  4. package/SUMMARY.md +59 -45
  5. package/adr/001-state-proxy-model.md +96 -0
  6. package/adr/002-observer-semantics.md +180 -0
  7. package/adr/003-deep-mutation-semantics.md +129 -0
  8. package/adr/004-array-mutation-semantics.md +128 -0
  9. package/adr/005-scheduler-contract.md +149 -0
  10. package/adr/006-context-isolation.md +92 -0
  11. package/adr/007-mutation-records.md +118 -0
  12. package/adr/008-transactions.md +106 -0
  13. package/adr/009-history-model.md +110 -0
  14. package/adr/README.md +46 -0
  15. package/adr/template.md +49 -0
  16. package/examples/basic.ts +115 -115
  17. package/examples/browser-vanilla.html +358 -358
  18. package/examples/cache.ts +72 -72
  19. package/examples/cross-platform-guards.ts +57 -57
  20. package/examples/history.ts +104 -0
  21. package/examples/idb.ts +109 -109
  22. package/examples/multi-tenant-context.ts +44 -44
  23. package/examples/node-server.ts +308 -308
  24. package/examples/observer.ts +60 -60
  25. package/examples/platform.ts +115 -115
  26. package/examples/react-app.tsx +362 -362
  27. package/examples/react-observer.tsx +63 -63
  28. package/examples/semantic-memory.ts +60 -60
  29. package/examples/session-advanced.ts +91 -91
  30. package/examples/sqlite-batched-writes.ts +57 -57
  31. package/examples/state-advanced.ts +89 -89
  32. package/examples/store-advanced.ts +117 -117
  33. package/examples/sync.ts +90 -0
  34. package/examples/typed-and-schema.ts +102 -100
  35. package/examples/useObserver.tsx +140 -141
  36. package/global.cjs +4594 -0
  37. package/global.d.ts +8 -0
  38. package/global.js +4532 -0
  39. package/index.cjs +700 -678
  40. package/index.d.ts +1 -0
  41. package/index.js +680 -677
  42. package/llms.txt +72 -4
  43. package/markdown/AUDIT-REPORT.md +135 -0
  44. package/markdown/CACHE.md +100 -0
  45. package/markdown/CHANGELOG.md +243 -0
  46. package/markdown/DEVTOOLS.md +129 -0
  47. package/markdown/DISPATCH.md +177 -0
  48. package/markdown/HISTORY.md +199 -0
  49. package/markdown/IDB.md +178 -0
  50. package/markdown/IMPORT.md +153 -0
  51. package/markdown/INSPECT.md +123 -0
  52. package/markdown/LOGGER.md +154 -0
  53. package/markdown/MEMORY-ATTACHMENT.md +96 -0
  54. package/markdown/MEMORY.md +162 -0
  55. package/markdown/OBSERVER.md +209 -0
  56. package/markdown/PLATFORM.md +271 -0
  57. package/markdown/PROJECT.md +311 -0
  58. package/markdown/SCHEMA.md +176 -0
  59. package/markdown/SECURITY.md +330 -0
  60. package/markdown/SESSION.md +165 -0
  61. package/markdown/SQLITE.md +190 -0
  62. package/markdown/STATE.md +160 -0
  63. package/markdown/STORE.md +171 -0
  64. package/markdown/SYNC.md +319 -0
  65. package/markdown/TYPED.md +165 -0
  66. package/markdown/USEOBSERVER.md +257 -0
  67. package/modules/redux.cjs +320 -167
  68. package/modules/redux.cjs.map +1 -1
  69. package/modules/redux.js +320 -167
  70. package/modules/redux.js.map +1 -1
  71. package/package.json +13 -3
  72. package/types/env.d.ts +19 -9
  73. package/types/exports.d.ts +20 -0
  74. package/types/history.d.ts +13 -1
  75. package/types/memorio.d.ts +17 -5
  76. package/types/mutation.d.ts +75 -0
@@ -1,63 +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' } // ✅
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, state, useObserver } from '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' } // ✅
@@ -1,60 +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'])
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'])
@@ -1,91 +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(), 'kilobytes')
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!')
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 { session } from '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(), 'kilobytes')
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!')
@@ -1,57 +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.
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.