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,6 +1,6 @@
1
1
  # Streaming API
2
2
 
3
- CandyPack provides a unified streaming API that automatically handles Server-Sent Events (SSE), with future support for WebSocket and HTTP/3.
3
+ Odac provides a unified streaming API that automatically handles Server-Sent Events (SSE), with future support for WebSocket and HTTP/3.
4
4
 
5
5
  ## Quick Start
6
6
 
@@ -8,8 +8,8 @@ CandyPack provides a unified streaming API that automatically handles Server-Sen
8
8
 
9
9
  ```javascript
10
10
  // route/www.js
11
- Candy.Route.get('/hello', async (Candy) => {
12
- Candy.stream('Hello World')
11
+ Odac.Route.get('/hello', async (Odac) => {
12
+ Odac.stream('Hello World')
13
13
  })
14
14
  ```
15
15
 
@@ -17,11 +17,11 @@ Candy.Route.get('/hello', async (Candy) => {
17
17
 
18
18
  ```javascript
19
19
  // route/www.js
20
- Candy.Route.get('/hello', 'hello')
20
+ Odac.Route.get('/hello', 'hello')
21
21
 
22
22
  // controller/hello/get/index.js
23
- module.exports = async (Candy) => {
24
- Candy.stream('Hello World')
23
+ module.exports = async (Odac) => {
24
+ Odac.stream('Hello World')
25
25
  }
26
26
  ```
27
27
 
@@ -29,34 +29,37 @@ module.exports = async (Candy) => {
29
29
 
30
30
  ```javascript
31
31
  // controller/get/index.js
32
- module.exports = async (Candy) => {
33
- Candy.stream({ message: 'Hello', time: Date.now() })
32
+ module.exports = async (Odac) => {
33
+ Odac.stream({ message: 'Hello', time: Date.now() })
34
34
  }
35
35
  ```
36
36
 
37
37
  ## Multiple Messages
38
38
 
39
- ### Callback Pattern
39
+ ### Callback Pattern with Auto-Cleanup
40
40
 
41
41
  ```javascript
42
42
  // controller/get/index.js
43
- module.exports = async (Candy) => {
44
- Candy.stream((send) => {
43
+ module.exports = async (Odac) => {
44
+ Odac.stream((send) => {
45
45
  send({ type: 'connected' })
46
46
 
47
- setInterval(() => {
47
+ // Use Odac.setInterval for automatic cleanup
48
+ Odac.setInterval(() => {
48
49
  send({ time: Date.now() })
49
50
  }, 1000)
50
51
  })
51
52
  }
52
53
  ```
53
54
 
54
- ### With Cleanup
55
+ **Important:** Always use `Odac.setInterval()` and `Odac.setTimeout()` instead of global functions. They are automatically cleaned up when the connection closes.
56
+
57
+ ### Manual Cleanup (Alternative)
55
58
 
56
59
  ```javascript
57
60
  // controller/get/index.js
58
- module.exports = async (Candy) => {
59
- Candy.stream((send, close) => {
61
+ module.exports = async (Odac) => {
62
+ Odac.stream((send, close) => {
60
63
  send({ type: 'connected' })
61
64
 
62
65
  const interval = setInterval(() => {
@@ -77,8 +80,8 @@ module.exports = async (Candy) => {
77
80
 
78
81
  ```javascript
79
82
  // controller/get/index.js
80
- module.exports = async (Candy) => {
81
- Candy.stream([1, 2, 3, 4, 5])
83
+ module.exports = async (Odac) => {
84
+ Odac.stream([1, 2, 3, 4, 5])
82
85
  }
83
86
  ```
84
87
 
@@ -86,9 +89,9 @@ module.exports = async (Candy) => {
86
89
 
87
90
  ```javascript
88
91
  // controller/users/get/index.js
89
- module.exports = async (Candy) => {
90
- Candy.stream(async function* () {
91
- const users = await Candy.Mysql.table('users').get()
92
+ module.exports = async (Odac) => {
93
+ Odac.stream(async function* () {
94
+ const users = await Odac.Mysql.table('users').get()
92
95
 
93
96
  for (const user of users) {
94
97
  yield user
@@ -101,8 +104,8 @@ module.exports = async (Candy) => {
101
104
 
102
105
  ```javascript
103
106
  // controller/app/get/index.js
104
- module.exports = async (Candy) => {
105
- Candy.stream(
107
+ module.exports = async (Odac) => {
108
+ Odac.stream(
106
109
  fetch('https://api.example.com/data')
107
110
  .then(r => r.json())
108
111
  )
@@ -113,9 +116,9 @@ module.exports = async (Candy) => {
113
116
 
114
117
  ```javascript
115
118
  // controller/file/get/index.js
116
- module.exports = async (Candy) => {
119
+ module.exports = async (Odac) => {
117
120
  const fs = require('fs')
118
- Candy.stream(fs.createReadStream('large-file.txt'))
121
+ Odac.stream(fs.createReadStream('large-file.txt'))
119
122
  }
120
123
  ```
121
124
 
@@ -125,27 +128,26 @@ module.exports = async (Candy) => {
125
128
 
126
129
  ```javascript
127
130
  // controller/monitor/get/index.js
128
- module.exports = async (Candy) => {
129
- const stream = Candy.stream()
130
-
131
- stream.send({ type: 'connected' })
132
-
133
- const interval = setInterval(() => {
134
- stream.send({ time: Date.now() })
135
- }, 1000)
136
-
137
- stream.on('close', () => {
138
- clearInterval(interval)
131
+ module.exports = async (Odac) => {
132
+ return Odac.stream((send) => {
133
+ send({ type: 'connected' })
134
+
135
+ // Use Odac.setInterval for automatic cleanup
136
+ Odac.setInterval(() => {
137
+ send({ time: Date.now() })
138
+ }, 1000)
139
139
  })
140
140
  }
141
141
  ```
142
142
 
143
+ **Note:** When using `Odac.setInterval()` or `Odac.setTimeout()`, cleanup is automatic. No need for manual `clearInterval()` or `clearTimeout()`.
144
+
143
145
  ### Error Handling
144
146
 
145
147
  ```javascript
146
148
  // controller/data/get/fetch.js
147
- module.exports = async (Candy) => {
148
- const stream = Candy.stream()
149
+ module.exports = async (Odac) => {
150
+ const stream = Odac.stream()
149
151
 
150
152
  try {
151
153
  const data = await fetchData()
@@ -162,11 +164,11 @@ module.exports = async (Candy) => {
162
164
 
163
165
  ```javascript
164
166
  // route/www.js
165
- Candy.Route.get('/logs', 'logs')
167
+ Odac.Route.get('/logs', 'logs')
166
168
 
167
169
  // controller/logs/get/index.js
168
- module.exports = async (Candy) => {
169
- Candy.stream(async function* () {
170
+ module.exports = async (Odac) => {
171
+ return Odac.stream(async function* () {
170
172
  const logStream = await getDeploymentLogs()
171
173
 
172
174
  for await (const log of logStream) {
@@ -183,16 +185,16 @@ module.exports = async (Candy) => {
183
185
 
184
186
  ```javascript
185
187
  // route/www.js
186
- Candy.Route.get('/posts', 'posts')
188
+ Odac.Route.get('/posts', 'posts')
187
189
 
188
190
  // controller/posts/get/index.js
189
- module.exports = async (Candy) => {
190
- Candy.stream(async function* () {
191
+ module.exports = async (Odac) => {
192
+ return Odac.stream(async function* () {
191
193
  let page = 1
192
194
  let hasMore = true
193
195
 
194
196
  while (hasMore) {
195
- const posts = await Candy.Mysql.table('posts')
197
+ const posts = await Odac.Mysql.table('posts')
196
198
  .limit(10)
197
199
  .offset((page - 1) * 10)
198
200
  .get()
@@ -261,7 +263,7 @@ function Dashboard() {
261
263
 
262
264
  ## Protocol
263
265
 
264
- CandyPack uses **Server-Sent Events (SSE)** for streaming:
266
+ Odac uses **Server-Sent Events (SSE)** for streaming:
265
267
  - ✅ One-way communication (server → client)
266
268
  - ✅ Automatic reconnection
267
269
  - ✅ Works over HTTP/2
@@ -275,26 +277,66 @@ CandyPack uses **Server-Sent Events (SSE)** for streaming:
275
277
  - **Reconnection:** Automatic (browser handles)
276
278
  - **Compression:** Supported via HTTP/2
277
279
 
280
+ ## Memory Management
281
+
282
+ Odac automatically manages timers and intervals in streaming contexts:
283
+
284
+ ```javascript
285
+ module.exports = async (Odac) => {
286
+ return Odac.stream((send) => {
287
+ // ✅ Automatically cleaned up when connection closes
288
+ Odac.setInterval(() => {
289
+ send({ time: Date.now() })
290
+ }, 1000)
291
+
292
+ Odac.setTimeout(() => {
293
+ send({ type: 'delayed' })
294
+ }, 5000)
295
+ })
296
+ }
297
+ ```
298
+
299
+ **Why this matters:**
300
+ - Prevents memory leaks
301
+ - No orphaned intervals after disconnect
302
+ - Automatic cleanup on connection close
303
+
304
+ **Manual cleanup (if needed):**
305
+ ```javascript
306
+ const intervalId = Odac.setInterval(() => { ... }, 1000)
307
+ Odac.clearInterval(intervalId)
308
+
309
+ const timeoutId = Odac.setTimeout(() => { ... }, 5000)
310
+ Odac.clearTimeout(timeoutId)
311
+ ```
312
+
278
313
  ## Best Practices
279
314
 
280
- 1. **Always handle cleanup:** Use the cleanup function or `stream.on('close')`
281
- 2. **Throttle messages:** Don't send too frequently (use intervals)
282
- 3. **Handle errors:** Use try-catch and `stream.error()`
283
- 4. **Close when done:** Call `stream.close()` when finished
315
+ 1. **Use Odac timers:** Always use `Odac.setInterval()` and `Odac.setTimeout()` instead of global functions
316
+ 2. **Return the stream:** Always `return Odac.stream(...)` from your controller
317
+ 3. **Throttle messages:** Don't send too frequently (use intervals)
318
+ 4. **Handle errors:** Use try-catch for async operations
284
319
  5. **Test reconnection:** Ensure your app handles connection drops
285
320
 
286
321
  ## Troubleshooting
287
322
 
288
323
  **Connection drops immediately:**
289
- - Check if you're calling `Candy.return()` or `res.end()`
324
+ - Check if you're calling `Odac.return()` or `res.end()`
290
325
  - Don't use both streaming and regular responses
326
+ - Make sure to `return Odac.stream(...)`
291
327
 
292
328
  **Messages not received:**
293
329
  - Verify JSON format
294
330
  - Check browser console for errors
295
331
  - Ensure CORS headers if cross-origin
296
332
 
297
- **High memory usage:**
298
- - Limit number of concurrent connections
299
- - Implement cleanup properly
300
- - Use throttling for frequent updates
333
+ **High memory usage / Memory leaks:**
334
+ - Use `Odac.setInterval()` instead of global `setInterval()`
335
+ - Use `Odac.setTimeout()` instead of global `setTimeout()`
336
+ - Avoid creating intervals outside the stream callback
337
+ - Check for other resource leaks (database connections, file handles)
338
+
339
+ **Intervals keep running after disconnect:**
340
+ - Replace `setInterval()` with `Odac.setInterval()`
341
+ - Replace `setTimeout()` with `Odac.setTimeout()`
342
+ - These are automatically cleaned up when the connection closes