odac 1.4.1 → 1.4.2

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.
Files changed (76) hide show
  1. package/.agent/rules/memory.md +5 -0
  2. package/.releaserc.js +9 -2
  3. package/CHANGELOG.md +35 -0
  4. package/bin/odac.js +3 -2
  5. package/client/odac.js +32 -13
  6. package/docs/ai/skills/backend/database.md +19 -0
  7. package/docs/ai/skills/backend/forms.md +107 -13
  8. package/docs/ai/skills/backend/migrations.md +8 -2
  9. package/docs/ai/skills/backend/validation.md +132 -32
  10. package/docs/ai/skills/frontend/forms.md +43 -15
  11. package/docs/backend/08-database/02-basics.md +49 -9
  12. package/docs/backend/08-database/04-migrations.md +1 -0
  13. package/package.json +1 -1
  14. package/src/Auth.js +15 -2
  15. package/src/Database/ConnectionFactory.js +1 -0
  16. package/src/Database/Migration.js +26 -1
  17. package/src/Database/nanoid.js +30 -0
  18. package/src/Database.js +122 -11
  19. package/src/Ipc.js +37 -0
  20. package/src/Odac.js +1 -1
  21. package/src/Route/Cron.js +11 -0
  22. package/src/Route.js +8 -0
  23. package/src/Server.js +77 -23
  24. package/src/Storage.js +15 -1
  25. package/src/Validator.js +22 -20
  26. package/test/{Auth.test.js → Auth/check.test.js} +91 -5
  27. package/test/Client/data.test.js +91 -0
  28. package/test/Client/get.test.js +90 -0
  29. package/test/Client/storage.test.js +87 -0
  30. package/test/Client/token.test.js +82 -0
  31. package/test/Client/ws.test.js +86 -0
  32. package/test/Config/deepMerge.test.js +14 -0
  33. package/test/Config/init.test.js +66 -0
  34. package/test/Config/interpolate.test.js +35 -0
  35. package/test/Database/ConnectionFactory/buildConnectionConfig.test.js +13 -0
  36. package/test/Database/ConnectionFactory/buildConnections.test.js +31 -0
  37. package/test/Database/ConnectionFactory/resolveClient.test.js +12 -0
  38. package/test/Database/Migration/migrate_column.test.js +52 -0
  39. package/test/Database/Migration/migrate_files.test.js +70 -0
  40. package/test/Database/Migration/migrate_index.test.js +89 -0
  41. package/test/Database/Migration/migrate_nanoid.test.js +160 -0
  42. package/test/Database/Migration/migrate_seed.test.js +77 -0
  43. package/test/Database/Migration/migrate_table.test.js +88 -0
  44. package/test/Database/Migration/rollback.test.js +61 -0
  45. package/test/Database/Migration/snapshot.test.js +38 -0
  46. package/test/Database/Migration/status.test.js +41 -0
  47. package/test/Database/autoNanoid.test.js +215 -0
  48. package/test/Database/nanoid.test.js +19 -0
  49. package/test/Lang/constructor.test.js +25 -0
  50. package/test/Lang/get.test.js +65 -0
  51. package/test/Lang/set.test.js +49 -0
  52. package/test/Odac/init.test.js +42 -0
  53. package/test/Odac/instance.test.js +58 -0
  54. package/test/Route/{Middleware.test.js → Middleware/chaining.test.js} +5 -29
  55. package/test/Route/Middleware/use.test.js +35 -0
  56. package/test/{Route.test.js → Route/check.test.js} +4 -55
  57. package/test/Route/set.test.js +52 -0
  58. package/test/Route/ws.test.js +23 -0
  59. package/test/View/EarlyHints/cache.test.js +32 -0
  60. package/test/View/EarlyHints/extractFromHtml.test.js +143 -0
  61. package/test/View/EarlyHints/formatLinkHeader.test.js +33 -0
  62. package/test/View/EarlyHints/send.test.js +99 -0
  63. package/test/View/{Form.test.js → Form/generateFieldHtml.test.js} +2 -2
  64. package/test/View/constructor.test.js +22 -0
  65. package/test/View/print.test.js +19 -0
  66. package/test/WebSocket/Client/limits.test.js +55 -0
  67. package/test/WebSocket/Server/broadcast.test.js +33 -0
  68. package/test/WebSocket/Server/route.test.js +37 -0
  69. package/test/Client.test.js +0 -197
  70. package/test/Config.test.js +0 -119
  71. package/test/Database/ConnectionFactory.test.js +0 -80
  72. package/test/Lang.test.js +0 -92
  73. package/test/Migration.test.js +0 -943
  74. package/test/Odac.test.js +0 -88
  75. package/test/View/EarlyHints.test.js +0 -282
  76. package/test/WebSocket.test.js +0 -238
@@ -43,6 +43,11 @@ trigger: always_on
43
43
  - **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
44
44
  - **Verify Correctness:** do not assume code works; prove it with a test that covers both success and failure scenarios (e.g., edge cases, error conditions).
45
45
  - **Update Existing Tests:** If a feature modifies existing behavior, update the relevant tests to reflect the new logic and ensure they pass.
46
+ - **Atomic Test Structure:**
47
+ - **Directory Mapping:** Each source class/module must have its own directory under `test/` (e.g., `src/Auth.js` -> `test/Auth/`).
48
+ - **Method-Level Files:** Every public method should have its own test file within the class directory (e.g., `test/Auth/check.test.js`).
49
+ - **Sub-module Context:** Nested modules should follow the same pattern (e.g., `src/View/Form.js` -> `test/View/Form/generateFieldHtml.test.js`).
50
+ - **Isolation & Parallelism:** This structure is mandatory to leverage Jest's multi-threaded execution and ensure strict isolation between test cases.
46
51
 
47
52
  ## Client Library (odac.js)
48
53
  - **Automatic JSON Parsing:** The `#ajax` method (and by extension `odac.get`) must automatically parse the response if the `Content-Type` header contains `application/json`, even if `dataType` is not explicitly set to `json`.
package/.releaserc.js CHANGED
@@ -4,7 +4,12 @@ module.exports = {
4
4
  [
5
5
  '@semantic-release/commit-analyzer',
6
6
  {
7
- preset: 'conventionalcommits'
7
+ preset: 'conventionalcommits',
8
+ releaseRules: [
9
+ {type: 'major', release: 'major'},
10
+ {type: 'minor', release: 'minor'},
11
+ {type: '*', release: 'patch'}
12
+ ]
8
13
  }
9
14
  ],
10
15
  [
@@ -16,6 +21,8 @@ module.exports = {
16
21
  const commit = JSON.parse(JSON.stringify(c))
17
22
 
18
23
  const map = {
24
+ major: '🚀 Major Updates',
25
+ minor: '🌟 Minor Updates',
19
26
  feat: "✨ What's New",
20
27
  fix: '🛠️ Fixes & Improvements',
21
28
  perf: '⚡️ Performance Upgrades',
@@ -134,4 +141,4 @@ Powered by [⚡ ODAC](https://odac.run)
134
141
  ],
135
142
  '@semantic-release/github'
136
143
  ]
137
- }
144
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  ### doc
2
2
 
3
+ - **forms:** update backend and frontend forms documentation with practical usage patterns and improved descriptions
4
+ - **validation:** enhance backend validation documentation with detailed usage patterns and examples
5
+
6
+ ### ⚙️ Engine Tuning
7
+
8
+ - **test:** restructure test suite into class-scoped directories and method-level atomic files
9
+
10
+ ### ✨ What's New
11
+
12
+ - **database:** add debug logging for schema parsing failures in nanoid metadata loader
13
+ - **database:** introduce NanoID support for automatic ID generation in schema
14
+ - **release:** enhance commit analyzer with release rules and custom labels
15
+ - **shutdown:** implement graceful shutdown for IPC, Database, and Cron services
16
+
17
+ ### 📚 Documentation
18
+
19
+ - **database:** remove underscore from nanoid example to reflect true alphanumeric output
20
+
21
+ ### 🛠️ Fixes & Improvements
22
+
23
+ - **Auth:** handle token rotation for WebSocket connections and update active timestamp
24
+ - **core:** explicitly stop session GC interval during graceful shutdown
25
+ - **database:** namespace nanoid schema cache by connection to prevent table to prevent collisions
26
+ - **forms:** initialize ODAC form handlers on DOMContentLoaded and after AJAX navigation
27
+ - **manageSkills:** correct targetPath assignment for skill synchronization
28
+ - **Validator:** pass Odac instance to Validator for improved access to global methods
29
+
30
+
31
+
32
+ ---
33
+
34
+ Powered by [⚡ ODAC](https://odac.run)
35
+
36
+ ### doc
37
+
3
38
  - enhance AI skills documentation with structured YAML front matter and detailed descriptions
4
39
 
5
40
  ### ⚙️ Engine Tuning
package/bin/odac.js CHANGED
@@ -210,7 +210,7 @@ async function manageSkills(targetDir = process.cwd()) {
210
210
  }
211
211
 
212
212
  const targetBase = path.resolve(targetDir, targetSubDir)
213
- const targetPath = targetBase
213
+ const targetPath = copySkillsOnly ? path.join(targetBase, 'odac.js') : targetBase
214
214
 
215
215
  try {
216
216
  fs.mkdirSync(targetPath, {recursive: true})
@@ -222,7 +222,8 @@ async function manageSkills(targetDir = process.cwd()) {
222
222
  fs.cpSync(aiSourceDir, targetPath, {recursive: true})
223
223
  }
224
224
 
225
- console.log(`\n✨ AI skills successfully synced to: \x1b[32m${targetSubDir}\x1b[0m`)
225
+ const finalSubDir = copySkillsOnly ? path.join(targetSubDir, 'odac.js') : targetSubDir
226
+ console.log(`\n✨ AI skills successfully synced to: \x1b[32m${finalSubDir}\x1b[0m`)
226
227
  console.log('Your AI Agent now has full knowledge of the ODAC Framework. 🚀')
227
228
  } catch (err) {
228
229
  console.error('❌ Failed to sync AI skills:', err.message)
package/client/odac.js CHANGED
@@ -111,6 +111,12 @@ if (typeof window !== 'undefined') {
111
111
  // In constructor we can't call this.data() easily if it uses 'this' for caching properly before init
112
112
  // But based on original code logic:
113
113
  this.#data = this.data()
114
+
115
+ if (document.readyState === 'loading') {
116
+ document.addEventListener('DOMContentLoaded', () => this.#initForms())
117
+ } else {
118
+ this.#initForms()
119
+ }
114
120
  }
115
121
 
116
122
  #ajax(options) {
@@ -795,6 +801,8 @@ if (typeof window !== 'undefined') {
795
801
  }
796
802
 
797
803
  #handleLoadComplete(data, callback) {
804
+ this.#initForms()
805
+
798
806
  if (this.actions.load)
799
807
  (Array.isArray(this.actions.load) ? this.actions.load : [this.actions.load]).forEach(fn => fn(this.page(), data.variables))
800
808
  if (this.actions.page && this.actions.page[this.page()])
@@ -808,6 +816,30 @@ if (typeof window !== 'undefined') {
808
816
  this.#isNavigating = false
809
817
  }
810
818
 
819
+ /**
820
+ * Scans the DOM for ODAC form components and registers submit handlers
821
+ * for any that haven't been initialized yet. Called on DOMContentLoaded
822
+ * and after every AJAX navigation to bind freshly rendered forms.
823
+ */
824
+ #initForms() {
825
+ const formTypes = [
826
+ {cls: 'odac-register-form', attr: 'data-odac-register'},
827
+ {cls: 'odac-login-form', attr: 'data-odac-login'},
828
+ {cls: 'odac-magic-login-form', attr: 'data-odac-magic-login'},
829
+ {cls: 'odac-custom-form', attr: 'data-odac-form'}
830
+ ]
831
+
832
+ for (const {cls, attr} of formTypes) {
833
+ document.querySelectorAll(`form.${cls}[${attr}]`).forEach(form => {
834
+ const token = form.getAttribute(attr)
835
+ const selector = `form[${attr}="${token}"]`
836
+ if (!this.#formSubmitHandlers.has(selector)) {
837
+ this.form({form: selector})
838
+ }
839
+ })
840
+ }
841
+ }
842
+
811
843
  loader(selector, elements, callback) {
812
844
  this.#loader.elements = elements
813
845
  this.#loader.callback = callback
@@ -993,19 +1025,6 @@ if (typeof window !== 'undefined') {
993
1025
  }
994
1026
  document.readyState === 'loading' ? document.addEventListener('DOMContentLoaded', init) : init()
995
1027
  })()
996
-
997
- document.addEventListener('DOMContentLoaded', () => {
998
- ;['register', 'login'].forEach(type => {
999
- document.querySelectorAll(`form.odac-${type}-form[data-odac-${type}]`).forEach(form => {
1000
- const token = form.getAttribute(`data-odac-${type}`)
1001
- window.Odac.form({form: `form[data-odac-${type}="${token}"]`})
1002
- })
1003
- })
1004
- document.querySelectorAll('form.odac-custom-form[data-odac-form]').forEach(form => {
1005
- const token = form.getAttribute('data-odac-form')
1006
- window.Odac.form({form: `form[data-odac-form="${token}"]`})
1007
- })
1008
- })
1009
1028
  } else {
1010
1029
  let socket = null
1011
1030
  const ports = new Set()
@@ -27,6 +27,25 @@ await Odac.DB.table('posts').insert({
27
27
  });
28
28
  ```
29
29
 
30
+ ## ID Strategy (NanoID)
31
+ 1. **Schema-Driven**: Define string IDs with `type: 'nanoid'` in `schema/*.js`.
32
+ 2. **Auto Generation**: On `insert()`, ODAC auto-generates missing nanoid fields.
33
+ 3. **No Override**: If ID is explicitly provided, ODAC preserves it.
34
+ 4. **Bulk Safe**: Auto-generation works for both single and bulk inserts.
35
+
36
+ ```javascript
37
+ // schema/posts.js
38
+ module.exports = {
39
+ columns: {
40
+ id: {type: 'nanoid', primary: true},
41
+ title: {type: 'string', length: 255}
42
+ }
43
+ }
44
+
45
+ // ID is generated automatically
46
+ await Odac.DB.table('posts').insert({title: 'Hello'});
47
+ ```
48
+
30
49
  ## Migration Awareness
31
50
  1. **Schema-First**: Structural DB changes must be defined in `schema/*.js`.
32
51
  2. **Auto-Migrate**: Migrations run automatically at startup from `Database.init()`.
@@ -1,26 +1,120 @@
1
1
  ---
2
2
  name: backend-forms-validation-skill
3
- description: Secure ODAC form processing workflow with validation, CSRF protection, and safe request-to-database handling.
3
+ description: Practical ODAC form usage patterns for register/login, magic-login, custom actions, and automatic database insert.
4
4
  metadata:
5
- tags: backend, forms, validation, csrf, input-security, request-processing
5
+ tags: backend, forms, validation, register, login, magic-login, request-processing
6
6
  ---
7
7
 
8
8
  # Backend Forms & Validation Skill
9
9
 
10
- Processing form data securely and validating inputs.
10
+ ODAC forms for validation, authentication flows, and safe request handling.
11
11
 
12
12
  ## Rules
13
- 1. **Validator**: Always use `Odac.Validator` for input.
14
- 2. **Auto-save**: Use `Odac.DB.table().save(Odac.Request.post())` for quick inserts.
15
- 3. **CSRF**: Ensure `{{ TOKEN }}` is in your HTML forms.
13
+ 1. **Use ODAC form tags**: Prefer `<odac:register>`, `<odac:login>`, `<odac:magic-login>`, `<odac:form>` instead of manual raw form handlers.
14
+ 2. **Do not add manual hidden security fields**: Keep forms clean and use ODAC defaults.
15
+ 3. **Validation in template**: Define rules with `<odac:validate rule="..." message="..."/>`; they become both frontend HTML constraints and backend validator checks.
16
+ 4. **Server-side enrichment**: Use `<odac:set>` for trusted fields (`compute`, `value`, `callback`, `if-empty`) instead of taking these values from user input.
17
+ 5. **Action vs table**: In `<odac:form>`, use `table="..."` for automatic insert or `action="Class.method"` for custom business logic.
18
+
19
+ ## Form Type Variants
20
+
21
+ ### 1) Register Form
22
+ ```html
23
+ <odac:register redirect="/dashboard" autologin="true">
24
+ <odac:input name="email" type="email" label="Email">
25
+ <odac:validate rule="required|email" message="Valid email required"/>
26
+ </odac:input>
27
+
28
+ <odac:input name="password" type="password" label="Password">
29
+ <odac:validate rule="required|minlen:8" message="Min 8 chars"/>
30
+ </odac:input>
31
+
32
+ <odac:set name="created_at" compute="now"/>
33
+ <odac:submit text="Register" loading="Processing..."/>
34
+ </odac:register>
35
+ ```
36
+
37
+ ### 2) Login Form
38
+ ```html
39
+ <odac:login redirect="/panel">
40
+ <odac:input name="email" type="email" label="Email">
41
+ <odac:validate rule="required|email" message="Email required"/>
42
+ </odac:input>
43
+
44
+ <odac:input name="password" type="password" label="Password">
45
+ <odac:validate rule="required" message="Password required"/>
46
+ </odac:input>
47
+
48
+ <odac:submit text="Login" loading="Logging in..."/>
49
+ </odac:login>
50
+ ```
51
+
52
+ ### 3) Magic Login Form
53
+ ```html
54
+ <odac:magic-login redirect="/dashboard" email-label="Work Email" submit-text="Send Link" />
55
+ ```
56
+
57
+ ```html
58
+ <odac:magic-login redirect="/dashboard">
59
+ <odac:input name="email" type="email" label="Email">
60
+ <odac:validate rule="required|email" message="Valid email required"/>
61
+ </odac:input>
62
+ <odac:submit text="Send Magic Link" loading="Sending..."/>
63
+ </odac:magic-login>
64
+ ```
65
+
66
+ ### 4) Custom Form with Automatic DB Insert
67
+ ```html
68
+ <odac:form table="waitlist" redirect="/" success="Thank you!" clear="true">
69
+ <odac:input name="email" type="email" label="Email">
70
+ <odac:validate rule="required|email|unique" message="Email already exists"/>
71
+ </odac:input>
72
+
73
+ <odac:set name="created_at" compute="now"/>
74
+ <odac:set name="ip" compute="ip"/>
75
+ <odac:submit text="Join" loading="Joining..."/>
76
+ </odac:form>
77
+ ```
78
+
79
+ ### 5) Custom Form with Controller Action
80
+ ```html
81
+ <odac:form action="Contact.submit" clear="false">
82
+ <odac:input name="subject" type="text" label="Subject">
83
+ <odac:validate rule="required|minlen:3" message="Subject is too short"/>
84
+ </odac:input>
85
+ <odac:submit text="Send" loading="Sending..."/>
86
+ </odac:form>
87
+ ```
16
88
 
17
- ## Patterns
18
89
  ```javascript
19
- // Validation
20
- const check = Odac.Validator.run(Odac.Request.post(), {
21
- email: 'required|email',
22
- password: 'required|min:8'
23
- });
90
+ module.exports = class Contact {
91
+ constructor(Odac) {
92
+ this.Odac = Odac
93
+ }
94
+
95
+ async submit(form) {
96
+ const data = form.data
97
+ if (!data.subject) return form.error('subject', 'Subject required')
98
+ return form.success('Message sent', '/thank-you')
99
+ }
100
+ }
101
+ ```
24
102
 
25
- if (check.failed()) return Odac.Request.error(check.errors());
103
+ ## Field-Level Variants
104
+ - **Input types**: `text`, `email`, `password`, `number`, `url`, `textarea`, `checkbox`.
105
+ - **Validation mapping**: `required|minlen|maxlen|min|max|alpha|alphanumeric|numeric|email|url|accepted`.
106
+ - **Pass-through attrs**: Unrecognized `<odac:input ...>` attributes are preserved into generated HTML input/textarea.
107
+ - **Skip persistence**: Use `skip` on `<odac:input>` to validate a field but exclude it from final payload.
108
+ - **Unique shorthand**: `unique` attribute on `<odac:input>` enables auth-register uniqueness list.
109
+
110
+ ## Patterns
111
+ ```javascript
112
+ // Access validated data in action-driven custom form
113
+ Odac.Route.class.post('/contact', class Contact {
114
+ async submit(form) {
115
+ const {email, message} = form.data
116
+ if (!email || !message) return form.error('_odac_form', 'Missing input')
117
+ return form.success('Saved successfully')
118
+ }
119
+ })
26
120
  ```
@@ -19,7 +19,8 @@ ODAC migrations are **declarative**. The `schema/` directory is the single sourc
19
19
  4. **Index Sync**: Define indexes in schema; engine adds/removes them automatically.
20
20
  5. **Drop Behavior**: If a column/index is removed from schema, it is removed from DB on next startup.
21
21
  6. **Seeds**: Use `seed` + `seedKey` for idempotent reference data.
22
- 7. **Data Transformations**: Use imperative files under `migration/` only for one-time data migration logic.
22
+ 7. **NanoID Columns**: `type: 'nanoid'` maps to string columns and missing values are auto-generated on insert/seed.
23
+ 8. **Data Transformations**: Use imperative files under `migration/` only for one-time data migration logic.
23
24
 
24
25
  ## Reference Patterns
25
26
  ### 1. Schema File (Final State)
@@ -29,7 +30,7 @@ ODAC migrations are **declarative**. The `schema/` directory is the single sourc
29
30
 
30
31
  module.exports = {
31
32
  columns: {
32
- id: {type: 'increments'},
33
+ id: {type: 'nanoid', primary: true},
33
34
  email: {type: 'string', length: 255, nullable: false},
34
35
  role: {type: 'enum', values: ['admin', 'user'], default: 'user'},
35
36
  timestamps: {type: 'timestamps'}
@@ -44,6 +45,11 @@ module.exports = {
44
45
  }
45
46
  ```
46
47
 
48
+ ### NanoID Notes
49
+ - `length` can be customized: `{type: 'nanoid', length: 12, primary: true}`.
50
+ - If seed rows omit the nanoid field, ODAC fills it automatically.
51
+ - If seed rows provide an explicit nanoid value, ODAC keeps it unchanged.
52
+
47
53
  ### 2. Multi-Database Layout
48
54
  ```
49
55
  schema/
@@ -1,60 +1,160 @@
1
1
  ---
2
2
  name: backend-validation-skill
3
- description: Fluent ODAC validation strategies for input hardening, brute-force protection, and standardized error responses.
3
+ description: Detailed ODAC Validator usage for request validation, security checks, brute-force protection, and consistent API responses.
4
4
  metadata:
5
5
  tags: backend, validation, fluent-api, input-security, brute-force, error-handling
6
6
  ---
7
7
 
8
8
  # Backend Validation Skill
9
9
 
10
- The `Validator` service provides a fluent, chainable API for securing user input and enforcing business rules.
11
-
12
- ## Architectural Approach
13
- Validation should happen as early as possible in the request lifecycle. The `Validator` service handles automatic error formatting and frontend integration.
10
+ ODAC validation should be centralized with the fluent `Validator` API and returned in framework-standard result format.
14
11
 
15
12
  ## Core Rules
16
- 1. **Chaining**: Use the fluent API: `.post(key).check(rules).message(msg)`.
17
- 2. **Brute Force**: Protect sensitive endpoints with `.brute(attempts)`.
18
- 3. **Automatic Errors**: Use `await validator.error()` to check status and `await validator.result()` to return standardized JSON.
19
- 4. **Inverse Rules**: Use `!` to invert any rule (e.g., `!required`).
13
+ 1. **Create validator per request**: Use `const validator = Odac.validator()`.
14
+ 2. **Fail fast**: Run all checks, then immediately return on `await validator.error()`.
15
+ 3. **Field-first messages**: Assign a specific `.message(...)` per check chain.
16
+ 4. **Use standard result shape**: Return `await validator.result('Validation failed')` for errors.
17
+ 5. **Protect sensitive flows**: Add `await validator.brute(n)` on login/reset/auth endpoints.
18
+
19
+ ## Minimal Flow
20
+ ```javascript
21
+ module.exports = async Odac => {
22
+ const validator = Odac.validator()
23
+
24
+ validator.post('email').check('required|email').message('Valid email required')
25
+ validator.post('password').check('required|minlen:8').message('Password must be at least 8 characters')
26
+
27
+ if (await validator.error()) {
28
+ await validator.brute(5)
29
+ return await validator.result('Validation failed')
30
+ }
31
+
32
+ return await validator.success({ok: true})
33
+ }
34
+ ```
35
+
36
+ ## API Surface
37
+ - `post(key)`: Validate POST payload field.
38
+ - `get(key)`: Validate querystring field.
39
+ - `var(name, value)`: Validate computed/custom value.
40
+ - `file(name)`: Validate uploaded file object.
41
+ - `check(rules | boolean)`: Apply pipe rules or direct boolean validation.
42
+ - `message(text)`: Set message for the latest check on current field.
43
+ - `error()`: Runs validation and returns `true` if any error exists.
44
+ - `result(message?, data?)`: Returns ODAC-standard response object.
45
+ - `success(dataOrMessage?)`: Convenience wrapper for success payload.
46
+ - `brute(maxAttempts = 5)`: Tracks failed attempts per hour/page/ip.
47
+
48
+ ## Rule Catalog
49
+
50
+ ### Type & format rules
51
+ - `required`, `accepted`
52
+ - `numeric`, `float`
53
+ - `alpha`, `alphaspace`, `alphanumeric`, `alphanumericspace`, `username`
54
+ - `email`, `ip`, `mac`, `domain`, `url`
55
+ - `array`, `date`, `xss`
56
+
57
+ ### Length & value rules
58
+ - `len:X`, `minlen:X`, `maxlen:X`
59
+ - `min:X`, `max:X`
60
+ - `equal:value`, `not:value`
61
+ - `same:field`, `different:field`
62
+
63
+ ### String/date matching rules
64
+ - `in:substring`, `notin:substring`
65
+ - `regex:pattern`
66
+ - `mindate:YYYY-MM-DD`, `maxdate:YYYY-MM-DD`
67
+
68
+ ### Auth/security rules
69
+ - `usercheck`: Must be authenticated.
70
+ - `user:field`: Input must match authenticated user field.
71
+ - `disposable`: Email must be disposable.
72
+ - `!disposable`: Email must not be disposable.
73
+
74
+ ### Inverse rules
75
+ - Prefix any rule with `!` to invert: `!required`, `!email`, `!equal:admin`.
20
76
 
21
77
  ## Reference Patterns
22
78
 
23
- ### 1. Standard Validation Chaining
79
+ ### 1) Multi-check per field with specific errors
24
80
  ```javascript
25
- module.exports = async function (Odac) {
26
- const validator = Odac.Validator;
81
+ module.exports = async Odac => {
82
+ const validator = Odac.validator()
27
83
 
28
84
  validator
29
- .post('email').check('required|email').message('Valid email required')
30
- .post('password').check('required|minlen:8').message('Password too short');
85
+ .post('password')
86
+ .check('required').message('Password is required')
87
+ .check('minlen:8').message('Minimum 8 characters')
88
+ .check('regex:[A-Z]').message('At least one uppercase letter')
89
+ .check('regex:[0-9]').message('At least one number')
31
90
 
32
91
  if (await validator.error()) {
33
- return validator.result('Please fix input errors');
92
+ return await validator.result('Please fix input errors')
34
93
  }
35
94
 
36
- // Proceed with validated data
37
- return validator.success('Success');
38
- };
95
+ return await validator.success('Success')
96
+ }
97
+ ```
98
+
99
+ ### 2) GET + POST + VAR together
100
+ ```javascript
101
+ module.exports = async Odac => {
102
+ const validator = Odac.validator()
103
+ const plan = await Odac.request('plan')
104
+
105
+ validator.get('page').check('numeric|min:1').message('Invalid page')
106
+ validator.post('email').check('required|email|!disposable').message('Corporate email required')
107
+ validator.var('plan', plan).check('in:pro').message('Only pro plan is allowed')
108
+
109
+ if (await validator.error()) return await validator.result('Validation failed')
110
+ return await validator.success({ok: true})
111
+ }
39
112
  ```
40
113
 
41
- ### 2. Custom Variable and Security Validation
114
+ ### 3) Boolean check for business rules
42
115
  ```javascript
43
- validator.var('age', userAge).check('numeric|min:18').message('Must be 18+');
116
+ module.exports = async Odac => {
117
+ const validator = Odac.validator()
118
+ const canPublish = await somePermissionCheck(Odac)
119
+
120
+ validator.post('title').check('required').message('Title required')
121
+ validator.var('permission', null).check(canPublish).message('No publish permission')
122
+
123
+ if (await validator.error()) return await validator.result('Validation failed')
124
+ return await validator.success('Published')
125
+ }
126
+ ```
127
+
128
+ ### 4) Brute-force on auth endpoint
129
+ ```javascript
130
+ module.exports = async Odac => {
131
+ const validator = Odac.validator()
132
+
133
+ validator.post('email').check('required|email').message('Email required')
134
+ validator.post('password').check('required').message('Password required')
135
+
136
+ if (await validator.error()) {
137
+ await validator.brute(5)
138
+ return await validator.result('Login failed')
139
+ }
44
140
 
45
- // Security checks
46
- validator.post('bio').check('xss').message('Malicious HTML detected');
47
- validator.var('auth', null).check('usercheck').message('Authentication required');
141
+ return await validator.success('OK')
142
+ }
48
143
  ```
49
144
 
50
- ### 3. Common Rules Reference
51
- - `required`, `email`, `numeric`, `username`, `url`, `ip`, `json`.
52
- - `len:X`, `minlen:X`, `maxlen:X`.
53
- - `mindate:YYYY-MM-DD`, `maxdate:YYYY-MM-DD`.
54
- - `regex:pattern`, `same:field`, `different:field`.
55
- - `!disposable`: Blocks temporary email providers.
145
+ ## Response Contract
146
+ - **Success**:
147
+ - `result.success: true`
148
+ - optional `result.message`
149
+ - optional `data`
150
+ - **Failure**:
151
+ - `result.success: false`
152
+ - `errors.{field}` map
153
+ - global errors may use `errors._odac_form`
56
154
 
57
155
  ## Best Practices
58
- - **Specific Messages**: Provide helpful error messages that guide the user.
59
- - **Security First**: Use the `xss` rule for any user-generated content that will be rendered later.
60
- - **Fail Fast**: Return the validation result immediately if `validator.error()` is true.
156
+ - Keep validation at route/controller entry; do not defer to deep service layers.
157
+ - Use separate `check()` calls when you need rule-specific messages.
158
+ - Prefer `var()` for derived values instead of re-reading mutable request state.
159
+ - Use `xss` for text fields that can later be rendered in HTML.
160
+ - Always combine auth endpoints with `brute()` to reduce credential-stuffing risk.
@@ -1,28 +1,56 @@
1
1
  ---
2
2
  name: frontend-forms-api-skill
3
- description: AJAX form and API request patterns in odac.js for interactive UX and predictable frontend data flows.
3
+ description: odac.js form submission patterns for parser-generated ODAC forms and predictable AJAX request handling.
4
4
  metadata:
5
- tags: frontend, forms, ajax, api-requests, odac-get, odac-post
5
+ tags: frontend, forms, ajax, odac-form, register, login, magic-login
6
6
  ---
7
7
 
8
8
  # Frontend Forms & API Skill
9
9
 
10
- Handling AJAX form submissions and API requests.
10
+ Handling ODAC AJAX form submissions generated from server-side form parsing.
11
11
 
12
12
  ## Rules
13
- 1. **Forms**: Use `odac.form('#id', callback)` for AJAX submission.
14
- 2. **Requests**: Use `odac.get()` and `odac.post()` for manual requests.
15
- 3. **Realtime**: Handle WebSocket events using Hub structures in `Odac.action()`.
13
+ 1. **Bind by form selector**: Use `Odac.form({ form: 'selector' }, callback)` or short form `Odac.form('selector', callback)`.
14
+ 2. **Leverage parser-generated forms**: `odac-register`, `odac-login`, `odac-magic-login`, and `odac-custom-form` are all auto-bound on page load and after every AJAX navigation.
15
+ 3. **Expect JSON result shape**: Handle `result.success`, `result.message`, `result.redirect`, and `errors` in callback.
16
+ 4. **Message/clear control**: Use `messages` and `clear` options for UX behavior.
16
17
 
17
18
  ## Patterns
18
19
  ```javascript
19
- // Form with automatic validation feedback
20
- odac.form('#my-form', (res) => {
21
- if(res.success) odac.visit('/done');
22
- });
23
-
24
- // Simple API Check
25
- odac.get('/api/status', (data) => {
26
- console.log('Status:', data);
27
- });
20
+ // 1) Bind a parsed custom form
21
+ Odac.form('form[data-odac-form]')
22
+
23
+ // 2) Bind parsed register/login forms explicitly (optional)
24
+ Odac.form('form[data-odac-register]')
25
+ Odac.form('form[data-odac-login]')
26
+
27
+ // 3) Bind parsed magic-login form manually
28
+ Odac.form('form[data-odac-magic-login]', response => {
29
+ if (response?.result?.success && !response.result.redirect) {
30
+ const info = document.querySelector('[data-status]')
31
+ if (info) info.textContent = response.result.message
32
+ }
33
+ })
34
+
35
+ // 4) Advanced options: disable auto clear and hide success messages
36
+ Odac.form(
37
+ {form: 'form[data-odac-form]', clear: false, messages: ['error']},
38
+ response => {
39
+ if (response?.result?.success && response.result.redirect) {
40
+ window.location.href = response.result.redirect
41
+ }
42
+ }
43
+ )
44
+
45
+ // 5) Manual GET request helper
46
+ Odac.get('/api/status', data => {
47
+ const status = document.querySelector('[data-api-status]')
48
+ if (status) status.textContent = String(data?.status ?? '')
49
+ })
28
50
  ```
51
+
52
+ ## Response Handling Contract
53
+ - **Success**: `response.result.success === true`
54
+ - **Redirect**: `response.result.redirect` exists when server wants navigation
55
+ - **Form errors**: `response.errors.{fieldName}` maps to `odac-form-error="fieldName"`
56
+ - **Global form error**: `response.errors._odac_form`