memorio 4.9.7 → 4.9.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +126 -0
- package/README.md +18 -2
- package/examples/basic.ts +115 -0
- package/examples/browser-vanilla.html +358 -0
- package/examples/cache.ts +72 -0
- package/examples/cross-platform-guards.ts +57 -0
- package/examples/idb.ts +109 -0
- package/examples/multi-tenant-context.ts +44 -0
- package/examples/node-server.ts +308 -0
- package/examples/observer.ts +60 -0
- package/examples/platform.ts +115 -0
- package/examples/react-app.tsx +362 -0
- package/examples/react-observer.tsx +63 -0
- package/examples/semantic-memory.ts +60 -0
- package/examples/session-advanced.ts +91 -0
- package/examples/sqlite-batched-writes.ts +57 -0
- package/examples/state-advanced.ts +89 -0
- package/examples/store-advanced.ts +117 -0
- package/examples/typed-and-schema.ts +100 -0
- package/examples/useObserver.tsx +141 -0
- package/index.cjs +58 -32
- package/index.js +58 -32
- package/package.json +1 -1
package/AGENTS.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# AGENTS.md — working with `memorio`
|
|
2
|
+
|
|
3
|
+
Operational conventions for AI coding agents working in a repository that depends on `memorio`.
|
|
4
|
+
See `README.md` and `llms.txt` for the full API. This file is decision rules: what to reach for,
|
|
5
|
+
when, and the specific footguns this library's own docs flag.
|
|
6
|
+
|
|
7
|
+
## Read this before generating any `memorio` code
|
|
8
|
+
|
|
9
|
+
`memorio`'s own `llms.txt` contains an explicit unresolved discrepancy:
|
|
10
|
+
|
|
11
|
+
> **Locking — VERIFY BEFORE PUBLISHING:** one source describes per-key locking
|
|
12
|
+
> (`state.config.lock()` freezes only `config`); another describes global locking
|
|
13
|
+
> (`state.lock()` / `state.unlock()` freezing everything). Do not generate code against this
|
|
14
|
+
> section until it's resolved against the real source.
|
|
15
|
+
|
|
16
|
+
**Do not write or suggest `.lock()` / `.unlock()` calls with a confident claim about scope**
|
|
17
|
+
(per-key vs. global) until you've confirmed the actual signature against the installed
|
|
18
|
+
package's type definitions or source — `node_modules/memorio/dist/*.d.ts` — in the target repo.
|
|
19
|
+
If you can't confirm it, say so explicitly rather than picking one behavior and presenting it
|
|
20
|
+
as fact.
|
|
21
|
+
|
|
22
|
+
## Choosing a layer
|
|
23
|
+
|
|
24
|
+
Don't default to `state` for everything just because it's the one-liner in the README. Walk the
|
|
25
|
+
decision tree from the README's ["Which layer should I use"](./README.md#which-layer-should-i-use)
|
|
26
|
+
section, and in particular:
|
|
27
|
+
|
|
28
|
+
| If the data is... | Use | Not |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| UI-reactive, doesn't need to survive reload | `state` | `store` (unnecessary persistence) |
|
|
31
|
+
| Small, must survive reload, any tab | `store` | `idb` (overkill for simple k/v) |
|
|
32
|
+
| Must survive reload, this tab only | `session` | `store` (wrong scope) |
|
|
33
|
+
| Relational, needs joins/SQL | `sqlite` | `idb` (no query language) |
|
|
34
|
+
| Structured, needs confidence/TTL/source/history | `memory` | `state`/`store` (no lifecycle semantics) |
|
|
35
|
+
| Large or structured, browser-only | `idb` | `store` (size/shape limits) |
|
|
36
|
+
|
|
37
|
+
`sqlite` and `memory` answer different questions — "what data do I have" vs. "what does the app
|
|
38
|
+
believe, and how sure is it." Don't reach for `memory` just because a value happens to come from
|
|
39
|
+
an LLM; if it doesn't need confidence scoring, TTL, or supersession history, plain `state`/`store`
|
|
40
|
+
is simpler and correct.
|
|
41
|
+
|
|
42
|
+
## Server-side and multi-request code — read this first
|
|
43
|
+
|
|
44
|
+
**`state` is a shared global namespace by default.** A value set in one request handler is
|
|
45
|
+
visible to every other read of `state` in the same process. There is no automatic per-request
|
|
46
|
+
or per-tab isolation.
|
|
47
|
+
|
|
48
|
+
Before generating any server-side code (API route, edge function, worker) that could handle more
|
|
49
|
+
than one user or request in the same process:
|
|
50
|
+
|
|
51
|
+
1. Create an explicit context per request/tenant: `const ctx = memorio.createContext(id)`, then
|
|
52
|
+
read/write via `ctx.state`, never the bare global `state`.
|
|
53
|
+
2. **Generate the context ID from trusted server-side data** (authenticated user id, request-scoped
|
|
54
|
+
token) — never directly from client-controlled input. Contexts are a key-prefix convention, not
|
|
55
|
+
a hard boundary, so a spoofable ID is a real collision risk, not just a naming concern.
|
|
56
|
+
3. Treat context isolation as *organizational*, not a security boundary. It does not replace
|
|
57
|
+
authorization checks at the backend layer — never use it as the only thing standing between
|
|
58
|
+
one tenant's data and another's.
|
|
59
|
+
|
|
60
|
+
If the code you're generating is client-side (a single browser tab, one user), bare `state` is
|
|
61
|
+
fine — this concern is specific to shared-process server/edge code.
|
|
62
|
+
|
|
63
|
+
## Known rough edges — don't paper over these in generated code
|
|
64
|
+
|
|
65
|
+
- **`store.quota()` returns `[0, 0]` for the `localStorage` backend** — it's a placeholder, not a
|
|
66
|
+
real reading. Don't generate capacity-check logic that branches on its result for `store`.
|
|
67
|
+
It may be meaningful for `idb`, but confirm before relying on it there too.
|
|
68
|
+
- **`idb` is disabled outright in Node.js/Deno** (calls warn and no-op). Any code that might run
|
|
69
|
+
outside a guaranteed browser context must guard: `if (idb.db.support())` or check
|
|
70
|
+
`memorio.getCapabilities().hasIndexedDB` — don't rely on the no-op warning alone reaching a log
|
|
71
|
+
anyone will see.
|
|
72
|
+
- **`sqlite` persistence serializes the entire database on every flush** — not incremental. Never
|
|
73
|
+
generate code that calls a persisting write inside a loop; batch writes in one transaction/run
|
|
74
|
+
and persist once after the batch, per the README's guidance.
|
|
75
|
+
- **`observer('state.some.path', cb)` paths are plain strings, unchecked against `state`'s actual
|
|
76
|
+
shape.** A typo or a later rename fails *silently* — the observer just never fires again. Prefer
|
|
77
|
+
`memorio.typed<T>()` + `registerSchema()` for anything you'd hate to have silently stop working,
|
|
78
|
+
and consider `memorio.pathExists('state.some.path')` as a startup sanity check for
|
|
79
|
+
observer-heavy code.
|
|
80
|
+
- **`memory.context()` is rule-based (tags/type/confidence/recency), not embedding-based.** Don't
|
|
81
|
+
imply semantic/meaning-based retrieval in comments or docs you generate — if the task genuinely
|
|
82
|
+
needs free-text similarity search, pair `memorio.memory` (for lifecycle: confidence, TTL,
|
|
83
|
+
supersession) with a separate embedding store, don't fake it with `context()` alone.
|
|
84
|
+
- **`memorio.logger` records the literal value written**, including tokens or PII, in
|
|
85
|
+
`getHistory()` / `exportLogs()`. Never enable it unconditionally on a production path that
|
|
86
|
+
touches sensitive data, and never wire `exportLogs()` output to analytics or error reporters
|
|
87
|
+
without redaction first.
|
|
88
|
+
- **Nothing in `store`, `session`, `idb`, or `memory` is encrypted.** Don't store auth tokens or
|
|
89
|
+
regulated data in any of these layers without adding your own encryption — flag it in code
|
|
90
|
+
review comments if you see it happening.
|
|
91
|
+
|
|
92
|
+
## React usage
|
|
93
|
+
|
|
94
|
+
`useObserver` has two modes — pick deliberately, don't mix them by accident:
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
// Auto-discovery: re-runs when any state path touched during the callback changes
|
|
98
|
+
useObserver(() => { console.debug('counter:', state.counter) }, state.counter)
|
|
99
|
+
|
|
100
|
+
// Explicit deps: only re-runs when the listed paths change
|
|
101
|
+
useObserver(() => { console.debug('user:', state.user) }, [state.user])
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`useObserver` is a thin bridge onto `observer` — it inherits the "unchecked path string" caveat
|
|
105
|
+
above for any path passed in string form.
|
|
106
|
+
|
|
107
|
+
## Cross-platform code
|
|
108
|
+
|
|
109
|
+
Before calling `idb`, `sqlite`, or `devtools`, or before relying on `store`/`session` durability,
|
|
110
|
+
check `memorio.getCapabilities()` rather than branching on `memorio.isNode()`/`isBrowser()` alone
|
|
111
|
+
— capabilities can differ inside a single platform category (e.g. some edge runtimes expose
|
|
112
|
+
`localStorage`, some don't). Generated code that assumes "browser == has IndexedDB" or
|
|
113
|
+
"Node == no persistence at all" will be wrong on edge runtimes.
|
|
114
|
+
|
|
115
|
+
## Testing
|
|
116
|
+
|
|
117
|
+
When testing `memory` lifecycle behavior (supersession, TTL, confidence), assert on the *history*
|
|
118
|
+
being preserved, not just the current `recall()` value — `update()` is documented to supersede,
|
|
119
|
+
not overwrite, and a test that only checks the latest value won't catch a regression that
|
|
120
|
+
silently starts overwriting instead.
|
|
121
|
+
|
|
122
|
+
## Related examples
|
|
123
|
+
|
|
124
|
+
See `examples/` in this repo for runnable reference implementations:
|
|
125
|
+
`multi-tenant-context.ts`, `react-observer.tsx`, `semantic-memory.ts`,
|
|
126
|
+
`sqlite-batched-writes.ts`, and `cross-platform-guards.ts`.
|
package/README.md
CHANGED
|
@@ -285,6 +285,18 @@ memorio.registerSchema('user', {
|
|
|
285
285
|
state.user = { name: 'Sara' } // rejected — missing required "email"
|
|
286
286
|
```
|
|
287
287
|
|
|
288
|
+
Array item validation is supported per-element with the `items` schema field:
|
|
289
|
+
|
|
290
|
+
```ts
|
|
291
|
+
memorio.registerSchema('tags', {
|
|
292
|
+
type: 'array',
|
|
293
|
+
items: { type: 'string' },
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
state.tags = ['a', 'b'] // accepted
|
|
297
|
+
state.tags = [1, 2, 3] // rejected — items must be strings
|
|
298
|
+
```
|
|
299
|
+
|
|
288
300
|
## Local-first sync
|
|
289
301
|
|
|
290
302
|
The local application owns its data; the cloud is optional transport.
|
|
@@ -302,7 +314,9 @@ memorio.memory.configure({
|
|
|
302
314
|
})
|
|
303
315
|
```
|
|
304
316
|
|
|
305
|
-
|
|
317
|
+
When `replay()` pulls remote operations, entries are ordered causally by HLC timestamp. For
|
|
318
|
+
*concurrent* writes to the same key (an HLC tie) the **remote entry wins by default**; provide a
|
|
319
|
+
`resolveConflict` to override:
|
|
306
320
|
|
|
307
321
|
```ts
|
|
308
322
|
memorio.memory.configure({
|
|
@@ -313,7 +327,9 @@ memorio.memory.configure({
|
|
|
313
327
|
})
|
|
314
328
|
```
|
|
315
329
|
|
|
316
|
-
|
|
330
|
+
When no resolver is supplied, Memorio logs a warn (dev only) and applies the remote entry on a
|
|
331
|
+
tie. The resolver only settles *client-side* divergence between what memorio has seen locally —
|
|
332
|
+
the provider is still responsible for the final server-side policy.
|
|
317
333
|
|
|
318
334
|
## Cross-platform support
|
|
319
335
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Basic Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows the fundamental features of Memorio:
|
|
5
|
+
* - State management
|
|
6
|
+
* - Store (localStorage)
|
|
7
|
+
* - Session storage
|
|
8
|
+
* - Platform detection
|
|
9
|
+
*
|
|
10
|
+
* Run: npx ts-node examples/basic.ts
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import 'memorio'
|
|
14
|
+
|
|
15
|
+
// ============================================
|
|
16
|
+
// PLATFORM DETECTION
|
|
17
|
+
// ============================================
|
|
18
|
+
|
|
19
|
+
console.debug('=== Platform Detection ===')
|
|
20
|
+
console.debug('Memorio version:', memorio.version)
|
|
21
|
+
|
|
22
|
+
// Check platform
|
|
23
|
+
if (memorio.isBrowser()) {
|
|
24
|
+
console.debug('Platform: Browser')
|
|
25
|
+
} else if (memorio.isNode()) {
|
|
26
|
+
console.debug('Platform: Node.js')
|
|
27
|
+
} else if (memorio.isDeno()) {
|
|
28
|
+
console.debug('Platform: Deno')
|
|
29
|
+
} else if (memorio.isEdge()) {
|
|
30
|
+
console.debug('Platform: Edge Worker')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Get capabilities
|
|
34
|
+
const caps = memorio.getCapabilities()
|
|
35
|
+
console.debug('Capabilities:', {
|
|
36
|
+
platform: caps.platform,
|
|
37
|
+
hasLocalStorage: caps.hasLocalStorage,
|
|
38
|
+
hasSessionStorage: caps.hasSessionStorage,
|
|
39
|
+
hasIndexedDB: caps.hasIndexedDB
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// ============================================
|
|
43
|
+
// STATE - In-memory reactive state
|
|
44
|
+
// ============================================
|
|
45
|
+
|
|
46
|
+
console.debug('\n=== State Management ===')
|
|
47
|
+
|
|
48
|
+
// Set some state values
|
|
49
|
+
state.name = 'Mario'
|
|
50
|
+
state.age = 30
|
|
51
|
+
state.isActive = true
|
|
52
|
+
state.profile = {
|
|
53
|
+
email: 'mario@example.com',
|
|
54
|
+
avatar: 'https://example.com/mario.png'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Get state values
|
|
58
|
+
console.debug('Name:', state.name)
|
|
59
|
+
console.debug('Age:', state.age)
|
|
60
|
+
console.debug('Profile:', state.profile)
|
|
61
|
+
|
|
62
|
+
// List all states
|
|
63
|
+
console.debug('All states:', state.list)
|
|
64
|
+
|
|
65
|
+
// ============================================
|
|
66
|
+
// STORE - Persistent localStorage
|
|
67
|
+
// ============================================
|
|
68
|
+
|
|
69
|
+
console.debug('\n=== Store (Persistent Storage) ===')
|
|
70
|
+
|
|
71
|
+
// Check persistence
|
|
72
|
+
console.debug('Is persistent:', store.isPersistent) // true in browser, false in Node.js/Deno
|
|
73
|
+
|
|
74
|
+
// Save to localStorage
|
|
75
|
+
store.set('username', 'mario')
|
|
76
|
+
store.set('preferences', { theme: 'dark', language: 'en' })
|
|
77
|
+
store.set('lastLogin', Date.now())
|
|
78
|
+
|
|
79
|
+
// Read from localStorage
|
|
80
|
+
console.debug('Username:', store.get('username'))
|
|
81
|
+
console.debug('Preferences:', store.get('preferences'))
|
|
82
|
+
|
|
83
|
+
// Get storage size
|
|
84
|
+
console.debug('Storage size:', store.size(), 'bytes')
|
|
85
|
+
|
|
86
|
+
// ============================================
|
|
87
|
+
// SESSION - Temporary session storage
|
|
88
|
+
// ============================================
|
|
89
|
+
|
|
90
|
+
console.debug('\n=== Session (Temporary Storage) ===')
|
|
91
|
+
|
|
92
|
+
// Check persistence
|
|
93
|
+
console.debug('Is persistent:', session.isPersistent) // true in browser, false in Node.js/Deno
|
|
94
|
+
|
|
95
|
+
// Save session data (cleared when tab closes)
|
|
96
|
+
session.set('token', 'abc123xyz')
|
|
97
|
+
session.set('userId', 42)
|
|
98
|
+
|
|
99
|
+
// Read session data
|
|
100
|
+
console.debug('Token:', session.get('token'))
|
|
101
|
+
console.debug('User ID:', session.get('userId'))
|
|
102
|
+
|
|
103
|
+
// ============================================
|
|
104
|
+
// CLEANUP
|
|
105
|
+
// ============================================
|
|
106
|
+
|
|
107
|
+
// Clear specific items
|
|
108
|
+
store.remove('username')
|
|
109
|
+
session.remove('token')
|
|
110
|
+
|
|
111
|
+
// Clear all
|
|
112
|
+
store.removeAll()
|
|
113
|
+
session.removeAll()
|
|
114
|
+
|
|
115
|
+
console.debug('\nExample complete!')
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Memorio Vanilla JS Example</title>
|
|
8
|
+
<style>
|
|
9
|
+
/* Simple CSS */
|
|
10
|
+
body {
|
|
11
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
12
|
+
max-width: 800px;
|
|
13
|
+
margin: 0 auto;
|
|
14
|
+
padding: 20px;
|
|
15
|
+
background: #f5f5f5;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.card {
|
|
19
|
+
background: white;
|
|
20
|
+
border-radius: 8px;
|
|
21
|
+
padding: 20px;
|
|
22
|
+
margin-bottom: 20px;
|
|
23
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
h1 {
|
|
27
|
+
color: #333;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
h2 {
|
|
31
|
+
color: #666;
|
|
32
|
+
margin-top: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
button {
|
|
36
|
+
background: #007bff;
|
|
37
|
+
color: white;
|
|
38
|
+
border: none;
|
|
39
|
+
padding: 10px 20px;
|
|
40
|
+
border-radius: 4px;
|
|
41
|
+
cursor: pointer;
|
|
42
|
+
margin: 5px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
button:hover {
|
|
46
|
+
background: #0056b3;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
button.danger {
|
|
50
|
+
background: #dc3545;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
button.danger:hover {
|
|
54
|
+
background: #c82333;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
button.success {
|
|
58
|
+
background: #28a745;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
button.success:hover {
|
|
62
|
+
background: #218838;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
input,
|
|
66
|
+
select {
|
|
67
|
+
padding: 8px;
|
|
68
|
+
border: 1px solid #ddd;
|
|
69
|
+
border-radius: 4px;
|
|
70
|
+
margin: 5px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pre {
|
|
74
|
+
background: #f8f9fa;
|
|
75
|
+
padding: 10px;
|
|
76
|
+
border-radius: 4px;
|
|
77
|
+
overflow-x: auto;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.output {
|
|
81
|
+
background: #e9ecef;
|
|
82
|
+
padding: 10px;
|
|
83
|
+
border-radius: 4px;
|
|
84
|
+
font-family: monospace;
|
|
85
|
+
white-space: pre-wrap;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.grid {
|
|
89
|
+
display: grid;
|
|
90
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
91
|
+
gap: 10px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.stat {
|
|
95
|
+
background: #e7f3ff;
|
|
96
|
+
padding: 10px;
|
|
97
|
+
border-radius: 4px;
|
|
98
|
+
text-align: center;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.stat-value {
|
|
102
|
+
font-size: 24px;
|
|
103
|
+
font-weight: bold;
|
|
104
|
+
color: #007bff;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.stat-label {
|
|
108
|
+
font-size: 12px;
|
|
109
|
+
color: #666;
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
|
|
114
|
+
<body>
|
|
115
|
+
<h1>🧠 Memorio Vanilla JS Demo</h1>
|
|
116
|
+
|
|
117
|
+
<!-- State Section -->
|
|
118
|
+
<div class="card">
|
|
119
|
+
<h2>1. State (Reactive)</h2>
|
|
120
|
+
<p>Global reactive state - changes trigger UI updates automatically</p>
|
|
121
|
+
|
|
122
|
+
<div class="grid">
|
|
123
|
+
<div class="stat">
|
|
124
|
+
<div class="stat-value" id="counter">0</div>
|
|
125
|
+
<div class="stat-label">Counter</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="stat">
|
|
128
|
+
<div class="stat-value" id="username">-</div>
|
|
129
|
+
<div class="stat-label">Username</div>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="stat">
|
|
132
|
+
<div class="stat-value" id="theme">light</div>
|
|
133
|
+
<div class="stat-label">Theme</div>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<br>
|
|
138
|
+
<button onclick="state.counter++">Increment Counter</button>
|
|
139
|
+
<button onclick="state.counter--">Decrement</button>
|
|
140
|
+
<button onclick="state.counter = 0">Reset</button>
|
|
141
|
+
|
|
142
|
+
<h3>Set Username:</h3>
|
|
143
|
+
<input type="text" id="nameInput" placeholder="Enter name">
|
|
144
|
+
<button onclick="state.username = document.getElementById('nameInput').value">Set</button>
|
|
145
|
+
|
|
146
|
+
<h3>Theme:</h3>
|
|
147
|
+
<button onclick="state.theme = 'light'">Light</button>
|
|
148
|
+
<button onclick="state.theme = 'dark'">Dark</button>
|
|
149
|
+
|
|
150
|
+
<h3>All State:</h3>
|
|
151
|
+
<pre id="stateOutput" class="output"></pre>
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
<!-- Store Section -->
|
|
155
|
+
<div class="card">
|
|
156
|
+
<h2>2. Store (localStorage)</h2>
|
|
157
|
+
<p>Permanent storage - survives page refresh</p>
|
|
158
|
+
<p>Persistent: <span id="storePersistent">checking...</span></p>
|
|
159
|
+
|
|
160
|
+
<input type="text" id="storeKey" placeholder="Key">
|
|
161
|
+
<input type="text" id="storeValue" placeholder="Value">
|
|
162
|
+
<button
|
|
163
|
+
onclick="store.set(document.getElementById('storeKey').value, document.getElementById('storeValue').value)">Save</button>
|
|
164
|
+
<button
|
|
165
|
+
onclick="document.getElementById('storeOutput').textContent = JSON.stringify(store.get(document.getElementById('storeKey').value), null, 2)">Get</button>
|
|
166
|
+
<button class="danger" onclick="store.remove(document.getElementById('storeKey').value)">Delete</button>
|
|
167
|
+
|
|
168
|
+
<h3>All Store Data:</h3>
|
|
169
|
+
<pre id="storeOutput" class="output"></pre>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<!-- Session Section -->
|
|
173
|
+
<div class="card">
|
|
174
|
+
<h2>3. Session (sessionStorage)</h2>
|
|
175
|
+
<p>Temporary storage - cleared when tab closes</p>
|
|
176
|
+
<p>Persistent: <span id="sessionPersistent">checking...</span></p>
|
|
177
|
+
|
|
178
|
+
<input type="text" id="sessionKey" placeholder="Key">
|
|
179
|
+
<input type="text" id="sessionValue" placeholder="Value">
|
|
180
|
+
<button
|
|
181
|
+
onclick="session.set(document.getElementById('sessionKey').value, document.getElementById('sessionValue').value)">Save</button>
|
|
182
|
+
<button
|
|
183
|
+
onclick="document.getElementById('sessionOutput').textContent = JSON.stringify(session.get(document.getElementById('sessionKey').value), null, 2)">Get</button>
|
|
184
|
+
<button class="danger" onclick="session.remove(document.getElementById('sessionKey').value)">Delete</button>
|
|
185
|
+
|
|
186
|
+
<h3>All Session Data:</h3>
|
|
187
|
+
<pre id="sessionOutput" class="output"></pre>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<!-- Cache Section -->
|
|
191
|
+
<div class="card">
|
|
192
|
+
<h2>4. Cache (In-Memory)</h2>
|
|
193
|
+
<p>Temporary cache - cleared on refresh</p>
|
|
194
|
+
|
|
195
|
+
<input type="text" id="cacheKey" placeholder="Key">
|
|
196
|
+
<input type="text" id="cacheValue" placeholder="Value">
|
|
197
|
+
<button
|
|
198
|
+
onclick="cache.set(document.getElementById('cacheKey').value, document.getElementById('cacheValue').value)">Save</button>
|
|
199
|
+
<button
|
|
200
|
+
onclick="document.getElementById('cacheOutput').textContent = JSON.stringify(cache.get(document.getElementById('cacheKey').value), null, 2)">Get</button>
|
|
201
|
+
<button class="danger" onclick="cache.remove(document.getElementById('cacheKey').value)">Delete</button>
|
|
202
|
+
|
|
203
|
+
<h3>All Cache Data:</h3>
|
|
204
|
+
<pre id="cacheOutput" class="output"></pre>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<!-- Observer Section -->
|
|
208
|
+
<div class="card">
|
|
209
|
+
<h2>5. Observer (Auto-Reactive)</h2>
|
|
210
|
+
<p>Watch for state changes automatically</p>
|
|
211
|
+
|
|
212
|
+
<button class="success" id="startObserver">Start Observer</button>
|
|
213
|
+
<button class="danger" id="stopObserver">Stop Observer</button>
|
|
214
|
+
<button onclick="state.testValue = Math.random()">Trigger Change</button>
|
|
215
|
+
|
|
216
|
+
<h3>Observer Log:</h3>
|
|
217
|
+
<pre id="observerLog" class="output"></pre>
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
<!-- Platform Info -->
|
|
221
|
+
<div class="card">
|
|
222
|
+
<h2>6. Platform Info</h2>
|
|
223
|
+
<div id="platformInfo" class="output"></div>
|
|
224
|
+
</div>
|
|
225
|
+
|
|
226
|
+
<!-- Load Memorio -->
|
|
227
|
+
<script src="https://cdn.jsdelivr.net/npm/memorio/index.cjs"></script>
|
|
228
|
+
|
|
229
|
+
<script>
|
|
230
|
+
// ============================================
|
|
231
|
+
// MEMORIO VANILLA JS EXAMPLE
|
|
232
|
+
// ============================================
|
|
233
|
+
|
|
234
|
+
// 1. REACTIVE STATE
|
|
235
|
+
// -----------------
|
|
236
|
+
|
|
237
|
+
// Initialize state
|
|
238
|
+
state.counter = 0
|
|
239
|
+
state.username = 'Guest'
|
|
240
|
+
state.theme = 'light'
|
|
241
|
+
state.testValue = 0
|
|
242
|
+
|
|
243
|
+
// Update UI when state changes
|
|
244
|
+
function updateStateUI() {
|
|
245
|
+
document.getElementById('counter').textContent = state.counter
|
|
246
|
+
document.getElementById('username').textContent = state.username
|
|
247
|
+
document.getElementById('theme').textContent = state.theme
|
|
248
|
+
document.getElementById('stateOutput').textContent = JSON.stringify({
|
|
249
|
+
counter: state.counter,
|
|
250
|
+
username: state.username,
|
|
251
|
+
theme: state.theme,
|
|
252
|
+
testValue: state.testValue
|
|
253
|
+
}, null, 2)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Use observer to react to changes
|
|
257
|
+
useObserver(() => {
|
|
258
|
+
updateStateUI()
|
|
259
|
+
document.body.className = state.theme
|
|
260
|
+
}, [state.counter, state.username, state.theme, state.testValue])
|
|
261
|
+
|
|
262
|
+
// 2. PERSISTENT STORE
|
|
263
|
+
// -------------------
|
|
264
|
+
|
|
265
|
+
// Check persistence
|
|
266
|
+
document.getElementById('storePersistent').textContent = store.isPersistent ? '✅ Yes (localStorage)' : '❌ No (memory)'
|
|
267
|
+
|
|
268
|
+
// Save sample data
|
|
269
|
+
store.set('visits', '0')
|
|
270
|
+
store.set('lastVisit', new Date().toISOString())
|
|
271
|
+
|
|
272
|
+
function updateStoreUI() {
|
|
273
|
+
document.getElementById('storeOutput').textContent = 'Visits: ' + store.get('visits') + '\nLast Visit: ' + store.get('lastVisit')
|
|
274
|
+
}
|
|
275
|
+
updateStoreUI()
|
|
276
|
+
|
|
277
|
+
// Increment visits
|
|
278
|
+
let visits = parseInt(store.get('visits') || '0')
|
|
279
|
+
store.set('visits', (visits + 1).toString())
|
|
280
|
+
|
|
281
|
+
// 3. SESSION STORAGE
|
|
282
|
+
// ------------------
|
|
283
|
+
|
|
284
|
+
document.getElementById('sessionPersistent').textContent = session.isPersistent ? '✅ Yes (sessionStorage)' : '❌ No (memory)'
|
|
285
|
+
|
|
286
|
+
session.set('pageViews', '0')
|
|
287
|
+
|
|
288
|
+
function updateSessionUI() {
|
|
289
|
+
document.getElementById('sessionOutput').textContent = 'Page Views: ' + session.get('pageViews')
|
|
290
|
+
}
|
|
291
|
+
updateSessionUI()
|
|
292
|
+
|
|
293
|
+
// Increment page views
|
|
294
|
+
let pageViews = parseInt(session.get('pageViews') || '0')
|
|
295
|
+
session.set('pageViews', (pageViews + 1).toString())
|
|
296
|
+
|
|
297
|
+
// 4. CACHE (In-Memory)
|
|
298
|
+
// --------------------
|
|
299
|
+
|
|
300
|
+
cache.set('tempData', { computed: true, timestamp: Date.now() })
|
|
301
|
+
|
|
302
|
+
function updateCacheUI() {
|
|
303
|
+
document.getElementById('cacheOutput').textContent = JSON.stringify(cache.get('tempData'), null, 2)
|
|
304
|
+
}
|
|
305
|
+
updateCacheUI()
|
|
306
|
+
|
|
307
|
+
// 5. OBSERVER
|
|
308
|
+
// -----------
|
|
309
|
+
|
|
310
|
+
let observerActive = false
|
|
311
|
+
const logEl = document.getElementById('observerLog')
|
|
312
|
+
|
|
313
|
+
document.getElementById('startObserver').onclick = () => {
|
|
314
|
+
observerActive = true
|
|
315
|
+
logEl.textContent += 'Observer started!\n'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
document.getElementById('stopObserver').onclick = () => {
|
|
319
|
+
observerActive = false
|
|
320
|
+
logEl.textContent += 'Observer stopped!\n'
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// This creates a persistent observer
|
|
324
|
+
useObserver((newVal, oldVal) => {
|
|
325
|
+
if (observerActive) {
|
|
326
|
+
logEl.textContent += `Changed: ${oldVal} → ${newVal}\n`
|
|
327
|
+
logEl.scrollTop = logEl.scrollHeight
|
|
328
|
+
}
|
|
329
|
+
}, [state.testValue])
|
|
330
|
+
|
|
331
|
+
// 6. PLATFORM INFO
|
|
332
|
+
// ----------------
|
|
333
|
+
|
|
334
|
+
const platformInfo = document.getElementById('platformInfo')
|
|
335
|
+
platformInfo.textContent = `
|
|
336
|
+
Platform Detection:
|
|
337
|
+
-----------------
|
|
338
|
+
Browser: ${memorio.isBrowser() ? '✅' : '❌'}
|
|
339
|
+
Node.js: ${memorio.isNode() ? '✅' : '❌'}
|
|
340
|
+
Deno: ${memorio.isDeno() ? '✅' : '❌'}
|
|
341
|
+
Edge: ${memorio.isEdge() ? '✅' : '❌'}
|
|
342
|
+
|
|
343
|
+
Capabilities:
|
|
344
|
+
-------------
|
|
345
|
+
localStorage: ${memorio.getCapabilities().hasLocalStorage ? '✅' : '❌'}
|
|
346
|
+
sessionStorage: ${memorio.getCapabilities().hasSessionStorage ? '✅' : '❌'}
|
|
347
|
+
IndexedDB: ${memorio.getCapabilities().hasIndexedDB ? '✅' : '❌'}
|
|
348
|
+
|
|
349
|
+
Session ID: ${memorio.getCapabilities().sessionId.substring(0, 8)}...
|
|
350
|
+
`.trim()
|
|
351
|
+
|
|
352
|
+
// Log welcome
|
|
353
|
+
console.debug('🧠 Memorio loaded!')
|
|
354
|
+
console.debug('Platform:', memorio.getCapabilities().platform)
|
|
355
|
+
</script>
|
|
356
|
+
</body>
|
|
357
|
+
|
|
358
|
+
</html>
|