odac 0.9.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.
- package/.editorconfig +21 -0
- package/.github/workflows/auto-pr-description.yml +49 -0
- package/.github/workflows/release.yml +32 -0
- package/.github/workflows/test-coverage.yml +58 -0
- package/.husky/pre-commit +2 -0
- package/.kiro/steering/code-style.md +56 -0
- package/.kiro/steering/product.md +20 -0
- package/.kiro/steering/structure.md +77 -0
- package/.kiro/steering/tech.md +87 -0
- package/.prettierrc +10 -0
- package/.releaserc.js +134 -0
- package/AGENTS.md +84 -0
- package/CHANGELOG.md +181 -0
- package/CODE_OF_CONDUCT.md +83 -0
- package/CONTRIBUTING.md +63 -0
- package/LICENSE +661 -0
- package/README.md +57 -0
- package/SECURITY.md +26 -0
- package/bin/candy +10 -0
- package/bin/candypack +10 -0
- package/cli/index.js +3 -0
- package/cli/src/Cli.js +348 -0
- package/cli/src/Connector.js +93 -0
- package/cli/src/Monitor.js +416 -0
- package/core/Candy.js +87 -0
- package/core/Commands.js +239 -0
- package/core/Config.js +1094 -0
- package/core/Lang.js +52 -0
- package/core/Log.js +43 -0
- package/core/Process.js +26 -0
- package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
- package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
- package/docs/backend/01-overview/03-development-server.md +79 -0
- package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
- package/docs/backend/03-config/00-configuration-overview.md +214 -0
- package/docs/backend/03-config/01-database-connection.md +60 -0
- package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
- package/docs/backend/03-config/03-request-timeout.md +11 -0
- package/docs/backend/03-config/04-environment-variables.md +227 -0
- package/docs/backend/03-config/05-early-hints.md +352 -0
- package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
- package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
- package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
- package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
- package/docs/backend/04-routing/05-advanced-routing.md +14 -0
- package/docs/backend/04-routing/06-error-pages.md +101 -0
- package/docs/backend/04-routing/07-cron-jobs.md +149 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
- package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
- package/docs/backend/05-controllers/03-controller-classes.md +93 -0
- package/docs/backend/05-forms/01-custom-forms.md +395 -0
- package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
- package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
- package/docs/backend/07-views/01-the-view-directory.md +73 -0
- package/docs/backend/07-views/02-rendering-a-view.md +179 -0
- package/docs/backend/07-views/03-template-syntax.md +181 -0
- package/docs/backend/07-views/03-variables.md +328 -0
- package/docs/backend/07-views/04-request-data.md +231 -0
- package/docs/backend/07-views/05-conditionals.md +290 -0
- package/docs/backend/07-views/06-loops.md +353 -0
- package/docs/backend/07-views/07-translations.md +358 -0
- package/docs/backend/07-views/08-backend-javascript.md +398 -0
- package/docs/backend/07-views/09-comments.md +297 -0
- package/docs/backend/08-database/01-database-connection.md +99 -0
- package/docs/backend/08-database/02-using-mysql.md +322 -0
- package/docs/backend/09-validation/01-the-validator-service.md +424 -0
- package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
- package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
- package/docs/backend/10-authentication/03-register.md +134 -0
- package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
- package/docs/backend/10-authentication/05-session-management.md +159 -0
- package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
- package/docs/backend/11-mail/01-the-mail-service.md +42 -0
- package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
- package/docs/backend/13-utilities/01-candy-var.md +504 -0
- package/docs/frontend/01-overview/01-introduction.md +146 -0
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
- package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
- package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
- package/docs/frontend/03-forms/01-form-handling.md +420 -0
- package/docs/frontend/04-api-requests/01-get-post.md +443 -0
- package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
- package/docs/index.json +452 -0
- package/docs/server/01-installation/01-quick-install.md +19 -0
- package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
- package/docs/server/02-get-started/01-core-concepts.md +7 -0
- package/docs/server/02-get-started/02-basic-commands.md +57 -0
- package/docs/server/02-get-started/03-cli-reference.md +276 -0
- package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
- package/docs/server/03-service/01-start-a-new-service.md +57 -0
- package/docs/server/03-service/02-delete-a-service.md +48 -0
- package/docs/server/04-web/01-create-a-website.md +36 -0
- package/docs/server/04-web/02-list-websites.md +9 -0
- package/docs/server/04-web/03-delete-a-website.md +29 -0
- package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
- package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
- package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
- package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
- package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
- package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
- package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
- package/docs/server/07-mail/04-change-account-password.md +23 -0
- package/eslint.config.mjs +120 -0
- package/framework/index.js +4 -0
- package/framework/src/Auth.js +309 -0
- package/framework/src/Candy.js +81 -0
- package/framework/src/Config.js +79 -0
- package/framework/src/Env.js +60 -0
- package/framework/src/Lang.js +57 -0
- package/framework/src/Mail.js +83 -0
- package/framework/src/Mysql.js +575 -0
- package/framework/src/Request.js +301 -0
- package/framework/src/Route/Cron.js +128 -0
- package/framework/src/Route/Internal.js +439 -0
- package/framework/src/Route.js +455 -0
- package/framework/src/Server.js +15 -0
- package/framework/src/Stream.js +163 -0
- package/framework/src/Token.js +37 -0
- package/framework/src/Validator.js +271 -0
- package/framework/src/Var.js +211 -0
- package/framework/src/View/EarlyHints.js +190 -0
- package/framework/src/View/Form.js +600 -0
- package/framework/src/View.js +513 -0
- package/framework/web/candy.js +838 -0
- package/jest.config.js +22 -0
- package/locale/de-DE.json +80 -0
- package/locale/en-US.json +79 -0
- package/locale/es-ES.json +80 -0
- package/locale/fr-FR.json +80 -0
- package/locale/pt-BR.json +80 -0
- package/locale/ru-RU.json +80 -0
- package/locale/tr-TR.json +85 -0
- package/locale/zh-CN.json +80 -0
- package/package.json +86 -0
- package/server/index.js +5 -0
- package/server/src/Api.js +88 -0
- package/server/src/DNS.js +940 -0
- package/server/src/Hub.js +535 -0
- package/server/src/Mail.js +571 -0
- package/server/src/SSL.js +180 -0
- package/server/src/Server.js +27 -0
- package/server/src/Service.js +248 -0
- package/server/src/Subdomain.js +64 -0
- package/server/src/Web/Firewall.js +170 -0
- package/server/src/Web/Proxy.js +134 -0
- package/server/src/Web.js +451 -0
- package/server/src/mail/imap.js +1091 -0
- package/server/src/mail/server.js +32 -0
- package/server/src/mail/smtp.js +786 -0
- package/test/cli/Cli.test.js +36 -0
- package/test/core/Candy.test.js +234 -0
- package/test/core/Commands.test.js +538 -0
- package/test/core/Config.test.js +1435 -0
- package/test/core/Lang.test.js +250 -0
- package/test/core/Process.test.js +156 -0
- package/test/framework/Route.test.js +239 -0
- package/test/framework/View/EarlyHints.test.js +282 -0
- package/test/scripts/check-coverage.js +132 -0
- package/test/server/Api.test.js +647 -0
- package/test/server/Client.test.js +338 -0
- package/test/server/DNS.test.js +2050 -0
- package/test/server/DNS.test.js.bak +2084 -0
- package/test/server/Log.test.js +73 -0
- package/test/server/Mail.account.test_.js +460 -0
- package/test/server/Mail.init.test_.js +411 -0
- package/test/server/Mail.test_.js +1340 -0
- package/test/server/SSL.test_.js +1491 -0
- package/test/server/Server.test.js +765 -0
- package/test/server/Service.test_.js +1127 -0
- package/test/server/Subdomain.test.js +440 -0
- package/test/server/Web/Firewall.test.js +175 -0
- package/test/server/Web.test_.js +1562 -0
- package/test/server/__mocks__/acme-client.js +17 -0
- package/test/server/__mocks__/bcrypt.js +50 -0
- package/test/server/__mocks__/child_process.js +389 -0
- package/test/server/__mocks__/crypto.js +432 -0
- package/test/server/__mocks__/fs.js +450 -0
- package/test/server/__mocks__/globalCandy.js +227 -0
- package/test/server/__mocks__/http-proxy.js +105 -0
- package/test/server/__mocks__/http.js +575 -0
- package/test/server/__mocks__/https.js +272 -0
- package/test/server/__mocks__/index.js +249 -0
- package/test/server/__mocks__/mail/server.js +100 -0
- package/test/server/__mocks__/mail/smtp.js +31 -0
- package/test/server/__mocks__/mailparser.js +81 -0
- package/test/server/__mocks__/net.js +369 -0
- package/test/server/__mocks__/node-forge.js +328 -0
- package/test/server/__mocks__/os.js +320 -0
- package/test/server/__mocks__/path.js +291 -0
- package/test/server/__mocks__/selfsigned.js +8 -0
- package/test/server/__mocks__/server/src/mail/server.js +100 -0
- package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
- package/test/server/__mocks__/smtp-server.js +106 -0
- package/test/server/__mocks__/sqlite3.js +394 -0
- package/test/server/__mocks__/testFactories.js +299 -0
- package/test/server/__mocks__/testHelpers.js +363 -0
- package/test/server/__mocks__/tls.js +229 -0
- package/watchdog/index.js +3 -0
- package/watchdog/src/Watchdog.js +156 -0
- package/web/config.json +5 -0
- package/web/controller/page/about.js +27 -0
- package/web/controller/page/index.js +34 -0
- package/web/package.json +18 -0
- package/web/public/assets/css/style.css +1835 -0
- package/web/public/assets/js/app.js +96 -0
- package/web/route/www.js +19 -0
- package/web/skeleton/main.html +22 -0
- package/web/view/content/about.html +65 -0
- package/web/view/content/home.html +205 -0
- package/web/view/footer/main.html +11 -0
- package/web/view/head/main.html +5 -0
- package/web/view/header/main.html +14 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Streaming API
|
|
2
|
+
|
|
3
|
+
CandyPack provides a unified streaming API that automatically handles Server-Sent Events (SSE), with future support for WebSocket and HTTP/3.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
### Inline Route (Simple)
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// route/www.js
|
|
11
|
+
Candy.Route.get('/hello', async (Candy) => {
|
|
12
|
+
Candy.stream('Hello World')
|
|
13
|
+
})
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Controller (Recommended)
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
// route/www.js
|
|
20
|
+
Candy.Route.get('/hello', 'hello')
|
|
21
|
+
|
|
22
|
+
// controller/hello/get/index.js
|
|
23
|
+
module.exports = async (Candy) => {
|
|
24
|
+
Candy.stream('Hello World')
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### JSON Message
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// controller/get/index.js
|
|
32
|
+
module.exports = async (Candy) => {
|
|
33
|
+
Candy.stream({ message: 'Hello', time: Date.now() })
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Multiple Messages
|
|
38
|
+
|
|
39
|
+
### Callback Pattern
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
// controller/get/index.js
|
|
43
|
+
module.exports = async (Candy) => {
|
|
44
|
+
Candy.stream((send) => {
|
|
45
|
+
send({ type: 'connected' })
|
|
46
|
+
|
|
47
|
+
setInterval(() => {
|
|
48
|
+
send({ time: Date.now() })
|
|
49
|
+
}, 1000)
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### With Cleanup
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
// controller/get/index.js
|
|
58
|
+
module.exports = async (Candy) => {
|
|
59
|
+
Candy.stream((send, close) => {
|
|
60
|
+
send({ type: 'connected' })
|
|
61
|
+
|
|
62
|
+
const interval = setInterval(() => {
|
|
63
|
+
send({ time: Date.now() })
|
|
64
|
+
}, 1000)
|
|
65
|
+
|
|
66
|
+
// Return cleanup function
|
|
67
|
+
return () => {
|
|
68
|
+
clearInterval(interval)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Automatic Piping
|
|
75
|
+
|
|
76
|
+
### Array
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
// controller/get/index.js
|
|
80
|
+
module.exports = async (Candy) => {
|
|
81
|
+
Candy.stream([1, 2, 3, 4, 5])
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Async Generator
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
// 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
|
+
|
|
93
|
+
for (const user of users) {
|
|
94
|
+
yield user
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Promise
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
// controller/app/get/index.js
|
|
104
|
+
module.exports = async (Candy) => {
|
|
105
|
+
Candy.stream(
|
|
106
|
+
fetch('https://api.example.com/data')
|
|
107
|
+
.then(r => r.json())
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Node.js Stream
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// controller/file/get/index.js
|
|
116
|
+
module.exports = async (Candy) => {
|
|
117
|
+
const fs = require('fs')
|
|
118
|
+
Candy.stream(fs.createReadStream('large-file.txt'))
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Advanced Usage
|
|
123
|
+
|
|
124
|
+
### Full Control
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
// 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)
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Error Handling
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// controller/data/get/fetch.js
|
|
147
|
+
module.exports = async (Candy) => {
|
|
148
|
+
const stream = Candy.stream()
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const data = await fetchData()
|
|
152
|
+
stream.send(data)
|
|
153
|
+
} catch (error) {
|
|
154
|
+
stream.error(error.message)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Real-World Examples
|
|
160
|
+
|
|
161
|
+
### Real-time Logs
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// route/www.js
|
|
165
|
+
Candy.Route.get('/logs', 'logs')
|
|
166
|
+
|
|
167
|
+
// controller/logs/get/index.js
|
|
168
|
+
module.exports = async (Candy) => {
|
|
169
|
+
Candy.stream(async function* () {
|
|
170
|
+
const logStream = await getDeploymentLogs()
|
|
171
|
+
|
|
172
|
+
for await (const log of logStream) {
|
|
173
|
+
yield {
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
message: log
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Database Pagination
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// route/www.js
|
|
186
|
+
Candy.Route.get('/posts', 'posts')
|
|
187
|
+
|
|
188
|
+
// controller/posts/get/index.js
|
|
189
|
+
module.exports = async (Candy) => {
|
|
190
|
+
Candy.stream(async function* () {
|
|
191
|
+
let page = 1
|
|
192
|
+
let hasMore = true
|
|
193
|
+
|
|
194
|
+
while (hasMore) {
|
|
195
|
+
const posts = await Candy.Mysql.table('posts')
|
|
196
|
+
.limit(10)
|
|
197
|
+
.offset((page - 1) * 10)
|
|
198
|
+
.get()
|
|
199
|
+
|
|
200
|
+
if (posts.length === 0) {
|
|
201
|
+
hasMore = false
|
|
202
|
+
} else {
|
|
203
|
+
for (const post of posts) {
|
|
204
|
+
yield post
|
|
205
|
+
}
|
|
206
|
+
page++
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Client-Side Usage
|
|
214
|
+
|
|
215
|
+
### JavaScript
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
const eventSource = new EventSource('/events')
|
|
219
|
+
|
|
220
|
+
eventSource.onmessage = (event) => {
|
|
221
|
+
const data = JSON.parse(event.data)
|
|
222
|
+
console.log(data)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
eventSource.onerror = (error) => {
|
|
226
|
+
console.error('Connection error:', error)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Close connection
|
|
230
|
+
eventSource.close()
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### React Hook
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
import { useEffect, useState } from 'react'
|
|
237
|
+
|
|
238
|
+
function useStream(url) {
|
|
239
|
+
const [data, setData] = useState(null)
|
|
240
|
+
|
|
241
|
+
useEffect(() => {
|
|
242
|
+
const eventSource = new EventSource(url)
|
|
243
|
+
|
|
244
|
+
eventSource.onmessage = (event) => {
|
|
245
|
+
setData(JSON.parse(event.data))
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return () => eventSource.close()
|
|
249
|
+
}, [url])
|
|
250
|
+
|
|
251
|
+
return data
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Usage
|
|
255
|
+
function Dashboard() {
|
|
256
|
+
const status = useStream('/auth/listen')
|
|
257
|
+
|
|
258
|
+
return <div>{status?.message}</div>
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Protocol
|
|
263
|
+
|
|
264
|
+
CandyPack uses **Server-Sent Events (SSE)** for streaming:
|
|
265
|
+
- ✅ One-way communication (server → client)
|
|
266
|
+
- ✅ Automatic reconnection
|
|
267
|
+
- ✅ Works over HTTP/2
|
|
268
|
+
- ✅ No extra ports needed
|
|
269
|
+
|
|
270
|
+
## Technical Details
|
|
271
|
+
|
|
272
|
+
- **Protocol:** HTTP/2 compatible
|
|
273
|
+
- **Port:** Standard HTTPS (443)
|
|
274
|
+
- **Heartbeat:** Automatic (every 30 seconds)
|
|
275
|
+
- **Reconnection:** Automatic (browser handles)
|
|
276
|
+
- **Compression:** Supported via HTTP/2
|
|
277
|
+
|
|
278
|
+
## Best Practices
|
|
279
|
+
|
|
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
|
|
284
|
+
5. **Test reconnection:** Ensure your app handles connection drops
|
|
285
|
+
|
|
286
|
+
## Troubleshooting
|
|
287
|
+
|
|
288
|
+
**Connection drops immediately:**
|
|
289
|
+
- Check if you're calling `Candy.return()` or `res.end()`
|
|
290
|
+
- Don't use both streaming and regular responses
|
|
291
|
+
|
|
292
|
+
**Messages not received:**
|
|
293
|
+
- Verify JSON format
|
|
294
|
+
- Check browser console for errors
|
|
295
|
+
- Ensure CORS headers if cross-origin
|
|
296
|
+
|
|
297
|
+
**High memory usage:**
|
|
298
|
+
- Limit number of concurrent connections
|
|
299
|
+
- Implement cleanup properly
|
|
300
|
+
- Use throttling for frequent updates
|