memorio 4.9.35 → 5.1.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 (82) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +95 -516
  3. package/SECURITY.md +159 -33
  4. package/SUMMARY.md +59 -45
  5. package/adr/001-state-proxy-model.md +95 -0
  6. package/adr/002-observer-semantics.md +179 -0
  7. package/adr/003-deep-mutation-semantics.md +128 -0
  8. package/adr/004-array-mutation-semantics.md +127 -0
  9. package/adr/005-scheduler-contract.md +148 -0
  10. package/adr/006-context-isolation.md +91 -0
  11. package/adr/007-mutation-records.md +117 -0
  12. package/adr/008-transactions.md +105 -0
  13. package/adr/009-history-model.md +109 -0
  14. package/adr/README.md +46 -0
  15. package/adr/template.md +48 -0
  16. package/bin/cli.js +68 -0
  17. package/examples/basic.ts +115 -115
  18. package/examples/browser-vanilla.html +358 -358
  19. package/examples/cache.ts +72 -72
  20. package/examples/cross-platform-guards.ts +57 -57
  21. package/examples/history.ts +104 -0
  22. package/examples/idb.ts +109 -109
  23. package/examples/multi-tenant-context.ts +44 -44
  24. package/examples/node-server.ts +308 -308
  25. package/examples/observer.ts +60 -60
  26. package/examples/platform.ts +115 -115
  27. package/examples/react-app.tsx +362 -362
  28. package/examples/react-observer.tsx +63 -63
  29. package/examples/semantic-memory.ts +60 -60
  30. package/examples/session-advanced.ts +91 -91
  31. package/examples/sqlite-batched-writes.ts +57 -57
  32. package/examples/state-advanced.ts +89 -89
  33. package/examples/store-advanced.ts +117 -117
  34. package/examples/sync.ts +90 -0
  35. package/examples/typed-and-schema.ts +102 -100
  36. package/examples/useObserver.tsx +140 -141
  37. package/global.cjs +5733 -0
  38. package/global.d.ts +8 -0
  39. package/global.js +5667 -0
  40. package/index.cjs +2202 -1041
  41. package/index.d.ts +2 -0
  42. package/index.js +2178 -1040
  43. package/llms.txt +113 -8
  44. package/markdown/AUDIT-REPORT.md +134 -0
  45. package/markdown/CACHE.md +191 -0
  46. package/markdown/DEVTOOLS.md +128 -0
  47. package/markdown/DISPATCH.md +176 -0
  48. package/markdown/HISTORY.md +198 -0
  49. package/markdown/IDB.md +177 -0
  50. package/markdown/IMPORT.md +152 -0
  51. package/markdown/INSPECT.md +122 -0
  52. package/markdown/LOGGER.md +153 -0
  53. package/markdown/MEMORY-ATTACHMENT.md +95 -0
  54. package/markdown/MEMORY.md +161 -0
  55. package/markdown/OBSERVER.md +208 -0
  56. package/markdown/PLATFORM.md +277 -0
  57. package/markdown/REDUX.md +54 -0
  58. package/markdown/SCHEMA.md +175 -0
  59. package/markdown/SESSION.md +164 -0
  60. package/markdown/SQLITE.md +189 -0
  61. package/markdown/STATE.md +159 -0
  62. package/markdown/STORE.md +170 -0
  63. package/markdown/SYNC.md +318 -0
  64. package/markdown/TYPED.md +164 -0
  65. package/markdown/USEOBSERVER.md +256 -0
  66. package/modules/redux.cjs +701 -177
  67. package/modules/redux.cjs.map +1 -1
  68. package/modules/redux.js +701 -177
  69. package/modules/redux.js.map +1 -1
  70. package/package.json +26 -4
  71. package/types/broadcast.d.ts +61 -0
  72. package/types/computed.d.ts +96 -0
  73. package/types/encryption.d.ts +129 -0
  74. package/types/env.d.ts +19 -9
  75. package/types/exports.d.ts +29 -0
  76. package/types/history.d.ts +13 -1
  77. package/types/memorio.d.ts +25 -6
  78. package/types/mutation.d.ts +75 -0
  79. package/types/security.d.ts +67 -0
  80. package/types/session.d.ts +23 -5
  81. package/types/store.d.ts +19 -3
  82. package/vsix/memorio.vsix +0 -0
package/SECURITY.md CHANGED
@@ -1,48 +1,174 @@
1
- # Security
1
+ # Security & Encryption
2
2
 
3
- Memorio follows NIST and NSA security standards at the enterprise level.
3
+ This document covers Memorio's optional encryption layer, client-side multitenancy, and general security posture. For the short version, see the **Security** section in the main [README](./README.md).
4
4
 
5
- ## Security Standards
5
+ ---
6
+
7
+ ## Nothing is encrypted by default
8
+
9
+ This applies to every layer: `state`, `store`, `session`, `idb`, `memory`, `sqlite`. Treat browser storage as client-controlled unless you explicitly opt in.
10
+
11
+ Core has zero production dependencies. No `eval`, no dynamic code execution, no bundled telemetry, sanitized keys, validated inputs, bounded journal entries, UUID-based session identifiers. Independently checkable via Socket.dev / Snyk.
12
+
13
+ ---
14
+
15
+ ## The encryption layer
16
+
17
+ Memorio ships an optional encryption layer built on the **Web Crypto API** (`crypto.subtle`): AES-GCM for authenticated encryption, PBKDF2 for password-based key derivation. It is opt-in - no layer encrypts by default, so the common case stays transparent and fast.
18
+
19
+ All `memorio.encryption` operations are **asynchronous by design** - the Web Crypto API runs cryptographic work on a separate internal thread managed by the browser/runtime, so `deriveKey()`, `encrypt()`, and `decrypt()` return Promises that never block the main thread. PBKDF2 derivation at the NIST SP 800-132-recommended 600,000 iterations typically completes in under 300ms on mid-range hardware. No explicit Web Worker offload is needed.
20
+
21
+ ### Encrypt a value
22
+
23
+ ```ts
24
+ import { memorio } from 'memorio'
25
+
26
+ // Derive a key from a password (PBKDF2, 600k iterations - NIST SP 800-132)
27
+ const key = await memorio.encryption.deriveKey('my-password', 'per-tenant-salt')
28
+ // or generate one directly, without a password:
29
+ const freshKey = await memorio.encryption.generateKey()
30
+ const freshSalt = memorio.encryption.generateSalt()
31
+
32
+ // Encrypt → envelope object { __memorio_encrypted, alg, iv, data }
33
+ const envelope = await memorio.encryption.encrypt({ token: 'abc123' }, key)
34
+
35
+ // Decrypt - wrong key rejects rather than returning garbage
36
+ const original = await memorio.encryption.decrypt(envelope, key)
37
+ // → { token: 'abc123' }
6
38
 
7
- - **NIST Guidelines**: Follows NIST SP 800-53 security controls and NIST Cybersecurity Framework
8
- - **NSA Standards**: Defense-grade security practices; considers nation-state level threats in risk assessment
39
+ // Check a value before trying to decrypt it
40
+ memorio.encryption.isEncrypted(envelope) // true - checks the __memorio_encrypted flag
41
+ memorio.encryption.isAvailable() // false in environments without Web Crypto (e.g. old runtimes)
42
+ ```
9
43
 
10
- ## Supply Chain Security
44
+ For the common "encrypt with a password, get a string back" case, skip manual key derivation:
11
45
 
12
- - **Socket.dev**: Minimum target score 90%; all alarms must be resolved before release
13
- - **Dependency Management**: Zero production dependencies (fully dependency-free); dev dependencies audited regularly
14
- - **Prohibited**: No `eval()` usage, no encrypted/obfuscated code in builds, no hardcoded secrets
46
+ ```ts
47
+ const cipherText = await memorio.encryption.encryptWithPassword({ token: 'abc123' }, 'my-password')
48
+ const plain = await memorio.encryption.decryptWithPassword(cipherText, 'my-password')
49
+ ```
15
50
 
16
- ## Code Security
51
+ Both accept/return an envelope object (not a JSON string when you already have a key):
17
52
 
18
- - No hardcoded credentials or API keys
19
- - Secure random session ID generation (`crypto.randomUUID` → `crypto.getRandomValues` → fallback)
20
- - Input validation on all public APIs
21
- - XSS prevention on DevTools data export
22
- - Property-based access control on global objects (`Object.defineProperty` with `enumerable: false`)
53
+ ```ts
54
+ const envelope = await memorio.encryption.encrypt({ token: 'abc123' }, key)
55
+ const plain = await memorio.encryption.decrypt(envelope, key) // returns the original value
56
+ ```
23
57
 
24
- ## OWASP Compliance
58
+ **Key import/export** for storing CryptoKey material securely (e.g. in an httpOnly cookie):
25
59
 
26
- Addresses OWASP Top 10 (2021):
27
- - A01:2021 - Broken Access Control (global object protection, property locks)
28
- - A02:2021 - Cryptographic Failures (crypto.randomUUID for session IDs)
29
- - A03:2021 - Injection (CSS sanitization in devtools)
30
- - A05:2021 - Security Misconfiguration (minimal surface area, no bundled secrets)
31
- - A06:2021 - Vulnerable and Outdated Components (regular npm audit, Socket.dev)
32
- - A07:2021 - Identification and Authentication Failures (N/A - library, no auth)
33
- - A08:2021 - Software and Data Integrity Failures (strict tsconfig, lock files)
34
- - A09:2021 - Security Logging and Monitoring Failures (DevTools inspect, Logger module)
35
- - A10:2021 - Server-Side Request Forgery (N/A - no network requests)
60
+ ```ts
61
+ const key = await memorio.encryption.generateKey()
62
+ const { key: b64, iv } = await memorio.encryption.exportKey(key)
63
+ // Store b64 securely, then restore later:
64
+ const restoredKey = await memorio.encryption.importKey(b64, iv)
65
+ ```
36
66
 
37
- ## Reporting Security Issues
67
+ ### Store with at-rest encryption
38
68
 
39
- If you find a security vulnerability:
69
+ ```ts
70
+ // Per-call encryption (opt-in)
71
+ await store.set('api_token', secretValue, { encrypt: key })
72
+ const token = await store.get('api_token', { decrypt: key })
40
73
 
41
- 1. Email [Dario Passariello](mailto:dariopassarielloa@gmail.com)
42
- 2. Or visit https://dario.passariello.ca/contact/
74
+ // Or configure a default key for automatic encryption:
75
+ store.config({ encryptionKey: key })
76
+ await store.set('api_token', secretValue) // encrypted transparently
77
+ const token = await store.get('api_token') // decrypted transparently
78
+ ```
43
79
 
44
- Do not open public issues for security vulnerabilities.
80
+ `session` supports the same `config` and per-call opts.
81
+
82
+ > ⚠️ **Sync vs async changes once encryption is on.** Without encryption, `store.get`/`store.set` (and `session`'s equivalents) are synchronous - they return the value directly. As soon as either call touches `encrypt`/`decrypt` (per-call, or because a default `encryptionKey` is configured), both become asynchronous and return a `Promise`. Code written against the plain synchronous API will silently receive a `Promise` instead of a value if encryption is turned on later - always `await` `store.get`/`store.set` defensively if there's any chance encryption gets enabled down the line.
45
83
 
46
84
  ---
47
- *Document version: 2.0 - Last updated: 2026-05-19*
48
- *Owner: BigLogic Security Team*
85
+
86
+ ## Contexts are not authorization
87
+
88
+ ```ts
89
+ memorio.createContext('tenant-123')
90
+ ```
91
+
92
+ Contexts organize and isolate application concerns by namespace-prefixing keys - they are **not** a security boundary. Any script running in the same JS runtime can attempt operations against another context. Real tenant isolation belongs at the auth/backend layer.
93
+
94
+ ---
95
+
96
+ ## Encrypted multitenancy
97
+
98
+ Pass an `encryptionKey` to `createContext()` and the tenant's isolated `store`/`session` automatically encrypt on write and decrypt on read:
99
+
100
+ ```ts
101
+ const tenantKey = await memorio.encryption.deriveKey(tenantPassword, tenantSalt)
102
+
103
+ const ctx = memorio.createContext('tenant-123', { encryptionKey: tenantKey })
104
+
105
+ // Values are encrypted at rest within this context's namespace
106
+ await ctx.store.set('secrets', { apiKey: '…' })
107
+ const secrets = await ctx.store.get('secrets') // decrypted transparently
108
+
109
+ // Another tenant with a different key can't read this data
110
+ const otherCtx = memorio.createContext('tenant-456', { encryptionKey: otherKey })
111
+ const leaked = await otherCtx.store.get('memorio_store_secrets') // fails to decrypt
112
+ ```
113
+
114
+ This adds a cryptographic wall on top of the namespace prefix: even if a tenant's key prefix is guessed, the ciphertext is unreadable without the key. It still doesn't replace backend authorization - see above.
115
+
116
+ > ⚠️ **`cache` is never encrypted, even inside an encrypted context.** `createContext(name, { encryptionKey })` only encrypts that context's `store` and `session` - its `cache` stays volatile, in-memory, and in plaintext regardless. Don't put anything in `ctx.cache` you wouldn't put in unencrypted `cache`.
117
+
118
+ `memorio.isolate()` is a thin alias for `createContext()` and accepts the same `{ encryptionKey }` option.
119
+
120
+ > **Key management is yours.** Derive keys from authenticated session material or generate them with `memorio.encryption.generateKey()`. Never hardcode keys in client bundles.
121
+
122
+ ---
123
+
124
+ ## Global API security
125
+
126
+ Memorio does **not** expose its APIs on `globalThis` by default. The opt-in entrypoint `import 'memorio/global'` installs `state`, `store`, `session`, `cache`, `idb`, `sqlite`, `observer`, and `useObserver` on `globalThis` - this is an explicit consumer choice, never inferred from `NODE_ENV` or bundler environment variables.
127
+
128
+ For application code, prefer explicit named imports:
129
+
130
+ ```ts
131
+ import { state } from 'memorio'
132
+ ```
133
+
134
+ ---
135
+
136
+ ## Code-level hardening
137
+
138
+ - No hardcoded credentials, API keys, or secrets anywhere in the codebase or build output.
139
+ - Session IDs use a secure random source with graceful fallback: `crypto.randomUUID` → `crypto.getRandomValues` → a documented fallback if neither is available.
140
+ - Public APIs validate their inputs rather than trusting caller-supplied shapes.
141
+ - Global objects installed via `memorio/global` use `Object.defineProperty` with `enumerable: false` where applicable, rather than plain assignment.
142
+ - No obfuscated or minified-beyond-readability code shipped in a way that would hide behavior from review.
143
+
144
+ ---
145
+
146
+ ## Supply chain
147
+
148
+ - Zero production dependencies in core - nothing to audit transitively for the parts you actually ship.
149
+ - Dev dependencies are audited periodically (`npm audit` and equivalent).
150
+ - Package integrity is checkable independently via Socket.dev / Snyk before you adopt a given version.
151
+
152
+ > If you require a specific minimum Socket.dev/Snyk score or a documented audit trail as part of a procurement process, verify the current score yourself at adoption time - this document doesn't pin a number that could go stale.
153
+
154
+ ---
155
+
156
+ ## Reporting a vulnerability
157
+
158
+ Please don't open a public issue for a security vulnerability. Instead:
159
+
160
+ 1. Email [Dario Passariello](mailto:dariopassarielloa@gmail.com), or
161
+ 2. Use the contact form at https://dario.passariello.ca/contact/
162
+
163
+ ---
164
+
165
+ ## Enterprise compliance notes
166
+
167
+ | Concern | What you get | Impact |
168
+ | --- | --- | --- |
169
+ | **Opt-in encryption** | `memorio.encryption` provides AES-GCM + PBKDF2. `store`/`session` and encrypted contexts support automatic encrypt-on-write/decrypt-on-read. | Not encrypted by default - you must opt in via `store.config({ encryptionKey })` or `createContext(id, { encryptionKey })`. Without it, PII, tokens, and regulated data (GDPR/HIPAA) are at rest in plaintext. Enabling it also changes the calling convention - see the sync/async warning above. |
170
+ | **Client-side multitenancy** | `createContext(id, { encryptionKey })` provides namespace + cryptographic isolation between tenants. | Still **not** a security boundary - any script in the same JS runtime can attempt context operations. Encryption makes cross-tenant reads cryptographically hard, but real authorization must live on the backend. |
171
+ | **DevTools** | Expose data for development only. | Only active when you opt into `memorio/global`; never ship enabled in production. |
172
+ | **Formal compliance frameworks (NIST, OWASP, etc.)** | No formal third-party audit or certification has been published for this project. | If your procurement process requires a certified compliance framework, commission an independent audit rather than relying on self-declared conformance - see [When to use something else](./README.md#when-to-use-something-else) in the README. |
173
+
174
+ If any of these gaps block adoption, keep Memorio for memory/state lifecycle and pair it with a dedicated secrets manager or backend-enforced authorization for the pieces it explicitly doesn't cover.
package/SUMMARY.md CHANGED
@@ -1,45 +1,59 @@
1
- # Table of contents
2
-
3
- * [README](README.md)
4
-
5
- ## Core Modules
6
-
7
- * [State](markdown/STATE.md) - Reactive global state management
8
- * [Observer](markdown/OBSERVER.md) - Object observation pattern
9
- * [useObserver](markdown/USEOBSERVER.md) - React hook for state observation
10
- * [Dispatch](markdown/DISPATCH.md) - Event system for vanilla JS applications
11
- * [Cache](markdown/CACHE.md) - In-memory caching (lost on refresh)
12
- * [Store](markdown/STORE.md) - Persistent localStorage management
13
- * [Session](markdown/SESSION.md) - Temporary sessionStorage management
14
- * [IDB](markdown/IDB.md) - IndexedDB for large data storage
15
- * [SQLite](markdown/SQLITE.md) - SQLite in the browser via sql.js (WASM)
16
-
17
- ## Typed & Validated
18
-
19
- * [Typed Stores](markdown/TYPED.md) - Compile-time type safety for state access
20
- * [Schema Validation](markdown/SCHEMA.md) - Runtime validation of state mutations
21
-
22
- ## History & Introspection
23
-
24
- * [History](markdown/HISTORY.md) - Snapshot, diff, undo/redo, trace
25
- * [Introspection](markdown/INSPECT.md) - stateKeys, pathExists, stateType, stateSchema
26
-
27
- ## Memory System
28
-
29
- * [Memory](markdown/MEMORY.md) - Semantic memory layer with TTL, confidence, scopes
30
- * [Node Attachment](markdown/MEMORY-ATTACHMENT.md) - Dynamic node attachment system
31
- * [Synchronization](markdown/SYNC.md) - Local-first sync, journal, conflict resolution, multi-device evolution
32
-
33
- ## Platform & Compatibility
34
-
35
- * [Platform & Context Isolation](markdown/PLATFORM.md) - Cross-platform support, session isolation
36
- * [Security](markdown/SECURITY.md) - Security measures, vulnerability prevention
37
- * [Changelog](markdown/CHANGELOG.md) - Version history and migration guide
38
- * [Classic Import](markdown/IMPORT.md) - Named export guide for `import { state } from 'memorio'`
39
-
40
- ## Additional Resources
41
-
42
- * [License](../LICENSE.md)
43
- * [Contributing](../CONTRIBUTING.md)
44
- * [Code of Conduct](../CODE_OF_CONDUCT.md)
45
- * [Security](../SECURITY.md)
1
+ # Riepilogo dei file - memorio examples & markdown
2
+
3
+ ## 📁 examples/ (20 file)
4
+
5
+ | File | Cosa mostra |
6
+ |---|---|
7
+ | `basic.ts` | Tour introduttivo: platform detection, `state`, `store`, `session` in un unico script. |
8
+ | `cache.ts` | Uso base della cache in-memory: set/get, oggetti complessi, cleanup. |
9
+ | `cross-platform-guards.ts` | Come usare `getCapabilities()` invece di `isBrowser()`/`isNode()` per gestire in modo esplicito gli ambienti dove `store`/`session`/`idb` non sono durevoli. |
10
+ | `history.ts` *(nuovo)* | State Intelligence: `snapshot()`/`diff()`/`rollback()` per il pattern "prova → ispeziona → conferma o annulla", undo/redo passo-passo, trace log con export/replay. |
11
+ | `idb.ts` | CRUD completo su IndexedDB: database, tabelle, record, info (size/version/exist). |
12
+ | `multi-tenant-context.ts` | Isolamento dati per richiesta/tenant con `memorio.createContext()` in un handler server-side, incluso cleanup. |
13
+ | `node-server.ts` | Memorio lato server: cache in-memory, fallback di `store`/`session`, contesti multi-tenant, più snippet commentati (Express, WebSocket, job queue, CLI). |
14
+ | `observer.ts` | Pattern observer: singolo valore, oggetti interi, più observer sullo stesso path, cleanup. |
15
+ | `platform.ts` | Platform detection dettagliata + isolamento contesti per server multi-tenant, con verifica esplicita dell'isolamento tra due utenti. |
16
+ | `react-app.tsx` | App React completa (header, profilo, cart, notifiche, settings, login/logout) costruita solo su `state`/`store`/`useObserver`. |
17
+ | `react-observer.tsx` | `useObserver` in due modalità (auto-discovery vs deps espliciti) combinato con `typed<T>()` e `registerSchema()`. |
18
+ | `semantic-memory.ts` | Memoria applicativa per un'app LLM-backed: remember/update con confidence e source, retrieval con `memory.context()` - con nota esplicita che non è ricerca semantica per embedding. |
19
+ | `session-advanced.ts` | Uso avanzato di `session`: auth token, bozza di form, carrello, dimensione dello storage. |
20
+ | `sqlite-batched-writes.ts` | Come evitare di serializzare l'intero DB SQLite ad ogni riga: batch di insert seguito da un solo flush. |
21
+ | `state-advanced.ts` | Stato annidato, array, locking di un valore (`.lock()`), path tracking, rimozione stato. |
22
+ | `store-advanced.ts` | `store` avanzato: persistenza, quota, alias dei metodi, gestione errori, serializzazione di vari tipi. |
23
+ | `sync.ts` *(nuovo)* | Local-first sync: configurazione di un `SyncProvider` (push/pull/resolve), scope `device`/`user`/`shared`, ispezione e replay del journal. |
24
+ | `typed-and-schema.ts` | Tutti gli snippet di `TYPED.md` e `SCHEMA.md` in un unico file TypeScript funzionante: typed state + validazione runtime combinati. |
25
+ | `useObserver.tsx` | Guida step-by-step all'hook `useObserver`: dipendenza singola, multiple, auto-discovery, sync con `useState`, mini to-do app. |
26
+ | `browser-vanilla.html` | Demo HTML/JS pura (nessun bundler) con UI per state, store, session, cache, observer e platform info. |
27
+
28
+ ## 📁 markdown/ (24 file)
29
+
30
+ | File | Cosa documenta |
31
+ |---|---|
32
+ | `AUDIT-REPORT.md` | Audit di sicurezza/performance/affidabilità/qualità del codice per la v5.1.0 (datato 2026-09-06), con azioni correttive già applicate. |
33
+ | `CACHE.md` | Reference della cache in-memory: API, quando usarla, limiti. |
34
+ | `CHANGELOG.md` | Storico versioni dalla v2.5.0 alla `Unreleased`, con bugfix, refactoring e note di sicurezza per ogni release. |
35
+ | `DEVTOOLS.md` | Strumenti di debug da console del browser per ispezionare/gestire lo stato di Memorio. |
36
+ | `DISPATCH.md` | Sistema di eventi pub/sub per app vanilla JS (alternativa a `useObserver` fuori da React). |
37
+ | `HISTORY.md` | Reference completa di time-travel: enable/snapshot/diff/undo/redo/rollback/trace, con "how it works" e best practice. |
38
+ | `IDB.md` | Reference IndexedDB: creazione DB/tabelle, CRUD, info sul database. |
39
+ | `IMPORT.md` | I due stili di import (named vs `memorio/global`) e perché condividono la stessa istanza. |
40
+ | `INSPECT.md` | Utility di introspezione per scoprire/verificare la forma dello `state` a runtime - utile per agenti AI. |
41
+ | `LOGGER.md` | Middleware di logging automatico per tracciare le modifiche di stato in console. |
42
+ | `MEMORY-ATTACHMENT.md` | Sistema di "attachment" tra elementi di memoria - estensione opzionale della memoria semantica. |
43
+ | `MEMORY.md` | Reference del layer di memoria semantica: remember/update/context, TTL, confidence, tag, scope. |
44
+ | `OBSERVER.md` | Reference del pattern observer per reagire ai cambi di stato. |
45
+ | `PLATFORM.md` | Platform detection + sistema di context isolation per applicazioni multi-tenant server-side. |
46
+ | `PROJECT.md` | Scheda di progetto interna: versione, team, moduli inclusi, stack tecnologico, target. |
47
+ | `SCHEMA.md` | Validazione runtime dei percorsi di stato: schema oggetto/array/enum/funzione custom. |
48
+ | `SECURITY.md` | Postura di sicurezza del progetto (minacce coperte, cosa NON fa Memorio, come viene gestito l'accesso ai dati). |
49
+ | `SESSION.md` | Reference di `session` (sessionStorage) con fallback in-memory fuori dal browser. |
50
+ | `SQLITE.md` | Reference del layer SQLite via `sql.js`: caricamento lazy, persistenza, query. |
51
+ | `STATE.md` | Reference dello stato reattivo basato su Proxy - il layer centrale di Memorio. |
52
+ | `STORE.md` | Reference di `store` (localStorage) con fallback in-memory fuori dal browser. |
53
+ | `SYNC.md` | Sincronizzazione local-first opzionale: scope, journal, conflict resolution, strategie avanzate per multi-device (HLC, tombstones, fractional indexing). |
54
+ | `TYPED.md` | `memorio.typed<T>()` per la sicurezza dei tipi a compile-time sullo stesso proxy di `state`. |
55
+ | `USEOBSERVER.md` | Reference dell'hook React `useObserver`: modalità auto-discovery vs deps espliciti, tutte le forme di `deps` supportate. |
56
+
57
+ ---
58
+
59
+ **Nota di copertura:** ogni doc in `markdown/` ha ora almeno un esempio corrispondente in `examples/`, **tranne** `DEVTOOLS.md`, `DISPATCH.md`, `INSPECT.md`, `LOGGER.md` e `MEMORY-ATTACHMENT.md` - utile saperlo se in futuro vuoi completare anche quelli.
@@ -0,0 +1,95 @@
1
+ # ADR-001: State Proxy Model
2
+
3
+ > **Status:** Accepted
4
+ > **Date:** 2026-09-12
5
+
6
+ ## Context
7
+
8
+ Memorio's reactive state is built on a JavaScript `Proxy`. Every read of
9
+ a nested object re-wraps the child in a Proxy so that deep
10
+ `set`/`delete` traps fire and dispatch events. The design must answer:
11
+
12
+ - How are proxy wrappers cached to avoid exponential allocation on
13
+ repeated reads?
14
+ - What is the reference-identity contract between the proxy and the
15
+ raw target?
16
+ - Which property names are reserved and cannot be used as state keys?
17
+ - How are non-plain objects (Date, Set, Map, functions) handled?
18
+
19
+ ## Assumptions
20
+
21
+ - The Proxy target is always a plain `Object` or `Array`.
22
+ - A single `buildProxy` invocation creates one Proxy per raw object.
23
+ - The Proxy is shared across all import paths via `globalThis`
24
+ singletons (see ADR-006).
25
+ - `__DEV__` is a build-time constant (replaced to `false` in
26
+ production, `true` in dev/test).
27
+
28
+ ## Decision
29
+
30
+ ### Wrapper caching
31
+
32
+ `buildProxy` caches the wrapper in a `WeakMap<rawTarget, proxyWrapper>`.
33
+ On every `get` trap, after unwrapping with `TARGET`, the result is looked
34
+ up in the cache; if present, the existing wrapper is returned instead of
35
+ allocating a new one. This keeps repeated nested reads at O(1) in
36
+ allocation and O(1) in proxy depth.
37
+
38
+ ### Reference identity
39
+
40
+ - `TARGET` is a `Symbol` accessor on the proxy that returns the raw
41
+ underlying target. `state[TARGET]` === raw root.
42
+ - `state.a[TARGET]` === raw target's `a` property.
43
+ - `JSON.stringify(proxy)` works because `ownKeys` and
44
+ `getOwnPropertyDescriptor` delegate to `Reflect`.
45
+ - `deepRaw(value)` unwraps all Memorio proxies recursively using
46
+ `TARGET` and a `WeakSet` for cycle safety, returning a plain clone
47
+ with no wrapper references.
48
+
49
+ ### Reserved property names
50
+
51
+ `protect` array in `core/internal.ts` reserves these keys on the root
52
+ `state` proxy:
53
+
54
+ ```
55
+ list, state, store, idb, cache, sqlite, observer, useObserver,
56
+ remove, removeAll, _platform, _capabilities, _sessionId
57
+ ```
58
+
59
+ Setting any of these keys as state data is rejected by the `set` trap
60
+ with a console error. Attempting to set any `_`-prefixed key that
61
+ collides with internal bookkeeping is also guarded.
62
+
63
+ ### Non-plain objects
64
+
65
+ - `Date`, `Set`, `Map`, `RegExp`, functions, and other non-plain
66
+ instances are returned by reference - they are **not** re-wrapped in a
67
+ Proxy.
68
+ - `deepRaw` only traverses `Object` and `Array` constructors; all other
69
+ types are returned by reference.
70
+
71
+ ### Path tracking
72
+
73
+ Every `get` trap for a string property sets `internal.lastAccessedPath`
74
+ to the full dotted path (`state.foo.bar`). When
75
+ `internal.tracking` is true, the path is also added to
76
+ `internal.trackedPaths`.
77
+
78
+ ## Consequences
79
+
80
+ - **Positive:** O(1) wrapper allocation on repeated nested reads; no
81
+ proxy-depth accumulation on spread updates (regression tested).
82
+ - **Positive:** `TARGET` symbol prevents prototype-pollution attacks
83
+ via `__proto__`/`constructor`/`prototype` because those are string
84
+ keys that are simply treated as data keys on the raw target.
85
+ - **Positive:** `deepRaw` produces clean data for persistence and
86
+ snapshots without leaking proxy wrappers.
87
+ - **Negative:** The `WeakMap` cache is per-module-instance, but since
88
+ the proxy target is a singleton on `globalThis`, the cache is
89
+ effectively shared across import paths.
90
+ - **Negative:** Non-plain objects cannot be deeply observed for inner
91
+ mutations - this is an accepted limitation documented in the API.
92
+
93
+ ## Compliance tests
94
+
95
+ - `tests/vitest/tests/contracts/adr-001-state.test.ts`
@@ -0,0 +1,179 @@
1
+ # ADR-002: Observer Semantics
2
+
3
+ > **Status:** Proposed
4
+ > **Date:** 2026-09-12
5
+
6
+ ## Context
7
+
8
+ Memorio must freeze observer behavior so that developers can reason
9
+ about *when* and *why* their callbacks fire. The observer system is the
10
+ bridge between state mutations and UI updates, between the Mutation
11
+ Engine and the reactive system. Before adding advanced features (impact
12
+ analysis, simulation, causal graph), the observer contract must be
13
+ explicit and deterministic.
14
+
15
+ The following questions must have explicit answers:
16
+
17
+ 1. Does a deep mutation notify the leaf observer?
18
+ 2. Does a deep mutation notify parent observers?
19
+ 3. What happens for array mutations?
20
+ 4. What is the reference identity contract for observer callbacks?
21
+ 5. Are notifications synchronous or asynchronous?
22
+ 6. Are multiple mutations batched into a single notification?
23
+ 7. What is the ordering guarantee when multiple observers listen to
24
+ overlapping paths?
25
+ 8. Can an observer mutate state synchronously?
26
+ 9. What happens when an observer throws?
27
+ 10. What happens when an observer is removed during dispatch?
28
+ 11. How are nested transactions handled with respect to observer
29
+ notification?
30
+
31
+ ## Assumptions
32
+
33
+ - The state Proxy fires exactly one callback per `set` or `delete`
34
+ trap (no double-firing for the same logical mutation).
35
+ - The dispatch layer (`core/dispatch.ts`) is the sole event bus.
36
+ - `useObserver` is a thin wrapper over `observer` + `dispatch.listen`.
37
+ - History tracking is opt-in (`enableHistory(true)`).
38
+
39
+ ## Decision
40
+
41
+ ### 1. Leaf notification
42
+
43
+ A mutation at path `state.user.profile.name` dispatches an event on
44
+ exactly the path `state.user.profile.name`. Only observers registered
45
+ on that exact path receive the notification. Observers on ancestor
46
+ paths (`state.user.profile`, `state.user`, `state`) do **not** fire for
47
+ a leaf mutation unless the mutation replaces the ancestor itself.
48
+
49
+ ```js
50
+ observer('state.user.profile.name', cb) // fires
51
+ observer('state.user.profile', cb) // does NOT fire
52
+ observer('state.user', cb) // does NOT fire
53
+ ```
54
+
55
+ This is the **exact-path observer** model. It is intentional:
56
+ coarse-grained ancestor notification is handled by `useObserver`'s
57
+ auto-discovery mode (which explicitly registers on each accessed path),
58
+ not by event bubbling.
59
+
60
+ ### 2. Deep mutation notification
61
+
62
+ When `state.user.profile.name = "Alice"` executes, the set trap fires
63
+ on the `name` property of the `profile` proxy. The callback receives
64
+ `{ path: 'user.profile.name', ... }`. The event dispatched is
65
+ `state.user.profile.name`. Only the leaf observer fires.
66
+
67
+ ### 3. Array mutations
68
+
69
+ Array mutations at index `i` dispatch on the **array path**
70
+ (`state.items`), not the index path. This is because array identity
71
+ matters for UI reconciliation (React list rendering).
72
+
73
+ ```js
74
+ state.items.push(3) // dispatches 'state.items'
75
+ state.items[0] = 99 // dispatches 'state.items'
76
+ state.items.sort() // dispatches 'state.items'
77
+ ```
78
+
79
+ ### 4. Reference identity
80
+
81
+ Observer callbacks receive the event object. The callback function
82
+ itself is stored by reference in the dispatch layer. Re-registering the
83
+ same function on the same path replaces the old listener (single-slot
84
+ behavior in `observer`, multi-subscriber in `dispatch.listen`).
85
+
86
+ `useObserver` deduplicates identical path registrations.
87
+
88
+ ### 5. Synchronous vs asynchronous
89
+
90
+ - **Event dispatch** (`dispatch.set`) is **synchronous** -
91
+ `globalThis.dispatchEvent` runs listeners inline.
92
+ - **Callback notification** via `dispatch.listen` is **asynchronous**
93
+ - wrapped in `queueMicrotask(cb)` (or `Promise.resolve().then(cb)`
94
+ as fallback). This ensures that by the time the callback fires, the
95
+ state Proxy has already committed the mutation.
96
+
97
+ This means:
98
+
99
+ ```js
100
+ state.counter = 1
101
+ // state.counter === 1 is TRUE here (mutation already committed)
102
+ // observer callback hasn't run yet (next microtask)
103
+ ```
104
+
105
+ ### 6. Batching
106
+
107
+ There is **no automatic batching** of observer callbacks. Each mutation
108
+ dispatches its own microtask. Multiple synchronous mutations in a
109
+ transaction produce multiple notifications - one per mutation.
110
+
111
+ A future scheduler (ADR-005) may introduce microtask/raf batching for
112
+ UI frameworks, but the core engine does not coalesce notifications.
113
+
114
+ ### 7. Ordering guarantee
115
+
116
+ When multiple observers listen to the same path, they fire in
117
+ **registration order** (FIFO). The dispatch layer maintains a list of
118
+ handlers per event name.
119
+
120
+ When multiple mutations occur synchronously (without history), they
121
+ dispatch in mutation order. When history is enabled, the undo/redo
122
+ stack preserves mutation order via array index.
123
+
124
+ ### 8. Observer mutating state
125
+
126
+ An observer callback **may** mutate state. Because notifications are
127
+ asynchronous (microtask), the mutation triggers a new dispatch cycle
128
+ with its own microtask. There is no immediate re-entrancy. However,
129
+ deeply recursive state mutations from observers are considered a
130
+ application-level bug and are **not** guarded at the engine level.
131
+
132
+ ### 9. Observer throwing
133
+
134
+ If an observer callback throws, the error propagates as an unhandled
135
+ rejection (since the callback runs inside a microtask). The dispatch
136
+ layer does **not** catch or swallow errors. A throwing observer does
137
+ not prevent other observers on the same path from firing (each
138
+ callback is wrapped in its own microtask boundary via
139
+ `Promise.resolve().then`).
140
+
141
+ ### 10. Observer removed during dispatch
142
+
143
+ Since dispatch is asynchronous (microtask), calling
144
+ `dispatch.remove(path)` or `observer.remove(path)` during a callback
145
+ removes the listener from subsequent dispatches. The current dispatch
146
+ cycle is unaffected - all handlers registered at dispatch time fire.
147
+
148
+ ### 11. Transactions and observer notification
149
+
150
+ During a transaction, each mutation inside the transaction dispatches
151
+ normally. The transaction grouping does not suppress notifications.
152
+ However, `undo()` and `redo()` temporarily disable history recording
153
+ (via `internal.historyEnabled = false`), and the inverse/forward
154
+ operations fire the proxy callback - which will dispatch to observers.
155
+
156
+ A future enhancement may batch observer notifications for all mutations
157
+ within a transaction into a single synthetic notification. This ADR
158
+ does **not** define that behavior yet.
159
+
160
+ ## Consequences
161
+
162
+ - **Positive:** Deterministic, FIFO-ordered, asynchronous notification
163
+ gives observers a consistent view of state on every call.
164
+ - **Positive:** Microtask scheduling avoids "state not yet committed"
165
+ bugs that plague synchronous observer models.
166
+ - **Positive:** Exact-path matching means observers fire only when the
167
+ specific watched path changes - no spurious re-renders.
168
+ - **Positive:** No batching in the core engine keeps it simple and
169
+ framework-agnostic. Batching is a scheduler-layer concern.
170
+ - **Negative:** Multiple synchronous mutations produce multiple
171
+ microtask notifications - consumers that need coalescing must
172
+ implement it (or use the future scheduler).
173
+ - **Negative:** Throwing observers produce unhandled rejections rather
174
+ than being caught - this is intentional (fail fast) but may surprise
175
+ consumers expecting error isolation.
176
+
177
+ ## Compliance tests
178
+
179
+ - `tests/vitest/tests/contracts/adr-002-observer.test.ts`