odac 1.4.7 → 1.4.9
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/CHANGELOG.md +45 -0
- package/client/odac.js +1 -1
- package/docs/ai/README.md +3 -2
- package/docs/ai/skills/SKILL.md +3 -2
- package/docs/ai/skills/backend/authentication.md +12 -6
- package/docs/ai/skills/backend/database.md +183 -12
- package/docs/ai/skills/backend/ipc.md +71 -12
- package/docs/ai/skills/backend/migrations.md +23 -0
- package/docs/ai/skills/backend/odac-var.md +155 -0
- package/docs/ai/skills/backend/utilities.md +1 -1
- package/docs/ai/skills/frontend/forms.md +23 -1
- package/docs/backend/04-routing/09-websocket-quick-reference.md +21 -1
- package/docs/backend/04-routing/09-websocket.md +22 -1
- package/docs/backend/08-database/05-write-behind-cache.md +230 -0
- package/docs/backend/08-database/06-read-through-cache.md +206 -0
- package/docs/backend/10-authentication/01-authentication-basics.md +53 -0
- package/docs/backend/10-authentication/05-session-management.md +12 -3
- package/docs/backend/13-utilities/01-odac-var.md +13 -19
- package/docs/backend/13-utilities/02-ipc.md +117 -0
- package/docs/frontend/03-forms/01-form-handling.md +15 -2
- package/docs/index.json +1 -1
- package/package.json +1 -1
- package/src/Auth.js +17 -0
- package/src/Database/Migration.js +219 -3
- package/src/Database/ReadCache.js +174 -0
- package/src/Database/WriteBuffer.js +605 -0
- package/src/Database.js +95 -1
- package/src/Ipc.js +343 -81
- package/src/Odac.js +2 -1
- package/src/Storage.js +4 -2
- package/src/Validator.js +1 -1
- package/src/Var.js +1 -0
- package/src/WebSocket.js +80 -23
- package/test/Database/Migration/migrate_column.test.js +168 -0
- package/test/Database/ReadCache/crossTable.test.js +179 -0
- package/test/Database/ReadCache/get.test.js +128 -0
- package/test/Database/ReadCache/invalidate.test.js +103 -0
- package/test/Database/ReadCache/proxy.test.js +184 -0
- package/test/Database/WriteBuffer/_recoverFromCheckpoint.test.js +207 -0
- package/test/Database/WriteBuffer/buffer.test.js +143 -0
- package/test/Database/WriteBuffer/flush.test.js +192 -0
- package/test/Database/WriteBuffer/get.test.js +72 -0
- package/test/Database/WriteBuffer/increment.test.js +118 -0
- package/test/Database/WriteBuffer/update.test.js +178 -0
- package/test/Database/insert.test.js +98 -0
- package/test/Ipc/hset.test.js +59 -0
- package/test/Ipc/incrBy.test.js +65 -0
- package/test/Ipc/lock.test.js +62 -0
- package/test/Ipc/rpush.test.js +68 -0
- package/test/Ipc/sadd.test.js +68 -0
- package/test/WebSocket/Client/fragmentation.test.js +130 -0
- package/test/WebSocket/Client/limits.test.js +10 -4
- package/test/WebSocket/Client/readyState.test.js +154 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +0 -55
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
### ⚙️ Engine Tuning
|
|
2
2
|
|
|
3
|
+
- migrate hashing from BCrypt to scrypt and update Odac.Var documentation and validation logic
|
|
4
|
+
|
|
5
|
+
### ✨ What's New
|
|
6
|
+
|
|
7
|
+
- **auth:** add token tracking and retrieval method
|
|
8
|
+
- **database:** add read-through cache API for SELECT query results
|
|
9
|
+
- **migration:** add cross-driver default value comparison
|
|
10
|
+
- **migration:** add foreign key introspection and synchronization
|
|
11
|
+
- **websocket:** add readyState tracking and message fragmentation support
|
|
12
|
+
|
|
13
|
+
### 📚 Documentation
|
|
14
|
+
|
|
15
|
+
- **forms:** add options to control message display for success and error handling
|
|
16
|
+
- **migrations:** add foreign keys and referential actions documentation
|
|
17
|
+
|
|
18
|
+
### 🛠️ Fixes & Improvements
|
|
19
|
+
|
|
20
|
+
- **database:** add catch method to write operation thenable for Promise compatibility
|
|
21
|
+
- **pr-review:** apply ReadCache null sentinel, fix invalidation test, correct auth docs login example
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
28
|
+
|
|
29
|
+
### ✨ What's New
|
|
30
|
+
|
|
31
|
+
- **database:** add Write-Behind Cache with counter, update, and batch insert buffering
|
|
32
|
+
- **ipc,database:** extend Ipc with atomic ops and delegate WriteBuffer state to Ipc layer
|
|
33
|
+
|
|
34
|
+
### 🛠️ Fixes & Improvements
|
|
35
|
+
|
|
36
|
+
- atomic queue drain, transaction-safe flush, hgetall clone & unhandled rejection
|
|
37
|
+
- **odac:** ensure proper token query parameter handling in get method
|
|
38
|
+
- **storage:** ensure synchronous operations for put and remove methods
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
45
|
+
|
|
46
|
+
### ⚙️ Engine Tuning
|
|
47
|
+
|
|
3
48
|
- **test:** remove unused fsPromises and standardize async I/O in View tests
|
|
4
49
|
|
|
5
50
|
### 📚 Documentation
|
package/client/odac.js
CHANGED
package/docs/ai/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Detailed instructions are organized into Backend and Frontend categories:
|
|
|
24
24
|
- `backend/config.md`: Configuration management and environment variables.
|
|
25
25
|
- `backend/controllers.md`: Request handling and Class-Based Controllers.
|
|
26
26
|
- `backend/cron.md`: Scheduled background tasks and automation.
|
|
27
|
-
- `backend/database.md`: High-performance query builder usage.
|
|
27
|
+
- `backend/database.md`: High-performance query builder usage, Write-Behind Cache (buffered counters, last-write-wins updates, batch inserts).
|
|
28
28
|
- `backend/forms.md`: Form processing and validation logic.
|
|
29
29
|
- `backend/ipc.md`: Inter-Process Communication and state sharing.
|
|
30
30
|
- `backend/mail.md`: Transactional email sending.
|
|
@@ -34,7 +34,8 @@ Detailed instructions are organized into Backend and Frontend categories:
|
|
|
34
34
|
- `backend/streaming.md`: Server-Sent Events (SSE) and Streaming API.
|
|
35
35
|
- `backend/structure.md`: Project organization and Service Classes.
|
|
36
36
|
- `backend/translations.md`: Multi-language support (i18n).
|
|
37
|
-
- `backend/
|
|
37
|
+
- `backend/odac-var.md`: Odac.Var (Fluent String Manipulation, Hashing, Encryption).
|
|
38
|
+
- `backend/utilities.md`: Flow Control (abort, direct, session).
|
|
38
39
|
- `backend/validation.md`: Input sanitization and security checks.
|
|
39
40
|
- `backend/views.md`: Template syntax, skeletons, and server-side JS.
|
|
40
41
|
|
package/docs/ai/skills/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ Read the specific rule files based on whether you are working on the Backend or
|
|
|
18
18
|
- [backend/config.md](backend/config.md) - Configuration management and environment variables
|
|
19
19
|
- [backend/controllers.md](backend/controllers.md) - Request handling and Class-Based Controllers
|
|
20
20
|
- [backend/cron.md](backend/cron.md) - Scheduled background tasks and automation
|
|
21
|
-
- [backend/database.md](backend/database.md) - Query Builder and DB best practices
|
|
21
|
+
- [backend/database.md](backend/database.md) - Query Builder, Write-Behind Cache, and DB best practices
|
|
22
22
|
- [backend/forms.md](backend/forms.md) - Form processing and Validation logic
|
|
23
23
|
- [backend/ipc.md](backend/ipc.md) - Inter-Process Communication and state sharing
|
|
24
24
|
- [backend/mail.md](backend/mail.md) - Transactional email sending
|
|
@@ -29,7 +29,8 @@ Read the specific rule files based on whether you are working on the Backend or
|
|
|
29
29
|
- [backend/streaming.md](backend/streaming.md) - Server-Sent Events (SSE) and Streaming API
|
|
30
30
|
- [backend/structure.md](backend/structure.md) - Project organization and Service Classes
|
|
31
31
|
- [backend/translations.md](backend/translations.md) - Multi-language support (i18n)
|
|
32
|
-
- [backend/
|
|
32
|
+
- [backend/odac-var.md](backend/odac-var.md) - Odac.Var (Fluent String Manipulation, Hashing, Encryption)
|
|
33
|
+
- [backend/utilities.md](backend/utilities.md) - Flow Control (abort, direct, session)
|
|
33
34
|
- [backend/validation.md](backend/validation.md) - Input sanitization and security checks
|
|
34
35
|
- [backend/views.md](backend/views.md) - Template syntax, skeletons, and server-side JS
|
|
35
36
|
|
|
@@ -19,21 +19,27 @@ ODAC provides built-in drivers for session handling (Memory/Redis) and multiple
|
|
|
19
19
|
4. **Magic Links**: Use `Odac.Auth.magic(email)` for passwordless flows.
|
|
20
20
|
5. **Token Rotation**: Enterprise-grade rotation is enabled by default. It uses a 60s grace period to prevent race conditions in SPAs.
|
|
21
21
|
6. **Persistence**: Cookies use the configured `maxAge` to persist beyond browser closure.
|
|
22
|
+
7. **Token Accessor**: Use `Odac.Auth.token()` to access the active auth session record (e.g., auth ID, IP, timestamps). Returns `false` if no active session.
|
|
23
|
+
|
|
22
24
|
## Reference Patterns
|
|
23
25
|
|
|
24
26
|
### 1. Standard Login & Check
|
|
25
27
|
```javascript
|
|
26
28
|
// Check if user is logged in
|
|
27
|
-
if (Odac.Auth.check()) {
|
|
28
|
-
const user = Odac.Auth.user()
|
|
29
|
-
const userId = Odac.Auth.id
|
|
29
|
+
if (await Odac.Auth.check()) {
|
|
30
|
+
const user = Odac.Auth.user() // Full user object
|
|
31
|
+
const userId = Odac.Auth.user('id') // Specific user field
|
|
32
|
+
|
|
33
|
+
const authRecord = Odac.Auth.token() // Full auth token record
|
|
34
|
+
const authId = Odac.Auth.token('id') // Auth session ID from token table
|
|
35
|
+
const authIp = Odac.Auth.token('ip') // IP address of the session
|
|
30
36
|
}
|
|
31
37
|
|
|
32
|
-
// Log in a user
|
|
33
|
-
await Odac.Auth.login(
|
|
38
|
+
// Log in a user
|
|
39
|
+
await Odac.Auth.login({ email, password })
|
|
34
40
|
|
|
35
41
|
// Log out
|
|
36
|
-
await Odac.Auth.logout()
|
|
42
|
+
await Odac.Auth.logout()
|
|
37
43
|
```
|
|
38
44
|
|
|
39
45
|
### 2. Magic Links (Passwordless)
|
|
@@ -1,30 +1,112 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend-database-skill
|
|
3
|
-
description: High-performance ODAC database querying patterns
|
|
3
|
+
description: High-performance ODAC database querying patterns including the Read-Through Cache for SELECT results and the Write-Behind Cache for counters, last-write-wins updates, and buffered batch inserts.
|
|
4
4
|
metadata:
|
|
5
|
-
tags: backend, database, query-builder, sql,
|
|
5
|
+
tags: backend, database, query-builder, sql, read-through-cache, write-behind-cache, performance, security
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Backend Database Skill
|
|
9
9
|
|
|
10
|
-
High-performance database operations using the ODAC Query Builder.
|
|
10
|
+
High-performance database operations using the ODAC Query Builder, Read-Through Cache, and Write-Behind Cache.
|
|
11
11
|
|
|
12
12
|
## Principles
|
|
13
13
|
1. **Directness**: Avoid ORM overhead. Use fluent Query Builder.
|
|
14
14
|
2. **Safety**: Always use parameterized queries (built-in).
|
|
15
15
|
3. **Efficiency**: Index foreign keys. No `SELECT *`.
|
|
16
|
+
4. **Read Caching**: Use `cache()` for frequently-read, rarely-changed data.
|
|
17
|
+
5. **Write Coalescing**: Use `buffer` for high-frequency writes to avoid DB saturation.
|
|
16
18
|
|
|
17
|
-
## Patterns
|
|
19
|
+
## Query Builder Patterns
|
|
18
20
|
```javascript
|
|
19
|
-
const user = await Odac.DB.
|
|
21
|
+
const user = await Odac.DB.users
|
|
20
22
|
.select('id', 'name', 'email')
|
|
21
23
|
.where('status', 'active')
|
|
22
|
-
.first()
|
|
24
|
+
.first()
|
|
23
25
|
|
|
24
|
-
await Odac.DB.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
await Odac.DB.posts.insert({title: 'Hello', user_id: 1})
|
|
27
|
+
|
|
28
|
+
await Odac.DB.users.where('id', 1).update({last_login: new Date()})
|
|
29
|
+
|
|
30
|
+
const count = await Odac.DB.users.where('active', true).count() // → Number
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Read-Through Cache (`cache`)
|
|
34
|
+
|
|
35
|
+
The `cache()` API stores SELECT results in `Odac.Ipc` and serves subsequent requests from memory. Cache keys are SHA-256 hashes of the compiled SQL + bindings — identical queries always hit the same key.
|
|
36
|
+
|
|
37
|
+
### When to suggest `cache`
|
|
38
|
+
|
|
39
|
+
Propose the Read-Through Cache when the data meets **all three** of these criteria:
|
|
40
|
+
1. **High read frequency** — the same query runs on many requests.
|
|
41
|
+
2. **Low write frequency** — the data changes infrequently (minutes to hours between updates).
|
|
42
|
+
3. **Not user-specific** — the same result is safe to serve to all users (no per-user filtering that could leak data).
|
|
43
|
+
|
|
44
|
+
**Typical candidates:**
|
|
45
|
+
- Blog posts, articles, product listings
|
|
46
|
+
- Navigation menus, category trees, tag lists
|
|
47
|
+
- Site settings, feature flags, configuration values
|
|
48
|
+
- Public API responses that don't vary by user
|
|
49
|
+
|
|
50
|
+
**Do NOT suggest `cache` for:**
|
|
51
|
+
- Queries with user-specific WHERE clauses (data leakage risk)
|
|
52
|
+
- Account balances, inventory counts, or anything requiring real-time accuracy
|
|
53
|
+
- Queries inside transactions where consistency is critical
|
|
54
|
+
|
|
55
|
+
### API
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
// Cache with explicit TTL (seconds)
|
|
59
|
+
const posts = await Odac.DB.posts.cache(60).where('active', true).select('id', 'title')
|
|
60
|
+
|
|
61
|
+
// Cache with default TTL (from config, default 300s)
|
|
62
|
+
const post = await Odac.DB.posts.cache().where({id: 5}).first()
|
|
63
|
+
|
|
64
|
+
// Named connection
|
|
65
|
+
const stats = await Odac.DB.analytics.events.cache(120).where('date', today).select()
|
|
66
|
+
|
|
67
|
+
// Manual invalidation — table-level
|
|
68
|
+
await Odac.DB.posts.cache.clear()
|
|
69
|
+
|
|
70
|
+
// Global invalidation
|
|
71
|
+
await Odac.DB.cache.clear('default', 'posts')
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Automatic Invalidation
|
|
75
|
+
|
|
76
|
+
`insert()`, `update()`, `delete()`, `del()`, and `truncate()` automatically purge all cached queries for that table. No manual `.cache.clear()` needed after writes.
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
// This update automatically clears all cached queries on 'posts'
|
|
80
|
+
await Odac.DB.posts.where({id: 5}).update({title: 'New Title'})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Cross-table (JOIN) invalidation:** When a cached query includes `JOIN` clauses, the cache key is registered in all joined tables' indexes. A write to any table involved in the query triggers invalidation automatically.
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
// Cached in both 'posts' and 'users' indexes
|
|
87
|
+
await Odac.DB.posts.cache(60).join('users', 'posts.user_id', '=', 'users.id').select(...)
|
|
88
|
+
|
|
89
|
+
// Writing to 'users' invalidates the cached join query above
|
|
90
|
+
await Odac.DB.users.where({id: 1}).update({name: 'New Name'})
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
> **`buffer` + `cache` interaction:** `buffer` writes are deferred and do NOT trigger automatic invalidation. Call `Odac.DB.posts.cache.clear()` after a `buffer.flush()` if the cache must reflect the latest state immediately.
|
|
94
|
+
|
|
95
|
+
### Read-Through Cache — Key Rules
|
|
96
|
+
1. **Ipc-Backed**: Cache state goes through `Odac.Ipc`. Memory driver = Primary holds state; Redis driver = Redis holds state.
|
|
97
|
+
2. **Horizontally Scalable**: With Redis driver, all servers share the same cache. Invalidation on one server is immediately visible to all others.
|
|
98
|
+
3. **TTL is the safety net**: Even without explicit invalidation, cached data expires after TTL seconds.
|
|
99
|
+
4. **maxKeys guard**: Once `maxKeys` is reached for a table, new entries are skipped — existing cache still served. Prevents unbounded memory growth.
|
|
100
|
+
5. **Ipc-safe**: If `Odac.Ipc` is not initialized (e.g., in isolated test environments), invalidation is a no-op — never throws.
|
|
101
|
+
|
|
102
|
+
## Configuration (`odac.json`)
|
|
103
|
+
```json
|
|
104
|
+
{
|
|
105
|
+
"cache": {
|
|
106
|
+
"ttl": 300,
|
|
107
|
+
"maxKeys": 10000
|
|
108
|
+
}
|
|
109
|
+
}
|
|
28
110
|
```
|
|
29
111
|
|
|
30
112
|
## ID Strategy (NanoID)
|
|
@@ -43,7 +125,96 @@ module.exports = {
|
|
|
43
125
|
}
|
|
44
126
|
|
|
45
127
|
// ID is generated automatically
|
|
46
|
-
await Odac.DB.
|
|
128
|
+
await Odac.DB.posts.insert({title: 'Hello'})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Write-Behind Cache (`buffer`)
|
|
132
|
+
|
|
133
|
+
The `buffer` API routes all writes through `Odac.Ipc`. The active IPC driver determines the scaling model — no code changes required when switching drivers.
|
|
134
|
+
|
|
135
|
+
- **`memory` driver** (default): Primary process holds all state; workers communicate via cluster IPC.
|
|
136
|
+
- **`redis` driver**: State lives in Redis — works transparently across multiple servers.
|
|
137
|
+
|
|
138
|
+
### When to suggest `buffer`
|
|
139
|
+
|
|
140
|
+
Propose the Write-Behind Cache when the user's write operation meets **all three** of these criteria:
|
|
141
|
+
1. **High frequency** — the same column or row is written on many requests (counters, timestamps, last-seen values).
|
|
142
|
+
2. **Eventual consistency is acceptable** — a few seconds' delay before the value reaches the DB is fine.
|
|
143
|
+
3. **Not safety-critical** — losing the buffered value in a crash has no financial, legal, or user-facing consequence.
|
|
144
|
+
|
|
145
|
+
**Typical candidates:**
|
|
146
|
+
- Page / post / download view counters (`increment`)
|
|
147
|
+
- Like, upvote, share counts (`increment`)
|
|
148
|
+
- User last-active timestamp, last IP (`update`)
|
|
149
|
+
- Analytics events, audit logs, activity streams (`insert`)
|
|
150
|
+
- Score accumulators, play counts, rating tallies (`increment`)
|
|
151
|
+
|
|
152
|
+
**Do NOT suggest `buffer` for:**
|
|
153
|
+
- Payments, charges, refunds, or any monetary operation
|
|
154
|
+
- Order creation, inventory decrements
|
|
155
|
+
- Anything the same request needs to read back immediately
|
|
156
|
+
- Inserts that return a generated ID the caller uses
|
|
157
|
+
|
|
158
|
+
When in doubt, use a direct DB call.
|
|
159
|
+
|
|
160
|
+
**Three operation types:**
|
|
161
|
+
|
|
162
|
+
### 1. Counter Increment (Write Coalescing)
|
|
163
|
+
Accumulates deltas — multiple increments merge into one `UPDATE col = col + delta`.
|
|
164
|
+
`get()` returns `base + pending delta` (always current, no DB read needed).
|
|
165
|
+
```javascript
|
|
166
|
+
// Increment — returns current total (DB base + buffered delta)
|
|
167
|
+
const views = await Odac.DB.posts.buffer.where(postId).increment('views')
|
|
168
|
+
const likes = await Odac.DB.posts.buffer.where(postId).increment('likes', 5)
|
|
169
|
+
|
|
170
|
+
// Read buffered counter (no extra DB round-trip)
|
|
171
|
+
const current = await Odac.DB.posts.buffer.where(postId).get('views')
|
|
172
|
+
|
|
173
|
+
// Composite key
|
|
174
|
+
await Odac.DB.post_stats.buffer.where({post_id: 1, date: '2026-04-01'}).increment('views')
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 2. Last-Write-Wins Update (Field Coalescing)
|
|
178
|
+
Multiple updates to the same row merge column maps — 50 requests = 1 `UPDATE` at flush.
|
|
179
|
+
```javascript
|
|
180
|
+
// Columns are merged per row: first update + second update = single UPDATE at flush
|
|
181
|
+
await Odac.DB.users.buffer.where(userId).update({active_date: new Date()})
|
|
182
|
+
await Odac.DB.users.buffer.where(userId).update({last_ip: req.ip})
|
|
183
|
+
// → UPDATE users SET active_date = ?, last_ip = ? WHERE id = ? (one query)
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### 3. Batch Insert (Queue)
|
|
187
|
+
Rows accumulate in memory; flushed in chunks of 1000. Auto-flushes when `maxQueueSize` is reached.
|
|
188
|
+
```javascript
|
|
189
|
+
await Odac.DB.activity_log.buffer.insert({user_id: userId, action: 'page_view', meta: url})
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Manual Flush
|
|
193
|
+
```javascript
|
|
194
|
+
await Odac.DB.posts.buffer.flush() // flush this table only
|
|
195
|
+
await Odac.DB.buffer.flush() // flush everything
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Write-Behind Cache — Key Rules
|
|
199
|
+
1. **Ipc-Backed**: All buffer state goes through `Odac.Ipc`. Memory driver = Primary holds state; Redis driver = Redis holds state.
|
|
200
|
+
2. **Horizontally Scalable**: With Redis driver, multiple servers share the same buffer state. Distributed lock (`Ipc.lock`) prevents duplicate flushes.
|
|
201
|
+
3. **Crash-Safe (memory)**: LMDB checkpoint written every 30s. On restart, pending data is recovered and flushed before serving traffic.
|
|
202
|
+
4. **Crash-Safe (redis)**: Redis persistence provides durability. LMDB checkpoints are skipped.
|
|
203
|
+
5. **get() is authoritative**: Always returns `DB base + buffered delta`. Never stale.
|
|
204
|
+
6. **Flush on shutdown**: `Database.close()` triggers a final flush automatically — no data loss on graceful shutdown.
|
|
205
|
+
7. **Error resilience**: If a flush fails, data is retained in Ipc for the next cycle. Never lost silently.
|
|
206
|
+
8. **NEVER use buffer for safety-critical writes**: Payment records, order confirmations, balance changes, inventory decrements — anything where data loss has real-world consequences MUST use direct DB transactions. The buffer does not guarantee delivery before a crash.
|
|
207
|
+
|
|
208
|
+
## Configuration (`odac.json`)
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"buffer": {
|
|
212
|
+
"flushInterval": 5000,
|
|
213
|
+
"checkpointInterval": 30000,
|
|
214
|
+
"maxQueueSize": 10000,
|
|
215
|
+
"primaryKey": "id"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
47
218
|
```
|
|
48
219
|
|
|
49
220
|
## Migration Awareness
|
|
@@ -53,4 +224,4 @@ await Odac.DB.table('posts').insert({title: 'Hello'});
|
|
|
53
224
|
4. **Indexes**: Keep index definitions in schema so add/drop is managed automatically.
|
|
54
225
|
5. **Data Changes**: Use `migration/*.js` only for one-time data transformation.
|
|
55
226
|
|
|
56
|
-
See: [migrations.md](./migrations.md)
|
|
227
|
+
See: [migrations.md](./migrations.md) | [write-behind-cache user docs](../../../backend/08-database/05-write-behind-cache.md) | [read-through-cache user docs](../../../backend/08-database/06-read-through-cache.md)
|
|
@@ -24,25 +24,81 @@ IPC abstracts the underlying driver, allowing seamless transition between `memor
|
|
|
24
24
|
### 1. Key-Value Storage
|
|
25
25
|
```javascript
|
|
26
26
|
// Set value with optional TTL (seconds)
|
|
27
|
-
await Odac.Ipc.set('maintenance_mode', true)
|
|
28
|
-
await Odac.Ipc.set('cache_key', {
|
|
27
|
+
await Odac.Ipc.set('maintenance_mode', true)
|
|
28
|
+
await Odac.Ipc.set('cache_key', {data: 123}, 60)
|
|
29
29
|
|
|
30
30
|
// Get value
|
|
31
|
-
const status = await Odac.Ipc.get('maintenance_mode')
|
|
31
|
+
const status = await Odac.Ipc.get('maintenance_mode')
|
|
32
32
|
|
|
33
33
|
// Delete value
|
|
34
|
-
await Odac.Ipc.del('maintenance_mode')
|
|
34
|
+
await Odac.Ipc.del('maintenance_mode')
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
### 2.
|
|
37
|
+
### 2. Atomic Counters
|
|
38
38
|
```javascript
|
|
39
|
-
//
|
|
39
|
+
// Increment / decrement a numeric key (returns new value)
|
|
40
|
+
await Odac.Ipc.incrBy('page:views', 1) // → 1
|
|
41
|
+
await Odac.Ipc.incrBy('page:views', 5) // → 6
|
|
42
|
+
await Odac.Ipc.decrBy('page:views', 2) // → 4
|
|
43
|
+
|
|
44
|
+
// Read current value
|
|
45
|
+
const views = await Odac.Ipc.get('page:views') // → 4
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Hash Maps
|
|
49
|
+
```javascript
|
|
50
|
+
// Set / merge fields (last-write-wins per field)
|
|
51
|
+
await Odac.Ipc.hset('user:1', {active_date: new Date(), last_ip: '1.2.3.4'})
|
|
52
|
+
await Odac.Ipc.hset('user:1', {score: 42})
|
|
53
|
+
|
|
54
|
+
// Get all fields
|
|
55
|
+
const fields = await Odac.Ipc.hgetall('user:1')
|
|
56
|
+
// → {active_date: ..., last_ip: '1.2.3.4', score: 42}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. Lists
|
|
60
|
+
```javascript
|
|
61
|
+
// Append items to the right (returns new length)
|
|
62
|
+
await Odac.Ipc.rpush('events', {type: 'click'}, {type: 'view'}) // → 2
|
|
63
|
+
|
|
64
|
+
// Read range (inclusive, -1 = last item)
|
|
65
|
+
const items = await Odac.Ipc.lrange('events', 0, -1)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 5. Sets
|
|
69
|
+
```javascript
|
|
70
|
+
// Add members
|
|
71
|
+
await Odac.Ipc.sadd('online_users', 'user:1', 'user:2')
|
|
72
|
+
|
|
73
|
+
// Get all members
|
|
74
|
+
const users = await Odac.Ipc.smembers('online_users') // → ['user:1', 'user:2']
|
|
75
|
+
|
|
76
|
+
// Remove members (returns removed count)
|
|
77
|
+
await Odac.Ipc.srem('online_users', 'user:1')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 6. Distributed Locks
|
|
81
|
+
```javascript
|
|
82
|
+
// Acquire lock with TTL in seconds (returns true if acquired, false if already held)
|
|
83
|
+
const acquired = await Odac.Ipc.lock('flush:lock', 10)
|
|
84
|
+
if (!acquired) return // another process is holding the lock
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// ... critical section ...
|
|
88
|
+
} finally {
|
|
89
|
+
await Odac.Ipc.unlock('flush:lock')
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 7. Pub/Sub Messaging
|
|
94
|
+
```javascript
|
|
95
|
+
// Subscribe to a channel
|
|
40
96
|
await Odac.Ipc.subscribe('notifications', (msg) => {
|
|
41
|
-
console.log('Received:', msg)
|
|
42
|
-
})
|
|
97
|
+
console.log('Received:', msg)
|
|
98
|
+
})
|
|
43
99
|
|
|
44
100
|
// Publish a message from any worker
|
|
45
|
-
await Odac.Ipc.publish('notifications', {
|
|
101
|
+
await Odac.Ipc.publish('notifications', {type: 'alert', text: 'System update'})
|
|
46
102
|
```
|
|
47
103
|
|
|
48
104
|
## Configuration
|
|
@@ -57,6 +113,9 @@ In `odac.json` or a config provider:
|
|
|
57
113
|
```
|
|
58
114
|
|
|
59
115
|
## Best Practices
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
116
|
+
- **Async First**: All IPC operations are asynchronous.
|
|
117
|
+
- **TTL Usage**: Always set a TTL for temporary cache data to prevent memory bloat.
|
|
118
|
+
- **Scaling**: Switch to `redis` driver when deploying across multiple containers or servers. No application code changes required.
|
|
119
|
+
- **Locks**: Always release locks in a `finally` block to prevent deadlocks.
|
|
120
|
+
- **Sets for indexes**: Use `sadd/smembers/srem` to track active keys — avoids expensive SCAN operations.
|
|
121
|
+
- **Atomic counters over get+set**: Use `incrBy`/`decrBy` instead of `get` → compute → `set` to prevent race conditions.
|
|
@@ -45,6 +45,29 @@ module.exports = {
|
|
|
45
45
|
}
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
### 2. Foreign Keys & Referential Actions
|
|
49
|
+
```javascript
|
|
50
|
+
// schema/posts.js
|
|
51
|
+
module.exports = {
|
|
52
|
+
columns: {
|
|
53
|
+
id: {type: 'nanoid', primary: true},
|
|
54
|
+
user_id: {
|
|
55
|
+
type: 'integer',
|
|
56
|
+
unsigned: true,
|
|
57
|
+
nullable: false,
|
|
58
|
+
references: {table: 'users', column: 'id'},
|
|
59
|
+
onDelete: 'CASCADE', // 'CASCADE' | 'SET NULL' | 'RESTRICT' | 'NO ACTION' | 'SET DEFAULT'
|
|
60
|
+
onUpdate: 'CASCADE'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Rules:**
|
|
67
|
+
- `references` + `onDelete`/`onUpdate` are supported in table creation, column addition, and schema-diff updates to existing columns.
|
|
68
|
+
- Schema diff supports adding, dropping, and replacing an existing column's foreign key constraint. Use an imperative `migration/` file only for cases that require custom data movement or engine-specific manual SQL.
|
|
69
|
+
- Always index foreign key columns for query performance.
|
|
70
|
+
|
|
48
71
|
### NanoID Notes
|
|
49
72
|
- `length` can be customized: `{type: 'nanoid', length: 12, primary: true}`.
|
|
50
73
|
- If seed rows omit the nanoid field, ODAC fills it automatically.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backend-odac-var-skill
|
|
3
|
+
description: Comprehensive API reference and practical patterns for the Odac.Var utility class. Includes every single method available in src/Var.js.
|
|
4
|
+
metadata:
|
|
5
|
+
tags: backend, utilities, strings, hashing, encryption, validation, mapping
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Backend Odac.Var API Reference
|
|
9
|
+
|
|
10
|
+
`Odac.Var` is a high-performance utility class for string manipulation, security, and validation.
|
|
11
|
+
|
|
12
|
+
## Core Principles
|
|
13
|
+
- **Direct Output**: Manipulation methods return raw values (string/object). Chaining is NOT supported.
|
|
14
|
+
- **Type Safety**: Methods handle various input types (strings, arrays, objects) gracefully.
|
|
15
|
+
- **Enterprise Security**: Uses scrypt for hashing and AES-256-CBC for encryption.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🛠 Full Method List & API Reference
|
|
20
|
+
|
|
21
|
+
### 1. `.clear(...args)`
|
|
22
|
+
Removes specified strings from the value using global regex.
|
|
23
|
+
```javascript
|
|
24
|
+
Odac.Var('a-b-c').clear('-'); // 'abc'
|
|
25
|
+
Odac.Var('hello123world').clear('1', '2', '3'); // 'helloworld'
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 2. `.contains(...args)`
|
|
29
|
+
Checks if the string contains **all** of the specified values (AND logic).
|
|
30
|
+
```javascript
|
|
31
|
+
Odac.Var('hello world').contains('hello', 'world'); // true
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 3. `.containsAny(...args)`
|
|
35
|
+
Checks if the string contains **any** of the specified values (OR logic).
|
|
36
|
+
```javascript
|
|
37
|
+
Odac.Var('hello world').containsAny('foo', 'world'); // true
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 4. `.date(format)`
|
|
41
|
+
Formats a date string/timestamp. Default: `Y-m-d H:i:s`.
|
|
42
|
+
- Tokens: `Y` (2024), `y` (24), `m` (01-12), `d` (01-31), `H` (00-23), `i` (00-59), `s` (00-59).
|
|
43
|
+
```javascript
|
|
44
|
+
Odac.Var('2024-04-10').date('d/m/Y'); // '10/04/2024'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 5. `.encrypt(key?)`
|
|
48
|
+
Encrypts the value using AES-256-CBC. Uses `Odac.Config.encrypt.key` by default. Returns Base64.
|
|
49
|
+
```javascript
|
|
50
|
+
const secret = Odac.Var('data').encrypt();
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 6. `.decrypt(key?)`
|
|
54
|
+
Decrypts an AES-256-CBC Base64 string.
|
|
55
|
+
```javascript
|
|
56
|
+
const data = Odac.Var(secret).decrypt();
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 7. `.hash()`
|
|
60
|
+
Generates a secure **scrypt** hash. Returns string prefixed with `$scrypt$`.
|
|
61
|
+
```javascript
|
|
62
|
+
const hash = Odac.Var('password').hash();
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 8. `.hashCheck(check)`
|
|
66
|
+
Verifies a plain text string against an existing scrypt hash. Uses `timingSafeEqual`.
|
|
67
|
+
```javascript
|
|
68
|
+
Odac.Var(hash).hashCheck('password'); // true
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 9. `.html()`
|
|
72
|
+
Escapes HTML special characters (`&`, `<`, `>`, `"`, `'`).
|
|
73
|
+
```javascript
|
|
74
|
+
Odac.Var('<div>').html(); // '<div>'
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 10. `.is(...args)`
|
|
78
|
+
Validates string against **all** specified rules (AND logic).
|
|
79
|
+
```javascript
|
|
80
|
+
Odac.Var('test@test.com').is('email'); // true
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### 11. `.isAny(...args)`
|
|
84
|
+
Validates string against **any** specified rule (OR logic).
|
|
85
|
+
```javascript
|
|
86
|
+
Odac.Var('user123').isAny('email', 'username'); // true
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 12. `.isBegin(...args)`
|
|
90
|
+
Checks if string starts with any of the specified values.
|
|
91
|
+
```javascript
|
|
92
|
+
Odac.Var('https://odac.run').isBegin('http', 'https'); // true
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 13. `.isEnd(...args)`
|
|
96
|
+
Checks if string ends with any of the specified values.
|
|
97
|
+
```javascript
|
|
98
|
+
Odac.Var('image.png').isEnd('.png', '.jpg'); // true
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### 14. `.md5()`
|
|
102
|
+
Generates an MD5 hash of the value.
|
|
103
|
+
```javascript
|
|
104
|
+
Odac.Var('hello').md5(); // '5d41402...'
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 15. `.replace(...args)`
|
|
108
|
+
Replaces values in the string. If the Var value is an object/array, it applies replacement recursively.
|
|
109
|
+
```javascript
|
|
110
|
+
// String replacement
|
|
111
|
+
Odac.Var('Hello {{n}}').replace('{{n}}', 'Emre');
|
|
112
|
+
|
|
113
|
+
// Bulk replacement
|
|
114
|
+
Odac.Var('{{a}} {{b}}').replace({ '{{a}}': '1', '{{b}}': '2' });
|
|
115
|
+
|
|
116
|
+
// Recursive object replacement
|
|
117
|
+
Odac.Var({ key: '{{v}}' }).replace('{{v}}', 'data'); // { key: 'data' }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 16. `.save(path)`
|
|
121
|
+
Saves the value to a file. **Auto-creates directories** recursively if they don't exist.
|
|
122
|
+
```javascript
|
|
123
|
+
Odac.Var('content').save('./storage/logs/app.log');
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 17. `.slug(separator = '-')`
|
|
127
|
+
Generates a URL-friendly slug. Replaces non-alphanumeric chars with separator.
|
|
128
|
+
```javascript
|
|
129
|
+
Odac.Var('Hello World!').slug(); // 'hello-world'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 18. `.format(format)`
|
|
133
|
+
Formats a string based on a pattern.
|
|
134
|
+
- `?`: Placeholder for a single character from the input.
|
|
135
|
+
- `*`: Placeholder for the remaining part of the input.
|
|
136
|
+
```javascript
|
|
137
|
+
Odac.Var('12345').format('??-???'); // '12-345'
|
|
138
|
+
Odac.Var('TR12345').format('?? *'); // 'TR 12345'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 🔍 Validation Rule Catalog (for `.is()` and `.isAny()`)
|
|
144
|
+
- `alpha`, `alphaspace`: `A-Z, a-z` (+ spaces).
|
|
145
|
+
- `alphanumeric`, `alphanumericspace`: `A-Z, a-z, 0-9` (+ spaces).
|
|
146
|
+
- `username`: `A-Z, a-z, 0-9` (Strictly alphanumeric).
|
|
147
|
+
- `numeric`, `float`: Numbers and decimals.
|
|
148
|
+
- `email`, `domain`, `url`: Standard web identifiers.
|
|
149
|
+
- `ip`, `host`, `mac`: Networking addresses.
|
|
150
|
+
- `json`: Check if value is valid JSON.
|
|
151
|
+
- `date`: Check if value is a valid date string.
|
|
152
|
+
- `hash`: Check if value follows `$scrypt$` hash format.
|
|
153
|
+
- `xss`: Check if value is free of HTML tags.
|
|
154
|
+
- `emoji`: Detect presence of emojis.
|
|
155
|
+
- `md5`: Check for 32-char hex string.
|
|
@@ -21,7 +21,7 @@ String manipulation, hashing, and flow control.
|
|
|
21
21
|
```javascript
|
|
22
22
|
const slug = Odac.Var('My Post Title').slug();
|
|
23
23
|
const isValid = Odac.Var('test@test.com').is('email');
|
|
24
|
-
const password = Odac.Var('secret').hash(); //
|
|
24
|
+
const password = Odac.Var('secret').hash(); // scrypt
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
### 2. Redirect and Abort
|