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.
@@ -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
+ }
@@ -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!')