odac 1.1.0 → 1.2.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 (113) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +33 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/release.yml +42 -1
  6. package/.github/workflows/test-coverage.yml +6 -5
  7. package/.github/workflows/test-publish.yml +36 -0
  8. package/.husky/pre-commit +10 -0
  9. package/.husky/pre-push +13 -0
  10. package/.releaserc.js +3 -3
  11. package/CHANGELOG.md +67 -0
  12. package/README.md +16 -0
  13. package/bin/odac.js +182 -40
  14. package/client/odac.js +10 -4
  15. package/docs/backend/01-overview/03-development-server.md +38 -45
  16. package/docs/backend/02-structure/01-typical-project-layout.md +59 -26
  17. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  18. package/docs/backend/03-config/01-database-connection.md +2 -2
  19. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  20. package/docs/backend/03-config/03-request-timeout.md +1 -1
  21. package/docs/backend/03-config/04-environment-variables.md +4 -4
  22. package/docs/backend/03-config/05-early-hints.md +2 -2
  23. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  24. package/docs/backend/04-routing/07-cron-jobs.md +17 -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 +9 -5
  38. package/src/Auth.js +58 -23
  39. package/src/Config.js +7 -7
  40. package/src/Env.js +3 -1
  41. package/src/Ipc.js +7 -0
  42. package/src/Lang.js +9 -2
  43. package/src/Odac.js +44 -35
  44. package/src/Request.js +1 -1
  45. package/src/Route/Cron.js +58 -17
  46. package/src/Route/Internal.js +1 -1
  47. package/src/Route.js +282 -99
  48. package/src/Server.js +40 -3
  49. package/src/Storage.js +4 -0
  50. package/src/Token.js +6 -4
  51. package/src/Validator.js +1 -1
  52. package/src/Var.js +22 -6
  53. package/src/View/EarlyHints.js +43 -33
  54. package/src/View/Form.js +17 -11
  55. package/src/View.js +62 -6
  56. package/template/package.json +3 -1
  57. package/template/view/content/home.html +3 -3
  58. package/template/view/head/main.html +2 -2
  59. package/test/Client.test.js +168 -0
  60. package/test/Config.test.js +112 -0
  61. package/test/Lang.test.js +92 -0
  62. package/test/Odac.test.js +86 -0
  63. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  64. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  65. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  66. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  67. package/test/scripts/check-coverage.js +4 -4
  68. package/test/cli/Cli.test.js +0 -36
  69. package/test/core/Commands.test.js +0 -538
  70. package/test/core/Config.test.js +0 -1432
  71. package/test/core/Lang.test.js +0 -250
  72. package/test/core/Odac.test.js +0 -234
  73. package/test/core/Process.test.js +0 -156
  74. package/test/server/Api.test.js +0 -647
  75. package/test/server/DNS.test.js +0 -2050
  76. package/test/server/DNS.test.js.bak +0 -2084
  77. package/test/server/Hub.test.js +0 -497
  78. package/test/server/Log.test.js +0 -73
  79. package/test/server/Mail.account.test_.js +0 -460
  80. package/test/server/Mail.init.test_.js +0 -411
  81. package/test/server/Mail.test_.js +0 -1340
  82. package/test/server/SSL.test_.js +0 -1491
  83. package/test/server/Server.test.js +0 -765
  84. package/test/server/Service.test_.js +0 -1127
  85. package/test/server/Subdomain.test.js +0 -440
  86. package/test/server/Web/Firewall.test.js +0 -175
  87. package/test/server/Web/Proxy.test.js +0 -397
  88. package/test/server/Web.test.js +0 -1494
  89. package/test/server/__mocks__/acme-client.js +0 -17
  90. package/test/server/__mocks__/bcrypt.js +0 -50
  91. package/test/server/__mocks__/child_process.js +0 -389
  92. package/test/server/__mocks__/crypto.js +0 -432
  93. package/test/server/__mocks__/fs.js +0 -450
  94. package/test/server/__mocks__/globalOdac.js +0 -227
  95. package/test/server/__mocks__/http.js +0 -575
  96. package/test/server/__mocks__/https.js +0 -272
  97. package/test/server/__mocks__/index.js +0 -249
  98. package/test/server/__mocks__/mail/server.js +0 -100
  99. package/test/server/__mocks__/mail/smtp.js +0 -31
  100. package/test/server/__mocks__/mailparser.js +0 -81
  101. package/test/server/__mocks__/net.js +0 -369
  102. package/test/server/__mocks__/node-forge.js +0 -328
  103. package/test/server/__mocks__/os.js +0 -320
  104. package/test/server/__mocks__/path.js +0 -291
  105. package/test/server/__mocks__/selfsigned.js +0 -8
  106. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  107. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  108. package/test/server/__mocks__/smtp-server.js +0 -106
  109. package/test/server/__mocks__/sqlite3.js +0 -394
  110. package/test/server/__mocks__/testFactories.js +0 -299
  111. package/test/server/__mocks__/testHelpers.js +0 -363
  112. package/test/server/__mocks__/tls.js +0 -229
  113. /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.2.0",
11
11
  "license": "MIT",
12
12
  "engines": {
13
13
  "node": ">=18.0.0"
@@ -21,13 +21,17 @@
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.7",
34
+ "undici": "^6.23.0"
31
35
  },
32
36
  "devDependencies": {
33
37
  "@eslint/js": "^9.33.0",
@@ -35,7 +39,7 @@
35
39
  "@semantic-release/commit-analyzer": "^13.0.1",
36
40
  "@semantic-release/git": "^10.0.1",
37
41
  "@semantic-release/github": "^12.0.2",
38
- "@semantic-release/npm": "^13.1.3",
42
+ "@semantic-release/npm": "^13.1.2",
39
43
  "@semantic-release/release-notes-generator": "^14.1.0",
40
44
  "conventional-changelog-conventionalcommits": "^9.1.0",
41
45
  "eslint": "^9.33.0",
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/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/Odac.js CHANGED
@@ -64,9 +64,11 @@ module.exports = {
64
64
  _odac.Var = (...args) => new (require('./Var.js'))(...args)
65
65
 
66
66
  if (req) {
67
- _odac.Request = new (require('./Request.js'))(id, req, res, _odac)
68
- _odac.Auth = new (require('./Auth.js'))(_odac.Request)
69
- _odac.Token = new (require('./Token.js'))(_odac.Request)
67
+ if (typeof req === 'object') {
68
+ _odac.Request = new (require('./Request.js'))(id, req, res, _odac)
69
+ _odac.Auth = new (require('./Auth.js'))(_odac.Request)
70
+ _odac.Token = new (require('./Token.js'))(_odac.Request)
71
+ }
70
72
  _odac.Lang = new (require('./Lang.js'))(_odac)
71
73
  if (res) {
72
74
  _odac.View = new (require('./View.js'))(_odac)
@@ -110,42 +112,49 @@ module.exports = {
110
112
  _odac.__ = function (...args) {
111
113
  return _odac.Lang.get(...args)
112
114
  }
113
- _odac.abort = function (code) {
114
- return _odac.Request.abort(code)
115
- }
116
- _odac.cookie = function (key, value, options) {
117
- return _odac.Request.cookie(key, value, options)
118
- }
119
- _odac.direct = function (url) {
120
- return _odac.Request.redirect(url)
115
+ if (typeof req === 'object') {
116
+ _odac.abort = function (code) {
117
+ return _odac.Request.abort(code)
118
+ }
119
+ _odac.cookie = function (key, value, options) {
120
+ return _odac.Request.cookie(key, value, options)
121
+ }
122
+ _odac.direct = function (url) {
123
+ return _odac.Request.redirect(url)
124
+ }
121
125
  }
122
126
  _odac.env = function (key, defaultValue) {
123
127
  return _odac.Env.get(key, defaultValue)
124
128
  }
125
- _odac.return = function (data) {
126
- return _odac.Request.end(data)
127
- }
128
- _odac.request = function (key) {
129
- return _odac.Request.request(key)
130
- }
131
- _odac.set = function (key, value) {
132
- return _odac.Request.set(key, value)
133
- }
134
- _odac.share = function (key, value) {
135
- return _odac.Request.share(key, value)
136
- }
137
- _odac.token = function (hash) {
138
- return hash ? _odac.Token.check(hash) : _odac.Token.generate()
139
- }
140
- _odac.validator = function () {
141
- return new (require('./Validator.js'))(_odac.Request)
142
- }
143
- _odac.write = function (value) {
144
- return _odac.Request.write(value)
145
- }
146
- _odac.stream = function (input) {
147
- _odac.Request.clearTimeout()
148
- return new (require('./Stream'))(_odac.Request.req, _odac.Request.res, input, _odac)
129
+ if (typeof req === 'object') {
130
+ _odac.return = function (data) {
131
+ return _odac.Request.end(data)
132
+ }
133
+ _odac.request = function (key) {
134
+ return _odac.Request.request(key)
135
+ }
136
+ _odac.set = function (key, value) {
137
+ return _odac.Request.set(key, value)
138
+ }
139
+ _odac.session = function (key, value) {
140
+ return _odac.Request.session(key, value)
141
+ }
142
+ _odac.share = function (key, value) {
143
+ return _odac.Request.share(key, value)
144
+ }
145
+ _odac.token = function (hash) {
146
+ return hash ? _odac.Token.check(hash) : _odac.Token.generate()
147
+ }
148
+ _odac.validator = function () {
149
+ return new (require('./Validator.js'))(_odac.Request)
150
+ }
151
+ _odac.write = function (value) {
152
+ return _odac.Request.write(value)
153
+ }
154
+ _odac.stream = function (input) {
155
+ _odac.Request.clearTimeout()
156
+ return new (require('./Stream'))(_odac.Request.req, _odac.Request.res, input, _odac)
157
+ }
149
158
  }
150
159
 
151
160
  if (global.Odac?.Route?.class) {
package/src/Request.js CHANGED
@@ -71,7 +71,7 @@ class OdacRequest {
71
71
  if (options.expires === undefined) options.expires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString()
72
72
  if (options.secure === undefined) options.secure = true
73
73
  if (options.httpOnly === undefined) options.httpOnly = true
74
- if (options.sameSite === undefined) options.sameSite = 'Strict'
74
+ if (options.sameSite === undefined) options.sameSite = 'Lax'
75
75
  if (typeof value === 'object') value = JSON.stringify(value)
76
76
  let cookie = `${key}=${value}`
77
77
  for (const option of Object.keys(options)) if (options[option]) cookie += `; ${option}=${options[option]}`