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.
Files changed (213) hide show
  1. package/.editorconfig +21 -0
  2. package/.github/workflows/auto-pr-description.yml +49 -0
  3. package/.github/workflows/release.yml +32 -0
  4. package/.github/workflows/test-coverage.yml +58 -0
  5. package/.husky/pre-commit +2 -0
  6. package/.kiro/steering/code-style.md +56 -0
  7. package/.kiro/steering/product.md +20 -0
  8. package/.kiro/steering/structure.md +77 -0
  9. package/.kiro/steering/tech.md +87 -0
  10. package/.prettierrc +10 -0
  11. package/.releaserc.js +134 -0
  12. package/AGENTS.md +84 -0
  13. package/CHANGELOG.md +181 -0
  14. package/CODE_OF_CONDUCT.md +83 -0
  15. package/CONTRIBUTING.md +63 -0
  16. package/LICENSE +661 -0
  17. package/README.md +57 -0
  18. package/SECURITY.md +26 -0
  19. package/bin/candy +10 -0
  20. package/bin/candypack +10 -0
  21. package/cli/index.js +3 -0
  22. package/cli/src/Cli.js +348 -0
  23. package/cli/src/Connector.js +93 -0
  24. package/cli/src/Monitor.js +416 -0
  25. package/core/Candy.js +87 -0
  26. package/core/Commands.js +239 -0
  27. package/core/Config.js +1094 -0
  28. package/core/Lang.js +52 -0
  29. package/core/Log.js +43 -0
  30. package/core/Process.js +26 -0
  31. package/docs/backend/01-overview/01-whats-in-the-candy-box.md +9 -0
  32. package/docs/backend/01-overview/02-super-handy-helper-functions.md +9 -0
  33. package/docs/backend/01-overview/03-development-server.md +79 -0
  34. package/docs/backend/02-structure/01-typical-project-layout.md +39 -0
  35. package/docs/backend/03-config/00-configuration-overview.md +214 -0
  36. package/docs/backend/03-config/01-database-connection.md +60 -0
  37. package/docs/backend/03-config/02-static-route-mapping-optional.md +20 -0
  38. package/docs/backend/03-config/03-request-timeout.md +11 -0
  39. package/docs/backend/03-config/04-environment-variables.md +227 -0
  40. package/docs/backend/03-config/05-early-hints.md +352 -0
  41. package/docs/backend/04-routing/01-basic-page-routes.md +28 -0
  42. package/docs/backend/04-routing/02-controller-less-view-routes.md +43 -0
  43. package/docs/backend/04-routing/03-api-and-data-routes.md +20 -0
  44. package/docs/backend/04-routing/04-authentication-aware-routes.md +48 -0
  45. package/docs/backend/04-routing/05-advanced-routing.md +14 -0
  46. package/docs/backend/04-routing/06-error-pages.md +101 -0
  47. package/docs/backend/04-routing/07-cron-jobs.md +149 -0
  48. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +17 -0
  49. package/docs/backend/05-controllers/02-your-trusty-candy-assistant.md +20 -0
  50. package/docs/backend/05-controllers/03-controller-classes.md +93 -0
  51. package/docs/backend/05-forms/01-custom-forms.md +395 -0
  52. package/docs/backend/05-forms/02-automatic-database-insert.md +297 -0
  53. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +96 -0
  54. package/docs/backend/06-request-and-response/02-sending-a-response-replying-to-the-user.md +40 -0
  55. package/docs/backend/07-views/01-the-view-directory.md +73 -0
  56. package/docs/backend/07-views/02-rendering-a-view.md +179 -0
  57. package/docs/backend/07-views/03-template-syntax.md +181 -0
  58. package/docs/backend/07-views/03-variables.md +328 -0
  59. package/docs/backend/07-views/04-request-data.md +231 -0
  60. package/docs/backend/07-views/05-conditionals.md +290 -0
  61. package/docs/backend/07-views/06-loops.md +353 -0
  62. package/docs/backend/07-views/07-translations.md +358 -0
  63. package/docs/backend/07-views/08-backend-javascript.md +398 -0
  64. package/docs/backend/07-views/09-comments.md +297 -0
  65. package/docs/backend/08-database/01-database-connection.md +99 -0
  66. package/docs/backend/08-database/02-using-mysql.md +322 -0
  67. package/docs/backend/09-validation/01-the-validator-service.md +424 -0
  68. package/docs/backend/10-authentication/01-user-logins-with-authjs.md +53 -0
  69. package/docs/backend/10-authentication/02-foiling-villains-with-csrf-protection.md +55 -0
  70. package/docs/backend/10-authentication/03-register.md +134 -0
  71. package/docs/backend/10-authentication/04-candy-register-forms.md +676 -0
  72. package/docs/backend/10-authentication/05-session-management.md +159 -0
  73. package/docs/backend/10-authentication/06-candy-login-forms.md +596 -0
  74. package/docs/backend/11-mail/01-the-mail-service.md +42 -0
  75. package/docs/backend/12-streaming/01-streaming-overview.md +300 -0
  76. package/docs/backend/13-utilities/01-candy-var.md +504 -0
  77. package/docs/frontend/01-overview/01-introduction.md +146 -0
  78. package/docs/frontend/02-ajax-navigation/01-quick-start.md +608 -0
  79. package/docs/frontend/02-ajax-navigation/02-configuration.md +370 -0
  80. package/docs/frontend/02-ajax-navigation/03-advanced-usage.md +519 -0
  81. package/docs/frontend/03-forms/01-form-handling.md +420 -0
  82. package/docs/frontend/04-api-requests/01-get-post.md +443 -0
  83. package/docs/frontend/05-streaming/01-client-streaming.md +163 -0
  84. package/docs/index.json +452 -0
  85. package/docs/server/01-installation/01-quick-install.md +19 -0
  86. package/docs/server/01-installation/02-manual-installation-via-npm.md +9 -0
  87. package/docs/server/02-get-started/01-core-concepts.md +7 -0
  88. package/docs/server/02-get-started/02-basic-commands.md +57 -0
  89. package/docs/server/02-get-started/03-cli-reference.md +276 -0
  90. package/docs/server/02-get-started/04-cli-quick-reference.md +102 -0
  91. package/docs/server/03-service/01-start-a-new-service.md +57 -0
  92. package/docs/server/03-service/02-delete-a-service.md +48 -0
  93. package/docs/server/04-web/01-create-a-website.md +36 -0
  94. package/docs/server/04-web/02-list-websites.md +9 -0
  95. package/docs/server/04-web/03-delete-a-website.md +29 -0
  96. package/docs/server/05-subdomain/01-create-a-subdomain.md +32 -0
  97. package/docs/server/05-subdomain/02-list-subdomains.md +33 -0
  98. package/docs/server/05-subdomain/03-delete-a-subdomain.md +41 -0
  99. package/docs/server/06-ssl/01-renew-an-ssl-certificate.md +34 -0
  100. package/docs/server/07-mail/01-create-a-mail-account.md +23 -0
  101. package/docs/server/07-mail/02-delete-a-mail-account.md +20 -0
  102. package/docs/server/07-mail/03-list-mail-accounts.md +20 -0
  103. package/docs/server/07-mail/04-change-account-password.md +23 -0
  104. package/eslint.config.mjs +120 -0
  105. package/framework/index.js +4 -0
  106. package/framework/src/Auth.js +309 -0
  107. package/framework/src/Candy.js +81 -0
  108. package/framework/src/Config.js +79 -0
  109. package/framework/src/Env.js +60 -0
  110. package/framework/src/Lang.js +57 -0
  111. package/framework/src/Mail.js +83 -0
  112. package/framework/src/Mysql.js +575 -0
  113. package/framework/src/Request.js +301 -0
  114. package/framework/src/Route/Cron.js +128 -0
  115. package/framework/src/Route/Internal.js +439 -0
  116. package/framework/src/Route.js +455 -0
  117. package/framework/src/Server.js +15 -0
  118. package/framework/src/Stream.js +163 -0
  119. package/framework/src/Token.js +37 -0
  120. package/framework/src/Validator.js +271 -0
  121. package/framework/src/Var.js +211 -0
  122. package/framework/src/View/EarlyHints.js +190 -0
  123. package/framework/src/View/Form.js +600 -0
  124. package/framework/src/View.js +513 -0
  125. package/framework/web/candy.js +838 -0
  126. package/jest.config.js +22 -0
  127. package/locale/de-DE.json +80 -0
  128. package/locale/en-US.json +79 -0
  129. package/locale/es-ES.json +80 -0
  130. package/locale/fr-FR.json +80 -0
  131. package/locale/pt-BR.json +80 -0
  132. package/locale/ru-RU.json +80 -0
  133. package/locale/tr-TR.json +85 -0
  134. package/locale/zh-CN.json +80 -0
  135. package/package.json +86 -0
  136. package/server/index.js +5 -0
  137. package/server/src/Api.js +88 -0
  138. package/server/src/DNS.js +940 -0
  139. package/server/src/Hub.js +535 -0
  140. package/server/src/Mail.js +571 -0
  141. package/server/src/SSL.js +180 -0
  142. package/server/src/Server.js +27 -0
  143. package/server/src/Service.js +248 -0
  144. package/server/src/Subdomain.js +64 -0
  145. package/server/src/Web/Firewall.js +170 -0
  146. package/server/src/Web/Proxy.js +134 -0
  147. package/server/src/Web.js +451 -0
  148. package/server/src/mail/imap.js +1091 -0
  149. package/server/src/mail/server.js +32 -0
  150. package/server/src/mail/smtp.js +786 -0
  151. package/test/cli/Cli.test.js +36 -0
  152. package/test/core/Candy.test.js +234 -0
  153. package/test/core/Commands.test.js +538 -0
  154. package/test/core/Config.test.js +1435 -0
  155. package/test/core/Lang.test.js +250 -0
  156. package/test/core/Process.test.js +156 -0
  157. package/test/framework/Route.test.js +239 -0
  158. package/test/framework/View/EarlyHints.test.js +282 -0
  159. package/test/scripts/check-coverage.js +132 -0
  160. package/test/server/Api.test.js +647 -0
  161. package/test/server/Client.test.js +338 -0
  162. package/test/server/DNS.test.js +2050 -0
  163. package/test/server/DNS.test.js.bak +2084 -0
  164. package/test/server/Log.test.js +73 -0
  165. package/test/server/Mail.account.test_.js +460 -0
  166. package/test/server/Mail.init.test_.js +411 -0
  167. package/test/server/Mail.test_.js +1340 -0
  168. package/test/server/SSL.test_.js +1491 -0
  169. package/test/server/Server.test.js +765 -0
  170. package/test/server/Service.test_.js +1127 -0
  171. package/test/server/Subdomain.test.js +440 -0
  172. package/test/server/Web/Firewall.test.js +175 -0
  173. package/test/server/Web.test_.js +1562 -0
  174. package/test/server/__mocks__/acme-client.js +17 -0
  175. package/test/server/__mocks__/bcrypt.js +50 -0
  176. package/test/server/__mocks__/child_process.js +389 -0
  177. package/test/server/__mocks__/crypto.js +432 -0
  178. package/test/server/__mocks__/fs.js +450 -0
  179. package/test/server/__mocks__/globalCandy.js +227 -0
  180. package/test/server/__mocks__/http-proxy.js +105 -0
  181. package/test/server/__mocks__/http.js +575 -0
  182. package/test/server/__mocks__/https.js +272 -0
  183. package/test/server/__mocks__/index.js +249 -0
  184. package/test/server/__mocks__/mail/server.js +100 -0
  185. package/test/server/__mocks__/mail/smtp.js +31 -0
  186. package/test/server/__mocks__/mailparser.js +81 -0
  187. package/test/server/__mocks__/net.js +369 -0
  188. package/test/server/__mocks__/node-forge.js +328 -0
  189. package/test/server/__mocks__/os.js +320 -0
  190. package/test/server/__mocks__/path.js +291 -0
  191. package/test/server/__mocks__/selfsigned.js +8 -0
  192. package/test/server/__mocks__/server/src/mail/server.js +100 -0
  193. package/test/server/__mocks__/server/src/mail/smtp.js +31 -0
  194. package/test/server/__mocks__/smtp-server.js +106 -0
  195. package/test/server/__mocks__/sqlite3.js +394 -0
  196. package/test/server/__mocks__/testFactories.js +299 -0
  197. package/test/server/__mocks__/testHelpers.js +363 -0
  198. package/test/server/__mocks__/tls.js +229 -0
  199. package/watchdog/index.js +3 -0
  200. package/watchdog/src/Watchdog.js +156 -0
  201. package/web/config.json +5 -0
  202. package/web/controller/page/about.js +27 -0
  203. package/web/controller/page/index.js +34 -0
  204. package/web/package.json +18 -0
  205. package/web/public/assets/css/style.css +1835 -0
  206. package/web/public/assets/js/app.js +96 -0
  207. package/web/route/www.js +19 -0
  208. package/web/skeleton/main.html +22 -0
  209. package/web/view/content/about.html +65 -0
  210. package/web/view/content/home.html +205 -0
  211. package/web/view/footer/main.html +11 -0
  212. package/web/view/head/main.html +5 -0
  213. package/web/view/header/main.html +14 -0
@@ -0,0 +1,99 @@
1
+ ## 🔌 Database Connection
2
+
3
+ CandyPack automatically connects to your MySQL database when you provide the configuration.
4
+
5
+ ### Configuration
6
+
7
+ Add your database credentials to `config.json`:
8
+
9
+ ```json
10
+ {
11
+ "database": {
12
+ "host": "localhost",
13
+ "user": "your_username",
14
+ "password": "your_password",
15
+ "database": "your_database_name"
16
+ }
17
+ }
18
+ ```
19
+
20
+ ### Multiple Databases
21
+
22
+ You can configure multiple database connections:
23
+
24
+ ```json
25
+ {
26
+ "database": {
27
+ "default": {
28
+ "host": "localhost",
29
+ "user": "user1",
30
+ "password": "pass1",
31
+ "database": "main_db"
32
+ },
33
+ "analytics": {
34
+ "host": "analytics.example.com",
35
+ "user": "user2",
36
+ "password": "pass2",
37
+ "database": "analytics_db"
38
+ }
39
+ }
40
+ }
41
+ ```
42
+
43
+ Access different databases:
44
+
45
+ ```javascript
46
+ // Default database
47
+ const users = await Candy.Mysql.table('users').get()
48
+
49
+ // Specific database
50
+ const stats = await Candy.Mysql.database('analytics').table('stats').get()
51
+ ```
52
+
53
+ ### Environment Variables
54
+
55
+ For security, use environment variables for sensitive data:
56
+
57
+ **.env file:**
58
+ ```
59
+ DB_HOST=localhost
60
+ DB_USER=myuser
61
+ DB_PASSWORD=mypassword
62
+ DB_NAME=mydatabase
63
+ ```
64
+
65
+ **config.json:**
66
+ ```json
67
+ {
68
+ "database": {
69
+ "host": "${DB_HOST}",
70
+ "user": "${DB_USER}",
71
+ "password": "${DB_PASSWORD}",
72
+ "database": "${DB_NAME}"
73
+ }
74
+ }
75
+ ```
76
+
77
+ ### Connection Options
78
+
79
+ Available configuration options:
80
+
81
+ - `host` - Database server hostname (default: `localhost`)
82
+ - `user` - Database username
83
+ - `password` - Database password
84
+ - `database` - Database name
85
+ - `type` - Database type (currently only `mysql` is supported)
86
+
87
+ ### Automatic Connection
88
+
89
+ The connection is established automatically when your application starts. You don't need to write any connection code - just use `Candy.Mysql` in your controllers.
90
+
91
+ ```javascript
92
+ module.exports = async function (Candy) {
93
+ // Connection is already established
94
+ const users = await Candy.Mysql.table('users').get()
95
+
96
+ Candy.set('users', users)
97
+ Candy.View.set({ skeleton: 'main', content: 'users' })
98
+ }
99
+ ```
@@ -0,0 +1,322 @@
1
+ ## 🗄️ Using MySQL
2
+
3
+ CandyPack provides a powerful query builder for safe and easy database operations.
4
+
5
+ ### Selecting Data
6
+
7
+ #### Get All Records
8
+
9
+ ```javascript
10
+ const users = await Candy.Mysql.table('users').get()
11
+ ```
12
+
13
+ #### Get First Record
14
+
15
+ ```javascript
16
+ const user = await Candy.Mysql.table('users').where('id', 1).first()
17
+ ```
18
+
19
+ #### Select Specific Columns
20
+
21
+ ```javascript
22
+ const users = await Candy.Mysql.table('users')
23
+ .select('id', 'name', 'email')
24
+ .get()
25
+ ```
26
+
27
+ ### Where Conditions
28
+
29
+ ```javascript
30
+ // Single condition
31
+ const users = await Candy.Mysql.table('users')
32
+ .where('status', 'active')
33
+ .get()
34
+
35
+ // Multiple conditions (AND)
36
+ const users = await Candy.Mysql.table('users')
37
+ .where('status', 'active')
38
+ .where('verified', 1)
39
+ .get()
40
+
41
+ // OR condition
42
+ const users = await Candy.Mysql.table('users')
43
+ .where('role', 'admin')
44
+ .orWhere('role', 'moderator')
45
+ .get()
46
+
47
+ // Comparison operators
48
+ const products = await Candy.Mysql.table('products')
49
+ .where('price', '>', 100)
50
+ .where('stock', '<=', 10)
51
+ .get()
52
+
53
+ // LIKE
54
+ const users = await Candy.Mysql.table('users')
55
+ .where('name', 'LIKE', '%John%')
56
+ .get()
57
+
58
+ // IN
59
+ const users = await Candy.Mysql.table('users')
60
+ .where('role', 'IN', ['admin', 'moderator'])
61
+ .get()
62
+ ```
63
+
64
+ ### Complex Where Conditions
65
+
66
+ For complex queries with nested AND/OR conditions, use arrays:
67
+
68
+ ```javascript
69
+ // SQL: WHERE (status = 'active' AND verified = 1) OR (role = 'admin')
70
+ const users = await Candy.Mysql.table('users')
71
+ .where([
72
+ ['status', 'active'],
73
+ ['verified', 1]
74
+ ])
75
+ .orWhere(['role', 'admin'])
76
+ .get()
77
+
78
+ // SQL: WHERE status = 'active' AND (role = 'admin' OR role = 'moderator')
79
+ const users = await Candy.Mysql.table('users')
80
+ .where('status', 'active')
81
+ .where([
82
+ ['role', 'admin'],
83
+ 'OR',
84
+ ['role', 'moderator']
85
+ ])
86
+ .get()
87
+
88
+ // Complex nested conditions
89
+ // SQL: WHERE (status = 'active' AND (role = 'admin' OR role = 'moderator'))
90
+ // AND (verified = 1 OR email_verified = 1)
91
+ const users = await Candy.Mysql.table('users')
92
+ .where([
93
+ ['status', 'active'],
94
+ [
95
+ ['role', 'admin'],
96
+ 'OR',
97
+ ['role', 'moderator']
98
+ ]
99
+ ])
100
+ .where([
101
+ ['verified', 1],
102
+ 'OR',
103
+ ['email_verified', 1]
104
+ ])
105
+ .get()
106
+ ```
107
+
108
+ ### Ordering and Limiting
109
+
110
+ ```javascript
111
+ // Order by
112
+ const users = await Candy.Mysql.table('users')
113
+ .order('created_at', 'desc')
114
+ .get()
115
+
116
+ // Limit
117
+ const users = await Candy.Mysql.table('users')
118
+ .limit(10)
119
+ .get()
120
+
121
+ // Pagination
122
+ const page = 2
123
+ const perPage = 20
124
+ const users = await Candy.Mysql.table('users')
125
+ .limit((page - 1) * perPage, perPage)
126
+ .get()
127
+ ```
128
+
129
+ ### Counting Records
130
+
131
+ ```javascript
132
+ const userCount = await Candy.Mysql.table('users')
133
+ .where('status', 'active')
134
+ .rows()
135
+ ```
136
+
137
+ ### Joins
138
+
139
+ ```javascript
140
+ const orders = await Candy.Mysql.table('orders')
141
+ .leftJoin('users', 'orders.user_id', 'users.id')
142
+ .select('orders.*', 'users.name', 'users.email')
143
+ .get()
144
+ ```
145
+
146
+ ### Inserting Data
147
+
148
+ ```javascript
149
+ // Insert single record
150
+ const result = await Candy.Mysql.table('users').insert({
151
+ name: 'John Doe',
152
+ email: 'john@example.com',
153
+ status: 'active'
154
+ })
155
+
156
+ console.log(result.id) // Inserted ID
157
+ console.log(result.affected) // Affected rows
158
+ ```
159
+
160
+ ### Updating Data
161
+
162
+ ```javascript
163
+ const result = await Candy.Mysql.table('users')
164
+ .where('id', 1)
165
+ .set({
166
+ name: 'Jane Doe',
167
+ email: 'jane@example.com'
168
+ })
169
+
170
+ console.log(result.affected) // Number of updated rows
171
+ ```
172
+
173
+ ### Deleting Data
174
+
175
+ ```javascript
176
+ const result = await Candy.Mysql.table('users')
177
+ .where('id', 1)
178
+ .delete()
179
+
180
+ console.log(result.affected) // Number of deleted rows
181
+ ```
182
+
183
+ ### Raw Queries
184
+
185
+ For complex queries, use raw SQL:
186
+
187
+ ```javascript
188
+ // Raw value in select
189
+ const users = await Candy.Mysql.table('users')
190
+ .select('id', 'name', Candy.Mysql.raw('COUNT(*) as total'))
191
+ .get()
192
+
193
+ // Raw query with parameters (SAFE - recommended)
194
+ const result = await Candy.Mysql.run('SELECT * FROM users WHERE status = ?', ['active'])
195
+ ```
196
+
197
+ #### ⚠️ Security Warning: Mysql.raw()
198
+
199
+ **CRITICAL**: `Mysql.raw()` bypasses ALL SQL injection protection. Only use with hardcoded, trusted values.
200
+
201
+ ```javascript
202
+ // ❌ DANGEROUS - Never do this!
203
+ const userInput = await Candy.request('search')
204
+ const users = await Candy.Mysql.table('users')
205
+ .where('name', Candy.Mysql.raw(userInput)) // SQL INJECTION RISK!
206
+ .get()
207
+
208
+ // ✅ SAFE - Use query builder instead
209
+ const userInput = await Candy.request('search')
210
+ const users = await Candy.Mysql.table('users')
211
+ .where('name', 'LIKE', `%${userInput}%`) // Automatically escaped
212
+ .get()
213
+
214
+ // ✅ SAFE - Use with hardcoded values only
215
+ const users = await Candy.Mysql.table('users')
216
+ .select('id', 'name', Candy.Mysql.raw('COUNT(*) as total')) // OK - hardcoded
217
+ .get()
218
+
219
+ // ✅ SAFE - Use parameterized queries for dynamic values
220
+ const status = await Candy.request('status')
221
+ const result = await Candy.Mysql.run(
222
+ 'SELECT * FROM users WHERE status = ?',
223
+ [status] // Automatically escaped
224
+ )
225
+ ```
226
+
227
+ **When to use raw()**:
228
+ - Aggregate functions: `COUNT(*)`, `SUM(price)`, `AVG(rating)`
229
+ - Database functions: `NOW()`, `CONCAT()`, `DATE_FORMAT()`
230
+ - Complex expressions that can't be built with query builder
231
+
232
+ **Never use raw() with**:
233
+ - User input from forms, URLs, or cookies
234
+ - Data from external APIs
235
+ - Any untrusted source
236
+
237
+ ### Group By
238
+
239
+ ```javascript
240
+ const stats = await Candy.Mysql.table('orders')
241
+ .select('user_id', Candy.Mysql.raw('COUNT(*) as order_count'))
242
+ .groupBy('user_id')
243
+ .get()
244
+ ```
245
+
246
+ ### Complete Example
247
+
248
+ ```javascript
249
+ module.exports = async function (Candy) {
250
+ const page = await Candy.request('page') || 1
251
+ const perPage = 20
252
+ const search = await Candy.request('search')
253
+
254
+ // Build query
255
+ let query = Candy.Mysql.table('products')
256
+ .select('id', 'name', 'price', 'stock')
257
+ .where('status', 'active')
258
+
259
+ // Add search filter
260
+ if (search) {
261
+ query = query.where('name', 'LIKE', `%${search}%`)
262
+ }
263
+
264
+ // Get products
265
+ const products = await query
266
+ .order('created_at', 'desc')
267
+ .limit((page - 1) * perPage, perPage)
268
+ .get()
269
+
270
+ // Get total count
271
+ const totalCount = await Candy.Mysql.table('products')
272
+ .where('status', 'active')
273
+ .rows()
274
+
275
+ Candy.set({
276
+ products: products,
277
+ currentPage: page,
278
+ totalPages: Math.ceil(totalCount / perPage)
279
+ })
280
+
281
+ Candy.View.set({
282
+ skeleton: 'main',
283
+ content: 'products.list'
284
+ })
285
+ }
286
+ ```
287
+
288
+ ### Best Practices
289
+
290
+ 1. **Always use the query builder** - It protects against SQL injection
291
+ 2. **Never use Mysql.raw() with user input** - Only use with hardcoded values
292
+ 3. **Use parameterized queries** - When using `Mysql.run()`, always pass parameters as array
293
+ 4. **Use async/await** - All database methods are asynchronous
294
+ 5. **Handle errors** - Wrap database calls in try/catch blocks
295
+ 6. **Limit results** - Use `.limit()` to prevent loading too much data
296
+ 7. **Select only needed columns** - Use `.select()` instead of selecting all columns
297
+
298
+ ### Error Handling
299
+
300
+ ```javascript
301
+ module.exports = async function (Candy) {
302
+ try {
303
+ const user = await Candy.Mysql.table('users')
304
+ .where('id', await Candy.request('id'))
305
+ .first()
306
+
307
+ if (!user) {
308
+ Candy.set('error', 'User not found')
309
+ } else {
310
+ Candy.set('user', user)
311
+ }
312
+ } catch (error) {
313
+ console.error('Database error:', error)
314
+ Candy.set('error', 'An error occurred')
315
+ }
316
+
317
+ Candy.View.set({
318
+ skeleton: 'main',
319
+ content: 'user.profile'
320
+ })
321
+ }
322
+ ```