memorio 4.6.9 → 4.7.1

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 (6) hide show
  1. package/FUNDING.yml +12 -0
  2. package/README.md +106 -199
  3. package/index.cjs +31 -2192
  4. package/index.js +9 -2167
  5. package/llms.txt +75 -91
  6. package/package.json +13 -2
package/FUNDING.yml ADDED
@@ -0,0 +1,12 @@
1
+ # These are supported funding model platforms
2
+
3
+ github: passariello
4
+ patreon: passariello
5
+ ko_fi: passariello
6
+ liberapay: passariello
7
+ issuehunt: passariello
8
+ custom:
9
+ [
10
+ "https://dario.passariello.ca",
11
+ "https://www.indiegogo.com/individuals/28513718",
12
+ ]
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🧠 memorio
2
2
 
3
- ![image](https://raw.githubusercontent.com/passariello/container/refs/heads/main/memorio/banner.svg)
3
+ ![banner](https://raw.githubusercontent.com/passariello/container/refs/heads/main/memorio/banner.svg)
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/memorio.svg)](https://npmjs.com/package/memorio)
6
6
  [![npm downloads](https://img.shields.io/npm/dm/memorio.svg)](https://npmjs.com/package/memorio)
@@ -13,197 +13,141 @@
13
13
  ![Tests](https://img.shields.io/badge/tests-101%20passed-green)
14
14
  ![License](https://img.shields.io/badge/License-MIT-gray)
15
15
 
16
- **State + Observer + Store + IDB. One import. Zero config.**
16
+ ### One import. Global state, persistence, and a IndexedDB layer — done.
17
17
 
18
- Memorio is a universal, cross-platform state management library for JavaScript and TypeScript. Reactive state, persistent store, session cache, IndexedDB, observer system, React hook, devtools, and logger — all from one import, zero dependencies.
18
+ ```javascript
19
+ import 'memorio'
19
20
 
20
- ---
21
+ state.user = { name: 'Sara' } // reactive, everywhere, instantly
22
+ ```
21
23
 
22
- ## Why memorio?
24
+ No provider tree. No reducers. No actions to dispatch before you can change a number. If you've ever set up a global store for an app that just needed *"a value, shared, that updates the UI when it changes,"* memorio is the toolkit for that thirty-second job — and it keeps growing with you: session storage, a cache, typed IndexedDB tables, an observer system, a React hook, devtools, and a change logger, all sharing one mental model.
23
25
 
24
- | Feature | 🔥 memorio | Redux | Zustand |
25
- |---|---|---|---|
26
- | **Setup** | ✅ **1 import** | ❌ Boilerplate hell | ⚠️ Moderate |
27
- | **Dependencies** | ✅ **Zero** | ❌ Many | ⚠️ Few |
28
- | **TypeScript** | ✅ Native | ✅ Yes | ✅ Yes |
29
- | **Binary storage** | ✅ Built-in IDB | ❌ Add-on | ❌ Add-on |
30
- | **Observer** | ✅ Built-in | ❌ Add-on | ❌ Add-on |
31
- | **DevTools** | ✅ Built-in + dphelper-manager | ⚠️ Extension | ⚠️ Extension |
32
- | **Edge runtime** | ✅ Workers, Deno | ❌ Limited | ❌ Limited |
33
- | **Learning curve** | ✅ **5 minutes** | ❌ Hours | ⚠️ 30 min |
34
- | **Boilerplate** | ✅ **None** | ❌ Tons | ⚠️ Some |
35
- | **React support** | ✅ `useObserver` hook | ✅ `connect` | ✅ `useSyncExternalStore` |
36
- | **Context isolation** | ✅ Multi-tenant | ⚠️ Manual | ⚠️ Manual |
26
+ ---
27
+
28
+ ## Table of Contents
37
29
 
38
- Zero dependencies. Lightweight. One import.
30
+ 1. [Is this for you?](#is-this-for-you)
31
+ 2. [Installation](#installation)
32
+ 3. [Quick Start](#quick-start)
33
+ 4. [API Reference](#api-reference)
34
+ 5. [Cross-Platform Behavior](#cross-platform-behavior)
35
+ 6. [Context Isolation (multi-tenant)](#context-isolation-multi-tenant)
36
+ 7. [Security](#security)
37
+ 8. [License](#license)
39
38
 
40
39
  ---
41
40
 
42
- ## 🚀 Features
43
-
44
- | | |
45
- |---|---|
46
- | **`state`** | Reactive, Proxy-based volatile state |
47
- | **`store`** | localStorage persistence (survives refresh) |
48
- | **`session`** | sessionStorage (dies with tab) |
49
- | **`cache`** | In-memory fastest cache |
50
- | **`idb`** | IndexedDB with typed tables, structured and async |
51
- | **`observer`** | Legacy object watcher for vanilla JS |
52
- | **`useObserver`** | React hook with auto-discovery |
53
- | **`dispatch`** | Event system: listen, emit, subscribe |
54
- | **`devtools`** | Inspect everything in console |
55
- | **`logger`** | Auto-log every state change with timestamps |
56
- | **Context isolation** | Per-request / multi-tenant namespace |
57
- | **Platform detection** | `isBrowser`, `isNode`, `isDeno`, `isEdge` |
58
-
59
- No Zustand. No Redux. No provider boilerplate.
60
- **Import → assign → done.**
41
+ ## Is this for you?
42
+
43
+ memorio is built for speed of thought: you think "I need this value everywhere," you write one line, it works. That's the entire pitch — and it's a real one, not a rewritten complaint about how much Redux boilerplate you've had to write.
44
+
45
+ **Reach for memorio when:**
46
+ - You want state that's simply *there* — no store setup, no provider wrapping your app.
47
+ - You want `localStorage`, `sessionStorage`, and `IndexedDB` behind one consistent API instead of learning three.
48
+ - You're prototyping, building an internal tool, or shipping something small-to-medium where iteration speed matters more than architectural ceremony.
49
+
50
+ **Reach for something else when:**
51
+ - You need every state path statically type-checked against every observer — memorio's `state.foo.bar` and `observer('state.foo.bar', ...)` are connected by a string, not by the compiler, so a rename won't be caught for you.
52
+ - You need Redux-style middleware, action logs, or time-travel debugging as a hard requirement for a large team — those tools exist because that discipline solves real problems at scale, not because their authors enjoy boilerplate.
53
+ - Your isolation requirements are a security boundary, not a convenience — see [Context Isolation](#context-isolation-multi-tenant) before you rely on it for that.
54
+
55
+ Pick the right tool. memorio is at its best when the answer to "how much state architecture do I need here?" is genuinely "not much."
61
56
 
62
57
  ---
63
58
 
64
- ## 📦 Installation
59
+ ## Installation
65
60
 
66
61
  ```bash
67
- # npm
68
62
  npm i memorio
63
+ # pnpm add memorio
64
+ # yarn add memorio
69
65
 
70
- # pnpm
71
- pnpm add memorio
72
-
73
- # yarn
74
- yarn add memorio
75
-
76
- # React peer dep (optional, React >= 16.8)
66
+ # Optional — only if you use the React hook
77
67
  npm i react react-dom
78
68
  ```
79
69
 
80
70
  ---
81
71
 
82
- ## 🎯 Quick Start
72
+ ## Quick Start
83
73
 
84
- ### Global style (original)
74
+ ### Global style — the whole point of memorio
85
75
 
86
76
  ```typescript
87
- // import 'memorio' once at your app entry point
88
- import 'memorio'
77
+ import 'memorio' // once, at your entry point
89
78
 
90
- // state is now available everywhere
91
79
  state.user = { name: 'Sara', role: 'admin' }
92
80
  state.counter++
93
- state.settings = { theme: 'dark', lang: 'it' }
94
81
 
95
- // React - automatic dependency discovery
96
82
  useObserver(
97
- () => { console.debug('user changed:', state.user) },
83
+ () => console.debug('user changed:', state.user),
98
84
  [state.user]
99
85
  )
100
-
101
- // Vanilla JS - event system
102
- memorio.dispatch.listen('state.user', (event) => {
103
- console.debug('user changed:', event.detail)
104
- })
105
86
  ```
106
87
 
107
- ### Classic `import` style (new)
108
-
109
- Every module is also a named export. Same instances, explicit dependencies.
88
+ ### Named imports same instances, explicit about it
110
89
 
111
90
  ```typescript
112
- // ESM
113
- import {
114
- state,
115
- store,
116
- session,
117
- cache,
118
- idb,
119
- observer,
120
- useObserver,
121
- dispatch,
122
- memorio
123
- } from 'memorio'
91
+ import { state, store, session, cache, idb, observer, useObserver, dispatch, memorio } from 'memorio'
124
92
 
125
93
  state.user = { name: 'Sara' }
126
94
  store.set('theme', 'dark')
127
-
128
- // CJS
129
- const { state, store, memorio } = require('memorio')
130
95
  ```
131
96
 
132
97
  ```tsx
133
- // React with named imports
134
- import { useObserver, state } from 'memorio'
135
-
136
98
  function Counter() {
137
99
  const [, forceUpdate] = useReducer(x => x + 1, 0)
138
-
139
100
  useObserver(forceUpdate, [state.counter])
140
-
141
101
  return <div>Count: {state.counter}</div>
142
102
  }
143
103
  ```
144
104
 
145
- Both styles share the exact same instances. Pick whichever fits your project.
105
+ Two styles, one engine underneath use whichever reads better in your codebase.
146
106
 
147
107
  ---
148
108
 
149
- ## 📚 API Reference
150
-
151
- ### `state` — Reactive volatile state
109
+ ## API Reference
152
110
 
153
- Global, Proxy-based, reactive. Access anywhere.
111
+ ### `state` — reactive, volatile, Proxy-based
154
112
 
155
113
  ```javascript
156
- // Set
157
114
  state.user = { name: 'Sara', role: 'admin' }
158
- state.items = [1, 2, 3]
159
-
160
- // Get
161
- const name = state.user.name // 'Sara'
162
-
163
- // List all keys
164
- console.debug(state.list) // ['user', 'items']
115
+ const name = state.user.name
165
116
 
166
- // Remove one key
117
+ state.list // ['user', 'items', ...]
167
118
  state.remove('items')
168
-
169
- // Clear all
170
119
  state.removeAll()
171
120
 
172
- // Lock/unlock (prevents modifications)
121
+ // Freeze a slice of state when you need to stop guessing who mutated it
173
122
  state.config = { maxUsers: 100 }
174
123
  state.config.lock()
175
- state.config.maxUsers = 200 // Error: state 'config' is locked
124
+ state.config.maxUsers = 200 // throws: state 'config' is locked
176
125
  state.config.unlock()
177
126
  ```
178
127
 
179
- ### `store` — Survives refresh
128
+ ### `store` — the value that survives a refresh
180
129
 
181
130
  ```javascript
182
131
  store.set('preferences', { theme: 'dark' })
183
- const prefs = store.get('preferences') // { theme: 'dark' } or null
184
- store.remove('preferences')
185
- store.removeAll()
186
- console.debug(store.size(), 'chars stored')
187
- console.debug(store.isPersistent) // true -> real localStorage
132
+ store.get('preferences') // { theme: 'dark' } or null
133
+ store.isPersistent // true when backed by real localStorage
188
134
  ```
189
135
 
190
- ### `session` — Dies with tab
136
+ ### `session` — lives as long as the tab does
191
137
 
192
138
  ```javascript
193
139
  session.set('token', 'user-abc-123')
194
- const token = session.get('token') // 'user-abc-123' or null
195
- session.removeAll()
140
+ session.get('token')
196
141
  ```
197
142
 
198
- ### `cache` — In-memory, disappears on refresh
143
+ ### `cache` — the fastest thing you own, gone on refresh
199
144
 
200
145
  ```javascript
201
146
  cache.set('temp', computeExpensiveResult())
202
- const result = cache.get('temp') // undefined or the value
203
- cache.clear() // empty it all
147
+ cache.get('temp')
204
148
  ```
205
149
 
206
- ### `idb` — Structured & typed
150
+ ### `idb` — typed, async, structured tables without the ceremony
207
151
 
208
152
  ```javascript
209
153
  await idb.db.create('my-db')
@@ -212,7 +156,9 @@ await idb.data.set('my-db', 'users', { id: 1, name: 'Sara' })
212
156
  const user = await idb.data.get('my-db', 'users', 1)
213
157
  ```
214
158
 
215
- ### `observer`Object watcher (legacy)
159
+ > IndexedDB is a browser-only primitive see [Cross-Platform Behavior](#cross-platform-behavior) for what happens off the browser.
160
+
161
+ ### `observer` / `useObserver` — watch a path, react to it
216
162
 
217
163
  ```javascript
218
164
  observer('state.user', (newVal, oldVal) => {
@@ -220,133 +166,94 @@ observer('state.user', (newVal, oldVal) => {
220
166
  })
221
167
  ```
222
168
 
223
- ### `useObserver` React observer hook
169
+ > Keys here are plain strings, not statically checked against `state`'s shape. Keep the observer near the code that shapes that state, and grep before you rename.
224
170
 
225
- ```jsx
226
- import { useObserver, state } from 'memorio'
227
-
228
- function Counter() {
229
- const [, forceUpdate] = useReducer(x => x + 1, 0)
230
-
231
- useObserver(forceUpdate, [state.counter])
232
-
233
- return <div>Count: {state.counter}</div>
234
- }
235
- ```
236
-
237
- ### `dispatch` — Event system
171
+ ### `dispatch` — the event bus underneath it all
238
172
 
239
173
  ```javascript
240
- // Listen
241
- memorio.dispatch.listen('state.user', (event) => {
242
- console.debug('user changed:', event.detail)
243
- })
244
-
245
- // Emit
246
- memorio.dispatch.set('state.user', { detail: { name: 'state.user' } })
247
-
248
- // Remove
249
- memorio.dispatch.remove('state.user')
174
+ memorio.dispatch.listen('state.user', (event) => console.debug(event.detail))
175
+ memorio.dispatch.set('state.user', { detail: { name: 'Sara' } })
250
176
  ```
251
177
 
252
- ### `devtools` — Inspect everything
178
+ ### `devtools` — see everything, instantly
253
179
 
254
180
  ```javascript
255
- memorio.devtools.inspect() // pretty-prints state, store, session, cache
256
- memorio.devtools.stats() // { stateKeys, storeKeys, sessionKeys, ... }
257
- memorio.devtools.clear('state')
258
- memorio.devtools.exportData() // JSON snapshot
259
- $state // console shortcut -> globalThis.state
181
+ memorio.devtools.inspect()
182
+ memorio.devtools.stats()
183
+ memorio.devtools.exportData()
260
184
  ```
261
185
 
262
- > 💡 **Browser Extension**: When used with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk), Memorio's global state is automatically detected and visualized with time-travel debugging.
186
+ Pairs with [dphelper-manager](https://chrome.google.com/webstore/detail/dphelper-manager-dev-tool/oppppldaoknfddeikfloonnialijngbk) for visual, time-travel inspection of your global state straight from devtools.
263
187
 
264
- ### `logger` — Track every change
188
+ ### `logger` — a black box for every change
265
189
 
266
190
  ```javascript
267
191
  memorio.logger.configure({ enabled: true, logToConsole: true })
268
- memorio.logger.getHistory() // [{ timestamp, module, action, path, value }, ...]
269
- memorio.logger.getStats() // { total, state, set, get, ... }
270
- memorio.logger.exportLogs() // JSON string of all history
192
+ memorio.logger.getHistory()
271
193
  ```
272
194
 
195
+ > It logs *everything* written to `state`/`store`/`session` — including anything sensitive you put there. Great for debugging, but don't leave it on unconditionally in production if secrets or PII pass through your state. See [Security](#security).
196
+
273
197
  ---
274
198
 
275
- ## 🌍 Platform detection
199
+ ## Cross-Platform Behavior
200
+
201
+ memorio runs everywhere JavaScript does — but "everywhere" means different guarantees in different places, and we'd rather tell you now than have you find out at 2am:
202
+
203
+ | API | Browser | Node.js | Deno | Edge / Workers |
204
+ |---|---|---|---|---|
205
+ | `state` | Proxy in memory | Proxy in memory | Proxy in memory | Proxy in memory |
206
+ | `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
207
+ | `cache` | ✅ in memory | ✅ in memory | ✅ in memory | ✅ in memory |
208
+ | `store` | `localStorage` | `Map` fallback — **not durable across restarts** | `Map` fallback — not durable | `localStorage` where available, else `Map` |
209
+ | `session` | `sessionStorage` | `Map` fallback — not durable | `Map` fallback — not durable | `sessionStorage` where available, else `Map` |
210
+ | `idb` | ✅ `IndexedDB` | ❌ not available | ❌ not available | ⚠️ check `getCapabilities()` |
211
+ | `devtools` | ✅ | ❌ | ❌ | ⚠️ |
212
+
213
+ Same API top to bottom — that's the promise. But if your server code leans on `store.get(...)` surviving a redeploy, know that on Node/Deno it won't; the fallback is an in-memory cache with the same shape, not durable storage.
276
214
 
277
215
  ```javascript
278
- memorio.isBrowser() // true in Chrome, Firefox, Safari
279
- memorio.isNode() // true in Node.js
280
- memorio.isDeno() // true in Deno
281
- memorio.isEdge() // true in Cloudflare Workers, Vercel Edge
216
+ memorio.isBrowser()
217
+ memorio.isNode()
218
+ memorio.isDeno()
219
+ memorio.isEdge()
282
220
 
283
- const caps = memorio.getCapabilities()
221
+ memorio.getCapabilities()
284
222
  // { platform: 'browser', hasLocalStorage: true, hasIndexedDB: true, ... }
285
223
  ```
286
224
 
287
- Named exports work too:
288
-
289
- ```typescript
290
- import { isBrowser, isNode, getCapabilities } from 'memorio'
291
- ```
225
+ Check `getCapabilities()` before leaning on `idb` or persistent `store`/`session` in code that might run on more than one platform.
292
226
 
293
227
  ---
294
228
 
295
- ## 🏢 Context isolation (multi-tenant)
229
+ ## Context Isolation (multi-tenant)
296
230
 
297
231
  ```javascript
298
- // Create isolated context
299
232
  const ctx = memorio.createContext('tenant-123')
300
233
 
301
- // Use context storage (prefix: 'tenant-123-key')
302
234
  ctx.state.user = { name: 'Isolated' }
303
235
  ctx.store.set('settings', { theme: 'dark' })
304
- ctx.session.set('token', 'abc123')
305
-
306
- // Context is completely isolated from global state
307
- console.debug(state.user) // undefined
308
236
 
309
- // Manage contexts
310
- memorio.listContexts() // ['tenant-123']
311
- memorio.deleteContext('tenant-123')
237
+ console.debug(state.user) // undefined — separate namespace
312
238
  ```
313
239
 
314
- Named exports:
315
-
316
- ```typescript
317
- import { createContext, listContexts, deleteContext, isolate } from 'memorio'
318
- ```
240
+ Handy for keeping per-tenant or per-request state from colliding in the same process. Under the hood it's a key prefix (`tenant-123-key`) inside the same storage — a naming convention, not a hard memory boundary. Great for a browser tab; if you're running this in a shared Node.js process or an edge isolate that might be reused across requests, make sure tenant IDs can't collide or be forged, and don't treat this as your only isolation layer if the stakes are real.
319
241
 
320
242
  ---
321
243
 
322
- ## 🖥️ Cross-Platform
244
+ ## Security
323
245
 
324
- Memorio runs in every JavaScript environment, with automatic fallbacks.
325
-
326
- | Tool | Browser | Node.js | Deno | Edge / Workers |
327
- |---|---|---|---|---|
328
- | `state` | ✅ | ✅ | ✅ | ✅ |
329
- | `observer` / `useObserver` | ✅ | ✅ | ✅ | ✅ |
330
- | `cache` | ✅ | ✅ | ✅ | ✅ |
331
- | `store` | localStorage | memory | memory | localStorage |
332
- | `session` | sessionStorage | memory | memory | sessionStorage |
333
- | `idb` | IndexedDB | ❌ | ❌ | ⚠️ |
334
- | `devtools` | ✅ | ❌ | ❌ | ⚠️ |
335
-
336
- > **Why memory fallbacks on the server?** There is no browser. `store` and `session` gracefully fall back to `Map`. You still get the same API. Same `state`, same `cache`, same `useObserver`. No extra config required.
337
-
338
- ---
246
+ - Zero production dependencies.
247
+ - No `eval`, no dynamic code execution, no bundled telemetry.
248
+ - Session IDs generated via `crypto.randomUUID`.
249
+ - Inputs validated, keys sanitized, errors caught at module boundaries.
339
250
 
340
- ## 🔒 Security
251
+ memorio does **not** encrypt what you put into `store`, `session`, or `idb` — they're thin, fast wrappers over `localStorage`/`sessionStorage`/`IndexedDB`, which store data in the clear on the user's device. Bring your own encryption layer for tokens, secrets, or regulated personal data.
341
252
 
342
- - Zero production dependencies — no supply chain surprises
343
- - NIST & NSA aligned — enterprise-grade security standards
344
- - No `eval`, no obfuscation, no hardcoded secrets
345
- - All inputs validated, keys sanitized, errors caught
346
- - Secure random session IDs via `crypto.randomUUID`
253
+ We build with recognized engineering guidance (NIST SP 800-53 practices) as an input to how we write code that's a design discipline, not a compliance certificate, and no third-party audit has been performed. Found an issue? Report it privately — see `SECURITY.md` — rather than opening a public issue.
347
254
 
348
255
  ---
349
256
 
350
- ## 📄 License
257
+ ## License
351
258
 
352
259
  MIT © [Dario Passariello](https://dario.passariello.ca)