odac 1.4.0 → 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 (96) hide show
  1. package/.agent/rules/memory.md +8 -0
  2. package/.github/workflows/release.yml +1 -1
  3. package/.releaserc.js +9 -2
  4. package/CHANGELOG.md +61 -0
  5. package/README.md +10 -0
  6. package/bin/odac.js +193 -2
  7. package/client/odac.js +32 -13
  8. package/docs/ai/skills/SKILL.md +4 -3
  9. package/docs/ai/skills/backend/authentication.md +7 -0
  10. package/docs/ai/skills/backend/config.md +7 -0
  11. package/docs/ai/skills/backend/controllers.md +7 -0
  12. package/docs/ai/skills/backend/cron.md +9 -2
  13. package/docs/ai/skills/backend/database.md +37 -2
  14. package/docs/ai/skills/backend/forms.md +112 -11
  15. package/docs/ai/skills/backend/ipc.md +7 -0
  16. package/docs/ai/skills/backend/mail.md +7 -0
  17. package/docs/ai/skills/backend/migrations.md +86 -0
  18. package/docs/ai/skills/backend/request_response.md +7 -0
  19. package/docs/ai/skills/backend/routing.md +7 -0
  20. package/docs/ai/skills/backend/storage.md +7 -0
  21. package/docs/ai/skills/backend/streaming.md +7 -0
  22. package/docs/ai/skills/backend/structure.md +8 -1
  23. package/docs/ai/skills/backend/translations.md +7 -0
  24. package/docs/ai/skills/backend/utilities.md +7 -0
  25. package/docs/ai/skills/backend/validation.md +138 -31
  26. package/docs/ai/skills/backend/views.md +7 -0
  27. package/docs/ai/skills/frontend/core.md +7 -0
  28. package/docs/ai/skills/frontend/forms.md +48 -13
  29. package/docs/ai/skills/frontend/navigation.md +7 -0
  30. package/docs/ai/skills/frontend/realtime.md +7 -0
  31. package/docs/backend/08-database/02-basics.md +49 -9
  32. package/docs/backend/08-database/04-migrations.md +259 -37
  33. package/package.json +1 -1
  34. package/src/Auth.js +82 -43
  35. package/src/Config.js +1 -1
  36. package/src/Database/ConnectionFactory.js +70 -0
  37. package/src/Database/Migration.js +1228 -0
  38. package/src/Database/nanoid.js +30 -0
  39. package/src/Database.js +157 -46
  40. package/src/Ipc.js +37 -0
  41. package/src/Odac.js +1 -1
  42. package/src/Route/Cron.js +11 -0
  43. package/src/Route.js +8 -0
  44. package/src/Server.js +77 -23
  45. package/src/Storage.js +15 -1
  46. package/src/Validator.js +22 -20
  47. package/template/schema/users.js +23 -0
  48. package/test/{Auth.test.js → Auth/check.test.js} +153 -6
  49. package/test/Client/data.test.js +91 -0
  50. package/test/Client/get.test.js +90 -0
  51. package/test/Client/storage.test.js +87 -0
  52. package/test/Client/token.test.js +82 -0
  53. package/test/Client/ws.test.js +86 -0
  54. package/test/Config/deepMerge.test.js +14 -0
  55. package/test/Config/init.test.js +66 -0
  56. package/test/Config/interpolate.test.js +35 -0
  57. package/test/Database/ConnectionFactory/buildConnectionConfig.test.js +13 -0
  58. package/test/Database/ConnectionFactory/buildConnections.test.js +31 -0
  59. package/test/Database/ConnectionFactory/resolveClient.test.js +12 -0
  60. package/test/Database/Migration/migrate_column.test.js +52 -0
  61. package/test/Database/Migration/migrate_files.test.js +70 -0
  62. package/test/Database/Migration/migrate_index.test.js +89 -0
  63. package/test/Database/Migration/migrate_nanoid.test.js +160 -0
  64. package/test/Database/Migration/migrate_seed.test.js +77 -0
  65. package/test/Database/Migration/migrate_table.test.js +88 -0
  66. package/test/Database/Migration/rollback.test.js +61 -0
  67. package/test/Database/Migration/snapshot.test.js +38 -0
  68. package/test/Database/Migration/status.test.js +41 -0
  69. package/test/Database/autoNanoid.test.js +215 -0
  70. package/test/Database/nanoid.test.js +19 -0
  71. package/test/Lang/constructor.test.js +25 -0
  72. package/test/Lang/get.test.js +65 -0
  73. package/test/Lang/set.test.js +49 -0
  74. package/test/Odac/init.test.js +42 -0
  75. package/test/Odac/instance.test.js +58 -0
  76. package/test/Route/{Middleware.test.js → Middleware/chaining.test.js} +5 -29
  77. package/test/Route/Middleware/use.test.js +35 -0
  78. package/test/{Route.test.js → Route/check.test.js} +4 -55
  79. package/test/Route/set.test.js +52 -0
  80. package/test/Route/ws.test.js +23 -0
  81. package/test/View/EarlyHints/cache.test.js +32 -0
  82. package/test/View/EarlyHints/extractFromHtml.test.js +143 -0
  83. package/test/View/EarlyHints/formatLinkHeader.test.js +33 -0
  84. package/test/View/EarlyHints/send.test.js +99 -0
  85. package/test/View/{Form.test.js → Form/generateFieldHtml.test.js} +2 -2
  86. package/test/View/constructor.test.js +22 -0
  87. package/test/View/print.test.js +19 -0
  88. package/test/WebSocket/Client/limits.test.js +55 -0
  89. package/test/WebSocket/Server/broadcast.test.js +33 -0
  90. package/test/WebSocket/Server/route.test.js +37 -0
  91. package/test/Client.test.js +0 -197
  92. package/test/Config.test.js +0 -112
  93. package/test/Lang.test.js +0 -92
  94. package/test/Odac.test.js +0 -88
  95. package/test/View/EarlyHints.test.js +0 -282
  96. package/test/WebSocket.test.js +0 -238
@@ -36,10 +36,18 @@ trigger: always_on
36
36
  ## Naming & Text Conventions
37
37
  - **ODAC Casing:** Always write "ODAC" in uppercase letters when referring to the framework name in strings, comments, log messages, or user-facing text. **EXCEPTION:** The class name itself (`class Odac`) and variable references to it should remain `Odac` (PascalCase) as per code conventions.
38
38
 
39
+ ## Documentation Standards
40
+ - **AI Skill Front Matter:** Every file under `docs/ai/skills/**/*.md` must start with YAML front matter containing `name`, `description`, and `metadata.tags`; values must be specific to that document's topic (never copied from generic examples).
41
+
39
42
  ## Testing & Validation
40
43
  - **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
41
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).
42
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.
43
51
 
44
52
  ## Client Library (odac.js)
45
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`.
@@ -62,6 +62,7 @@ jobs:
62
62
  with:
63
63
  github-token: ${{ secrets.GITHUB_TOKEN }}
64
64
  michelangelo: ${{ secrets.MICHELANGELO }}
65
+ version: ${{ steps.version.outputs.version }}
65
66
  timeout-minutes: 15
66
67
  continue-on-error: true
67
68
 
@@ -96,7 +97,6 @@ jobs:
96
97
  if: steps.ai_notes.outcome == 'success' && steps.version.outputs.version != ''
97
98
  with:
98
99
  tag_name: v${{ steps.version.outputs.version }}
99
- name: ${{ steps.ai_notes.outputs.release-title }}
100
100
  body_path: RELEASE_NOTE.md
101
101
  draft: false
102
102
  prerelease: false
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,3 +1,64 @@
1
+ ### doc
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
+
38
+ - enhance AI skills documentation with structured YAML front matter and detailed descriptions
39
+
40
+ ### ⚙️ Engine Tuning
41
+
42
+ - **database:** centralize knex connection bootstrap for runtime and CLI
43
+
44
+ ### 📚 Documentation
45
+
46
+ - add section for loading and updating AI skills in projects
47
+
48
+ ### 🛠️ Fixes & Improvements
49
+
50
+ - **auth:** improve token rotation logic and ensure proper cookie attributes
51
+ - **cli:** parse .env values consistently in migration loader
52
+ - **config:** update interpolation regex to support variable names with hyphens
53
+ - **migration:** normalize column-level unique constraints and enhance idempotency in migrations
54
+ - **release:** add version output to release notes and update release title condition
55
+
56
+
57
+
58
+ ---
59
+
60
+ Powered by [⚡ ODAC](https://odac.run)
61
+
1
62
  ### ⚙️ Engine Tuning
2
63
 
3
64
  - Extract MIME type definitions into a dedicated module.
package/README.md CHANGED
@@ -53,6 +53,16 @@ cd my-app
53
53
  npm run dev
54
54
  ```
55
55
 
56
+ ## 🤖 AI Skills in Projects
57
+
58
+ Load or update ODAC skills from your project root with:
59
+
60
+ ```bash
61
+ npx odac skills
62
+ ```
63
+
64
+ This command syncs built-in skills to your selected AI tool folder and can be re-run anytime to update them.
65
+
56
66
  ## 📂 Project Structure
57
67
 
58
68
  ```
package/bin/odac.js CHANGED
@@ -5,6 +5,8 @@ const path = require('node:path')
5
5
  const readline = require('node:readline')
6
6
  const {execSync, spawn} = require('node:child_process')
7
7
  const cluster = require('node:cluster')
8
+ const Env = require('../src/Env')
9
+ const {buildConnections} = require('../src/Database/ConnectionFactory')
8
10
 
9
11
  const command = process.argv[2]
10
12
  const args = process.argv.slice(3)
@@ -208,7 +210,7 @@ async function manageSkills(targetDir = process.cwd()) {
208
210
  }
209
211
 
210
212
  const targetBase = path.resolve(targetDir, targetSubDir)
211
- const targetPath = targetBase
213
+ const targetPath = copySkillsOnly ? path.join(targetBase, 'odac.js') : targetBase
212
214
 
213
215
  try {
214
216
  fs.mkdirSync(targetPath, {recursive: true})
@@ -220,13 +222,196 @@ async function manageSkills(targetDir = process.cwd()) {
220
222
  fs.cpSync(aiSourceDir, targetPath, {recursive: true})
221
223
  }
222
224
 
223
- 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`)
224
227
  console.log('Your AI Agent now has full knowledge of the ODAC Framework. 🚀')
225
228
  } catch (err) {
226
229
  console.error('❌ Failed to sync AI skills:', err.message)
227
230
  }
228
231
  }
229
232
 
233
+ /**
234
+ * Bootstraps the database and migration engine, then executes the requested migration command.
235
+ * Why: Migration commands need DB connections but not the full server stack.
236
+ * @param {string} cmd - The migration subcommand
237
+ * @param {string[]} cliArgs - CLI arguments (e.g. --db=analytics)
238
+ */
239
+ async function runMigration(cmd, cliArgs) {
240
+ const projectDir = process.cwd()
241
+ const envPath = path.join(projectDir, '.env')
242
+ const configPath = path.join(projectDir, 'odac.json')
243
+
244
+ // Load .env
245
+ if (fs.existsSync(envPath)) {
246
+ const envContent = fs.readFileSync(envPath, 'utf8')
247
+ envContent.split('\n').forEach(line => {
248
+ line = line.trim()
249
+ if (!line || line.startsWith('#')) return
250
+ const idx = line.indexOf('=')
251
+ if (idx === -1) return
252
+ const key = line.slice(0, idx).trim()
253
+ const value = Env._parseValue(line.slice(idx + 1).trim())
254
+ if (process.env[key] === undefined) process.env[key] = value
255
+ })
256
+ }
257
+
258
+ // Load config
259
+ if (!fs.existsSync(configPath)) {
260
+ console.error('❌ No odac.json found in current directory.')
261
+ process.exit(1)
262
+ }
263
+
264
+ let config
265
+ try {
266
+ let raw = fs.readFileSync(configPath, 'utf8')
267
+ config = JSON.parse(raw)
268
+ // Interpolate env vars safely by traversing parsed object values.
269
+ const interpolateConfig = input => {
270
+ if (typeof input === 'string') {
271
+ return input.replace(/\$\{([^{}]+)\}/g, (_, key) => process.env[key] || '')
272
+ }
273
+
274
+ if (Array.isArray(input)) {
275
+ return input.map(item => interpolateConfig(item))
276
+ }
277
+
278
+ if (input && typeof input === 'object') {
279
+ const output = {}
280
+ for (const key of Object.keys(input)) {
281
+ output[key] = interpolateConfig(input[key])
282
+ }
283
+ return output
284
+ }
285
+
286
+ return input
287
+ }
288
+
289
+ config = interpolateConfig(config)
290
+ } catch (err) {
291
+ console.error('❌ Failed to parse odac.json:', err.message)
292
+ process.exit(1)
293
+ }
294
+
295
+ const dbConfig = config.database
296
+ if (!dbConfig) {
297
+ console.error('❌ No database configuration found in odac.json.')
298
+ process.exit(1)
299
+ }
300
+
301
+ const connections = buildConnections(dbConfig)
302
+
303
+ for (const key of Object.keys(connections)) {
304
+ try {
305
+ await connections[key].raw('SELECT 1')
306
+ } catch (e) {
307
+ console.error(`❌ Failed to connect to '${key}' database:`, e.message)
308
+ process.exit(1)
309
+ }
310
+ }
311
+
312
+ // Parse --db flag
313
+ const dbFlag = cliArgs.find(a => a.startsWith('--db='))
314
+ const options = dbFlag ? {db: dbFlag.split('=')[1]} : {}
315
+
316
+ // Initialize migration engine
317
+ const Migration = require('../src/Database/Migration')
318
+ Migration.init(projectDir, connections)
319
+
320
+ try {
321
+ if (cmd === 'migrate') {
322
+ console.log('🔄 Running migrations...\n')
323
+ const summary = await Migration.migrate(options)
324
+ printMigrationSummary(summary)
325
+ } else if (cmd === 'migrate:status') {
326
+ console.log('📋 Migration Status (dry-run):\n')
327
+ const summary = await Migration.status(options)
328
+ printMigrationSummary(summary)
329
+ } else if (cmd === 'migrate:rollback') {
330
+ console.log('⏪ Rolling back last batch...\n')
331
+ const summary = await Migration.rollback(options)
332
+ printMigrationSummary(summary)
333
+ } else if (cmd === 'migrate:snapshot') {
334
+ console.log('📸 Generating schema files from database...\n')
335
+ const result = await Migration.snapshot(options)
336
+ for (const [key, files] of Object.entries(result)) {
337
+ console.log(` \x1b[36m${key}\x1b[0m: ${files.length} schema file(s) generated`)
338
+ for (const f of files) {
339
+ console.log(` → ${path.relative(projectDir, f)}`)
340
+ }
341
+ }
342
+ console.log('\n✅ Snapshot complete.')
343
+ }
344
+ } catch (err) {
345
+ console.error('❌ Migration error:', err.message)
346
+ process.exit(1)
347
+ } finally {
348
+ for (const conn of Object.values(connections)) {
349
+ await conn.destroy()
350
+ }
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Formats and prints migration operation summary to stdout.
356
+ * @param {object} summary - Migration result per connection
357
+ */
358
+ function printMigrationSummary(summary) {
359
+ let totalOps = 0
360
+
361
+ for (const [key, result] of Object.entries(summary)) {
362
+ console.log(` \x1b[36m[${key}]\x1b[0m`)
363
+
364
+ const allOps = [...(result.schema || []), ...(result.files || []), ...(result.seeds || [])]
365
+
366
+ if (allOps.length === 0) {
367
+ console.log(' Nothing to do.')
368
+ }
369
+
370
+ for (const op of allOps) {
371
+ totalOps++
372
+ const label = formatOp(op)
373
+ console.log(` ${label}`)
374
+ }
375
+ console.log('')
376
+ }
377
+
378
+ console.log(totalOps > 0 ? `✅ ${totalOps} operation(s) completed.` : '✅ Everything is up to date.')
379
+ }
380
+
381
+ /**
382
+ * Formats a single migration operation for CLI display.
383
+ * @param {object} op - Operation descriptor
384
+ * @returns {string} Formatted label
385
+ */
386
+ function formatOp(op) {
387
+ switch (op.type) {
388
+ case 'create_table':
389
+ return `\x1b[32m+ CREATE TABLE\x1b[0m ${op.table}`
390
+ case 'add_column':
391
+ return `\x1b[32m+ ADD COLUMN\x1b[0m ${op.table}.${op.column}`
392
+ case 'drop_column':
393
+ return `\x1b[31m- DROP COLUMN\x1b[0m ${op.table}.${op.column}`
394
+ case 'alter_column':
395
+ return `\x1b[33m~ ALTER COLUMN\x1b[0m ${op.table}.${op.column}`
396
+ case 'add_index':
397
+ return `\x1b[32m+ ADD INDEX\x1b[0m ${op.table} (${op.index.columns.join(', ')})`
398
+ case 'drop_index':
399
+ return `\x1b[31m- DROP INDEX\x1b[0m ${op.table} (${op.index.columns.join(', ')})`
400
+ case 'pending_file':
401
+ return `\x1b[33m⏳ PENDING\x1b[0m ${op.name}`
402
+ case 'applied_file':
403
+ return `\x1b[32m✓ APPLIED\x1b[0m ${op.name}`
404
+ case 'rolled_back':
405
+ return `\x1b[33m↩ ROLLED BACK\x1b[0m ${op.name}`
406
+ case 'seed_insert':
407
+ return `\x1b[32m+ SEED INSERT\x1b[0m ${op.table} (${op.key})`
408
+ case 'seed_update':
409
+ return `\x1b[33m~ SEED UPDATE\x1b[0m ${op.table} (${op.key})`
410
+ default:
411
+ return ` ${op.type}`
412
+ }
413
+ }
414
+
230
415
  async function run() {
231
416
  if (command === 'init') {
232
417
  const projectName = args[0] || '.'
@@ -376,6 +561,8 @@ async function run() {
376
561
  require('../index.js')
377
562
  } else if (command === 'skills') {
378
563
  await manageSkills()
564
+ } else if (command === 'migrate' || command === 'migrate:status' || command === 'migrate:rollback' || command === 'migrate:snapshot') {
565
+ await runMigration(command, args)
379
566
  } else {
380
567
  console.log('Usage:')
381
568
  console.log(' npx odac init (Interactive mode)')
@@ -384,6 +571,10 @@ async function run() {
384
571
  console.log(' npx odac build (Production build)')
385
572
  console.log(' npx odac start (Start server)')
386
573
  console.log(' npx odac skills (Sync AI Agent skills)')
574
+ console.log(' npx odac migrate (Run pending migrations)')
575
+ console.log(' npx odac migrate:status (Show pending changes)')
576
+ console.log(' npx odac migrate:rollback (Rollback last batch)')
577
+ console.log(' npx odac migrate:snapshot (Reverse-engineer DB to schema/)')
387
578
  }
388
579
 
389
580
  rl.close()
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()
@@ -1,8 +1,8 @@
1
1
  ---
2
- name: odac-framework
3
- description: Comprehensive AI developer skills for the ODAC Framework (Backend & Frontend).
2
+ name: odac-framework-skill-catalog
3
+ description: Comprehensive ODAC backend and frontend AI skill index for architecture, security, and high-performance application development.
4
4
  metadata:
5
- tags: nodejs, framework, backend, frontend, architecture, security
5
+ tags: odac, skills, backend, frontend, architecture, security, performance
6
6
  ---
7
7
 
8
8
  ## When to use
@@ -22,6 +22,7 @@ Read the specific rule files based on whether you are working on the Backend or
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
25
+ - [backend/migrations.md](backend/migrations.md) - Schema-first, auto-run, cluster-safe DB migrations
25
26
  - [backend/request_response.md](backend/request_response.md) - Handling Odac.Request and Odac.Response
26
27
  - [backend/routing.md](backend/routing.md) - Route definitions, Middlewares, and Error Pages
27
28
  - [backend/storage.md](backend/storage.md) - Persistent key-value storage (LMDB)
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: backend-authentication-realtime-skill
3
+ description: Secure ODAC authentication patterns for sessions, guards, passwordless flows, and realtime channel protection.
4
+ metadata:
5
+ tags: backend, authentication, session, auth-guard, magic-link, realtime, security
6
+ ---
7
+
1
8
  # Backend Authentication & Realtime Skill
2
9
 
3
10
  Secure user authentication, session management, and bidirectional communication.
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: backend-configuration-skill
3
+ description: ODAC configuration standards for odac.json usage, environment variable mapping, and secure runtime settings.
4
+ metadata:
5
+ tags: backend, configuration, odac-json, environment, secrets, settings
6
+ ---
7
+
1
8
  # Backend Configuration Skill
2
9
 
3
10
  Managing application settings using `odac.json` and environment variables.
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: backend-controllers-skill
3
+ description: Best practices for ODAC controller architecture, class-based routing, and clean request handling boundaries.
4
+ metadata:
5
+ tags: backend, controllers, class-based, route-mapping, architecture, maintainability
6
+ ---
7
+
1
8
  # Backend Controllers Skill
2
9
 
3
10
  Controllers are the bridge between routes and views/services. This skill covers how to write clean, professional controllers in ODAC.
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: backend-cron-jobs-skill
3
+ description: Reliable ODAC cron scheduling patterns for background automation, timing control, and safe execution design.
4
+ metadata:
5
+ tags: backend, cron, scheduler, background-jobs, automation, timing
6
+ ---
7
+
1
8
  # Backend Cron Jobs Skill
2
9
 
3
10
  ODAC provides a built-in cron system for automating background tasks without external dependencies.
@@ -21,14 +28,14 @@ Odac.Route.cron('cleanup').at('03:00'); // Runs daily at 03:00
21
28
  // controller/cron/cleanup.js
22
29
  module.exports = async (Odac) => {
23
30
  console.log('Running nightly cleanup...');
24
- await Odac.Db.table('logs').where('created_at', '<', 'NOW() - INTERVAL 30 DAY').delete();
31
+ await Odac.DB.table('logs').where('created_at', '<', 'NOW() - INTERVAL 30 DAY').delete();
25
32
  };
26
33
  ```
27
34
 
28
35
  ### 2. Inline Function Cron
29
36
  ```javascript
30
37
  Odac.Route.cron(async () => {
31
- const stats = await Odac.Db.table('orders').count();
38
+ const stats = await Odac.DB.table('orders').count();
32
39
  console.log('Current orders:', stats);
33
40
  }).everyHour(1);
34
41
  ```
@@ -1,3 +1,10 @@
1
+ ---
2
+ name: backend-database-skill
3
+ description: High-performance ODAC database querying patterns using the built-in query builder with secure and efficient data access.
4
+ metadata:
5
+ tags: backend, database, query-builder, sql, indexing, performance, security
6
+ ---
7
+
1
8
  # Backend Database Skill
2
9
 
3
10
  High-performance database operations using the ODAC Query Builder.
@@ -9,13 +16,41 @@ High-performance database operations using the ODAC Query Builder.
9
16
 
10
17
  ## Patterns
11
18
  ```javascript
12
- const user = await Odac.Db.table('users')
19
+ const user = await Odac.DB.table('users')
13
20
  .select('id', 'name', 'email')
14
21
  .where('status', 'active')
15
22
  .first();
16
23
 
17
- await Odac.Db.table('posts').insert({
24
+ await Odac.DB.table('posts').insert({
18
25
  title: 'Hello',
19
26
  user_id: 1
20
27
  });
21
28
  ```
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
+
49
+ ## Migration Awareness
50
+ 1. **Schema-First**: Structural DB changes must be defined in `schema/*.js`.
51
+ 2. **Auto-Migrate**: Migrations run automatically at startup from `Database.init()`.
52
+ 3. **Cluster-Safe**: Migration execution is limited to primary process (`cluster.isPrimary`).
53
+ 4. **Indexes**: Keep index definitions in schema so add/drop is managed automatically.
54
+ 5. **Data Changes**: Use `migration/*.js` only for one-time data transformation.
55
+
56
+ See: [migrations.md](./migrations.md)