odac 0.9.0 → 1.0.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 (208) hide show
  1. package/.github/workflows/auto-pr-description.yml +0 -2
  2. package/.github/workflows/codeql.yml +46 -0
  3. package/.github/workflows/release.yml +13 -6
  4. package/.github/workflows/test-coverage.yml +10 -9
  5. package/.releaserc.js +9 -6
  6. package/CHANGELOG.md +62 -150
  7. package/CODE_OF_CONDUCT.md +1 -1
  8. package/CONTRIBUTING.md +8 -8
  9. package/LICENSE +21 -661
  10. package/README.md +12 -12
  11. package/SECURITY.md +4 -4
  12. package/bin/odac.js +101 -0
  13. package/{framework/web/candy.js → client/odac.js} +310 -44
  14. package/docs/backend/01-overview/{01-whats-in-the-candy-box.md → 01-whats-in-the-odac-box.md} +4 -2
  15. package/docs/backend/01-overview/02-super-handy-helper-functions.md +29 -1
  16. package/docs/backend/01-overview/03-development-server.md +11 -11
  17. package/docs/backend/02-structure/01-typical-project-layout.md +4 -4
  18. package/docs/backend/03-config/00-configuration-overview.md +6 -6
  19. package/docs/backend/03-config/01-database-connection.md +1 -1
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +4 -4
  21. package/docs/backend/03-config/04-environment-variables.md +20 -20
  22. package/docs/backend/03-config/05-early-hints.md +4 -4
  23. package/docs/backend/04-routing/01-basic-page-routes.md +4 -4
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +5 -5
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +3 -3
  26. package/docs/backend/04-routing/04-authentication-aware-routes.md +5 -5
  27. package/docs/backend/04-routing/05-advanced-routing.md +3 -3
  28. package/docs/backend/04-routing/06-error-pages.md +17 -17
  29. package/docs/backend/04-routing/07-cron-jobs.md +13 -13
  30. package/docs/backend/04-routing/08-middleware.md +214 -0
  31. package/docs/backend/04-routing/09-websocket-auth-middleware.md +292 -0
  32. package/docs/backend/04-routing/09-websocket-examples.md +381 -0
  33. package/docs/backend/04-routing/09-websocket-quick-reference.md +211 -0
  34. package/docs/backend/04-routing/09-websocket.md +298 -0
  35. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +3 -3
  36. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +41 -0
  37. package/docs/backend/05-controllers/03-controller-classes.md +19 -19
  38. package/docs/backend/05-forms/01-custom-forms.md +114 -114
  39. package/docs/backend/05-forms/02-automatic-database-insert.md +82 -82
  40. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +26 -26
  41. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +10 -10
  42. package/docs/backend/07-views/01-the-view-directory.md +1 -1
  43. package/docs/backend/07-views/02-rendering-a-view.md +22 -22
  44. package/docs/backend/07-views/03-template-syntax.md +52 -52
  45. package/docs/backend/07-views/03-variables.md +84 -84
  46. package/docs/backend/07-views/04-request-data.md +57 -57
  47. package/docs/backend/07-views/05-conditionals.md +78 -78
  48. package/docs/backend/07-views/06-loops.md +114 -114
  49. package/docs/backend/07-views/07-translations.md +66 -66
  50. package/docs/backend/07-views/08-backend-javascript.md +103 -103
  51. package/docs/backend/07-views/09-comments.md +71 -71
  52. package/docs/backend/08-database/01-database-connection.md +8 -8
  53. package/docs/backend/08-database/02-using-mysql.md +49 -49
  54. package/docs/backend/09-validation/01-the-validator-service.md +38 -38
  55. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +15 -15
  56. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +10 -10
  57. package/docs/backend/10-authentication/03-register.md +12 -12
  58. package/docs/backend/10-authentication/{04-candy-register-forms.md → 04-odac-register-forms.md} +141 -141
  59. package/docs/backend/10-authentication/05-session-management.md +10 -10
  60. package/docs/backend/10-authentication/{06-candy-login-forms.md → 06-odac-login-forms.md} +125 -125
  61. package/docs/backend/11-mail/01-the-mail-service.md +5 -5
  62. package/docs/backend/12-streaming/01-streaming-overview.md +96 -54
  63. package/docs/backend/13-utilities/{01-candy-var.md → 01-odac-var.md} +109 -109
  64. package/docs/frontend/01-overview/01-introduction.md +30 -30
  65. package/docs/frontend/02-ajax-navigation/01-quick-start.md +45 -45
  66. package/docs/frontend/02-ajax-navigation/02-configuration.md +14 -14
  67. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +36 -36
  68. package/docs/frontend/03-forms/01-form-handling.md +32 -32
  69. package/docs/frontend/04-api-requests/01-get-post.md +33 -33
  70. package/docs/frontend/05-streaming/01-client-streaming.md +15 -15
  71. package/docs/frontend/06-websocket/00-overview.md +76 -0
  72. package/docs/frontend/06-websocket/01-websocket-client.md +139 -0
  73. package/docs/frontend/06-websocket/02-shared-websocket.md +149 -0
  74. package/docs/index.json +49 -11
  75. package/eslint.config.mjs +6 -6
  76. package/{framework/index.js → index.js} +1 -1
  77. package/package.json +14 -39
  78. package/{framework/src → src}/Auth.js +59 -59
  79. package/{framework/src → src}/Config.js +3 -3
  80. package/{framework/src → src}/Lang.js +7 -7
  81. package/{framework/src → src}/Mail.js +5 -5
  82. package/{framework/src → src}/Mysql.js +42 -42
  83. package/src/Odac.js +112 -0
  84. package/{framework/src → src}/Request.js +38 -36
  85. package/{framework/src → src}/Route/Internal.js +116 -116
  86. package/src/Route/Middleware.js +75 -0
  87. package/src/Route.js +621 -0
  88. package/src/Server.js +22 -0
  89. package/{framework/src → src}/Stream.js +11 -3
  90. package/{framework/src → src}/Validator.js +21 -21
  91. package/{framework/src → src}/Var.js +5 -5
  92. package/{framework/src → src}/View/EarlyHints.js +1 -1
  93. package/{framework/src → src}/View/Form.js +69 -69
  94. package/{framework/src → src}/View.js +78 -81
  95. package/src/WebSocket.js +403 -0
  96. package/template/config.json +5 -0
  97. package/{web → template}/controller/page/about.js +6 -6
  98. package/{web → template}/controller/page/index.js +9 -9
  99. package/{web → template}/package.json +4 -5
  100. package/{web → template}/public/assets/css/style.css +4 -4
  101. package/{web → template}/public/assets/js/app.js +6 -6
  102. package/{web → template}/route/www.js +6 -6
  103. package/{web → template}/skeleton/main.html +1 -1
  104. package/{web → template}/view/content/about.html +5 -5
  105. package/{web → template}/view/content/home.html +12 -12
  106. package/template/view/footer/main.html +11 -0
  107. package/{web → template}/view/head/main.html +1 -1
  108. package/{web → template}/view/header/main.html +2 -2
  109. package/test/core/Candy.test.js +58 -58
  110. package/test/core/Commands.test.js +7 -7
  111. package/test/core/Config.test.js +82 -85
  112. package/test/core/Lang.test.js +2 -2
  113. package/test/core/Process.test.js +6 -6
  114. package/test/framework/Route.test.js +56 -37
  115. package/test/framework/View/EarlyHints.test.js +2 -2
  116. package/test/framework/WebSocket.test.js +100 -0
  117. package/test/framework/middleware.test.js +85 -0
  118. package/test/server/Api.test.js +31 -31
  119. package/test/server/DNS.test.js +11 -11
  120. package/test/server/Hub.test.js +497 -0
  121. package/test/server/Mail.account.test_.js +3 -3
  122. package/test/server/Mail.init.test_.js +10 -10
  123. package/test/server/Mail.test_.js +20 -20
  124. package/test/server/SSL.test_.js +54 -54
  125. package/test/server/Server.test.js +39 -39
  126. package/test/server/Service.test_.js +7 -7
  127. package/test/server/Subdomain.test.js +7 -7
  128. package/test/server/Web/Firewall.test.js +87 -87
  129. package/test/server/Web/Proxy.test.js +397 -0
  130. package/test/server/{Web.test_.js → Web.test.js} +137 -205
  131. package/test/server/__mocks__/fs.js +2 -2
  132. package/test/server/__mocks__/{globalCandy.js → globalOdac.js} +5 -5
  133. package/test/server/__mocks__/index.js +6 -6
  134. package/test/server/__mocks__/testFactories.js +1 -1
  135. package/test/server/__mocks__/testHelpers.js +7 -7
  136. package/.husky/pre-commit +0 -2
  137. package/.kiro/steering/code-style.md +0 -56
  138. package/.kiro/steering/product.md +0 -20
  139. package/.kiro/steering/structure.md +0 -77
  140. package/.kiro/steering/tech.md +0 -87
  141. package/AGENTS.md +0 -84
  142. package/bin/candy +0 -10
  143. package/bin/candypack +0 -10
  144. package/cli/index.js +0 -3
  145. package/cli/src/Cli.js +0 -348
  146. package/cli/src/Connector.js +0 -93
  147. package/cli/src/Monitor.js +0 -416
  148. package/core/Candy.js +0 -87
  149. package/core/Commands.js +0 -239
  150. package/core/Config.js +0 -1094
  151. package/core/Lang.js +0 -52
  152. package/core/Log.js +0 -43
  153. package/core/Process.js +0 -26
  154. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +0 -20
  155. package/docs/server/01-installation/01-quick-install.md +0 -19
  156. package/docs/server/01-installation/02-manual-installation-via-npm.md +0 -9
  157. package/docs/server/02-get-started/01-core-concepts.md +0 -7
  158. package/docs/server/02-get-started/02-basic-commands.md +0 -57
  159. package/docs/server/02-get-started/03-cli-reference.md +0 -276
  160. package/docs/server/02-get-started/04-cli-quick-reference.md +0 -102
  161. package/docs/server/03-service/01-start-a-new-service.md +0 -57
  162. package/docs/server/03-service/02-delete-a-service.md +0 -48
  163. package/docs/server/04-web/01-create-a-website.md +0 -36
  164. package/docs/server/04-web/02-list-websites.md +0 -9
  165. package/docs/server/04-web/03-delete-a-website.md +0 -29
  166. package/docs/server/05-subdomain/01-create-a-subdomain.md +0 -32
  167. package/docs/server/05-subdomain/02-list-subdomains.md +0 -33
  168. package/docs/server/05-subdomain/03-delete-a-subdomain.md +0 -41
  169. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +0 -34
  170. package/docs/server/07-mail/01-create-a-mail-account.md +0 -23
  171. package/docs/server/07-mail/02-delete-a-mail-account.md +0 -20
  172. package/docs/server/07-mail/03-list-mail-accounts.md +0 -20
  173. package/docs/server/07-mail/04-change-account-password.md +0 -23
  174. package/framework/src/Candy.js +0 -81
  175. package/framework/src/Route.js +0 -455
  176. package/framework/src/Server.js +0 -15
  177. package/locale/de-DE.json +0 -80
  178. package/locale/en-US.json +0 -79
  179. package/locale/es-ES.json +0 -80
  180. package/locale/fr-FR.json +0 -80
  181. package/locale/pt-BR.json +0 -80
  182. package/locale/ru-RU.json +0 -80
  183. package/locale/tr-TR.json +0 -85
  184. package/locale/zh-CN.json +0 -80
  185. package/server/index.js +0 -5
  186. package/server/src/Api.js +0 -88
  187. package/server/src/DNS.js +0 -940
  188. package/server/src/Hub.js +0 -535
  189. package/server/src/Mail.js +0 -571
  190. package/server/src/SSL.js +0 -180
  191. package/server/src/Server.js +0 -27
  192. package/server/src/Service.js +0 -248
  193. package/server/src/Subdomain.js +0 -64
  194. package/server/src/Web/Firewall.js +0 -170
  195. package/server/src/Web/Proxy.js +0 -134
  196. package/server/src/Web.js +0 -451
  197. package/server/src/mail/imap.js +0 -1091
  198. package/server/src/mail/server.js +0 -32
  199. package/server/src/mail/smtp.js +0 -786
  200. package/test/server/Client.test.js +0 -338
  201. package/test/server/__mocks__/http-proxy.js +0 -105
  202. package/watchdog/index.js +0 -3
  203. package/watchdog/src/Watchdog.js +0 -156
  204. package/web/config.json +0 -5
  205. package/web/view/footer/main.html +0 -11
  206. /package/{framework/src → src}/Env.js +0 -0
  207. /package/{framework/src → src}/Route/Cron.js +0 -0
  208. /package/{framework/src → src}/Token.js +0 -0
@@ -1,13 +1,13 @@
1
- # API Requests with candy.js
1
+ # API Requests with odac.js
2
2
 
3
- Learn how to make GET and POST requests to your API endpoints using candy.js.
3
+ Learn how to make GET and POST requests to your API endpoints using odac.js.
4
4
 
5
5
  ## GET Requests
6
6
 
7
7
  ### Basic GET Request
8
8
 
9
9
  ```javascript
10
- Candy.get('/api/data', function(response) {
10
+ odac.get('/api/data', function(response) {
11
11
  console.log('Data:', response)
12
12
  })
13
13
  ```
@@ -16,7 +16,7 @@ Candy.get('/api/data', function(response) {
16
16
 
17
17
  ```javascript
18
18
  const userId = 123
19
- Candy.get(`/api/users/${userId}`, function(user) {
19
+ odac.get(`/api/users/${userId}`, function(user) {
20
20
  console.log('User:', user.name)
21
21
  console.log('Email:', user.email)
22
22
  })
@@ -25,7 +25,7 @@ Candy.get(`/api/users/${userId}`, function(user) {
25
25
  ### Error Handling
26
26
 
27
27
  ```javascript
28
- Candy.get('/api/data', function(response) {
28
+ odac.get('/api/data', function(response) {
29
29
  if (response.error) {
30
30
  console.error('Error:', response.error)
31
31
  return
@@ -40,10 +40,10 @@ Candy.get('/api/data', function(response) {
40
40
 
41
41
  ### Using Forms
42
42
 
43
- The recommended way to make POST requests is using `Candy.form()`:
43
+ The recommended way to make POST requests is using `Odac.form()`:
44
44
 
45
45
  ```javascript
46
- Candy.form('#my-form', function(data) {
46
+ odac.form('#my-form', function(data) {
47
47
  if (data.result.success) {
48
48
  console.log('Success!')
49
49
  }
@@ -58,12 +58,12 @@ For custom POST requests without forms, use the internal AJAX method:
58
58
 
59
59
  ```javascript
60
60
  // Note: This is an advanced pattern
61
- // For most cases, use Candy.form() instead
61
+ // For most cases, use odac.form() instead
62
62
 
63
63
  const formData = new FormData()
64
64
  formData.append('name', 'John')
65
65
  formData.append('email', 'john@example.com')
66
- formData.append('_token', Candy.token())
66
+ formData.append('_token', odac.token())
67
67
 
68
68
  fetch('/api/submit', {
69
69
  method: 'POST',
@@ -79,16 +79,16 @@ fetch('/api/submit', {
79
79
 
80
80
  ### Automatic Token Management
81
81
 
82
- candy.js automatically handles CSRF tokens:
82
+ odac.js automatically handles CSRF tokens:
83
83
 
84
84
  ```javascript
85
85
  // Token is automatically included
86
- Candy.get('/api/data', function(response) {
86
+ odac.get('/api/data', function(response) {
87
87
  // ...
88
88
  })
89
89
 
90
90
  // Token is automatically included in forms
91
- Candy.form('#my-form', function(data) {
91
+ odac.form('#my-form', function(data) {
92
92
  // ...
93
93
  })
94
94
  ```
@@ -98,7 +98,7 @@ Candy.form('#my-form', function(data) {
98
98
  If you need the token manually:
99
99
 
100
100
  ```javascript
101
- const token = Candy.token()
101
+ const token = odac.token()
102
102
  console.log('Current token:', token)
103
103
  ```
104
104
 
@@ -108,7 +108,7 @@ console.log('Current token:', token)
108
108
 
109
109
  ```javascript
110
110
  // Get user profile
111
- Candy.get('/api/profile', function(profile) {
111
+ odac.get('/api/profile', function(profile) {
112
112
  document.querySelector('#username').textContent = profile.name
113
113
  document.querySelector('#email').textContent = profile.email
114
114
  })
@@ -118,7 +118,7 @@ Candy.get('/api/profile', function(profile) {
118
118
 
119
119
  ```javascript
120
120
  // Get products
121
- Candy.get('/api/products', function(products) {
121
+ odac.get('/api/products', function(products) {
122
122
  const container = document.querySelector('#products')
123
123
 
124
124
  products.forEach(product => {
@@ -143,7 +143,7 @@ searchInput.addEventListener('input', function() {
143
143
 
144
144
  if (query.length < 3) return
145
145
 
146
- Candy.get(`/api/search?q=${encodeURIComponent(query)}`, function(results) {
146
+ odac.get(`/api/search?q=${encodeURIComponent(query)}`, function(results) {
147
147
  displayResults(results)
148
148
  })
149
149
  })
@@ -172,7 +172,7 @@ document.querySelector('#autocomplete').addEventListener('input', function() {
172
172
  debounceTimer = setTimeout(() => {
173
173
  if (value.length < 2) return
174
174
 
175
- Candy.get(`/api/autocomplete?q=${value}`, function(suggestions) {
175
+ odac.get(`/api/autocomplete?q=${value}`, function(suggestions) {
176
176
  showSuggestions(suggestions)
177
177
  })
178
178
  }, 300)
@@ -200,7 +200,7 @@ function loadMore() {
200
200
  loading = true
201
201
  page++
202
202
 
203
- Candy.get(`/api/posts?page=${page}`, function(posts) {
203
+ odac.get(`/api/posts?page=${page}`, function(posts) {
204
204
  posts.forEach(post => {
205
205
  appendPost(post)
206
206
  })
@@ -214,7 +214,7 @@ function loadMore() {
214
214
  ```javascript
215
215
  // Poll for updates every 30 seconds
216
216
  setInterval(function() {
217
- Candy.get('/api/notifications', function(notifications) {
217
+ odac.get('/api/notifications', function(notifications) {
218
218
  updateNotificationBadge(notifications.count)
219
219
  })
220
220
  }, 30000)
@@ -226,7 +226,7 @@ setInterval(function() {
226
226
 
227
227
  ```javascript
228
228
  // controller/get/users.js
229
- module.exports = function(Candy) {
229
+ module.exports = function(Odac) {
230
230
  // Get all users
231
231
  const users = [
232
232
  {id: 1, name: 'John', email: 'john@example.com'},
@@ -239,21 +239,21 @@ module.exports = function(Candy) {
239
239
 
240
240
  ```javascript
241
241
  // route/www.js
242
- Candy.Route.get('/api/users', 'users')
242
+ odac.Route.get('/api/users', 'users')
243
243
  ```
244
244
 
245
245
  ### GET with Parameters
246
246
 
247
247
  ```javascript
248
248
  // controller/get/user.js
249
- module.exports = async function(Candy) {
250
- const userId = Candy.Request.data.url.id
249
+ module.exports = async function(Odac) {
250
+ const userId = odac.Request.data.url.id
251
251
 
252
252
  // Fetch user from database
253
253
  const user = await getUserById(userId)
254
254
 
255
255
  if (!user) {
256
- Candy.Request.status(404)
256
+ odac.Request.status(404)
257
257
  return {error: 'User not found'}
258
258
  }
259
259
 
@@ -263,16 +263,16 @@ module.exports = async function(Candy) {
263
263
 
264
264
  ```javascript
265
265
  // route/www.js
266
- Candy.Route.get('/api/users/{id}', 'user')
266
+ odac.Route.get('/api/users/{id}', 'user')
267
267
  ```
268
268
 
269
269
  ### POST Endpoint
270
270
 
271
271
  ```javascript
272
272
  // controller/post/create.js
273
- module.exports = async function(Candy) {
274
- const name = await Candy.Request.request('name')
275
- const email = await Candy.Request.request('email')
273
+ module.exports = async function(Odac) {
274
+ const name = await odac.Request.request('name')
275
+ const email = await odac.Request.request('email')
276
276
 
277
277
  // Validation
278
278
  if (!name || !email) {
@@ -300,7 +300,7 @@ module.exports = async function(Candy) {
300
300
 
301
301
  ```javascript
302
302
  // route/www.js
303
- Candy.Route.post('/api/users/create', 'create')
303
+ odac.Route.post('/api/users/create', 'create')
304
304
  ```
305
305
 
306
306
  ## Response Format
@@ -357,7 +357,7 @@ function getCached(url, callback) {
357
357
  return
358
358
  }
359
359
 
360
- Candy.get(url, function(data) {
360
+ odac.get(url, function(data) {
361
361
  cache[url] = data
362
362
  callback(data)
363
363
  })
@@ -386,7 +386,7 @@ function processQueue() {
386
386
  processing = true
387
387
  const {url, callback} = requestQueue.shift()
388
388
 
389
- Candy.get(url, function(data) {
389
+ odac.get(url, function(data) {
390
390
  callback(data)
391
391
  processing = false
392
392
  processQueue()
@@ -403,7 +403,7 @@ function getWithRetry(url, callback, maxRetries = 3) {
403
403
  function attempt() {
404
404
  attempts++
405
405
 
406
- Candy.get(url, function(data) {
406
+ odac.get(url, function(data) {
407
407
  if (data.error && attempts < maxRetries) {
408
408
  setTimeout(attempt, 1000 * attempts)
409
409
  } else {
@@ -440,4 +440,4 @@ function getWithRetry(url, callback, maxRetries = 3) {
440
440
 
441
441
  - Learn about [Form Handling](../03-forms/01-form-handling.md)
442
442
  - Explore [AJAX Navigation](../02-ajax-navigation/01-quick-start.md)
443
- - Check [candy.js Overview](../01-overview/01-introduction.md)
443
+ - Check [odac.js Overview](../01-overview/01-introduction.md)
@@ -1,12 +1,12 @@
1
1
  # Client-Side Streaming
2
2
 
3
- CandyPack provides a simple client-side API for consuming Server-Sent Events (SSE) streams.
3
+ Odac provides a simple client-side API for consuming Server-Sent Events (SSE) streams.
4
4
 
5
5
  ### Basic Usage
6
6
 
7
7
  ```javascript
8
8
  // Simple callback
9
- Candy.listen('/events', (data) => {
9
+ odac.listen('/events', (data) => {
10
10
  console.log('Received:', data)
11
11
  })
12
12
  ```
@@ -14,7 +14,7 @@ Candy.listen('/events', (data) => {
14
14
  ### With Options
15
15
 
16
16
  ```javascript
17
- const stream = Candy.listen('/events',
17
+ const stream = odac.listen('/events',
18
18
  (data) => {
19
19
  console.log('Message:', data)
20
20
  },
@@ -34,7 +34,7 @@ stream.close()
34
34
 
35
35
  ```javascript
36
36
  // Server sends: data: {"message": "Hello"}\n\n
37
- Candy.listen('/events', (data) => {
37
+ odac.listen('/events', (data) => {
38
38
  console.log(data.message) // "Hello"
39
39
  })
40
40
  ```
@@ -47,8 +47,8 @@ Candy.listen('/events', (data) => {
47
47
 
48
48
  ```javascript
49
49
  // Server
50
- Candy.Route.get('/dashboard/stats', async (Candy) => {
51
- Candy.stream((send) => {
50
+ odac.Route.get('/dashboard/stats', async (Odac) => {
51
+ odac.stream((send) => {
52
52
  const interval = setInterval(async () => {
53
53
  const stats = await getServerStats()
54
54
  send(stats)
@@ -62,7 +62,7 @@ Candy.Route.get('/dashboard/stats', async (Candy) => {
62
62
  })
63
63
 
64
64
  // Client
65
- Candy.listen('/dashboard/stats', (stats) => {
65
+ odac.listen('/dashboard/stats', (stats) => {
66
66
  document.getElementById('cpu').textContent = stats.cpu + '%'
67
67
  document.getElementById('memory').textContent = stats.memory + 'MB'
68
68
  })
@@ -72,16 +72,16 @@ Candy.listen('/dashboard/stats', (stats) => {
72
72
 
73
73
  ```javascript
74
74
  // Server
75
- Candy.Route.get('/notifications', async (Candy) => {
76
- const userId = await Candy.request('userId')
75
+ odac.Route.get('/notifications', async (Odac) => {
76
+ const userId = await odac.request('userId')
77
77
 
78
- Candy.stream((send) => {
78
+ odac.stream((send) => {
79
79
  global.notificationStreams[userId] = { send }
80
80
  })
81
81
  })
82
82
 
83
83
  // Client
84
- Candy.listen('/notifications', (notification) => {
84
+ odac.listen('/notifications', (notification) => {
85
85
  showToast(notification.message)
86
86
  })
87
87
  ```
@@ -90,8 +90,8 @@ Candy.listen('/notifications', (notification) => {
90
90
 
91
91
  ```javascript
92
92
  // Server
93
- Candy.Route.get('/build/logs', async (Candy) => {
94
- Candy.stream(async function* () {
93
+ odac.Route.get('/build/logs', async (Odac) => {
94
+ odac.stream(async function* () {
95
95
  for await (const log of getBuildLogs()) {
96
96
  yield { timestamp: Date.now(), message: log }
97
97
  }
@@ -100,7 +100,7 @@ Candy.Route.get('/build/logs', async (Candy) => {
100
100
 
101
101
  // Client
102
102
  const logs = []
103
- Candy.listen('/build/logs', (log) => {
103
+ odac.listen('/build/logs', (log) => {
104
104
  logs.push(log)
105
105
  updateLogsUI(logs)
106
106
  })
@@ -108,7 +108,7 @@ Candy.listen('/build/logs', (log) => {
108
108
 
109
109
  ## API Reference
110
110
 
111
- ### Candy.listen(url, onMessage, options)
111
+ ### odac.listen(url, onMessage, options)
112
112
 
113
113
  **Parameters:**
114
114
  - `url` (string): Stream endpoint URL
@@ -0,0 +1,76 @@
1
+ # WebSocket Overview
2
+
3
+ Odac provides built-in WebSocket support with automatic reconnection and cross-tab sharing capabilities.
4
+
5
+ ## Quick Start
6
+
7
+ **Backend (route/main.js):**
8
+ ```javascript
9
+ odac.Route.ws('/chat', (ws, Odac) => {
10
+ ws.on('message', data => {
11
+ ws.broadcast(data)
12
+ })
13
+ })
14
+ ```
15
+
16
+ **Frontend:**
17
+ ```javascript
18
+ const ws = Odac.ws('/chat')
19
+ ws.on('message', data => console.log(data))
20
+ ws.send({message: 'Hello!'})
21
+ ```
22
+
23
+ ## Key Features
24
+
25
+ ### 🔄 Automatic Reconnection
26
+ Automatically reconnects on connection loss with configurable retry logic.
27
+
28
+ ### 🔗 Cross-Tab Sharing
29
+ Share a single WebSocket connection across multiple browser tabs using SharedWorker.
30
+
31
+ ### 🏠 Room Support
32
+ Group clients into rooms for targeted broadcasting.
33
+
34
+ ### 🔐 Authentication
35
+ Full access to Odac context for authentication and authorization.
36
+
37
+ ### 📦 JSON Auto-Parsing
38
+ Automatically parses JSON messages on both client and server.
39
+
40
+ ### 🎯 URL Parameters
41
+ Support for dynamic route parameters like `/room/{id}`.
42
+
43
+ ## Architecture
44
+
45
+ ```
46
+ Browser Tab 1 ─┐
47
+ Browser Tab 2 ─┼─> SharedWorker ─> WebSocket ─> Odac Server ─> Your Handler
48
+ Browser Tab 3 ─┘
49
+ ```
50
+
51
+ With `shared: true`, all tabs share one connection through a SharedWorker.
52
+
53
+ ## Use Cases
54
+
55
+ - **Real-time chat applications**
56
+ - **Live notifications**
57
+ - **Collaborative editing**
58
+ - **Live dashboards**
59
+ - **Multiplayer games**
60
+ - **Stock/crypto price updates**
61
+ - **IoT device monitoring**
62
+
63
+ ## Browser Support
64
+
65
+ | Feature | Chrome | Firefox | Safari | Edge |
66
+ |---------|--------|---------|--------|------|
67
+ | WebSocket | ✅ | ✅ | ✅ | ✅ |
68
+ | Shared WebSocket | ✅ | ✅ | ❌ | ✅ |
69
+
70
+ *Shared WebSocket automatically falls back to regular WebSocket in unsupported browsers.*
71
+
72
+ ## Next Steps
73
+
74
+ - [WebSocket Client](01-websocket-client.md) - Basic client usage
75
+ - [Shared WebSocket](02-shared-websocket.md) - Cross-tab communication
76
+ - [Backend WebSocket](../../backend/04-routing/09-websocket.md) - Server-side implementation
@@ -0,0 +1,139 @@
1
+ # WebSocket Client
2
+
3
+ odac.js provides a simple WebSocket client with automatic reconnection and cross-tab sharing support.
4
+
5
+ ## Basic Usage
6
+
7
+ ```javascript
8
+ const ws = Odac.ws('/chat')
9
+
10
+ ws.on('open', () => {
11
+ console.log('Connected!')
12
+ })
13
+
14
+ ws.on('message', data => {
15
+ console.log('Received:', data)
16
+ })
17
+
18
+ ws.send({type: 'hello', message: 'Hi there!'})
19
+ ```
20
+
21
+ ## Configuration Options
22
+
23
+ ```javascript
24
+ const ws = Odac.ws('/chat', {
25
+ autoReconnect: true, // Auto-reconnect on disconnect (default: true)
26
+ reconnectDelay: 3000, // Delay between reconnect attempts (default: 3000ms)
27
+ maxReconnectAttempts: 10, // Max reconnect attempts (default: 10)
28
+ shared: false, // Share connection across browser tabs (default: false)
29
+ token: true // Send CSRF token (default: true)
30
+ })
31
+ ```
32
+
33
+ ## CSRF Token Protection
34
+
35
+ By default, odac.js automatically sends a CSRF token during the WebSocket handshake (similar to AJAX requests). The token is sent via the `Sec-WebSocket-Protocol` header.
36
+
37
+ **Disable token (for public WebSockets):**
38
+ ```javascript
39
+ const ws = Odac.ws('/public', {token: false})
40
+ ```
41
+
42
+ **How it works:**
43
+ 1. Client calls `Odac.token()` to get current CSRF token
44
+ 2. Token is sent as `odac-token-{token}` in WebSocket protocol header
45
+ 3. Server validates token before accepting connection
46
+ 4. If invalid, connection closes with code `4002`
47
+
48
+ ## Shared WebSocket (Cross-Tab)
49
+
50
+ Enable `shared: true` to share a single WebSocket connection across all browser tabs:
51
+
52
+ ```javascript
53
+ const ws = Odac.ws('/chat', {shared: true})
54
+ ```
55
+
56
+ **Benefits:**
57
+ - Single connection shared across all tabs
58
+ - Reduced server load
59
+ - Synchronized state across tabs
60
+ - Automatic cleanup when all tabs close
61
+
62
+ **Browser Support:**
63
+ - Uses SharedWorker API (supported in Chrome, Edge, Firefox)
64
+ - Falls back to regular WebSocket if SharedWorker is unavailable
65
+
66
+ **Example:**
67
+ ```javascript
68
+ // Tab 1
69
+ const ws = Odac.ws('/notifications', {shared: true})
70
+ ws.on('message', data => {
71
+ console.log('Notification:', data)
72
+ })
73
+
74
+ // Tab 2 (same connection)
75
+ const ws2 = Odac.ws('/notifications', {shared: true})
76
+ ws2.on('message', data => {
77
+ console.log('Same notification:', data)
78
+ })
79
+ ```
80
+
81
+ ## Event Handlers
82
+
83
+ ```javascript
84
+ ws.on('open', () => {}) // Connection established
85
+ ws.on('message', data => {}) // Message received (auto-parsed JSON)
86
+ ws.on('close', event => {}) // Connection closed
87
+ ws.on('error', event => {}) // Error occurred
88
+ ```
89
+
90
+ ## Sending Messages
91
+
92
+ ```javascript
93
+ // Objects are automatically JSON-stringified
94
+ ws.send({type: 'chat', message: 'Hello!'})
95
+
96
+ // Strings sent as-is
97
+ ws.send('Plain text')
98
+ ```
99
+
100
+ ## Connection State
101
+
102
+ ```javascript
103
+ ws.connected // true if connected
104
+ ws.state // WebSocket.OPEN, CLOSED, etc.
105
+ ```
106
+
107
+ ## Closing Connection
108
+
109
+ ```javascript
110
+ ws.close()
111
+ ```
112
+
113
+ ## Removing Event Handlers
114
+
115
+ ```javascript
116
+ const handler = data => console.log(data)
117
+ ws.on('message', handler)
118
+ ws.off('message', handler) // Remove specific handler
119
+ ws.off('message') // Remove all message handlers
120
+ ```
121
+
122
+ ## Example: Chat Application
123
+
124
+ ```javascript
125
+ const ws = Odac.ws('/chat')
126
+ const messages = document.getElementById('messages')
127
+ const input = document.getElementById('input')
128
+
129
+ ws.on('message', data => {
130
+ if (data.type === 'chat') {
131
+ messages.innerHTML += `<p>${data.user}: ${data.text}</p>`
132
+ }
133
+ })
134
+
135
+ document.getElementById('send').onclick = () => {
136
+ ws.send({type: 'chat', text: input.value})
137
+ input.value = ''
138
+ }
139
+ ```
@@ -0,0 +1,149 @@
1
+ # Shared WebSocket (Cross-Tab Communication)
2
+
3
+ Shared WebSocket allows multiple browser tabs to share a single WebSocket connection using the SharedWorker API.
4
+
5
+ ## Why Use Shared WebSocket?
6
+
7
+ **Without Shared WebSocket:**
8
+ - Each tab creates its own connection
9
+ - 5 tabs = 5 WebSocket connections
10
+ - Higher server load
11
+ - Inconsistent state across tabs
12
+
13
+ **With Shared WebSocket:**
14
+ - All tabs share one connection
15
+ - 5 tabs = 1 WebSocket connection
16
+ - Lower server load
17
+ - Synchronized state across tabs
18
+
19
+ ## Basic Usage
20
+
21
+ ```javascript
22
+ const ws = Odac.ws('/chat', {shared: true})
23
+
24
+ ws.on('message', data => {
25
+ console.log('Message received in this tab:', data)
26
+ })
27
+
28
+ ws.send({message: 'Hello from this tab'})
29
+ ```
30
+
31
+ ## Real-World Example: Notification System
32
+
33
+ ```javascript
34
+ // All tabs share the same notification connection
35
+ const notifications = Odac.ws('/notifications', {
36
+ shared: true,
37
+ autoReconnect: true
38
+ })
39
+
40
+ notifications.on('open', () => {
41
+ console.log('Notification system connected')
42
+ })
43
+
44
+ notifications.on('message', data => {
45
+ if (data.type === 'notification') {
46
+ showNotification(data.title, data.message)
47
+ }
48
+ })
49
+
50
+ function showNotification(title, message) {
51
+ if (Notification.permission === 'granted') {
52
+ new Notification(title, {body: message})
53
+ }
54
+ }
55
+ ```
56
+
57
+ ## Chat Application Example
58
+
59
+ ```javascript
60
+ // Shared chat connection across all tabs
61
+ const chat = Odac.ws('/chat/room/general', {shared: true})
62
+
63
+ chat.on('message', data => {
64
+ if (data.type === 'message') {
65
+ addMessageToUI(data.user, data.text)
66
+ }
67
+ })
68
+
69
+ document.getElementById('send').onclick = () => {
70
+ const text = document.getElementById('input').value
71
+ chat.send({type: 'message', text})
72
+ }
73
+
74
+ // All tabs will receive the same messages
75
+ // Only one connection to the server
76
+ ```
77
+
78
+ ## Browser Compatibility
79
+
80
+ Shared WebSocket uses the SharedWorker API:
81
+
82
+ - ✅ Chrome/Edge: Full support
83
+ - ✅ Firefox: Full support
84
+ - ❌ Safari: Not supported (falls back to regular WebSocket)
85
+
86
+ **Automatic Fallback:**
87
+ ```javascript
88
+ // If SharedWorker is not available, automatically falls back to regular WebSocket
89
+ const ws = Odac.ws('/chat', {shared: true})
90
+ // Works in all browsers, shared only where supported
91
+ ```
92
+
93
+ ## Lifecycle Management
94
+
95
+ The shared connection automatically manages its lifecycle:
96
+
97
+ ```javascript
98
+ // Tab 1 opens
99
+ const ws1 = Odac.ws('/chat', {shared: true})
100
+ // Connection established
101
+
102
+ // Tab 2 opens
103
+ const ws2 = Odac.ws('/chat', {shared: true})
104
+ // Reuses existing connection
105
+
106
+ // Tab 1 closes
107
+ // Connection stays alive (Tab 2 still using it)
108
+
109
+ // Tab 2 closes
110
+ // Connection automatically closes (no tabs using it)
111
+ ```
112
+
113
+ ## When to Use Shared WebSocket
114
+
115
+ **Good Use Cases:**
116
+ - Notification systems
117
+ - Real-time updates (stock prices, sports scores)
118
+ - Chat applications
119
+ - Collaborative editing
120
+ - Live dashboards
121
+
122
+ **Not Recommended For:**
123
+ - User-specific authenticated connections
124
+ - File uploads/downloads
125
+ - Connections requiring per-tab state
126
+
127
+ ## Performance Comparison
128
+
129
+ **Regular WebSocket (5 tabs):**
130
+ - Server connections: 5
131
+ - Memory usage: ~5MB
132
+ - Network overhead: 5x
133
+
134
+ **Shared WebSocket (5 tabs):**
135
+ - Server connections: 1
136
+ - Memory usage: ~1MB
137
+ - Network overhead: 1x
138
+
139
+ ## Debugging
140
+
141
+ Check if SharedWorker is being used:
142
+
143
+ ```javascript
144
+ const ws = Odac.ws('/chat', {shared: true})
145
+
146
+ // In Chrome DevTools:
147
+ // chrome://inspect/#workers
148
+ // You'll see "odac-ws-/chat" if shared
149
+ ```