memorio 4.7.1 → 4.8.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/README.md +447 -34
- package/SECURITY.md +10 -10
- package/SUMMARY.md +21 -0
- package/index.cjs +170 -31
- package/index.d.ts +4 -0
- package/index.js +125 -8
- package/llms.txt +119 -28
- package/markdown/CHANGELOG.md +8 -8
- package/markdown/HISTORY.md +192 -0
- package/markdown/INSPECT.md +116 -0
- package/markdown/MEMORY-ATTACHMENT.md +95 -0
- package/markdown/MEMORY.md +155 -0
- package/markdown/SCHEMA.md +169 -0
- package/markdown/SECURITY.md +1 -1
- package/markdown/SQLITE.md +181 -0
- package/markdown/SYNC.md +170 -0
- package/markdown/TYPED.md +158 -0
- package/package.json +15 -9
- package/types/exports.d.ts +27 -0
- package/types/history.d.ts +27 -0
- package/types/inspect.d.ts +14 -0
- package/types/memorio.d.ts +77 -2
- package/types/memory.d.ts +127 -0
- package/types/schema.d.ts +53 -0
- package/types/sqlite.d.ts +35 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Node Attachment System
|
|
2
|
+
|
|
3
|
+
`memorio.memory` extends with a **Node Attachment System** allowing memory elements to dynamically attach, reference, or connect to other memory elements at runtime.
|
|
4
|
+
|
|
5
|
+
This is an **extension**, not a replacement, of existing memory semantics.
|
|
6
|
+
|
|
7
|
+
## Concepts
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Memory
|
|
11
|
+
└── Node
|
|
12
|
+
├── identity
|
|
13
|
+
├── value
|
|
14
|
+
├── metadata
|
|
15
|
+
└── relationships
|
|
16
|
+
└── Edge
|
|
17
|
+
├── target
|
|
18
|
+
├── relation
|
|
19
|
+
├── metadata
|
|
20
|
+
├── confidence
|
|
21
|
+
└── lifecycle
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## API
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
const user = await memory.remember("user", { name: "Alice" })
|
|
28
|
+
const project = await memory.remember("project", { name: "Memorio" })
|
|
29
|
+
|
|
30
|
+
await user.connect(project, { relation: "works-on", confidence: 0.95 })
|
|
31
|
+
await user.attach(project) // → connect with relation "attached-to"
|
|
32
|
+
|
|
33
|
+
const related = await user.related({ direction: "both", relation: "works-on" })
|
|
34
|
+
const tree = await user.traverse({ depth: 3 })
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Methods
|
|
38
|
+
|
|
39
|
+
| Method | Description |
|
|
40
|
+
|--------|-------------|
|
|
41
|
+
| `node.connect(target, opts?)` | Create a directed relationship |
|
|
42
|
+
| `node.disconnect(target, opts?)` | Remove a relationship |
|
|
43
|
+
| `node.related(opts?)` | Query related nodes |
|
|
44
|
+
| `node.traverse(opts?)` | Bounded graph traversal |
|
|
45
|
+
| `memory.connect(source, target, opts?)` | Low-level connect |
|
|
46
|
+
| `memory.disconnect(source, target, opts?)` | Low-level disconnect |
|
|
47
|
+
|
|
48
|
+
## Scopes
|
|
49
|
+
|
|
50
|
+
Relationship visibility respects memory scopes — a query in one scope cannot leak inaccessible nodes from another.
|
|
51
|
+
|
|
52
|
+
## Persistence
|
|
53
|
+
|
|
54
|
+
Relationships use **IDs**, never embedded objects:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"id": "user",
|
|
59
|
+
"relationships": [
|
|
60
|
+
{ "target": "project", "relation": "works-on" }
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Lifecycle
|
|
66
|
+
|
|
67
|
+
- **TTL**: independent per node/edge — does not propagate
|
|
68
|
+
- **Confidence**: independent — node and edge have separate confidence values
|
|
69
|
+
- **Deletion**: deleting a node removes its edges (default policy)
|
|
70
|
+
- **Cycles**: supported with visited-set protection during traversal
|
|
71
|
+
- **Dangling references**: `retain` by default (configurable)
|
|
72
|
+
|
|
73
|
+
## Security
|
|
74
|
+
|
|
75
|
+
Relationship traversal inherits the same scope/access rules as direct retrieval. A user cannot gain access to Node B merely because Node A connects to it.
|
|
76
|
+
|
|
77
|
+
## Experimental Substrate Evaluation
|
|
78
|
+
|
|
79
|
+
Runtime substrate choices (Object Graph, WeakMap, DOM) are under benchmark evaluation. See:
|
|
80
|
+
|
|
81
|
+
- [Leak Detection Protocol](../experimental/memory-substrates/LEAK-DETECTION.md)
|
|
82
|
+
|
|
83
|
+
## Serialization
|
|
84
|
+
|
|
85
|
+
Nodes serialize to IDs only — never recursively embedded graphs.
|
|
86
|
+
|
|
87
|
+
## Performance
|
|
88
|
+
|
|
89
|
+
- `O(1)` indexed lookup for direct node retrieval
|
|
90
|
+
- Graph traversal only on explicit request
|
|
91
|
+
- Indexable by `source`, `target`, `relation`
|
|
92
|
+
|
|
93
|
+
## Backward Compatibility
|
|
94
|
+
|
|
95
|
+
Existing APIs (`remember`, `get`, `forget`, `search`) continue to work unchanged. A node-enabled memory entry remains usable as a normal memory entry.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Memory System
|
|
2
|
+
|
|
3
|
+
`memorio.memory` provides a semantic memory layer — a key/value store with type safety, TTL, confidence scoring, tagging, and scope-based persistence.
|
|
4
|
+
|
|
5
|
+
## Core API
|
|
6
|
+
|
|
7
|
+
### `memorio.memory.remember(key, value, opts?)`
|
|
8
|
+
|
|
9
|
+
Stores a memory entry.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
await memorio.memory.remember('user.name', 'Dario', {
|
|
13
|
+
type: 'preference',
|
|
14
|
+
confidence: 0.92,
|
|
15
|
+
scope: 'local',
|
|
16
|
+
ttl: null,
|
|
17
|
+
tags: ['user', 'profile'],
|
|
18
|
+
source: 'conversation'
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
| Option | Type | Default | Description |
|
|
23
|
+
|--------|------|---------|-------------|
|
|
24
|
+
| `id` | `string` | `key` | Custom entry ID |
|
|
25
|
+
| `type` | `'fact' \| 'preference' \| 'decision' \| 'task' \| 'context'` | `'fact'` | Semantic classification |
|
|
26
|
+
| `confidence` | `number` | `1.0` | Trust score 0.0–1.0 |
|
|
27
|
+
| `scope` | `'hot' \| 'session' \| 'local' \| 'durable'` | auto | Storage backend (see below) |
|
|
28
|
+
| `ttl` | `number \| null` | `null` | Milliseconds before expiry (`null` = never) |
|
|
29
|
+
| `tags` | `string \| string[]` | `[]` | Metadata for filtering |
|
|
30
|
+
| `source` | `string` | `undefined` | Origin of the memory |
|
|
31
|
+
|
|
32
|
+
### `memorio.memory.recall(query, opts?)`
|
|
33
|
+
|
|
34
|
+
Retrieves a memory by key.
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const name = await memorio.memory.recall('user.name')
|
|
38
|
+
const highConfidence = await memorio.memory.recall('project', {
|
|
39
|
+
minConfidence: 0.8
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
| Option | Type | Default | Description |
|
|
44
|
+
|--------|------|---------|-------------|
|
|
45
|
+
| `type` | `MemoryType \| MemoryType[]` | `*` | Filter by type |
|
|
46
|
+
| `tags` | `string \| string[]` | `*` | Filter by tags |
|
|
47
|
+
| `minConfidence` | `number` | `0` | Minimum confidence threshold |
|
|
48
|
+
| `includeObsolete` | `boolean` | `false` | Include expired/superseded entries |
|
|
49
|
+
|
|
50
|
+
### `memorio.memory.update(key, value, opts?)`
|
|
51
|
+
|
|
52
|
+
Updates an existing memory. Creates a **superseded copy** of the old entry (preserving history).
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
await memorio.memory.update('user.name', 'Alice', { confidence: 0.95 })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### `memorio.memory.forget(key)`
|
|
59
|
+
|
|
60
|
+
Permanently removes a memory.
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
await memorio.memory.forget('user.temp')
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `memorio.memory.context(opts?)`
|
|
67
|
+
|
|
68
|
+
Returns ranked, relevant memories. Uses recency × confidence × type/scope boost algorithm.
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const ctx = await memorio.memory.context({
|
|
72
|
+
tags: 'project',
|
|
73
|
+
types: ['decision', 'preference'],
|
|
74
|
+
minConfidence: 0.7,
|
|
75
|
+
maxEntries: 10,
|
|
76
|
+
scopes: ['local', 'durable']
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
```ts
|
|
82
|
+
Array<{
|
|
83
|
+
key: string
|
|
84
|
+
value: any
|
|
85
|
+
type: MemoryType
|
|
86
|
+
confidence: number
|
|
87
|
+
scope: MemoryScope
|
|
88
|
+
tags: string[]
|
|
89
|
+
age: number // milliseconds since createdAt
|
|
90
|
+
accessCount: number
|
|
91
|
+
lastAccessedAt: number
|
|
92
|
+
}>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### `memorio.memory.stats()`
|
|
96
|
+
|
|
97
|
+
Returns usage statistics:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
{
|
|
101
|
+
total: number
|
|
102
|
+
byScope: Record<MemoryScope, number>
|
|
103
|
+
byType: Partial<Record<MemoryType, number>>
|
|
104
|
+
expired: number
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### `memorio.memory.forgetExpired()`
|
|
109
|
+
|
|
110
|
+
Cleans all expired entries. Returns count removed.
|
|
111
|
+
|
|
112
|
+
### `memorio.memory.clear()`
|
|
113
|
+
|
|
114
|
+
Wipes all memories and the index.
|
|
115
|
+
|
|
116
|
+
## Memory Entry Model
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
interface MemoryEntry<T = any> {
|
|
120
|
+
id: string
|
|
121
|
+
key: string
|
|
122
|
+
value: T
|
|
123
|
+
type: 'fact' | 'preference' | 'decision' | 'task' | 'context'
|
|
124
|
+
confidence: number
|
|
125
|
+
scope: 'hot' | 'session' | 'local' | 'durable'
|
|
126
|
+
ttl?: number | null
|
|
127
|
+
tags: string[]
|
|
128
|
+
source?: string
|
|
129
|
+
status: 'active' | 'obsolete' | 'superseded'
|
|
130
|
+
createdAt: number
|
|
131
|
+
lastConfirmedAt: number
|
|
132
|
+
supersededId?: string | null
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Scopes
|
|
137
|
+
|
|
138
|
+
| Scope | Storage | TTL | Cross-session | Size limit |
|
|
139
|
+
|-------|---------|-----|---------------|------------|
|
|
140
|
+
| `hot` | `state` proxy | ✅ | ❌ | ~5MB (RAM) |
|
|
141
|
+
| `session` | `sessionStorage` | ✅ | Tab only | ~5MB |
|
|
142
|
+
| `local` | `localStorage` | ✅ | ✅ | ~10MB |
|
|
143
|
+
| `durable` | `IndexedDB` | ✅ | ✅ | ~1GB+ |
|
|
144
|
+
|
|
145
|
+
Default scope is `local` for values ≤100KB, `durable` for larger values.
|
|
146
|
+
|
|
147
|
+
## Memory Lifecycle
|
|
148
|
+
|
|
149
|
+
| Event | Behavior |
|
|
150
|
+
|-------|----------|
|
|
151
|
+
| `remember()` on existing key | Old entry → `superseded`, new entry → `active` |
|
|
152
|
+
| Entry with TTL expires | Status → `obsolete` (cleaned by `forgetExpired()`) |
|
|
153
|
+
| `recall()` expired entry | Returns `null` unless `includeObsolete: true` |
|
|
154
|
+
| `update()` | Creates superseded copy + updated active entry |
|
|
155
|
+
| `clear()` | Wipes all memories + index |
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# Schema Validation - Memorio
|
|
2
|
+
|
|
3
|
+
> ✅ **Universal**: Works in Browser, Node.js, Deno, and Edge Workers
|
|
4
|
+
|
|
5
|
+
Schema validation guards your `state` against invalid writes. It runs inside the state proxy's `set` trap, so any `state.somePath = value` that violates a registered schema is rejected at runtime — before the value is ever stored.
|
|
6
|
+
|
|
7
|
+
Schema validation is **opt-in** and **zero-dependency**.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
import 'memorio'
|
|
15
|
+
|
|
16
|
+
// Register a validator for a top-level state key
|
|
17
|
+
memorio.registerSchema('user', {
|
|
18
|
+
type: 'object',
|
|
19
|
+
required: ['name', 'email'],
|
|
20
|
+
properties: {
|
|
21
|
+
name: { type: 'string', min: 1 },
|
|
22
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
23
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
// Valid write — accepted
|
|
28
|
+
state.user = { name: 'Sara', email: 'sara@test.com', age: 30 }
|
|
29
|
+
|
|
30
|
+
// Invalid write — rejected, returns false
|
|
31
|
+
state.user = { name: 'Sara' } // missing 'email'
|
|
32
|
+
state.user = { name: 42, email: 'x' } // wrong type for 'name'
|
|
33
|
+
state.user = { age: -5 } // out of range
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Schema Definition
|
|
39
|
+
|
|
40
|
+
A `Schema` object supports the following fields:
|
|
41
|
+
|
|
42
|
+
| Field | Type | Description |
|
|
43
|
+
|-------|------|-------------|
|
|
44
|
+
| `type` | `'string' \| 'number' \| 'boolean' \| 'object' \| 'array' \| 'any'` | Runtime type check |
|
|
45
|
+
| `required` | `string[]` | Property names that must exist (objects only) |
|
|
46
|
+
| `properties` | `Record<string, Schema>` | Nested property schemas (validated recursively) |
|
|
47
|
+
| `min` | `number` | Number: minimum value. String: minimum length |
|
|
48
|
+
| `max` | `number` | Number: maximum value. String: maximum length |
|
|
49
|
+
| `pattern` | `RegExp` | Regex the string value must match |
|
|
50
|
+
| `enum` | `any[]` | Whitelist of allowed values |
|
|
51
|
+
| `validator` | `(value) => boolean \| string` | Custom validator function |
|
|
52
|
+
|
|
53
|
+
### Custom validator functions
|
|
54
|
+
|
|
55
|
+
For logic that's hard to express declaratively, pass a function instead of a schema object:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
memorio.registerSchema('counter', (value) => {
|
|
59
|
+
if (typeof value !== 'number') return 'counter must be a number'
|
|
60
|
+
if (value < 0) return 'counter must be >= 0'
|
|
61
|
+
return true
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
A custom validator receives the raw value. Return `true` to accept, or a **string** describing the error to reject.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Path-based registration
|
|
70
|
+
|
|
71
|
+
Schemas are keyed by their **state path**, relative to `state`:
|
|
72
|
+
|
|
73
|
+
| API call | Catches |
|
|
74
|
+
|----------|---------|
|
|
75
|
+
| `registerSchema('user', schema)` | `state.user = value` |
|
|
76
|
+
| `registerSchema('user.age', schema)` | `state.user.age = value` |
|
|
77
|
+
| `registerSchema('items', schema)` | `state.items = value` |
|
|
78
|
+
|
|
79
|
+
The full dotted path is constructed from the proxy's tree depth. Nested sets propagate the full path automatically.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Manual validation
|
|
84
|
+
|
|
85
|
+
You can validate a value without writing it to state:
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
memorio.validate('user', { name: 'Sara', email: 'sara@test.com' })
|
|
89
|
+
// { valid: true }
|
|
90
|
+
|
|
91
|
+
memorio.validate('user', { name: 'Sara' })
|
|
92
|
+
// { valid: false, errors: ["user: missing required property 'email'"] }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
When no schema is registered for a path, `validate` returns `{ valid: true }`.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Schema management
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
memorio.listSchemas() // ['user', 'theme', 'items', 'counter']
|
|
103
|
+
memorio.unregisterSchema('counter') // removes the schema
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Full API
|
|
109
|
+
|
|
110
|
+
| Method | Parameters | Returns | Description |
|
|
111
|
+
|--------|-----------|---------|-------------|
|
|
112
|
+
| `memorio.registerSchema(path, schema)` | `string`, `Schema \| fn` | `void` | Register a validator |
|
|
113
|
+
| `memorio.validate(path, value)` | `string`, `any` | `{ valid, errors? }` | Manually validate a value |
|
|
114
|
+
| `memorio.unregisterSchema(path)` | `string` | `boolean` | Remove a registered schema |
|
|
115
|
+
| `memorio.listSchemas()` | none | `string[]` | List all registered paths |
|
|
116
|
+
| `memorio.registerSchema()` is also importable | `registerSchema` | named export | same function |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Combine with Typed Stores
|
|
121
|
+
|
|
122
|
+
Schema validation gives you **runtime** safety; typed stores give you **compile-time** safety. Use both for full coverage:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import 'memorio'
|
|
126
|
+
|
|
127
|
+
interface AppState {
|
|
128
|
+
user: { name: string; email: string; age: number }
|
|
129
|
+
theme: 'light' | 'dark'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const app = memorio.typed<AppState>()
|
|
133
|
+
|
|
134
|
+
memorio.registerSchema('user', {
|
|
135
|
+
type: 'object',
|
|
136
|
+
required: ['name', 'email'],
|
|
137
|
+
properties: {
|
|
138
|
+
name: { type: 'string', min: 1 },
|
|
139
|
+
email: { type: 'string', pattern: /^[^@]+@[^@]+$/ },
|
|
140
|
+
age: { type: 'number', min: 0, max: 150 }
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
app.user = { name: '', email: 'bad' } // ❌ TypeScript: age missing
|
|
145
|
+
// ❌ Runtime: missing required fields
|
|
146
|
+
app.user = { name: 'Sara', email: 'ok', age: 30 } // ✅ both checks pass
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
See [Typed Stores](TYPED.md) for compile-time type safety.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## How it works
|
|
154
|
+
|
|
155
|
+
1. When you call `registerSchema(path, schema)`, the schema is stored in an internal `Map`.
|
|
156
|
+
2. On every `state.set` operation, the proxy's `set` trap computes the full path (e.g. `'user.name'`).
|
|
157
|
+
3. If a schema is registered for that path, the value is validated.
|
|
158
|
+
4. If validation fails, the write is rejected (`return false`), and an error is logged via `console.error` (when `memorio.debug = true`) or `console.debug` (via the internal `message` helper).
|
|
159
|
+
5. If no schema is registered, the write proceeds normally.
|
|
160
|
+
|
|
161
|
+
The validation adds negligible overhead when no schemas are registered (a single `Map` lookup that returns `undefined`).
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Limitations
|
|
166
|
+
|
|
167
|
+
- Schema validation hooks into the `state` proxy only. `store`, `session`, and `cache` are not validated (they use separate storage). Use `validate()` before writing to other modules.
|
|
168
|
+
- Path matching is **exact**: `registerSchema('user')` guards `state.user = ...`, but does **not** recursively validate `state.user.name = 'new'`. Register schemas at each path you need to guard.
|
|
169
|
+
- The schema system is not a replacement for server-side validation. It protects against accidental misuse and provides defense-in-depth in the browser.
|
package/markdown/SECURITY.md
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# SQLite - Memorio
|
|
2
|
+
|
|
3
|
+
> The `sql.js` package is **optional** — if it is not installed, Memorio loads `sql.js` on first use by injecting the `sql-wasm-browser.js` UMD build from the jsDelivr CDN as a classic `<script>` (which exposes the `initSqlJs` factory on `globalThis`). No bare `import('sql.js')` is ever emitted, so bundlers never resolve the optional dependency at build time. Not available in Node.js/Deno.
|
|
4
|
+
|
|
5
|
+
SQLite brings a full SQL engine into the browser through [`sql.js`](https://sql.js.org), a port of SQLite to JavaScript via WebAssembly. Memorio loads it **lazily** on first use, so it only affects bundles that actually use the `sqlite` module.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install memorio
|
|
11
|
+
# Optional: install the SQLite engine locally (the default loader fetches it from the CDN)
|
|
12
|
+
npm install sql.js
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
import 'memorio';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
> The default loader injects the sql.js UMD build (`sql-wasm-browser.js`) from the jsDelivr CDN as a classic `<script>`, exposing the `initSqlJs` factory on `globalThis`. This keeps `sql.js` (and its WASM) out of your build graph entirely — it's only fetched when `sqlite` is first used. For production/self-hosted setups, install `sql.js` and configure a custom `loader` (e.g. `sqlite.config({ loader: () => import('sql.js') })` under a bundler), or set the wasm base with `sqlite.config({ wasmUrl })` / `sqlite.config({ locateFile })`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Examples
|
|
24
|
+
|
|
25
|
+
### Example 1: Basic Usage
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// Create an in-memory database and a table
|
|
29
|
+
await sqlite.db.create('myApp');
|
|
30
|
+
await sqlite.query.run('myApp', 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
31
|
+
|
|
32
|
+
// Insert data
|
|
33
|
+
await sqlite.query.run('myApp', 'INSERT INTO users (name) VALUES (?)', ['Mario']);
|
|
34
|
+
|
|
35
|
+
// Select data
|
|
36
|
+
const rows = await sqlite.query.select('myApp', 'SELECT * FROM users');
|
|
37
|
+
console.debug(rows); // [{ id: 1, name: 'Mario' }]
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Example 2: CRUD shortcuts
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
const db = 'store';
|
|
44
|
+
|
|
45
|
+
await sqlite.db.create(db);
|
|
46
|
+
await sqlite.query.run(db, 'CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)');
|
|
47
|
+
await sqlite.data.set(db, 'INSERT INTO products (name, price) VALUES (?, ?)', ['Apple', 1.5]);
|
|
48
|
+
await sqlite.data.set(db, 'INSERT INTO products (name, price) VALUES (?, ?)', ['Banana', 0.8]);
|
|
49
|
+
|
|
50
|
+
// First matching row
|
|
51
|
+
const apple = await sqlite.data.get(db, 'SELECT * FROM products WHERE name = ?', ['Apple']);
|
|
52
|
+
console.debug(apple); // { id: 1, name: 'Apple', price: 1.5 }
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Example 3: Export / Import
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
await sqlite.db.create('myApp');
|
|
59
|
+
await sqlite.query.run('myApp', 'CREATE TABLE todos (id INTEGER PRIMARY KEY, task TEXT)');
|
|
60
|
+
await sqlite.data.set('myApp', 'INSERT INTO todos (task) VALUES (?)', ['Write docs']);
|
|
61
|
+
|
|
62
|
+
// Export the in-memory database to a portable binary dump
|
|
63
|
+
const dump = await sqlite.db.export('myApp');
|
|
64
|
+
|
|
65
|
+
// Later... import it back
|
|
66
|
+
await sqlite.db.import('restored', dump);
|
|
67
|
+
const rows = await sqlite.query.select('restored', 'SELECT * FROM todos');
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Example 4: Engine control
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
// Configure the wasm location BEFORE first use (sets the locateFile base)
|
|
74
|
+
sqlite.config({ wasmUrl: '/static/sql.js/' });
|
|
75
|
+
|
|
76
|
+
// Or provide a fully custom loader (e.g. a local/npm build of sql.js)
|
|
77
|
+
sqlite.config({ loader: async () => {
|
|
78
|
+
return await import('sql.js'); // local install — resolves at runtime
|
|
79
|
+
}});
|
|
80
|
+
|
|
81
|
+
// Enable automatic persistence of one or more databases (see Example 5)
|
|
82
|
+
sqlite.config({ persistence: true });
|
|
83
|
+
|
|
84
|
+
// Wait until the engine is ready
|
|
85
|
+
await sqlite.ready;
|
|
86
|
+
console.debug('SQLite version:', sqlite.db.version());
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Example 5: Persistence & dev download
|
|
90
|
+
|
|
91
|
+
> **Important:** sql.js databases live in **WebAssembly memory** — they are
|
|
92
|
+
> in-memory and **volatile** (lost on page refresh) unless you persist them.
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// Opt into auto-persistence at create time (global config or per-create)
|
|
96
|
+
await sqlite.db.create('app', { persistence: true });
|
|
97
|
+
|
|
98
|
+
await sqlite.query.run('app', 'CREATE TABLE notes (id INTEGER PRIMARY KEY, body TEXT)');
|
|
99
|
+
// writes are snapshotted to localStorage automatically (debounced via updateHook)
|
|
100
|
+
|
|
101
|
+
await sqlite.db.persist('app'); // force an immediate snapshot
|
|
102
|
+
// After a refresh, reopening restores the data:
|
|
103
|
+
await sqlite.db.create('app', { persistence: true });
|
|
104
|
+
|
|
105
|
+
// Dev convenience: download the current db as a .sqlite file from the browser
|
|
106
|
+
await sqlite.db.download('app', 'app.sqlite');
|
|
107
|
+
|
|
108
|
+
// Flush + persist + release the handle
|
|
109
|
+
await sqlite.db.close('app');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Persistence is stored on `store` (localStorage, namespaced per
|
|
113
|
+
`sqlite.config({ namespace })` / memorio context) as a base64 snapshot under
|
|
114
|
+
`memorio:sqlite:db:<ns>:<name>`. It is best-effort: if `store` is unavailable
|
|
115
|
+
the database simply behaves as in-memory.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## API Reference
|
|
120
|
+
|
|
121
|
+
### Engine Control
|
|
122
|
+
|
|
123
|
+
| Method | Parameters | Returns | Description |
|
|
124
|
+
|--------|------------|---------|-------------|
|
|
125
|
+
| `sqlite.config(opts)` | `opts: { loader?, locateFile?, wasmUrl?, persistence?, namespace? }` | `sqlite` | Configure the engine. `loader` fully replaces the initializer (default = CDN `<script>`); `locateFile` customizes wasm resolution; `wasmUrl` sets the wasm base directory; `persistence` toggles automatic db snapshots; `namespace` partitions persisted snapshots. Chainable. |
|
|
126
|
+
| `sqlite.db.create(name, opts)` | `name: string, opts?: { data?, persistence? }` | `Database` | Opens/creates a named in-memory db. With `persistence: true` (or global config), a snapshot is restored if present and writes are auto-saved. |
|
|
127
|
+
| `sqlite.db.persist(name)` | `name: string` | `Promise<boolean>` | Force an immediate snapshot of a persisted database to `store`. |
|
|
128
|
+
| `sqlite.db.download(name, filename?)` | `name: string, filename?: string` | `Promise<boolean>` | Dev-only: trigger a browser download of the db as a `.sqlite` file. |
|
|
129
|
+
| `sqlite.db.close(name)` | `name: string` | `Promise<void>` | Persist (if enabled), close, and release the handle. |
|
|
130
|
+
| `sqlite.ready` | none | `Promise<void>` | Resolves once `sql.js` has been initialized. Rejects (and sets `_disabled`) if the engine can't load. |
|
|
131
|
+
| `sqlite.db.version()` | none | `string \| null` | Engine version, available after initialization. |
|
|
132
|
+
| `sqlite._disabled` | none | `boolean` | `true` when the module is disabled (non-browser env / load failure). |
|
|
133
|
+
| `sqlite._warning` | none | `string \| undefined` | Reason text when disabled, if applicable. |
|
|
134
|
+
|
|
135
|
+
### Database Methods
|
|
136
|
+
|
|
137
|
+
| Method | Parameters | Returns | Description |
|
|
138
|
+
|--------|------------|---------|-------------|
|
|
139
|
+
| `sqlite.db.support()` | none | `boolean` | Check whether SQLite can run in this environment. |
|
|
140
|
+
| `sqlite.db.create(name, opts?)` | `name: string`, `opts?: { data }` | `Promise<Database>` | Create an in-memory database (or reopen one from a binary dump). |
|
|
141
|
+
| `sqlite.db.get(name)` | `name: string` | `Database` | Retrieve an open database handle. |
|
|
142
|
+
| `sqlite.db.delete(name)` | `name: string` | `boolean` | Close and remove a database handle. |
|
|
143
|
+
| `sqlite.db.list()` | none | `string[]` | List open database names. |
|
|
144
|
+
| `sqlite.db.size(name?)` | `name?: string` | `Promise<number>` | Size in bytes of one or all databases. |
|
|
145
|
+
| `sqlite.db.export(name)` | `name: string` | `Promise<Uint8Array>` | Export a database to a binary dump. |
|
|
146
|
+
| `sqlite.db.import(name, data)` | `name: string`, `data` | `Promise<Database>` | Create/replace a database from a binary dump. |
|
|
147
|
+
|
|
148
|
+
### Query Methods
|
|
149
|
+
|
|
150
|
+
| Method | Parameters | Returns | Description |
|
|
151
|
+
|--------|------------|---------|-------------|
|
|
152
|
+
| `sqlite.query.run(name, sql, params?)` | `name: string`, `sql: string`, `params?: any[]` | `Promise<number>` | Run a statement that does not return rows (INSERT / UPDATE / DELETE / CREATE). Returns modified-row count. |
|
|
153
|
+
| `sqlite.query.select(name, sql, params?)` | `name: string`, `sql: string`, `params?: any[]` | `Promise<object[]>` | Run a query and return the rows as plain objects. |
|
|
154
|
+
|
|
155
|
+
### Data Methods (CRUD shortcuts)
|
|
156
|
+
|
|
157
|
+
| Method | Parameters | Returns | Description |
|
|
158
|
+
|--------|------------|---------|-------------|
|
|
159
|
+
| `sqlite.data.set(name, sql, params?)` | `name: string`, `sql: string`, `params?: any[]` | `Promise<number>` | Alias of `sqlite.query.run`. |
|
|
160
|
+
| `sqlite.data.get(name, sql, params?)` | `name: string`, `sql: string`, `params?: any[]` | `Promise<object \| null>` | Alias of `sqlite.query.select` returning the first row (or `null`). |
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Platform Support
|
|
165
|
+
|
|
166
|
+
| Platform | Support | Notes |
|
|
167
|
+
|----------|---------|-------|
|
|
168
|
+
| Browser | ✅ Full | Requires WebAssembly + the `sql.js` package (or CDN fallback) |
|
|
169
|
+
| Edge Worker | ⚠️ Limited | WebAssembly may be available; configure a `loader` manually |
|
|
170
|
+
| Node.js | ❌ Not available | Use `store` or `session` instead |
|
|
171
|
+
| Deno | ❌ Not available | Use `store` or `session` instead |
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## Best Practices
|
|
176
|
+
|
|
177
|
+
1. Configure the wasm path for production: `sqlite.config({ wasmUrl })` or `sqlite.config({ loader })`.
|
|
178
|
+
2. Create one named database per feature and reuse the handle: `const db = await sqlite.db.create('app')`.
|
|
179
|
+
3. Always release databases you no longer need: `sqlite.db.delete('temp')`.
|
|
180
|
+
4. Use `await sqlite.ready` before running statements to ensure the engine is loaded.
|
|
181
|
+
5. Use `?` placeholders and `params` to avoid SQL injection.
|