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.
- package/AGENTS.md +3 -3
- package/README.md +307 -359
- package/SECURITY.md +17 -1
- package/SUMMARY.md +59 -45
- package/adr/001-state-proxy-model.md +96 -0
- package/adr/002-observer-semantics.md +180 -0
- package/adr/003-deep-mutation-semantics.md +129 -0
- package/adr/004-array-mutation-semantics.md +128 -0
- package/adr/005-scheduler-contract.md +149 -0
- package/adr/006-context-isolation.md +92 -0
- package/adr/007-mutation-records.md +118 -0
- package/adr/008-transactions.md +106 -0
- package/adr/009-history-model.md +110 -0
- package/adr/README.md +46 -0
- package/adr/template.md +49 -0
- package/examples/basic.ts +115 -115
- package/examples/browser-vanilla.html +358 -358
- package/examples/cache.ts +72 -72
- package/examples/cross-platform-guards.ts +57 -57
- package/examples/history.ts +104 -0
- package/examples/idb.ts +109 -109
- package/examples/multi-tenant-context.ts +44 -44
- package/examples/node-server.ts +308 -308
- package/examples/observer.ts +60 -60
- package/examples/platform.ts +115 -115
- package/examples/react-app.tsx +362 -362
- package/examples/react-observer.tsx +63 -63
- package/examples/semantic-memory.ts +60 -60
- package/examples/session-advanced.ts +91 -91
- package/examples/sqlite-batched-writes.ts +57 -57
- package/examples/state-advanced.ts +89 -89
- package/examples/store-advanced.ts +117 -117
- package/examples/sync.ts +90 -0
- package/examples/typed-and-schema.ts +102 -100
- package/examples/useObserver.tsx +140 -141
- package/global.cjs +4594 -0
- package/global.d.ts +8 -0
- package/global.js +4532 -0
- package/index.cjs +700 -678
- package/index.d.ts +1 -0
- package/index.js +680 -677
- package/llms.txt +72 -4
- package/markdown/AUDIT-REPORT.md +135 -0
- package/markdown/CACHE.md +100 -0
- package/markdown/CHANGELOG.md +243 -0
- package/markdown/DEVTOOLS.md +129 -0
- package/markdown/DISPATCH.md +177 -0
- package/markdown/HISTORY.md +199 -0
- package/markdown/IDB.md +178 -0
- package/markdown/IMPORT.md +153 -0
- package/markdown/INSPECT.md +123 -0
- package/markdown/LOGGER.md +154 -0
- package/markdown/MEMORY-ATTACHMENT.md +96 -0
- package/markdown/MEMORY.md +162 -0
- package/markdown/OBSERVER.md +209 -0
- package/markdown/PLATFORM.md +271 -0
- package/markdown/PROJECT.md +311 -0
- package/markdown/SCHEMA.md +176 -0
- package/markdown/SECURITY.md +330 -0
- package/markdown/SESSION.md +165 -0
- package/markdown/SQLITE.md +190 -0
- package/markdown/STATE.md +160 -0
- package/markdown/STORE.md +171 -0
- package/markdown/SYNC.md +319 -0
- package/markdown/TYPED.md +165 -0
- package/markdown/USEOBSERVER.md +257 -0
- package/modules/redux.cjs +320 -167
- package/modules/redux.cjs.map +1 -1
- package/modules/redux.js +320 -167
- package/modules/redux.js.map +1 -1
- package/package.json +13 -3
- package/types/env.d.ts +19 -9
- package/types/exports.d.ts +20 -0
- package/types/history.d.ts +13 -1
- package/types/memorio.d.ts +17 -5
- package/types/mutation.d.ts +75 -0
package/examples/cache.ts
CHANGED
|
@@ -1,72 +1,72 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memorio Cache Example
|
|
3
|
-
*
|
|
4
|
-
* This example demonstrates the cache module for in-memory storage.
|
|
5
|
-
* Cache is perfect for temporary data that doesn't need persistence.
|
|
6
|
-
*
|
|
7
|
-
* Run: npx ts-node examples/cache.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import 'memorio'
|
|
11
|
-
|
|
12
|
-
// ============================================
|
|
13
|
-
// BASIC CACHE OPERATIONS
|
|
14
|
-
// ============================================
|
|
15
|
-
|
|
16
|
-
// Set values
|
|
17
|
-
cache.set('username', 'Mario')
|
|
18
|
-
cache.set('score', 1500)
|
|
19
|
-
cache.set('player', { name: 'Mario', level: 5 })
|
|
20
|
-
|
|
21
|
-
// Get values
|
|
22
|
-
console.debug('Username:', cache.get('username'))
|
|
23
|
-
console.debug('Score:', cache.get('score'))
|
|
24
|
-
console.debug('Player:', cache.get('player'))
|
|
25
|
-
|
|
26
|
-
// Direct property access
|
|
27
|
-
cache.tempData = 'Hello'
|
|
28
|
-
console.debug('Temp data:', cache.tempData)
|
|
29
|
-
|
|
30
|
-
// ============================================
|
|
31
|
-
// CACHE WITH OBJECTS
|
|
32
|
-
// ============================================
|
|
33
|
-
|
|
34
|
-
// Store complex objects
|
|
35
|
-
cache.set('gameState', {
|
|
36
|
-
level: 3,
|
|
37
|
-
score: 2500,
|
|
38
|
-
inventory: ['sword', 'shield', 'potion'],
|
|
39
|
-
position: { x: 100, y: 200 }
|
|
40
|
-
})
|
|
41
|
-
|
|
42
|
-
const gameState = cache.get('gameState')
|
|
43
|
-
console.debug('Game level:', gameState?.level)
|
|
44
|
-
console.debug('Inventory:', gameState?.inventory)
|
|
45
|
-
|
|
46
|
-
// ============================================
|
|
47
|
-
// CACHE SIZE
|
|
48
|
-
// ============================================
|
|
49
|
-
|
|
50
|
-
// Add multiple items
|
|
51
|
-
for (let i = 0; i < 10; i++) {
|
|
52
|
-
cache.set(`item_${i}`, { id: i, data: `item-${i}` })
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.debug('Cache keys:', Object.keys(cache))
|
|
56
|
-
|
|
57
|
-
// ============================================
|
|
58
|
-
// CLEANUP
|
|
59
|
-
// ============================================
|
|
60
|
-
|
|
61
|
-
// Remove single item
|
|
62
|
-
cache.remove('username')
|
|
63
|
-
|
|
64
|
-
// Clear all cache
|
|
65
|
-
cache.removeAll()
|
|
66
|
-
|
|
67
|
-
// Or use clearAll alias
|
|
68
|
-
// cache.clearAll()
|
|
69
|
-
|
|
70
|
-
console.debug('Cache after clear:', cache.get('score'))
|
|
71
|
-
|
|
72
|
-
console.debug('Cache example complete!')
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Cache Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the cache module for in-memory storage.
|
|
5
|
+
* Cache is perfect for temporary data that doesn't need persistence.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx ts-node examples/cache.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { cache } from 'memorio'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// BASIC CACHE OPERATIONS
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
// Set values
|
|
17
|
+
cache.set('username', 'Mario')
|
|
18
|
+
cache.set('score', 1500)
|
|
19
|
+
cache.set('player', { name: 'Mario', level: 5 })
|
|
20
|
+
|
|
21
|
+
// Get values
|
|
22
|
+
console.debug('Username:', cache.get('username'))
|
|
23
|
+
console.debug('Score:', cache.get('score'))
|
|
24
|
+
console.debug('Player:', cache.get('player'))
|
|
25
|
+
|
|
26
|
+
// Direct property access
|
|
27
|
+
cache.tempData = 'Hello'
|
|
28
|
+
console.debug('Temp data:', cache.tempData)
|
|
29
|
+
|
|
30
|
+
// ============================================
|
|
31
|
+
// CACHE WITH OBJECTS
|
|
32
|
+
// ============================================
|
|
33
|
+
|
|
34
|
+
// Store complex objects
|
|
35
|
+
cache.set('gameState', {
|
|
36
|
+
level: 3,
|
|
37
|
+
score: 2500,
|
|
38
|
+
inventory: ['sword', 'shield', 'potion'],
|
|
39
|
+
position: { x: 100, y: 200 }
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const gameState = cache.get('gameState')
|
|
43
|
+
console.debug('Game level:', gameState?.level)
|
|
44
|
+
console.debug('Inventory:', gameState?.inventory)
|
|
45
|
+
|
|
46
|
+
// ============================================
|
|
47
|
+
// CACHE SIZE
|
|
48
|
+
// ============================================
|
|
49
|
+
|
|
50
|
+
// Add multiple items
|
|
51
|
+
for (let i = 0; i < 10; i++) {
|
|
52
|
+
cache.set(`item_${i}`, { id: i, data: `item-${i}` })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.debug('Cache keys:', Object.keys(cache))
|
|
56
|
+
|
|
57
|
+
// ============================================
|
|
58
|
+
// CLEANUP
|
|
59
|
+
// ============================================
|
|
60
|
+
|
|
61
|
+
// Remove single item
|
|
62
|
+
cache.remove('username')
|
|
63
|
+
|
|
64
|
+
// Clear all cache
|
|
65
|
+
cache.removeAll()
|
|
66
|
+
|
|
67
|
+
// Or use clearAll alias
|
|
68
|
+
// cache.clearAll()
|
|
69
|
+
|
|
70
|
+
console.debug('Cache after clear:', cache.get('score'))
|
|
71
|
+
|
|
72
|
+
console.debug('Cache example complete!')
|
|
@@ -1,57 +1,57 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cross-platform-guards.ts
|
|
3
|
-
*
|
|
4
|
-
* Scenario: code that might run in a browser, Node.js, Deno, or an edge
|
|
5
|
-
* runtime, and needs to behave correctly (not just "not crash") in each.
|
|
6
|
-
*
|
|
7
|
-
* Prefer `getCapabilities()` over branching on `isBrowser()`/`isNode()`
|
|
8
|
-
* alone - capability support can differ *within* a platform category
|
|
9
|
-
* (some edge runtimes expose `localStorage`, some don't).
|
|
10
|
-
*/
|
|
11
|
-
import { memorio, idb, store, session } from 'memorio'
|
|
12
|
-
|
|
13
|
-
export async function savePreferences(prefs: Record<string, unknown>) {
|
|
14
|
-
const caps = memorio.getCapabilities()
|
|
15
|
-
|
|
16
|
-
// store/session silently fall back to a non-durable in-memory Map outside
|
|
17
|
-
// the browser - that's not a crash, but it's also not what "save" implies
|
|
18
|
-
// to a caller. Surface the distinction instead of hiding it.
|
|
19
|
-
store.set('preferences', prefs)
|
|
20
|
-
|
|
21
|
-
if (!store.isPersistent) {
|
|
22
|
-
console.warn(
|
|
23
|
-
'[preferences] store is not durable in this environment (%s) - ' +
|
|
24
|
-
'data will not survive a process restart',
|
|
25
|
-
caps.platform
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return store.isPersistent
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export async function saveStructuredRecord(dbName: string, table: string, record: unknown) {
|
|
33
|
-
// idb is disabled outright in Node.js/Deno (no-op + warning). Guard
|
|
34
|
-
// explicitly instead of relying on that warning reaching anyone.
|
|
35
|
-
if (!idb.db.support()) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`[saveStructuredRecord] IndexedDB is not available in this environment ` +
|
|
38
|
-
`(platform: ${memorio.getCapabilities().platform}). ` +
|
|
39
|
-
`Use sqlite or store instead when running outside a browser.`
|
|
40
|
-
)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
await idb.db.create(dbName)
|
|
44
|
-
await idb.table.create(dbName, table)
|
|
45
|
-
await idb.data.set(dbName, table, record)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function describeEnvironment() {
|
|
49
|
-
const caps = memorio.getCapabilities()
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
platform: caps.platform,
|
|
53
|
-
canPersistAcrossTabs: caps.hasLocalStorage,
|
|
54
|
-
canPersistThisTabOnly: session.isPersistent,
|
|
55
|
-
canUseStructuredStorage: caps.hasIndexedDB,
|
|
56
|
-
}
|
|
57
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* cross-platform-guards.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: code that might run in a browser, Node.js, Deno, or an edge
|
|
5
|
+
* runtime, and needs to behave correctly (not just "not crash") in each.
|
|
6
|
+
*
|
|
7
|
+
* Prefer `getCapabilities()` over branching on `isBrowser()`/`isNode()`
|
|
8
|
+
* alone - capability support can differ *within* a platform category
|
|
9
|
+
* (some edge runtimes expose `localStorage`, some don't).
|
|
10
|
+
*/
|
|
11
|
+
import { memorio, idb, store, session } from 'memorio'
|
|
12
|
+
|
|
13
|
+
export async function savePreferences(prefs: Record<string, unknown>) {
|
|
14
|
+
const caps = memorio.getCapabilities()
|
|
15
|
+
|
|
16
|
+
// store/session silently fall back to a non-durable in-memory Map outside
|
|
17
|
+
// the browser - that's not a crash, but it's also not what "save" implies
|
|
18
|
+
// to a caller. Surface the distinction instead of hiding it.
|
|
19
|
+
store.set('preferences', prefs)
|
|
20
|
+
|
|
21
|
+
if (!store.isPersistent) {
|
|
22
|
+
console.warn(
|
|
23
|
+
'[preferences] store is not durable in this environment (%s) - ' +
|
|
24
|
+
'data will not survive a process restart',
|
|
25
|
+
caps.platform
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return store.isPersistent
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function saveStructuredRecord(dbName: string, table: string, record: unknown) {
|
|
33
|
+
// idb is disabled outright in Node.js/Deno (no-op + warning). Guard
|
|
34
|
+
// explicitly instead of relying on that warning reaching anyone.
|
|
35
|
+
if (!idb.db.support()) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`[saveStructuredRecord] IndexedDB is not available in this environment ` +
|
|
38
|
+
`(platform: ${memorio.getCapabilities().platform}). ` +
|
|
39
|
+
`Use sqlite or store instead when running outside a browser.`
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await idb.db.create(dbName)
|
|
44
|
+
await idb.table.create(dbName, table)
|
|
45
|
+
await idb.data.set(dbName, table, record)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function describeEnvironment() {
|
|
49
|
+
const caps = memorio.getCapabilities()
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
platform: caps.platform,
|
|
53
|
+
canPersistAcrossTabs: caps.hasLocalStorage,
|
|
54
|
+
canPersistThisTabOnly: session.isPersistent,
|
|
55
|
+
canUseStructuredStorage: caps.hasIndexedDB,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* history.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: an AI agent (or any code) that wants to try a batch of state
|
|
5
|
+
* mutations, inspect what actually changed, and only keep them if they
|
|
6
|
+
* look right - otherwise roll everything back in one shot.
|
|
7
|
+
*
|
|
8
|
+
* History tracking is opt-in and disabled by default to avoid overhead -
|
|
9
|
+
* call `memorio.enableHistory()` before you rely on undo/redo/trace.
|
|
10
|
+
* `snapshot()`/`diff()`/`rollback()` work independently of that flag.
|
|
11
|
+
*/
|
|
12
|
+
import { memorio, state } from 'memorio'
|
|
13
|
+
|
|
14
|
+
export function tryExperiment(mutate: () => void) {
|
|
15
|
+
// Snapshot first - this is the safe rollback point, independent of
|
|
16
|
+
// whether history tracking is enabled.
|
|
17
|
+
const before = memorio.snapshot()
|
|
18
|
+
|
|
19
|
+
mutate()
|
|
20
|
+
|
|
21
|
+
const changes = memorio.diff(before)
|
|
22
|
+
console.debug('Proposed changes:', changes)
|
|
23
|
+
|
|
24
|
+
return { before, changes }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function commitOrRollback(before: Record<string, unknown>, accept: boolean) {
|
|
28
|
+
if (!accept) {
|
|
29
|
+
// Full-state restore, in one step - not the same as calling undo()
|
|
30
|
+
// repeatedly, which only unwinds one mutation at a time.
|
|
31
|
+
memorio.rollback(before)
|
|
32
|
+
console.debug('Rolled back - state restored to pre-experiment snapshot')
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Undo / redo, step by step
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
export function stepByStepDemo() {
|
|
41
|
+
memorio.enableHistory()
|
|
42
|
+
|
|
43
|
+
state.user = { name: 'Sara' }
|
|
44
|
+
state.counter = 100
|
|
45
|
+
state.items = ['a', 'b']
|
|
46
|
+
|
|
47
|
+
memorio.undo() // removes state.items
|
|
48
|
+
memorio.undo() // counter → undefined
|
|
49
|
+
memorio.redo() // counter → 100 again
|
|
50
|
+
|
|
51
|
+
console.debug('canUndo:', memorio.canUndo())
|
|
52
|
+
console.debug('canRedo:', memorio.canRedo())
|
|
53
|
+
|
|
54
|
+
// Keep at most 50 mutations per undo/redo stack (default: 100). Lower
|
|
55
|
+
// this in memory-constrained environments.
|
|
56
|
+
memorio.setMaxHistory(50)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Trace log - every mutation, with before/after values and a timestamp
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
export function auditTrail() {
|
|
64
|
+
memorio.enableHistory()
|
|
65
|
+
|
|
66
|
+
state.user.name = 'Sara'
|
|
67
|
+
state.counter = 1
|
|
68
|
+
state.counter = 2
|
|
69
|
+
|
|
70
|
+
const log = memorio.trace()
|
|
71
|
+
console.debug('Mutation log:', log)
|
|
72
|
+
|
|
73
|
+
// ⚠️ The trace log records every value written, including tokens/PII.
|
|
74
|
+
// Don't ship it as-is to a client, and clear it (or disable tracking)
|
|
75
|
+
// on paths that touch sensitive data.
|
|
76
|
+
return log
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function exportAndReplayTrace() {
|
|
80
|
+
const log = memorio.trace()
|
|
81
|
+
const saved = JSON.stringify(log)
|
|
82
|
+
|
|
83
|
+
// ... persist `saved` somewhere (store, a file, a request body) ...
|
|
84
|
+
|
|
85
|
+
const restored = JSON.parse(saved) as Array<{ action: string; path: string; newValue: unknown }>
|
|
86
|
+
for (const record of restored) {
|
|
87
|
+
if (record.action === 'set') {
|
|
88
|
+
// `state` accepts dotted-path-style top-level keys as used in the
|
|
89
|
+
// trace log; nested paths need a small helper to walk into `state`.
|
|
90
|
+
;(state as Record<string, unknown>)[record.path] = record.newValue
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function resetHistory() {
|
|
96
|
+
memorio.clearHistory() // wipes undo/redo stacks + trace log (state itself is untouched)
|
|
97
|
+
memorio.clearRedo() // or just drop the redo stack, keeping undo intact
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Usage:
|
|
101
|
+
// const { before, changes } = tryExperiment(() => {
|
|
102
|
+
// state.user = { name: 'Sara', role: 'admin' }
|
|
103
|
+
// })
|
|
104
|
+
// commitOrRollback(before, /* accept = */ changes.length <= 3)
|
package/examples/idb.ts
CHANGED
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memorio IDB Example
|
|
3
|
-
*
|
|
4
|
-
* This example shows how to use IndexedDB for large data storage.
|
|
5
|
-
* IDB is perfect for caching, offline data, and large datasets.
|
|
6
|
-
*
|
|
7
|
-
* Run: npx ts-node examples/idb.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import 'memorio'
|
|
11
|
-
|
|
12
|
-
// ============================================
|
|
13
|
-
// CHECK SUPPORT
|
|
14
|
-
// ============================================
|
|
15
|
-
|
|
16
|
-
if (!idb.db.support()) {
|
|
17
|
-
console.debug('IndexedDB not supported')
|
|
18
|
-
process.exit(0)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ============================================
|
|
22
|
-
// CREATE DATABASE
|
|
23
|
-
// ============================================
|
|
24
|
-
|
|
25
|
-
// Create a new database
|
|
26
|
-
idb.db.create('myApp')
|
|
27
|
-
|
|
28
|
-
// Create tables (stores)
|
|
29
|
-
idb.table.create('myApp', 'users')
|
|
30
|
-
idb.table.create('myApp', 'products')
|
|
31
|
-
|
|
32
|
-
// ============================================
|
|
33
|
-
// ADD DATA
|
|
34
|
-
// ============================================
|
|
35
|
-
|
|
36
|
-
// Add user records
|
|
37
|
-
idb.data.set('myApp', 'users', {
|
|
38
|
-
id: 1,
|
|
39
|
-
name: 'Mario',
|
|
40
|
-
email: 'mario@example.com',
|
|
41
|
-
role: 'admin'
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
idb.data.set('myApp', 'users', {
|
|
45
|
-
id: 2,
|
|
46
|
-
name: 'Luigi',
|
|
47
|
-
email: 'luigi@example.com',
|
|
48
|
-
role: 'user'
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Add product records
|
|
52
|
-
idb.data.set('myApp', 'products', {
|
|
53
|
-
id: 1,
|
|
54
|
-
name: 'Super Mushroom',
|
|
55
|
-
price: 99,
|
|
56
|
-
inStock: true
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
idb.data.set('myApp', 'products', {
|
|
60
|
-
id: 2,
|
|
61
|
-
name: 'Fire Flower',
|
|
62
|
-
price: 199,
|
|
63
|
-
inStock: true
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
// ============================================
|
|
67
|
-
// READ DATA
|
|
68
|
-
// ============================================
|
|
69
|
-
|
|
70
|
-
const user1 = idb.data.get('myApp', 'users', 1)
|
|
71
|
-
console.debug('User 1:', user1)
|
|
72
|
-
|
|
73
|
-
const product1 = idb.data.get('myApp', 'products', 1)
|
|
74
|
-
console.debug('Product 1:', product1)
|
|
75
|
-
|
|
76
|
-
// ============================================
|
|
77
|
-
// DATABASE INFO
|
|
78
|
-
// ============================================
|
|
79
|
-
|
|
80
|
-
// List all databases
|
|
81
|
-
const databases = idb.db.list()
|
|
82
|
-
console.debug('Databases:', databases)
|
|
83
|
-
|
|
84
|
-
// Check if database exists
|
|
85
|
-
const exists = idb.db.exist('myApp')
|
|
86
|
-
console.debug('myApp exists:', exists)
|
|
87
|
-
|
|
88
|
-
// Get database version
|
|
89
|
-
const version = idb.db.version('myApp')
|
|
90
|
-
console.debug('myApp version:', version)
|
|
91
|
-
|
|
92
|
-
// Get database size
|
|
93
|
-
const size = idb.db.size('myApp')
|
|
94
|
-
console.debug('myApp size:', size, 'bytes')
|
|
95
|
-
|
|
96
|
-
// ============================================
|
|
97
|
-
// DELETE DATA
|
|
98
|
-
// ============================================
|
|
99
|
-
|
|
100
|
-
// Delete a record
|
|
101
|
-
idb.data.delete('myApp', 'users', 1)
|
|
102
|
-
|
|
103
|
-
// Delete entire table
|
|
104
|
-
// idb.table.delete('myApp', 'users');
|
|
105
|
-
|
|
106
|
-
// Delete entire database
|
|
107
|
-
// idb.db.delete('myApp');
|
|
108
|
-
|
|
109
|
-
console.debug('IDB example complete!')
|
|
1
|
+
/**
|
|
2
|
+
* Memorio IDB Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to use IndexedDB for large data storage.
|
|
5
|
+
* IDB is perfect for caching, offline data, and large datasets.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx ts-node examples/idb.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { idb } from 'memorio'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// CHECK SUPPORT
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
if (!idb.db.support()) {
|
|
17
|
+
console.debug('IndexedDB not supported')
|
|
18
|
+
process.exit(0)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ============================================
|
|
22
|
+
// CREATE DATABASE
|
|
23
|
+
// ============================================
|
|
24
|
+
|
|
25
|
+
// Create a new database
|
|
26
|
+
idb.db.create('myApp')
|
|
27
|
+
|
|
28
|
+
// Create tables (stores)
|
|
29
|
+
idb.table.create('myApp', 'users')
|
|
30
|
+
idb.table.create('myApp', 'products')
|
|
31
|
+
|
|
32
|
+
// ============================================
|
|
33
|
+
// ADD DATA
|
|
34
|
+
// ============================================
|
|
35
|
+
|
|
36
|
+
// Add user records
|
|
37
|
+
idb.data.set('myApp', 'users', {
|
|
38
|
+
id: 1,
|
|
39
|
+
name: 'Mario',
|
|
40
|
+
email: 'mario@example.com',
|
|
41
|
+
role: 'admin'
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
idb.data.set('myApp', 'users', {
|
|
45
|
+
id: 2,
|
|
46
|
+
name: 'Luigi',
|
|
47
|
+
email: 'luigi@example.com',
|
|
48
|
+
role: 'user'
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Add product records
|
|
52
|
+
idb.data.set('myApp', 'products', {
|
|
53
|
+
id: 1,
|
|
54
|
+
name: 'Super Mushroom',
|
|
55
|
+
price: 99,
|
|
56
|
+
inStock: true
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
idb.data.set('myApp', 'products', {
|
|
60
|
+
id: 2,
|
|
61
|
+
name: 'Fire Flower',
|
|
62
|
+
price: 199,
|
|
63
|
+
inStock: true
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
// ============================================
|
|
67
|
+
// READ DATA
|
|
68
|
+
// ============================================
|
|
69
|
+
|
|
70
|
+
const user1 = idb.data.get('myApp', 'users', 1)
|
|
71
|
+
console.debug('User 1:', user1)
|
|
72
|
+
|
|
73
|
+
const product1 = idb.data.get('myApp', 'products', 1)
|
|
74
|
+
console.debug('Product 1:', product1)
|
|
75
|
+
|
|
76
|
+
// ============================================
|
|
77
|
+
// DATABASE INFO
|
|
78
|
+
// ============================================
|
|
79
|
+
|
|
80
|
+
// List all databases
|
|
81
|
+
const databases = idb.db.list()
|
|
82
|
+
console.debug('Databases:', databases)
|
|
83
|
+
|
|
84
|
+
// Check if database exists
|
|
85
|
+
const exists = idb.db.exist('myApp')
|
|
86
|
+
console.debug('myApp exists:', exists)
|
|
87
|
+
|
|
88
|
+
// Get database version
|
|
89
|
+
const version = idb.db.version('myApp')
|
|
90
|
+
console.debug('myApp version:', version)
|
|
91
|
+
|
|
92
|
+
// Get database size
|
|
93
|
+
const size = idb.db.size('myApp')
|
|
94
|
+
console.debug('myApp size:', size, 'bytes')
|
|
95
|
+
|
|
96
|
+
// ============================================
|
|
97
|
+
// DELETE DATA
|
|
98
|
+
// ============================================
|
|
99
|
+
|
|
100
|
+
// Delete a record
|
|
101
|
+
idb.data.delete('myApp', 'users', 1)
|
|
102
|
+
|
|
103
|
+
// Delete entire table
|
|
104
|
+
// idb.table.delete('myApp', 'users');
|
|
105
|
+
|
|
106
|
+
// Delete entire database
|
|
107
|
+
// idb.db.delete('myApp');
|
|
108
|
+
|
|
109
|
+
console.debug('IDB example complete!')
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* multi-tenant-context.ts
|
|
3
|
-
*
|
|
4
|
-
* Scenario: a server-side handler (API route, edge function) that may
|
|
5
|
-
* process requests from different users/tenants in the same process.
|
|
6
|
-
*
|
|
7
|
-
* `state` is a shared global namespace by default - writing to it directly
|
|
8
|
-
* in a multi-request handler leaks data between requests. Use an explicit
|
|
9
|
-
* context per request instead.
|
|
10
|
-
*/
|
|
11
|
-
import { memorio } from 'memorio'
|
|
12
|
-
|
|
13
|
-
interface RequestContext {
|
|
14
|
-
userId: string
|
|
15
|
-
tenantId: string
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function handleRequest(req: RequestContext, payload: { language: string }) {
|
|
19
|
-
// Derive the context id from trusted, server-verified data - never from a
|
|
20
|
-
// raw client-supplied header or query param, since contexts are a
|
|
21
|
-
// key-prefix convention, not a hard isolation boundary.
|
|
22
|
-
const contextId = `tenant:${req.tenantId}:user:${req.userId}`
|
|
23
|
-
const ctx = memorio.createContext(contextId)
|
|
24
|
-
|
|
25
|
-
// All reads/writes go through ctx.state, not the bare global `state` -
|
|
26
|
-
// this is what actually keeps this request's data from leaking into the
|
|
27
|
-
// next one handled by the same process.
|
|
28
|
-
ctx.state.language = payload.language
|
|
29
|
-
|
|
30
|
-
return { language: ctx.state.language }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Cleanup for long-lived processes that create many short-lived contexts
|
|
34
|
-
// (e.g. one per request in a busy server): drop the context when the
|
|
35
|
-
// request is done, or it accumulates for the life of the process.
|
|
36
|
-
export function teardownRequest(req: RequestContext) {
|
|
37
|
-
const contextId = `tenant:${req.tenantId}:user:${req.userId}`
|
|
38
|
-
memorio.deleteContext(contextId)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// NOTE: context isolation here is organizational (key-prefixing under the
|
|
42
|
-
// same storage), not a security boundary. Don't rely on it as the only
|
|
43
|
-
// thing preventing one tenant from reaching another tenant's data - enforce
|
|
44
|
-
// that at the auth/backend layer as well.
|
|
1
|
+
/**
|
|
2
|
+
* multi-tenant-context.ts
|
|
3
|
+
*
|
|
4
|
+
* Scenario: a server-side handler (API route, edge function) that may
|
|
5
|
+
* process requests from different users/tenants in the same process.
|
|
6
|
+
*
|
|
7
|
+
* `state` is a shared global namespace by default - writing to it directly
|
|
8
|
+
* in a multi-request handler leaks data between requests. Use an explicit
|
|
9
|
+
* context per request instead.
|
|
10
|
+
*/
|
|
11
|
+
import { memorio } from 'memorio'
|
|
12
|
+
|
|
13
|
+
interface RequestContext {
|
|
14
|
+
userId: string
|
|
15
|
+
tenantId: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function handleRequest(req: RequestContext, payload: { language: string }) {
|
|
19
|
+
// Derive the context id from trusted, server-verified data - never from a
|
|
20
|
+
// raw client-supplied header or query param, since contexts are a
|
|
21
|
+
// key-prefix convention, not a hard isolation boundary.
|
|
22
|
+
const contextId = `tenant:${req.tenantId}:user:${req.userId}`
|
|
23
|
+
const ctx = memorio.createContext(contextId)
|
|
24
|
+
|
|
25
|
+
// All reads/writes go through ctx.state, not the bare global `state` -
|
|
26
|
+
// this is what actually keeps this request's data from leaking into the
|
|
27
|
+
// next one handled by the same process.
|
|
28
|
+
ctx.state.language = payload.language
|
|
29
|
+
|
|
30
|
+
return { language: ctx.state.language }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Cleanup for long-lived processes that create many short-lived contexts
|
|
34
|
+
// (e.g. one per request in a busy server): drop the context when the
|
|
35
|
+
// request is done, or it accumulates for the life of the process.
|
|
36
|
+
export function teardownRequest(req: RequestContext) {
|
|
37
|
+
const contextId = `tenant:${req.tenantId}:user:${req.userId}`
|
|
38
|
+
memorio.deleteContext(contextId)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// NOTE: context isolation here is organizational (key-prefixing under the
|
|
42
|
+
// same storage), not a security boundary. Don't rely on it as the only
|
|
43
|
+
// thing preventing one tenant from reaching another tenant's data - enforce
|
|
44
|
+
// that at the auth/backend layer as well.
|