odac 1.1.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +42 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/release.yml +68 -1
  6. package/.github/workflows/test-coverage.yml +6 -5
  7. package/.husky/pre-commit +10 -0
  8. package/.husky/pre-push +13 -0
  9. package/.releaserc.js +1 -1
  10. package/CHANGELOG.md +99 -0
  11. package/README.md +16 -0
  12. package/bin/odac.js +196 -38
  13. package/client/odac.js +238 -171
  14. package/docs/backend/01-overview/03-development-server.md +38 -45
  15. package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
  16. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  17. package/docs/backend/03-config/01-database-connection.md +2 -2
  18. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  19. package/docs/backend/03-config/03-request-timeout.md +1 -1
  20. package/docs/backend/03-config/04-environment-variables.md +4 -4
  21. package/docs/backend/03-config/05-early-hints.md +2 -2
  22. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  23. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  24. package/docs/backend/04-routing/09-websocket.md +14 -1
  25. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  26. package/docs/backend/05-controllers/03-controller-classes.md +40 -20
  27. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  28. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  29. package/docs/backend/08-database/01-getting-started.md +2 -2
  30. package/docs/backend/10-authentication/03-register.md +1 -1
  31. package/docs/backend/10-authentication/04-odac-register-forms.md +2 -2
  32. package/docs/backend/10-authentication/05-session-management.md +15 -1
  33. package/docs/backend/10-authentication/06-odac-login-forms.md +2 -2
  34. package/docs/backend/10-authentication/07-magic-links.md +1 -1
  35. package/docs/index.json +5 -1
  36. package/jest.config.js +1 -1
  37. package/package.json +18 -14
  38. package/src/Auth.js +58 -23
  39. package/src/Config.js +7 -7
  40. package/src/Database.js +1 -1
  41. package/src/Env.js +3 -1
  42. package/src/Ipc.js +7 -0
  43. package/src/Lang.js +9 -2
  44. package/src/Mail.js +19 -9
  45. package/src/Odac.js +56 -44
  46. package/src/Request.js +6 -2
  47. package/src/Route/Cron.js +58 -17
  48. package/src/Route/Internal.js +1 -1
  49. package/src/Route.js +375 -125
  50. package/src/Server.js +40 -3
  51. package/src/Storage.js +4 -0
  52. package/src/Token.js +6 -4
  53. package/src/Validator.js +24 -15
  54. package/src/Var.js +22 -6
  55. package/src/View/EarlyHints.js +43 -33
  56. package/src/View/Form.js +17 -11
  57. package/src/View.js +68 -12
  58. package/src/WebSocket.js +45 -12
  59. package/template/package.json +3 -1
  60. package/template/view/content/home.html +3 -3
  61. package/template/view/head/main.html +2 -2
  62. package/test/Client.test.js +168 -0
  63. package/test/Config.test.js +112 -0
  64. package/test/Lang.test.js +92 -0
  65. package/test/Odac.test.js +88 -0
  66. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  67. package/test/{framework/Route.test.js → Route.test.js} +105 -1
  68. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  69. package/test/WebSocket.test.js +238 -0
  70. package/test/scripts/check-coverage.js +4 -4
  71. package/test/cli/Cli.test.js +0 -36
  72. package/test/core/Commands.test.js +0 -538
  73. package/test/core/Config.test.js +0 -1432
  74. package/test/core/Lang.test.js +0 -250
  75. package/test/core/Odac.test.js +0 -234
  76. package/test/core/Process.test.js +0 -156
  77. package/test/framework/WebSocket.test.js +0 -100
  78. package/test/server/Api.test.js +0 -647
  79. package/test/server/DNS.test.js +0 -2050
  80. package/test/server/DNS.test.js.bak +0 -2084
  81. package/test/server/Hub.test.js +0 -497
  82. package/test/server/Log.test.js +0 -73
  83. package/test/server/Mail.account.test_.js +0 -460
  84. package/test/server/Mail.init.test_.js +0 -411
  85. package/test/server/Mail.test_.js +0 -1340
  86. package/test/server/SSL.test_.js +0 -1491
  87. package/test/server/Server.test.js +0 -765
  88. package/test/server/Service.test_.js +0 -1127
  89. package/test/server/Subdomain.test.js +0 -440
  90. package/test/server/Web/Firewall.test.js +0 -175
  91. package/test/server/Web/Proxy.test.js +0 -397
  92. package/test/server/Web.test.js +0 -1494
  93. package/test/server/__mocks__/acme-client.js +0 -17
  94. package/test/server/__mocks__/bcrypt.js +0 -50
  95. package/test/server/__mocks__/child_process.js +0 -389
  96. package/test/server/__mocks__/crypto.js +0 -432
  97. package/test/server/__mocks__/fs.js +0 -450
  98. package/test/server/__mocks__/globalOdac.js +0 -227
  99. package/test/server/__mocks__/http.js +0 -575
  100. package/test/server/__mocks__/https.js +0 -272
  101. package/test/server/__mocks__/index.js +0 -249
  102. package/test/server/__mocks__/mail/server.js +0 -100
  103. package/test/server/__mocks__/mail/smtp.js +0 -31
  104. package/test/server/__mocks__/mailparser.js +0 -81
  105. package/test/server/__mocks__/net.js +0 -369
  106. package/test/server/__mocks__/node-forge.js +0 -328
  107. package/test/server/__mocks__/os.js +0 -320
  108. package/test/server/__mocks__/path.js +0 -291
  109. package/test/server/__mocks__/selfsigned.js +0 -8
  110. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  111. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  112. package/test/server/__mocks__/smtp-server.js +0 -106
  113. package/test/server/__mocks__/sqlite3.js +0 -394
  114. package/test/server/__mocks__/testFactories.js +0 -299
  115. package/test/server/__mocks__/testHelpers.js +0 -363
  116. package/test/server/__mocks__/tls.js +0 -229
  117. /package/template/{config.json → odac.json} +0 -0
@@ -0,0 +1,93 @@
1
+ # 🎨 Styling & Tailwind CSS
2
+
3
+ Odac comes with built-in, **Zero-Config** support for Tailwind CSS v4. This means you can start building beautiful, modern interfaces right out of the box without messing with configuration files or build pipelines.
4
+
5
+ ## How it Works
6
+
7
+ The framework adopts a "Convention over Configuration" approach:
8
+
9
+ 1. **Development (`npm run dev`)**:
10
+ * The framework automatically watches your HTML, JS, and View files.
11
+ * It compiles your CSS classes using the high-performance Rust-based Tailwind CLI.
12
+ * Changes are reflected instantly.
13
+
14
+ 2. **Production (`npm run build`)**:
15
+ * The framework scans your project, builds the final CSS, and minifies it.
16
+ * The output is saved to `public/assets/css/app.css`.
17
+
18
+ 3. **Serving (`npm start`)**:
19
+ * The compiled CSS file is served statically. No background processes, no overhead.
20
+
21
+ ## Customizing CSS
22
+
23
+ By default, Odac manages everything for you internally. However, if you need to add custom CSS, fonts, or Tailwind configuration (like `@theme`), you can do so easily.
24
+
25
+ ### The Source File
26
+
27
+ Simply create a file at **`view/css/app.css`**.
28
+
29
+ If this file exists, Odac will use it as the **source** (input) for Tailwind.
30
+
31
+ **Example `view/css/app.css`:**
32
+
33
+ ```css
34
+ @import "tailwindcss";
35
+
36
+ @theme {
37
+ --font-display: "Satoshi", "sans-serif";
38
+ --color-brand: #ff5733;
39
+ }
40
+
41
+ /* Your custom CSS rules */
42
+ .hero-gradient {
43
+ background: linear-gradient(to right, var(--color-brand), #ff0000);
44
+ }
45
+ ```
46
+
47
+ ### The Output File
48
+
49
+ The compiled CSS is always output to:
50
+ **`public/assets/css/app.css`**
51
+
52
+ > **⚠️ Important:** Never edit `public/assets/css/app.css` manually. It is a generated file and will be overwritten by Odac during build or development. Always edit `view/css/app.css` instead.
53
+
54
+ ## HTML Integration
55
+
56
+ In your layout files (e.g., `view/head/main.html`), simply link to the compiled asset:
57
+
58
+ ```html
59
+ <link rel="stylesheet" href="/assets/css/app.css" />
60
+ ```
61
+
62
+ This is already set up for you in the default project template.
63
+
64
+ ## Multiple CSS Files
65
+
66
+ Odac supports multiple CSS entry points. If your application has distinct sections (e.g., a Landing Page, a Dashboard, and an Admin Panel) that require separate stylesheets, you can organize them easily.
67
+
68
+ ### How to use
69
+
70
+ Any `.css` file you place in the **`view/css/`** directory will be automatically detected, watched, and compiled by Odac.
71
+
72
+ **Input:**
73
+ * `view/css/app.css`
74
+ * `view/css/admin.css`
75
+ * `view/css/landing.css`
76
+
77
+ **Output (Compiled):**
78
+ * `public/assets/css/app.css`
79
+ * `public/assets/css/admin.css`
80
+ * `public/assets/css/landing.css`
81
+
82
+ You can then link each specific stylesheet in its respective layout file:
83
+
84
+ ```html
85
+ <!-- In Admin Layout -->
86
+ <link rel="stylesheet" href="/assets/css/admin.css" />
87
+ ```
88
+
89
+ ## Using Tailwind v4
90
+
91
+ Tailwind v4 is radically simpler. You generally don't need a `tailwind.config.js` file anymore. You can define your theme directly in CSS using the `@theme` block as shown above.
92
+
93
+ However, Odac respects standard Tailwind behavior. If you absolutely need a config file for plugins or legacy reasons, you can create one, and the Tailwind CLI will detect it. But for 99% of use cases, the Zero-Config approach is cleaner and faster.
@@ -4,7 +4,7 @@ ODAC supports multiple database connections including **MySQL**, **PostgreSQL**
4
4
 
5
5
  ## Configuration
6
6
 
7
- Add your database credentials to `config.json`.
7
+ Add your database credentials to `odac.json`.
8
8
 
9
9
  Supported configuration options:
10
10
 
@@ -79,7 +79,7 @@ DB_USER=myuser
79
79
  DB_PASSWORD=mypassword
80
80
  ```
81
81
 
82
- **config.json:**
82
+ **odac.json:**
83
83
  ```json
84
84
  {
85
85
  "database": {
@@ -113,7 +113,7 @@ module.exports = async function (Odac) {
113
113
 
114
114
  ## Configuration
115
115
 
116
- Make sure your `config.json` has the auth configuration:
116
+ Make sure your `odac.json` has the auth configuration:
117
117
 
118
118
  ```json
119
119
  {
@@ -4,7 +4,7 @@ The `<odac:register>` component provides a zero-configuration way to create secu
4
4
 
5
5
  ## Quick Start
6
6
 
7
- ### 1. Configure Database (config.json)
7
+ ### 1. Configure Database (odac.json)
8
8
 
9
9
  ```json
10
10
  {
@@ -653,7 +653,7 @@ Then handle the response in JavaScript if needed (though not required for basic
653
653
 
654
654
  ### Form Not Submitting
655
655
 
656
- - Check that `config.json` has auth configuration
656
+ - Check that `odac.json` has auth configuration
657
657
  - Verify database table exists
658
658
  - Check browser console for JavaScript errors
659
659
 
@@ -31,7 +31,7 @@ Sessions use a **sliding window** approach (similar to NextAuth.js):
31
31
 
32
32
  ### Configuration
33
33
 
34
- Configure session behavior in `config.json`:
34
+ Configure session behavior in `odac.json`:
35
35
 
36
36
  ```json
37
37
  {
@@ -127,9 +127,23 @@ const isLoggedIn = await Odac.Auth.check()
127
127
  **Get user info:**
128
128
  ```javascript
129
129
  const user = Odac.Auth.user(null) // Full user object
130
+ const user = Odac.Auth.user(null) // Full user object
130
131
  const email = Odac.Auth.user('email') // Specific field
131
132
  ```
132
133
 
134
+ ### Custom Session Data
135
+
136
+ If you need to store your own data in the session (e.g. shopping cart ID, preferences), use the `Odac.session()` helper:
137
+
138
+ ```javascript
139
+ // Store data
140
+ Odac.session('theme', 'dark')
141
+
142
+ // Retrieve data
143
+ const theme = Odac.session('theme')
144
+ ```
145
+
146
+
133
147
  ### Best Practices
134
148
 
135
149
  1. **Choose appropriate timeouts** based on your app's security needs
@@ -4,7 +4,7 @@ The `<odac:login>` component provides a zero-configuration way to create secure
4
4
 
5
5
  ## Quick Start
6
6
 
7
- ### 1. Configure Database (config.json)
7
+ ### 1. Configure Database (odac.json)
8
8
 
9
9
  ```json
10
10
  {
@@ -559,7 +559,7 @@ The login form supports multiple authentication methods:
559
559
 
560
560
  ### Form Not Submitting
561
561
 
562
- - Check that `config.json` has MySQL configuration
562
+ - Check that `odac.json` has MySQL configuration
563
563
  - Verify database table exists
564
564
  - Check browser console for JavaScript errors
565
565
  - Ensure CSRF token is valid
@@ -79,7 +79,7 @@ Odac handles the verification route automatically at `/_odac/magic-verify`. Howe
79
79
 
80
80
  ## Configuration
81
81
 
82
- Magic links usage is configured in your `config.json` file under the `auth` object.
82
+ Magic links usage is configured in your `odac.json` file under the `auth` object.
83
83
 
84
84
  ```json
85
85
  {
package/docs/index.json CHANGED
@@ -14,7 +14,7 @@
14
14
  },
15
15
  {
16
16
  "file": "03-development-server.md",
17
- "title": "Development Server"
17
+ "title": "CLI Commands & Deployment"
18
18
  }
19
19
  ]
20
20
  },
@@ -201,6 +201,10 @@
201
201
  {
202
202
  "file": "09-comments.md",
203
203
  "title": "Comments"
204
+ },
205
+ {
206
+ "file": "10-styling-and-tailwind.md",
207
+ "title": "Styling & Tailwind CSS"
204
208
  }
205
209
  ]
206
210
  },
package/jest.config.js CHANGED
@@ -5,7 +5,7 @@ const config = {
5
5
  testMatch: ['**/test/**/*.test.js', '**/?(*.)+(spec|test).js'],
6
6
  collectCoverage: true,
7
7
  coverageDirectory: 'coverage',
8
- collectCoverageFrom: ['core/**/*.js', 'server/**/*.js']
8
+ collectCoverageFrom: ['src/**/*.js', 'client/**/*.js']
9
9
  // Coverage thresholds disabled - focus on test pass/fail only
10
10
  // Coverage is still collected and reported, but doesn't block commits
11
11
  // This allows incremental test development without artificial barriers
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "email": "mail@emre.red",
8
8
  "url": "https://emre.red"
9
9
  },
10
- "version": "1.1.0",
10
+ "version": "1.3.0",
11
11
  "license": "MIT",
12
12
  "engines": {
13
13
  "node": ">=18.0.0"
@@ -21,32 +21,36 @@
21
21
  "url": "https://github.com/odac-run/odac.js.git"
22
22
  },
23
23
  "dependencies": {
24
- "axios": "^1.7.9",
25
- "bcrypt": "^5.1.1",
24
+ "@tailwindcss/cli": "^4.1.18",
26
25
  "knex": "^3.1.0",
27
26
  "lmdb": "^3.4.4",
28
27
  "mysql2": "^3.16.0",
29
28
  "pg": "^8.16.3",
30
- "redis": "^5.10.0"
29
+ "redis": "^5.10.0",
30
+ "tailwindcss": "^4.1.18"
31
+ },
32
+ "overrides": {
33
+ "tar": "7.5.9",
34
+ "cross-spawn": "7.0.6"
31
35
  },
32
36
  "devDependencies": {
33
- "@eslint/js": "^9.33.0",
37
+ "@eslint/js": "^9.39.2",
34
38
  "@semantic-release/changelog": "^6.0.3",
35
39
  "@semantic-release/commit-analyzer": "^13.0.1",
36
40
  "@semantic-release/git": "^10.0.1",
37
- "@semantic-release/github": "^12.0.2",
38
- "@semantic-release/npm": "^13.1.3",
41
+ "@semantic-release/github": "^12.0.6",
42
+ "@semantic-release/npm": "^13.1.4",
39
43
  "@semantic-release/release-notes-generator": "^14.1.0",
40
44
  "conventional-changelog-conventionalcommits": "^9.1.0",
41
- "eslint": "^9.33.0",
45
+ "eslint": "^9.39.2",
42
46
  "eslint-config-prettier": "^10.1.8",
43
- "eslint-plugin-prettier": "^5.5.4",
44
- "globals": "^16.3.0",
47
+ "eslint-plugin-prettier": "^5.5.5",
48
+ "globals": "^17.3.0",
45
49
  "husky": "^9.1.7",
46
- "jest": "^30.0.5",
47
- "lint-staged": "^16.1.5",
48
- "prettier": "3.6.2",
49
- "semantic-release": "^25.0.2",
50
+ "jest": "^30.2.0",
51
+ "lint-staged": "^16.2.7",
52
+ "prettier": "^3.8.1",
53
+ "semantic-release": "^25.0.3",
50
54
  "sqlite3": "^5.1.7"
51
55
  },
52
56
  "scripts": {
package/src/Auth.js CHANGED
@@ -35,29 +35,54 @@ class Auth {
35
35
 
36
36
  // Knex build queries differently than previous builder
37
37
  // Need to chain where clauses
38
- for (let key in where) {
39
- query = query.orWhere(key, where[key] instanceof Promise ? await where[key] : where[key])
38
+ // Resolve input promises upfront to avoid side effects and race conditions
39
+ const criteria = {}
40
+ const keys = Object.keys(where)
41
+
42
+ if (keys.length === 0) return false
43
+
44
+ for (const key of keys) {
45
+ criteria[key] = where[key] instanceof Promise ? await where[key] : where[key]
46
+ }
47
+
48
+ // Chain where clauses
49
+ for (const key in criteria) {
50
+ query = query.orWhere(key, criteria[key])
40
51
  }
41
52
 
42
53
  // Execute query
43
- let get = await query
44
-
45
- if (!get || get.length === 0) return false
46
-
47
- let equal = false
48
- for (var user of get) {
49
- equal = Object.keys(where).length > 0
50
- for (let key of Object.keys(where)) {
51
- if (where[key] instanceof Promise) where[key] = await where[key]
52
- if (!user[key]) equal = false
53
- if (user[key] === where[key]) equal = equal && true
54
- else if (Odac.Var(user[key]).is('bcrypt')) equal = equal && Odac.Var(user[key]).hashCheck(where[key])
55
- else if (Odac.Var(user[key]).is('md5')) equal = equal && Odac.Var(where[key]).md5() === user[key]
54
+ const candidates = await query
55
+
56
+ if (!candidates || candidates.length === 0) return false
57
+
58
+ // Iterate candidates to find the exact match
59
+ candidateLoop: for (const user of candidates) {
60
+ for (const key of keys) {
61
+ const userValue = user[key]
62
+ const targetValue = criteria[key]
63
+
64
+ if (!userValue) continue candidateLoop
65
+
66
+ // Strict equality check
67
+ if (userValue === targetValue) continue
68
+
69
+ // Security: Check hashed fields (Bcrypt/MD5)
70
+ const valueHandler = Odac.Var(userValue)
71
+ let hashMatch = false
72
+
73
+ if (valueHandler.is('hash')) {
74
+ hashMatch = valueHandler.hashCheck(targetValue)
75
+ } else if (valueHandler.is('md5')) {
76
+ hashMatch = Odac.Var(targetValue).md5() === userValue
77
+ }
78
+
79
+ if (!hashMatch) continue candidateLoop
56
80
  }
57
- if (equal) break
81
+
82
+ return user
58
83
  }
59
- if (!equal) return false
60
- return user
84
+
85
+ return false
61
86
  } else if (this.#user) {
62
87
  return true
63
88
  } else {
@@ -126,12 +151,15 @@ class Auth {
126
151
 
127
152
  this.#cleanupExpiredTokens(token)
128
153
 
129
- let token_y = Odac.Var(Math.random().toString() + Date.now().toString() + this.#request.id + this.#request.ip).md5()
154
+ // Generate secure token using generic CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
155
+ // Why: Math.random() is predictable and MD5 is a broken hashing algorithm.
156
+ // We use 32 bytes (256 bits) of entropy which is industry standard.
157
+ let token_y = nodeCrypto.randomBytes(32).toString('hex')
130
158
 
131
159
  let cookie = {
132
160
  id: Odac.DB.nanoid(),
133
161
  user: user[key],
134
- token_x: Odac.Var(Math.random().toString() + Date.now().toString()).md5(),
162
+ token_x: nodeCrypto.randomBytes(32).toString('hex'),
135
163
  token_y: Odac.Var(token_y).hash(),
136
164
  browser: this.#request.header('user-agent'),
137
165
  ip: this.#request.ip
@@ -182,7 +210,7 @@ class Auth {
182
210
  return {success: false, error: 'Invalid data provided'}
183
211
  }
184
212
 
185
- if (data[passwordField] && !Odac.Var(data[passwordField]).is('bcrypt')) {
213
+ if (data[passwordField] && !Odac.Var(data[passwordField]).is('hash')) {
186
214
  data[passwordField] = Odac.Var(data[passwordField]).hash()
187
215
  }
188
216
 
@@ -557,10 +585,17 @@ class Auth {
557
585
  })
558
586
  }
559
587
 
560
- user(col) {
588
+ /**
589
+ * Retrieves the authenticated user or a specific column.
590
+ * Why: To provide access to the current user's session data securely.
591
+ *
592
+ * @param {string|null} [col=null] - The column to retrieve, or null for the full user object.
593
+ * @returns {object|string|number|boolean|false} The user object, column value, or false if not logged in.
594
+ */
595
+ user(col = null) {
561
596
  if (!this.#user) return false
562
597
  if (col === null) return this.#user
563
- else return this.#user[col]
598
+ return this.#user[col]
564
599
  }
565
600
  }
566
601
 
package/src/Config.js CHANGED
@@ -5,13 +5,13 @@ const os = require('os')
5
5
  module.exports = {
6
6
  auth: {
7
7
  key: 'id',
8
- token: 'odac_auth'
8
+ token: 'odac_auth' // This is the TABLE NAME for tokens, not a secret token.
9
9
  },
10
10
  request: {
11
11
  timeout: 10000
12
12
  },
13
13
  encrypt: {
14
- key: 'odac'
14
+ key: 'odac' // Default encryption key. MUST be overridden in production.
15
15
  },
16
16
  earlyHints: {
17
17
  enabled: true,
@@ -22,7 +22,7 @@ module.exports = {
22
22
  driver: 'memory',
23
23
  redis: 'default'
24
24
  },
25
- debug: false,
25
+ debug: process.env.NODE_ENV !== 'production',
26
26
 
27
27
  init: function () {
28
28
  try {
@@ -31,17 +31,17 @@ module.exports = {
31
31
  this.system = {}
32
32
  }
33
33
 
34
- if (fs.existsSync(__dir + '/config.json')) {
34
+ if (fs.existsSync(__dir + '/odac.json')) {
35
35
  let config = {}
36
36
  try {
37
- config = JSON.parse(fs.readFileSync(__dir + '/config.json'))
37
+ config = JSON.parse(fs.readFileSync(__dir + '/odac.json'))
38
38
  config = this._interpolate(config)
39
39
  } catch (err) {
40
- console.error('Error reading config file:', __dir + '/config.json', err.message)
40
+ console.error('Error reading config file:', __dir + '/odac.json', err.message)
41
41
  }
42
42
  this._deepMerge(this, config)
43
43
  }
44
- this.encrypt.key = nodeCrypto.createHash('md5').update(this.encrypt.key).digest('hex')
44
+ this.encrypt.key = nodeCrypto.createHash('sha256').update(this.encrypt.key).digest()
45
45
  },
46
46
 
47
47
  _interpolate: function (obj) {
package/src/Database.js CHANGED
@@ -18,7 +18,7 @@ class DatabaseManager {
18
18
  if (db.type === 'postgres' || db.type === 'pg' || db.type === 'postgresql') client = 'pg'
19
19
  if (db.type === 'sqlite' || db.type === 'sqlite3') client = 'sqlite3'
20
20
 
21
- let connectionConfig = {}
21
+ let connectionConfig
22
22
 
23
23
  if (client === 'sqlite3') {
24
24
  connectionConfig = {
package/src/Env.js CHANGED
@@ -20,7 +20,9 @@ module.exports = {
20
20
  // Parse quoted values
21
21
  value = this._parseValue(value)
22
22
 
23
- process.env[key] = value
23
+ if (process.env[key] === undefined) {
24
+ process.env[key] = value
25
+ }
24
26
  })
25
27
  } catch (err) {
26
28
  console.error('Error reading .env file:', err.message)
package/src/Ipc.js CHANGED
@@ -10,6 +10,13 @@ class Ipc extends EventEmitter {
10
10
  this._subs = new Map() // For memory driver subscriptions
11
11
  }
12
12
 
13
+ /**
14
+ * ARCHITECTURE NOTE:
15
+ * This module implements a "Primary-Replica" pattern for the 'memory' driver.
16
+ * - The Primary process holds the 'Source of Truth' in local Maps.
17
+ * - Workers communicate via IPC (process.send) to read/write to this central store.
18
+ * This ensures state consistency across the cluster without needing Redis.
19
+ */
13
20
  async init() {
14
21
  if (this.initialized) return
15
22
  this.initialized = true
package/src/Lang.js CHANGED
@@ -43,9 +43,16 @@ class Lang {
43
43
 
44
44
  set(lang) {
45
45
  if (!lang || lang.length !== 2 || !this.#odac.Var(lang).is('alpha')) {
46
- if (this.#odac.Request.header('ACCEPT-LANGUAGE') && this.#odac.Request.header('ACCEPT-LANGUAGE').length > 1)
46
+ if (
47
+ this.#odac.Request &&
48
+ this.#odac.Request.header &&
49
+ this.#odac.Request.header('ACCEPT-LANGUAGE') &&
50
+ this.#odac.Request.header('ACCEPT-LANGUAGE').length > 1
51
+ ) {
47
52
  lang = this.#odac.Request.header('ACCEPT-LANGUAGE').substr(0, 2)
48
- else lang = this.#odac.Config.lang?.default || 'en'
53
+ } else {
54
+ lang = this.#odac.Config.lang?.default || 'en'
55
+ }
49
56
  }
50
57
  this.#lang = lang
51
58
  if (fs.existsSync(__dir + '/storage/language/' + lang + '.json'))
package/src/Mail.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const net = require('net')
2
2
  const nodeCrypto = require('crypto')
3
3
  const fs = require('fs')
4
+ const fsPromises = fs.promises
4
5
  // const Form = require('./View/Form')
5
6
 
6
7
  const CACHE_DIR = './storage/.cache'
@@ -155,13 +156,14 @@ class Mail {
155
156
  }
156
157
 
157
158
  async #render(file, data) {
158
- const fd = fs.openSync(file, 'r')
159
159
  let mtime, content
160
+ const handle = await fsPromises.open(file, 'r')
160
161
  try {
161
- mtime = fs.fstatSync(fd).mtimeMs
162
- content = fs.readFileSync(fd, 'utf8')
162
+ const stats = await handle.stat()
163
+ mtime = stats.mtimeMs
164
+ content = await handle.readFile('utf8')
163
165
  } finally {
164
- fs.closeSync(fd)
166
+ await handle.close()
165
167
  }
166
168
 
167
169
  // Since mail doesn't have a persistent Odac instance access like View cache, we manage a simple cache or just re-compile.
@@ -251,8 +253,8 @@ class Mail {
251
253
  }
252
254
 
253
255
  let cache = `${nodeCrypto.createHash('md5').update(file).digest('hex')}`
254
- if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, {recursive: true})
255
- fs.writeFileSync(
256
+ await fsPromises.mkdir(CACHE_DIR, {recursive: true})
257
+ await fsPromises.writeFile(
256
258
  `${CACHE_DIR}/${cache}`,
257
259
  `module.exports = async (Odac, data, get, __) => {\n
258
260
  // Destructure data keys into local scope variables
@@ -351,7 +353,9 @@ class Mail {
351
353
  let textContent = ''
352
354
 
353
355
  if (this.#template) {
354
- if (!fs.existsSync(__dir + '/view/mail/' + this.#template + '.html')) {
356
+ try {
357
+ await fsPromises.access(__dir + '/view/mail/' + this.#template + '.html')
358
+ } catch {
355
359
  console.error(`[Mail] Template not found: ${__dir}/view/mail/${this.#template}.html`)
356
360
  return resolve(false)
357
361
  }
@@ -409,7 +413,7 @@ class Mail {
409
413
  ]
410
414
  }
411
415
 
412
- const socketPath = process.env.ODAC_API_SOCKET || '/var/run/odac.sock'
416
+ const socketPath = process.env.ODAC_API_SOCKET || '/odac/api.sock'
413
417
 
414
418
  if (Odac.Config.debug) console.log(`[Mail] Connecting to Odac Core via Unix Socket: ${socketPath}...`)
415
419
 
@@ -431,7 +435,13 @@ class Mail {
431
435
  })
432
436
 
433
437
  client.on('error', error => {
434
- console.error('[Mail] Socket Error:', error)
438
+ if (error.code === 'ENOENT' && error.address === socketPath) {
439
+ console.error(
440
+ '[Mail] Socket Error: If you are using ODAC, you must grant permissions or enter SMTP information in the config.'
441
+ )
442
+ } else {
443
+ console.error('[Mail] Socket Error:', error)
444
+ }
435
445
  resolve(false)
436
446
  })
437
447