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,443 @@
|
|
|
1
|
+
# API Requests with candy.js
|
|
2
|
+
|
|
3
|
+
Learn how to make GET and POST requests to your API endpoints using candy.js.
|
|
4
|
+
|
|
5
|
+
## GET Requests
|
|
6
|
+
|
|
7
|
+
### Basic GET Request
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
Candy.get('/api/data', function(response) {
|
|
11
|
+
console.log('Data:', response)
|
|
12
|
+
})
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### With Parameters
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const userId = 123
|
|
19
|
+
Candy.get(`/api/users/${userId}`, function(user) {
|
|
20
|
+
console.log('User:', user.name)
|
|
21
|
+
console.log('Email:', user.email)
|
|
22
|
+
})
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Error Handling
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
Candy.get('/api/data', function(response) {
|
|
29
|
+
if (response.error) {
|
|
30
|
+
console.error('Error:', response.error)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Process data
|
|
35
|
+
console.log('Success:', response)
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## POST Requests
|
|
40
|
+
|
|
41
|
+
### Using Forms
|
|
42
|
+
|
|
43
|
+
The recommended way to make POST requests is using `Candy.form()`:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
Candy.form('#my-form', function(data) {
|
|
47
|
+
if (data.result.success) {
|
|
48
|
+
console.log('Success!')
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
See [Form Handling](../03-forms/01-form-handling.md) for details.
|
|
54
|
+
|
|
55
|
+
### Manual POST (Advanced)
|
|
56
|
+
|
|
57
|
+
For custom POST requests without forms, use the internal AJAX method:
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// Note: This is an advanced pattern
|
|
61
|
+
// For most cases, use Candy.form() instead
|
|
62
|
+
|
|
63
|
+
const formData = new FormData()
|
|
64
|
+
formData.append('name', 'John')
|
|
65
|
+
formData.append('email', 'john@example.com')
|
|
66
|
+
formData.append('_token', Candy.token())
|
|
67
|
+
|
|
68
|
+
fetch('/api/submit', {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
body: formData
|
|
71
|
+
})
|
|
72
|
+
.then(response => response.json())
|
|
73
|
+
.then(data => {
|
|
74
|
+
console.log('Success:', data)
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## CSRF Protection
|
|
79
|
+
|
|
80
|
+
### Automatic Token Management
|
|
81
|
+
|
|
82
|
+
candy.js automatically handles CSRF tokens:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Token is automatically included
|
|
86
|
+
Candy.get('/api/data', function(response) {
|
|
87
|
+
// ...
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// Token is automatically included in forms
|
|
91
|
+
Candy.form('#my-form', function(data) {
|
|
92
|
+
// ...
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Manual Token Access
|
|
97
|
+
|
|
98
|
+
If you need the token manually:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const token = Candy.token()
|
|
102
|
+
console.log('Current token:', token)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Common Patterns
|
|
106
|
+
|
|
107
|
+
### Fetching Data
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
// Get user profile
|
|
111
|
+
Candy.get('/api/profile', function(profile) {
|
|
112
|
+
document.querySelector('#username').textContent = profile.name
|
|
113
|
+
document.querySelector('#email').textContent = profile.email
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Loading List Data
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
// Get products
|
|
121
|
+
Candy.get('/api/products', function(products) {
|
|
122
|
+
const container = document.querySelector('#products')
|
|
123
|
+
|
|
124
|
+
products.forEach(product => {
|
|
125
|
+
const html = `
|
|
126
|
+
<div class="product">
|
|
127
|
+
<h3>${product.name}</h3>
|
|
128
|
+
<p>${product.price}</p>
|
|
129
|
+
</div>
|
|
130
|
+
`
|
|
131
|
+
container.insertAdjacentHTML('beforeend', html)
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Search
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const searchInput = document.querySelector('#search')
|
|
140
|
+
|
|
141
|
+
searchInput.addEventListener('input', function() {
|
|
142
|
+
const query = this.value
|
|
143
|
+
|
|
144
|
+
if (query.length < 3) return
|
|
145
|
+
|
|
146
|
+
Candy.get(`/api/search?q=${encodeURIComponent(query)}`, function(results) {
|
|
147
|
+
displayResults(results)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
function displayResults(results) {
|
|
152
|
+
const container = document.querySelector('#results')
|
|
153
|
+
container.innerHTML = ''
|
|
154
|
+
|
|
155
|
+
results.forEach(result => {
|
|
156
|
+
const html = `<div class="result">${result.title}</div>`
|
|
157
|
+
container.insertAdjacentHTML('beforeend', html)
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Autocomplete
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
let debounceTimer
|
|
166
|
+
|
|
167
|
+
document.querySelector('#autocomplete').addEventListener('input', function() {
|
|
168
|
+
clearTimeout(debounceTimer)
|
|
169
|
+
|
|
170
|
+
const value = this.value
|
|
171
|
+
|
|
172
|
+
debounceTimer = setTimeout(() => {
|
|
173
|
+
if (value.length < 2) return
|
|
174
|
+
|
|
175
|
+
Candy.get(`/api/autocomplete?q=${value}`, function(suggestions) {
|
|
176
|
+
showSuggestions(suggestions)
|
|
177
|
+
})
|
|
178
|
+
}, 300)
|
|
179
|
+
})
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Infinite Scroll
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
let page = 1
|
|
186
|
+
let loading = false
|
|
187
|
+
|
|
188
|
+
window.addEventListener('scroll', function() {
|
|
189
|
+
if (loading) return
|
|
190
|
+
|
|
191
|
+
const scrollPosition = window.innerHeight + window.scrollY
|
|
192
|
+
const threshold = document.body.offsetHeight - 500
|
|
193
|
+
|
|
194
|
+
if (scrollPosition >= threshold) {
|
|
195
|
+
loadMore()
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
function loadMore() {
|
|
200
|
+
loading = true
|
|
201
|
+
page++
|
|
202
|
+
|
|
203
|
+
Candy.get(`/api/posts?page=${page}`, function(posts) {
|
|
204
|
+
posts.forEach(post => {
|
|
205
|
+
appendPost(post)
|
|
206
|
+
})
|
|
207
|
+
loading = false
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Real-time Updates
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
// Poll for updates every 30 seconds
|
|
216
|
+
setInterval(function() {
|
|
217
|
+
Candy.get('/api/notifications', function(notifications) {
|
|
218
|
+
updateNotificationBadge(notifications.count)
|
|
219
|
+
})
|
|
220
|
+
}, 30000)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Server-Side Setup
|
|
224
|
+
|
|
225
|
+
### GET Endpoint
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// controller/get/users.js
|
|
229
|
+
module.exports = function(Candy) {
|
|
230
|
+
// Get all users
|
|
231
|
+
const users = [
|
|
232
|
+
{id: 1, name: 'John', email: 'john@example.com'},
|
|
233
|
+
{id: 2, name: 'Jane', email: 'jane@example.com'}
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
return users
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
```javascript
|
|
241
|
+
// route/www.js
|
|
242
|
+
Candy.Route.get('/api/users', 'users')
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### GET with Parameters
|
|
246
|
+
|
|
247
|
+
```javascript
|
|
248
|
+
// controller/get/user.js
|
|
249
|
+
module.exports = async function(Candy) {
|
|
250
|
+
const userId = Candy.Request.data.url.id
|
|
251
|
+
|
|
252
|
+
// Fetch user from database
|
|
253
|
+
const user = await getUserById(userId)
|
|
254
|
+
|
|
255
|
+
if (!user) {
|
|
256
|
+
Candy.Request.status(404)
|
|
257
|
+
return {error: 'User not found'}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return user
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
// route/www.js
|
|
266
|
+
Candy.Route.get('/api/users/{id}', 'user')
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### POST Endpoint
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
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')
|
|
276
|
+
|
|
277
|
+
// Validation
|
|
278
|
+
if (!name || !email) {
|
|
279
|
+
return {
|
|
280
|
+
result: {success: false},
|
|
281
|
+
errors: {
|
|
282
|
+
name: !name ? 'Name is required' : null,
|
|
283
|
+
email: !email ? 'Email is required' : null
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Create user
|
|
289
|
+
const user = await createUser({name, email})
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
result: {
|
|
293
|
+
success: true,
|
|
294
|
+
message: 'User created successfully'
|
|
295
|
+
},
|
|
296
|
+
user: user
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
```javascript
|
|
302
|
+
// route/www.js
|
|
303
|
+
Candy.Route.post('/api/users/create', 'create')
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Response Format
|
|
307
|
+
|
|
308
|
+
### Success Response
|
|
309
|
+
|
|
310
|
+
```json
|
|
311
|
+
{
|
|
312
|
+
"result": {
|
|
313
|
+
"success": true,
|
|
314
|
+
"message": "Operation successful"
|
|
315
|
+
},
|
|
316
|
+
"data": {
|
|
317
|
+
"id": 123,
|
|
318
|
+
"name": "John"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Error Response
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"result": {
|
|
328
|
+
"success": false,
|
|
329
|
+
"message": "Validation failed"
|
|
330
|
+
},
|
|
331
|
+
"errors": {
|
|
332
|
+
"email": "Email is required",
|
|
333
|
+
"password": "Password must be at least 8 characters"
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Best Practices
|
|
339
|
+
|
|
340
|
+
1. **Use Appropriate Methods**: GET for reading, POST for writing
|
|
341
|
+
2. **Validate Input**: Always validate on the server
|
|
342
|
+
3. **Handle Errors**: Provide meaningful error messages
|
|
343
|
+
4. **Use HTTPS**: Always use HTTPS for sensitive data
|
|
344
|
+
5. **Rate Limiting**: Implement rate limiting on the server
|
|
345
|
+
6. **Debounce Requests**: Debounce rapid requests (search, autocomplete)
|
|
346
|
+
|
|
347
|
+
## Advanced Techniques
|
|
348
|
+
|
|
349
|
+
### Request Caching
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const cache = {}
|
|
353
|
+
|
|
354
|
+
function getCached(url, callback) {
|
|
355
|
+
if (cache[url]) {
|
|
356
|
+
callback(cache[url])
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
Candy.get(url, function(data) {
|
|
361
|
+
cache[url] = data
|
|
362
|
+
callback(data)
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Usage
|
|
367
|
+
getCached('/api/data', function(data) {
|
|
368
|
+
console.log('Data:', data)
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Request Queue
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
const requestQueue = []
|
|
376
|
+
let processing = false
|
|
377
|
+
|
|
378
|
+
function queueRequest(url, callback) {
|
|
379
|
+
requestQueue.push({url, callback})
|
|
380
|
+
processQueue()
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function processQueue() {
|
|
384
|
+
if (processing || requestQueue.length === 0) return
|
|
385
|
+
|
|
386
|
+
processing = true
|
|
387
|
+
const {url, callback} = requestQueue.shift()
|
|
388
|
+
|
|
389
|
+
Candy.get(url, function(data) {
|
|
390
|
+
callback(data)
|
|
391
|
+
processing = false
|
|
392
|
+
processQueue()
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Retry Logic
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
function getWithRetry(url, callback, maxRetries = 3) {
|
|
401
|
+
let attempts = 0
|
|
402
|
+
|
|
403
|
+
function attempt() {
|
|
404
|
+
attempts++
|
|
405
|
+
|
|
406
|
+
Candy.get(url, function(data) {
|
|
407
|
+
if (data.error && attempts < maxRetries) {
|
|
408
|
+
setTimeout(attempt, 1000 * attempts)
|
|
409
|
+
} else {
|
|
410
|
+
callback(data)
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
attempt()
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Troubleshooting
|
|
420
|
+
|
|
421
|
+
### Request Not Working
|
|
422
|
+
|
|
423
|
+
- Check the URL is correct
|
|
424
|
+
- Verify the endpoint exists in your routes
|
|
425
|
+
- Check browser console for errors
|
|
426
|
+
- Ensure CSRF token is valid
|
|
427
|
+
|
|
428
|
+
### CORS Errors
|
|
429
|
+
|
|
430
|
+
- CORS is not an issue for same-origin requests
|
|
431
|
+
- For cross-origin requests, configure server CORS headers
|
|
432
|
+
|
|
433
|
+
### Token Errors
|
|
434
|
+
|
|
435
|
+
- Tokens are managed automatically
|
|
436
|
+
- If you get token errors, check server configuration
|
|
437
|
+
- Ensure cookies are enabled
|
|
438
|
+
|
|
439
|
+
## Next Steps
|
|
440
|
+
|
|
441
|
+
- Learn about [Form Handling](../03-forms/01-form-handling.md)
|
|
442
|
+
- Explore [AJAX Navigation](../02-ajax-navigation/01-quick-start.md)
|
|
443
|
+
- Check [candy.js Overview](../01-overview/01-introduction.md)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Client-Side Streaming
|
|
2
|
+
|
|
3
|
+
CandyPack provides a simple client-side API for consuming Server-Sent Events (SSE) streams.
|
|
4
|
+
|
|
5
|
+
### Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Simple callback
|
|
9
|
+
Candy.listen('/events', (data) => {
|
|
10
|
+
console.log('Received:', data)
|
|
11
|
+
})
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### With Options
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const stream = Candy.listen('/events',
|
|
18
|
+
(data) => {
|
|
19
|
+
console.log('Message:', data)
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
onOpen: () => console.log('Connected'),
|
|
23
|
+
onError: (err) => console.error('Error:', err),
|
|
24
|
+
autoReconnect: true,
|
|
25
|
+
reconnectDelay: 3000
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// Close connection manually
|
|
30
|
+
stream.close()
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Auto JSON Parsing
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// Server sends: data: {"message": "Hello"}\n\n
|
|
37
|
+
Candy.listen('/events', (data) => {
|
|
38
|
+
console.log(data.message) // "Hello"
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## Real-World Examples
|
|
45
|
+
|
|
46
|
+
### Live Dashboard
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
// Server
|
|
50
|
+
Candy.Route.get('/dashboard/stats', async (Candy) => {
|
|
51
|
+
Candy.stream((send) => {
|
|
52
|
+
const interval = setInterval(async () => {
|
|
53
|
+
const stats = await getServerStats()
|
|
54
|
+
send(stats)
|
|
55
|
+
}, 1000)
|
|
56
|
+
|
|
57
|
+
// Cleanup function
|
|
58
|
+
return () => {
|
|
59
|
+
clearInterval(interval)
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Client
|
|
65
|
+
Candy.listen('/dashboard/stats', (stats) => {
|
|
66
|
+
document.getElementById('cpu').textContent = stats.cpu + '%'
|
|
67
|
+
document.getElementById('memory').textContent = stats.memory + 'MB'
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Real-time Notifications
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
// Server
|
|
75
|
+
Candy.Route.get('/notifications', async (Candy) => {
|
|
76
|
+
const userId = await Candy.request('userId')
|
|
77
|
+
|
|
78
|
+
Candy.stream((send) => {
|
|
79
|
+
global.notificationStreams[userId] = { send }
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// Client
|
|
84
|
+
Candy.listen('/notifications', (notification) => {
|
|
85
|
+
showToast(notification.message)
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Build Logs
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Server
|
|
93
|
+
Candy.Route.get('/build/logs', async (Candy) => {
|
|
94
|
+
Candy.stream(async function* () {
|
|
95
|
+
for await (const log of getBuildLogs()) {
|
|
96
|
+
yield { timestamp: Date.now(), message: log }
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// Client
|
|
102
|
+
const logs = []
|
|
103
|
+
Candy.listen('/build/logs', (log) => {
|
|
104
|
+
logs.push(log)
|
|
105
|
+
updateLogsUI(logs)
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Reference
|
|
110
|
+
|
|
111
|
+
### Candy.listen(url, onMessage, options)
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
- `url` (string): Stream endpoint URL
|
|
115
|
+
- `onMessage` (function): Callback for each message
|
|
116
|
+
- `options` (object): Optional configuration
|
|
117
|
+
|
|
118
|
+
**Options:**
|
|
119
|
+
- `onOpen` (function): Called when connection opens
|
|
120
|
+
- `onError` (function): Called on error
|
|
121
|
+
- `autoReconnect` (boolean): Auto-reconnect on disconnect (default: true)
|
|
122
|
+
- `reconnectDelay` (number): Delay before reconnect in ms (default: 3000)
|
|
123
|
+
|
|
124
|
+
**Returns:**
|
|
125
|
+
- `close()` (function): Close the connection
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Best Practices
|
|
130
|
+
|
|
131
|
+
1. **Always cleanup:** Close streams when component unmounts
|
|
132
|
+
2. **Handle errors:** Provide error UI feedback
|
|
133
|
+
3. **Show connection status:** Let users know when disconnected
|
|
134
|
+
4. **Limit data:** Don't store unlimited messages in state
|
|
135
|
+
5. **Throttle updates:** Use debounce for frequent updates
|
|
136
|
+
|
|
137
|
+
## Troubleshooting
|
|
138
|
+
|
|
139
|
+
**Connection keeps dropping:**
|
|
140
|
+
- Check server heartbeat (should be < 60s)
|
|
141
|
+
- Verify network stability
|
|
142
|
+
- Check browser console for errors
|
|
143
|
+
|
|
144
|
+
**Messages not received:**
|
|
145
|
+
- Verify JSON format from server
|
|
146
|
+
- Check CORS headers
|
|
147
|
+
- Ensure EventSource is supported
|
|
148
|
+
|
|
149
|
+
**Memory leaks:**
|
|
150
|
+
- Always call `close()` or use hooks
|
|
151
|
+
- Limit stored messages
|
|
152
|
+
- Clear intervals/timers
|
|
153
|
+
|
|
154
|
+
## Browser Support
|
|
155
|
+
|
|
156
|
+
- ✅ Chrome 6+
|
|
157
|
+
- ✅ Firefox 6+
|
|
158
|
+
- ✅ Safari 5+
|
|
159
|
+
- ✅ Edge (all versions)
|
|
160
|
+
- ✅ Opera 11+
|
|
161
|
+
- ❌ IE (not supported)
|
|
162
|
+
|
|
163
|
+
For IE support, use a polyfill like `event-source-polyfill`.
|