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
|
@@ -0,0 +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!')
|
|
@@ -0,0 +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
|
+
}
|
package/examples/idb.ts
ADDED
|
@@ -0,0 +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!')
|
|
@@ -0,0 +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.
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Node.js Server Example
|
|
3
|
+
*
|
|
4
|
+
* This example shows how to use Memorio in a Node.js server environment.
|
|
5
|
+
* Includes context isolation for multi-tenant applications.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx ts-node examples/node-server.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'memorio'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// 1. BASIC USAGE
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
console.debug('=== 1. Basic Node.js Usage ===')
|
|
17
|
+
|
|
18
|
+
// Check platform
|
|
19
|
+
console.debug('Platform:', memorio.isNode() ? 'Node.js ✅' : 'Other')
|
|
20
|
+
|
|
21
|
+
// Check persistence (false in Node.js - uses memory fallback)
|
|
22
|
+
console.debug('Store persistent:', store.isPersistent) // false
|
|
23
|
+
console.debug('Session persistent:', session.isPersistent) // false
|
|
24
|
+
|
|
25
|
+
// State works exactly like in browser
|
|
26
|
+
state.appName = 'My Server App'
|
|
27
|
+
state.startTime = Date.now()
|
|
28
|
+
state.config = {
|
|
29
|
+
port: 3000,
|
|
30
|
+
env: 'production'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.debug('App name:', state.appName)
|
|
34
|
+
console.debug('Config:', state.config)
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// 2. CACHE (In-Memory - Perfect for Server)
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
console.debug('\n=== 2. Cache (In-Memory) ===')
|
|
41
|
+
|
|
42
|
+
// Cache is perfect for temporary server data
|
|
43
|
+
cache.set('apiResponse', { data: 'cached value' })
|
|
44
|
+
cache.set('userCount', 42)
|
|
45
|
+
|
|
46
|
+
console.debug('Cached API response:', cache.get('apiResponse'))
|
|
47
|
+
console.debug('User count:', cache.get('userCount'))
|
|
48
|
+
|
|
49
|
+
// Clear cache when needed
|
|
50
|
+
cache.remove('apiResponse')
|
|
51
|
+
// cache.clearAll() - clear all
|
|
52
|
+
|
|
53
|
+
// ============================================
|
|
54
|
+
// 3. STORE & SESSION (Memory Fallback in Node.js)
|
|
55
|
+
// ============================================
|
|
56
|
+
|
|
57
|
+
console.debug('\n=== 3. Store & Session (Memory Fallback) ===')
|
|
58
|
+
|
|
59
|
+
// Store and session work but don't persist (no localStorage in Node.js)
|
|
60
|
+
store.set('serverConfig', { debug: true })
|
|
61
|
+
session.set('requestData', { path: '/api/users' })
|
|
62
|
+
|
|
63
|
+
console.debug('Server config:', store.get('serverConfig'))
|
|
64
|
+
console.debug('Request data:', session.get('requestData'))
|
|
65
|
+
|
|
66
|
+
// ⚠️ Data is lost on process restart!
|
|
67
|
+
// For persistence in Node.js, use a database
|
|
68
|
+
|
|
69
|
+
// ============================================
|
|
70
|
+
// 4. CONTEXT ISOLATION (Multi-Tenant)
|
|
71
|
+
// ============================================
|
|
72
|
+
|
|
73
|
+
console.debug('\n=== 4. Context Isolation (Multi-Tenant) ===')
|
|
74
|
+
|
|
75
|
+
// Create isolated contexts for different tenants/requests
|
|
76
|
+
const userAContext = memorio.createContext('tenant-A')
|
|
77
|
+
const userBContext = memorio.createContext('tenant-B')
|
|
78
|
+
|
|
79
|
+
// Each context has completely separate data
|
|
80
|
+
userAContext.state.user = { name: 'Alice', id: 1 }
|
|
81
|
+
userAContext.state.secret = 'Alice secret data'
|
|
82
|
+
userAContext.cache.set('temp', 'A temp data')
|
|
83
|
+
|
|
84
|
+
userBContext.state.user = { name: 'Bob', id: 2 }
|
|
85
|
+
userBContext.state.secret = 'Bob secret data'
|
|
86
|
+
userBContext.cache.set('temp', 'B temp data')
|
|
87
|
+
|
|
88
|
+
// Verify isolation
|
|
89
|
+
console.debug('User A name:', userAContext.state.user.name) // Alice
|
|
90
|
+
console.debug('User B name:', userBContext.state.user.name) // Bob
|
|
91
|
+
|
|
92
|
+
// Global state is separate
|
|
93
|
+
console.debug('Global state:', state.appName) // 'My Server App'
|
|
94
|
+
console.debug('User A global secret:', userAContext.state.secret) // 'Alice secret data'
|
|
95
|
+
console.debug('User B global secret:', userBContext.state.secret) // 'Bob secret data'
|
|
96
|
+
|
|
97
|
+
// ============================================
|
|
98
|
+
// 5. EXPRESS.JS MIDDLEWARE EXAMPLE
|
|
99
|
+
// ============================================
|
|
100
|
+
|
|
101
|
+
console.debug('\n=== 5. Express.js Middleware Example ===')
|
|
102
|
+
|
|
103
|
+
/*
|
|
104
|
+
// In a real Express app:
|
|
105
|
+
|
|
106
|
+
import express from 'express'
|
|
107
|
+
const app = express()
|
|
108
|
+
|
|
109
|
+
// Middleware to create isolated context per request
|
|
110
|
+
app.use((req, res, next) => {
|
|
111
|
+
// Create unique context for this request
|
|
112
|
+
const ctx = memorio.createContext(`req-${req.id}`)
|
|
113
|
+
|
|
114
|
+
// Attach to request for use in handlers
|
|
115
|
+
req.memorio = ctx
|
|
116
|
+
|
|
117
|
+
// Clean up on response finish
|
|
118
|
+
res.on('finish', () => {
|
|
119
|
+
memorio.deleteContext(ctx.id)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
next()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Route handler using isolated context
|
|
126
|
+
app.get('/api/user', (req, res) => {
|
|
127
|
+
const ctx = req.memorio
|
|
128
|
+
|
|
129
|
+
// Set request-specific state
|
|
130
|
+
ctx.state.requestId = req.id
|
|
131
|
+
ctx.state.startTime = Date.now()
|
|
132
|
+
|
|
133
|
+
// Cache data for this request
|
|
134
|
+
ctx.cache.set('query', req.query)
|
|
135
|
+
|
|
136
|
+
// Get user data
|
|
137
|
+
const user = getUserFromDB(req.params.id)
|
|
138
|
+
|
|
139
|
+
// Return response
|
|
140
|
+
res.json({
|
|
141
|
+
user,
|
|
142
|
+
requestId: ctx.state.requestId
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
app.listen(3000)
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
console.debug('See code comments for Express.js integration')
|
|
150
|
+
|
|
151
|
+
// ============================================
|
|
152
|
+
// 6. WEBSOCKET EXAMPLE
|
|
153
|
+
// ============================================
|
|
154
|
+
|
|
155
|
+
console.debug('\n=== 6. WebSocket Example ===')
|
|
156
|
+
|
|
157
|
+
/*
|
|
158
|
+
// For WebSocket connections:
|
|
159
|
+
|
|
160
|
+
const activeConnections = new Map()
|
|
161
|
+
|
|
162
|
+
function handleConnection(ws, userId) {
|
|
163
|
+
// Create isolated context for this user
|
|
164
|
+
const ctx = memorio.createContext(`ws-${userId}`)
|
|
165
|
+
|
|
166
|
+
// Store user data
|
|
167
|
+
ctx.state.userId = userId
|
|
168
|
+
ctx.state.connected = true
|
|
169
|
+
|
|
170
|
+
// Store in connection map
|
|
171
|
+
activeConnections.set(userId, ctx)
|
|
172
|
+
|
|
173
|
+
ws.on('message', (message) => {
|
|
174
|
+
// Process message using isolated context
|
|
175
|
+
ctx.cache.set('lastMessage', message)
|
|
176
|
+
handleMessage(ctx, message)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
ws.on('close', () => {
|
|
180
|
+
// Clean up
|
|
181
|
+
ctx.state.connected = false
|
|
182
|
+
activeConnections.delete(userId)
|
|
183
|
+
memorio.deleteContext(ctx.id)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
*/
|
|
187
|
+
|
|
188
|
+
console.debug('See code comments for WebSocket integration')
|
|
189
|
+
|
|
190
|
+
// ============================================
|
|
191
|
+
// 7. JOB QUEUE / WORKER EXAMPLE
|
|
192
|
+
// ============================================
|
|
193
|
+
|
|
194
|
+
console.debug('\n=== 7. Job Queue Example ===')
|
|
195
|
+
|
|
196
|
+
/*
|
|
197
|
+
// For background jobs:
|
|
198
|
+
|
|
199
|
+
async function processJob(jobId, jobData) {
|
|
200
|
+
// Create isolated context for this job
|
|
201
|
+
const ctx = memorio.createContext(`job-${jobId}`)
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// Track job progress
|
|
205
|
+
ctx.state.jobId = jobId
|
|
206
|
+
ctx.state.status = 'processing'
|
|
207
|
+
ctx.state.progress = 0
|
|
208
|
+
|
|
209
|
+
// Process in stages
|
|
210
|
+
for (let i = 0; i < 10; i++) {
|
|
211
|
+
await doWork(jobData)
|
|
212
|
+
ctx.state.progress = (i + 1) * 10
|
|
213
|
+
|
|
214
|
+
// Cache intermediate results
|
|
215
|
+
ctx.cache.set(`stage-${i}`, true)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
ctx.state.status = 'completed'
|
|
219
|
+
return { success: true }
|
|
220
|
+
|
|
221
|
+
} catch (error) {
|
|
222
|
+
ctx.state.status = 'failed'
|
|
223
|
+
ctx.state.error = error.message
|
|
224
|
+
throw error
|
|
225
|
+
|
|
226
|
+
} finally {
|
|
227
|
+
// Clean up after job completes
|
|
228
|
+
memorio.deleteContext(ctx.id)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
console.debug('See code comments for Job Queue integration')
|
|
234
|
+
|
|
235
|
+
// ============================================
|
|
236
|
+
// 8. CLI APPLICATION EXAMPLE
|
|
237
|
+
// ============================================
|
|
238
|
+
|
|
239
|
+
console.debug('\n=== 8. CLI Application ===')
|
|
240
|
+
|
|
241
|
+
// Memorio works great in CLI apps too
|
|
242
|
+
state.command = 'build'
|
|
243
|
+
state.options = {
|
|
244
|
+
minify: true,
|
|
245
|
+
sourceMap: false
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.debug('Command:', state.command)
|
|
249
|
+
console.debug('Options:', state.options)
|
|
250
|
+
|
|
251
|
+
// ============================================
|
|
252
|
+
// 9. PLATFORM DETECTION
|
|
253
|
+
// ============================================
|
|
254
|
+
|
|
255
|
+
console.debug('\n=== 9. Platform Detection ===')
|
|
256
|
+
|
|
257
|
+
const caps = memorio.getCapabilities()
|
|
258
|
+
console.debug('Platform:', caps.platform)
|
|
259
|
+
console.debug('localStorage:', caps.hasLocalStorage ? '✅' : '❌')
|
|
260
|
+
console.debug('sessionStorage:', caps.hasSessionStorage ? '✅' : '❌')
|
|
261
|
+
console.debug('IndexedDB:', caps.hasIndexedDB ? '✅' : '❌')
|
|
262
|
+
console.debug('Session ID:', caps.sessionId.substring(0, 8) + '...')
|
|
263
|
+
|
|
264
|
+
// ============================================
|
|
265
|
+
// 10. CLEANUP
|
|
266
|
+
// ============================================
|
|
267
|
+
|
|
268
|
+
console.debug('\n=== 10. Cleanup ===')
|
|
269
|
+
|
|
270
|
+
// List all contexts
|
|
271
|
+
console.debug('Active contexts:', memorio.listContexts())
|
|
272
|
+
|
|
273
|
+
// Clean up when done
|
|
274
|
+
memorio.deleteContext('tenant-A')
|
|
275
|
+
memorio.deleteContext('tenant-B')
|
|
276
|
+
|
|
277
|
+
console.debug('After cleanup:', memorio.listContexts())
|
|
278
|
+
|
|
279
|
+
// ============================================
|
|
280
|
+
// SUMMARY
|
|
281
|
+
// ============================================
|
|
282
|
+
|
|
283
|
+
console.debug('\n=== Summary ===')
|
|
284
|
+
console.debug(`
|
|
285
|
+
MEMORIO NODE.JS USAGE:
|
|
286
|
+
|
|
287
|
+
✅ WORKS:
|
|
288
|
+
- state: In-memory global state
|
|
289
|
+
- cache: In-memory temporary cache
|
|
290
|
+
- store: In-memory (NOT persistent!)
|
|
291
|
+
- session: In-memory (NOT persistent!)
|
|
292
|
+
- createContext: Perfect for isolation
|
|
293
|
+
|
|
294
|
+
⚠️ NOTES:
|
|
295
|
+
- store/session don't persist in Node.js
|
|
296
|
+
- Use database for persistence
|
|
297
|
+
- Always use contexts for request isolation
|
|
298
|
+
- Clean up contexts after use
|
|
299
|
+
|
|
300
|
+
🔧 BEST PRACTICES:
|
|
301
|
+
1. Create context per request: createContext(\`req-\${req.id}\`)
|
|
302
|
+
2. Use cache for temporary data
|
|
303
|
+
3. Use state for request-scoped data
|
|
304
|
+
4. Delete contexts after response
|
|
305
|
+
5. Check isPersistent before relying on persistence
|
|
306
|
+
`)
|
|
307
|
+
|
|
308
|
+
console.debug('\nNode.js example complete!')
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memorio Observer Example
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates the observer pattern for reactive state changes.
|
|
5
|
+
* Perfect for logging, analytics, auto-save, and UI updates.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx ts-node examples/observer.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import 'memorio'
|
|
11
|
+
|
|
12
|
+
// ============================================
|
|
13
|
+
// SIMPLE OBSERVER
|
|
14
|
+
// ============================================
|
|
15
|
+
|
|
16
|
+
// Watch a single value
|
|
17
|
+
observer('state.counter', (newValue, oldValue) => {
|
|
18
|
+
console.debug(`Counter: ${oldValue} → ${newValue}`)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
state.counter = 0
|
|
22
|
+
state.counter = 1
|
|
23
|
+
state.counter = 2
|
|
24
|
+
|
|
25
|
+
// ============================================
|
|
26
|
+
// OBJECT OBSERVER
|
|
27
|
+
// ============================================
|
|
28
|
+
|
|
29
|
+
// Watch entire objects
|
|
30
|
+
observer('state.user', (newUser, oldUser) => {
|
|
31
|
+
console.debug(`User changed: ${oldUser?.name} → ${newUser?.name}`)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
state.user = { name: 'Mario', level: 1 }
|
|
35
|
+
state.user = { name: 'Luigi', level: 2 }
|
|
36
|
+
|
|
37
|
+
// ============================================
|
|
38
|
+
// MULTIPLE OBSERVERS
|
|
39
|
+
// ============================================
|
|
40
|
+
|
|
41
|
+
// Multiple observers on same path
|
|
42
|
+
observer('state.notifications', (count) => {
|
|
43
|
+
console.debug(`New notification count: ${count}`)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// List all observers
|
|
47
|
+
console.debug('Active observers:', observer.list)
|
|
48
|
+
|
|
49
|
+
// ============================================
|
|
50
|
+
// CLEANUP
|
|
51
|
+
// ============================================
|
|
52
|
+
|
|
53
|
+
// Remove specific observer
|
|
54
|
+
observer.remove('state.counter')
|
|
55
|
+
|
|
56
|
+
// Remove all observers - call remove for each path
|
|
57
|
+
// observer.remove('state.counter')
|
|
58
|
+
// observer.remove('state.notifications')
|
|
59
|
+
|
|
60
|
+
console.debug('Observer example complete!')
|