odac 1.4.8 → 1.4.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/CHANGELOG.md +43 -0
- package/docs/ai/README.md +2 -1
- package/docs/ai/skills/SKILL.md +2 -1
- package/docs/ai/skills/backend/authentication.md +12 -6
- package/docs/ai/skills/backend/database.md +85 -5
- 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/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/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 +321 -10
- package/src/Database/ReadCache.js +174 -0
- package/src/Database/WriteBuffer.js +15 -1
- package/src/Database.js +78 -1
- 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 +311 -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/insert.test.js +118 -0
- package/test/Database/insert.test.js +98 -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,3 +1,46 @@
|
|
|
1
|
+
### 🛠️ Fixes & Improvements
|
|
2
|
+
|
|
3
|
+
- **cache:** preserve Knex chain for insert operations during invalidation
|
|
4
|
+
- **database:** resolve duplicate error handler bug in insert cache invalidation
|
|
5
|
+
- **migration:** add column type comparison to detect schema changes
|
|
6
|
+
- **migration:** handle PostgreSQL primary key type changes safely
|
|
7
|
+
- **migration:** preserve column nullable state when altering without explicit schema
|
|
8
|
+
- **write-buffer:** auto-generate nanoid values for buffered inserts
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
15
|
+
|
|
16
|
+
### ⚙️ Engine Tuning
|
|
17
|
+
|
|
18
|
+
- migrate hashing from BCrypt to scrypt and update Odac.Var documentation and validation logic
|
|
19
|
+
|
|
20
|
+
### ✨ What's New
|
|
21
|
+
|
|
22
|
+
- **auth:** add token tracking and retrieval method
|
|
23
|
+
- **database:** add read-through cache API for SELECT query results
|
|
24
|
+
- **migration:** add cross-driver default value comparison
|
|
25
|
+
- **migration:** add foreign key introspection and synchronization
|
|
26
|
+
- **websocket:** add readyState tracking and message fragmentation support
|
|
27
|
+
|
|
28
|
+
### 📚 Documentation
|
|
29
|
+
|
|
30
|
+
- **forms:** add options to control message display for success and error handling
|
|
31
|
+
- **migrations:** add foreign keys and referential actions documentation
|
|
32
|
+
|
|
33
|
+
### 🛠️ Fixes & Improvements
|
|
34
|
+
|
|
35
|
+
- **database:** add catch method to write operation thenable for Promise compatibility
|
|
36
|
+
- **pr-review:** apply ReadCache null sentinel, fix invalidation test, correct auth docs login example
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
43
|
+
|
|
1
44
|
### ✨ What's New
|
|
2
45
|
|
|
3
46
|
- **database:** add Write-Behind Cache with counter, update, and batch insert buffering
|
package/docs/ai/README.md
CHANGED
|
@@ -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
|
@@ -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,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend-database-skill
|
|
3
|
-
description: High-performance ODAC database querying patterns including the Write-Behind Cache for counters, last-write-wins updates, and buffered batch inserts.
|
|
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, write-behind-cache, performance, security
|
|
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 and Write-Behind Cache.
|
|
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. **
|
|
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.
|
|
17
18
|
|
|
18
19
|
## Query Builder Patterns
|
|
19
20
|
```javascript
|
|
@@ -29,6 +30,85 @@ 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
|
|
30
31
|
```
|
|
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
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
32
112
|
## ID Strategy (NanoID)
|
|
33
113
|
1. **Schema-Driven**: Define string IDs with `type: 'nanoid'` in `schema/*.js`.
|
|
34
114
|
2. **Auto Generation**: On `insert()`, ODAC auto-generates missing nanoid fields.
|
|
@@ -144,4 +224,4 @@ await Odac.DB.buffer.flush() // flush everything
|
|
|
144
224
|
4. **Indexes**: Keep index definitions in schema so add/drop is managed automatically.
|
|
145
225
|
5. **Data Changes**: Use `migration/*.js` only for one-time data transformation.
|
|
146
226
|
|
|
147
|
-
See: [migrations.md](./migrations.md) | [write-behind-cache user docs](../../../backend/08-database/05-write-behind-cache.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)
|
|
@@ -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
|
|
@@ -42,7 +42,29 @@ Odac.form(
|
|
|
42
42
|
}
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
// 5)
|
|
45
|
+
// 5) Disable all automatic messages and handle everything in the callback
|
|
46
|
+
Odac.form(
|
|
47
|
+
{form: 'form[data-odac-form]', messages: false},
|
|
48
|
+
response => {
|
|
49
|
+
if (response?.result?.success) {
|
|
50
|
+
// Custom success: e.g. show a toast, update UI
|
|
51
|
+
} else if (response?.errors) {
|
|
52
|
+
// Custom error: manually map errors to DOM
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
// 6) Only show success messages, handle errors manually
|
|
58
|
+
Odac.form(
|
|
59
|
+
{form: 'form[data-odac-form]', messages: ['success']},
|
|
60
|
+
response => {
|
|
61
|
+
if (!response?.result?.success && response?.errors) {
|
|
62
|
+
// Custom error handling
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
// 7) Manual GET request helper
|
|
46
68
|
Odac.get('/api/status', data => {
|
|
47
69
|
const status = document.querySelector('[data-api-status]')
|
|
48
70
|
if (status) status.textContent = String(data?.status ?? '')
|
|
@@ -31,6 +31,7 @@ Odac.Route.ws('/path', Odac => {
|
|
|
31
31
|
| Property | Description |
|
|
32
32
|
|----------|-------------|
|
|
33
33
|
| `Odac.ws.id` | Unique client ID |
|
|
34
|
+
| `Odac.ws.readyState` | RFC 6455 state: `0` CONNECTING, `1` OPEN, `2` CLOSING, `3` CLOSED |
|
|
34
35
|
| `Odac.ws.rooms` | Array of joined rooms |
|
|
35
36
|
| `Odac.ws.data` | Custom data storage |
|
|
36
37
|
|
|
@@ -169,14 +170,33 @@ const ws = Odac.ws('/chat', {shared: true})
|
|
|
169
170
|
|------|-------------|
|
|
170
171
|
| `1000` | Normal closure |
|
|
171
172
|
| `1001` | Going away |
|
|
172
|
-
| `1002` | Protocol error |
|
|
173
|
+
| `1002` | Protocol error (e.g. unexpected continuation frame) |
|
|
173
174
|
| `1003` | Unsupported data |
|
|
174
175
|
| `1006` | Abnormal closure |
|
|
176
|
+
| `1008` | Rate limit exceeded |
|
|
177
|
+
| `1009` | Payload too large |
|
|
175
178
|
| `4000` | Middleware rejected |
|
|
176
179
|
| `4001` | Unauthorized |
|
|
177
180
|
| `4002` | Invalid/missing token |
|
|
178
181
|
| `4003` | Forbidden (middleware) |
|
|
179
182
|
|
|
183
|
+
## readyState Constants
|
|
184
|
+
|
|
185
|
+
| Value | Constant | Description |
|
|
186
|
+
|-------|----------|-------------|
|
|
187
|
+
| `0` | `CONNECTING` | Handshake done, handler not yet invoked |
|
|
188
|
+
| `1` | `OPEN` | Active — messages can be sent/received |
|
|
189
|
+
| `2` | `CLOSING` | Close frame sent, draining |
|
|
190
|
+
| `3` | `CLOSED` | Fully terminated |
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const {READY_STATE} = require('odac')
|
|
194
|
+
|
|
195
|
+
if (Odac.ws.readyState === READY_STATE.OPEN) {
|
|
196
|
+
Odac.ws.send({status: 'alive'})
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
180
200
|
## Best Practices
|
|
181
201
|
|
|
182
202
|
1. **Always handle authentication**
|
|
@@ -97,9 +97,30 @@ Odac.ws.on('error', err => {}) // Error occurred
|
|
|
97
97
|
### Connection Management
|
|
98
98
|
|
|
99
99
|
```javascript
|
|
100
|
-
Odac.ws.close() // Close connection
|
|
100
|
+
Odac.ws.close() // Close connection (sends close frame, transitions to CLOSED)
|
|
101
101
|
Odac.ws.ping() // Send ping frame
|
|
102
102
|
Odac.ws.id // Unique client ID
|
|
103
|
+
Odac.ws.readyState // Current connection state (0–3)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Connection State
|
|
107
|
+
|
|
108
|
+
`Odac.ws.readyState` reflects the RFC 6455 lifecycle. Use the static constants on `WebSocketClient` or the exported `READY_STATE` enum for readable comparisons:
|
|
109
|
+
|
|
110
|
+
| Value | Constant | Description |
|
|
111
|
+
|-------|----------|-------------|
|
|
112
|
+
| `0` | `CONNECTING` | Handshake complete, handler not yet called |
|
|
113
|
+
| `1` | `OPEN` | Connection active, messages can be sent |
|
|
114
|
+
| `2` | `CLOSING` | Close frame sent, waiting for socket drain |
|
|
115
|
+
| `3` | `CLOSED` | Connection fully terminated |
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const {READY_STATE} = require('odac')
|
|
119
|
+
|
|
120
|
+
Odac.ws.on('message', data => {
|
|
121
|
+
if (Odac.ws.readyState !== READY_STATE.OPEN) return
|
|
122
|
+
Odac.ws.send({echo: data})
|
|
123
|
+
})
|
|
103
124
|
```
|
|
104
125
|
|
|
105
126
|
## Rooms
|