miolo 3.0.0-beta.21 → 3.0.0-beta.210

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 (246) hide show
  1. package/bin/build/build.mjs +53 -0
  2. package/bin/build/build_bin.mjs +17 -0
  3. package/bin/build/cli/client.mjs +52 -0
  4. package/bin/build/cli/css.mjs +22 -0
  5. package/bin/build/cli/index.mjs +40 -0
  6. package/bin/build/cli/ssr.mjs +21 -0
  7. package/bin/build/server/aliases.mjs +112 -0
  8. package/bin/build/server/babel.config.js +24 -0
  9. package/bin/build/server/banner.mjs +20 -0
  10. package/bin/build/server/bundle.mjs +111 -0
  11. package/bin/build/server/fix.mjs +15 -0
  12. package/bin/build/server/index.mjs +69 -0
  13. package/bin/build/server/options.mjs +83 -0
  14. package/bin/create/auth.mjs +23 -0
  15. package/bin/create/copy.mjs +175 -0
  16. package/bin/create/docker.mjs +25 -0
  17. package/bin/create/index.mjs +137 -0
  18. package/bin/create/pkgjson.mjs +72 -0
  19. package/bin/create/prepare-template.mjs +158 -0
  20. package/bin/create/validation.mjs +27 -0
  21. package/bin/dev/dev.mjs +32 -23
  22. package/bin/dev/dev_start.mjs +6 -5
  23. package/bin/index.mjs +94 -52
  24. package/bin/prod-bin/create-bin.mjs +42 -27
  25. package/bin/prod-bin/run.mjs +13 -9
  26. package/bin/{prod-run → run}/pid.mjs +4 -4
  27. package/bin/run/restart.mjs +13 -0
  28. package/bin/run/start.mjs +18 -0
  29. package/bin/run/stop.mjs +20 -0
  30. package/bin/util.mjs +35 -11
  31. package/package.json +59 -39
  32. package/src/config/.env +34 -12
  33. package/src/config/defaults.mjs +253 -185
  34. package/src/config/env.mjs +40 -22
  35. package/src/config/index.mjs +19 -24
  36. package/src/config/util.mjs +25 -10
  37. package/src/db-conn.mjs +34 -0
  38. package/src/engines/cron/emails.mjs +10 -5
  39. package/src/engines/cron/index.mjs +45 -51
  40. package/src/engines/cron/init.mjs +16 -17
  41. package/src/engines/cron/ipsum.mjs +65 -60
  42. package/src/engines/cron/syscheck.mjs +30 -30
  43. package/src/engines/emailer/index.mjs +1 -2
  44. package/src/engines/emailer/queue.mjs +14 -20
  45. package/src/engines/emailer/transporter.mjs +86 -74
  46. package/src/engines/geoip/index.mjs +23 -28
  47. package/src/engines/http/index.mjs +26 -15
  48. package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
  49. package/src/engines/logger/index.mjs +114 -122
  50. package/src/engines/logger/injectStackTrace.mjs +59 -0
  51. package/src/engines/logger/logger_mail.mjs +47 -61
  52. package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
  53. package/src/engines/parser/Parser.mjs +77 -60
  54. package/src/engines/parser/index.mjs +1 -1
  55. package/src/engines/schema/diffObjs.mjs +41 -0
  56. package/src/engines/schema/index.mjs +4 -0
  57. package/src/engines/schema/input.mjs +54 -0
  58. package/src/engines/schema/output.mjs +66 -0
  59. package/src/engines/socket/index.mjs +44 -46
  60. package/src/index.mjs +15 -10
  61. package/src/middleware/auth/basic.mjs +41 -40
  62. package/src/middleware/auth/custom.mjs +10 -13
  63. package/src/middleware/auth/guest.mjs +27 -27
  64. package/src/middleware/auth/passport/index.mjs +374 -0
  65. package/src/middleware/auth/passport/session/index.mjs +43 -0
  66. package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
  67. package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
  68. package/src/middleware/context/cache/index.mjs +78 -33
  69. package/src/middleware/context/cache/options.mjs +19 -21
  70. package/src/middleware/context/db.mjs +45 -20
  71. package/src/middleware/context/index.mjs +12 -12
  72. package/src/middleware/extra.mjs +4 -5
  73. package/src/middleware/http/body.mjs +25 -25
  74. package/src/middleware/http/catcher.mjs +81 -8
  75. package/src/middleware/http/custom_blacklist.mjs +19 -16
  76. package/src/middleware/http/headers.mjs +37 -34
  77. package/src/middleware/http/ratelimit.mjs +16 -23
  78. package/src/middleware/http/request.mjs +60 -65
  79. package/src/middleware/routes/catch_js_error.mjs +30 -23
  80. package/src/middleware/routes/robots.mjs +4 -7
  81. package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
  82. package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
  83. package/src/middleware/routes/router/defaults.mjs +6 -19
  84. package/src/middleware/routes/router/index.mjs +17 -21
  85. package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
  86. package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
  87. package/src/middleware/routes/router/utils.mjs +41 -26
  88. package/src/middleware/ssr/context.mjs +5 -7
  89. package/src/middleware/ssr/html.mjs +66 -43
  90. package/src/middleware/ssr/loader.mjs +11 -14
  91. package/src/middleware/ssr/ssr_render.mjs +39 -22
  92. package/src/middleware/static/index.mjs +33 -14
  93. package/src/middleware/vite/devserver.mjs +38 -22
  94. package/src/middleware/vite/watcher.mjs +12 -14
  95. package/src/server-cron.mjs +13 -8
  96. package/src/server-dev.mjs +13 -16
  97. package/src/server.mjs +49 -51
  98. package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
  99. package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
  100. package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
  101. package/template/.agent/skills/miolo-database/SKILL.md +358 -0
  102. package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
  103. package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
  104. package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
  105. package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
  106. package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
  107. package/template/.editorconfig +18 -0
  108. package/template/.env +120 -0
  109. package/template/biome.json +63 -0
  110. package/template/components.json +21 -0
  111. package/template/db/init.sh +89 -0
  112. package/template/db/sql/00_drop.sql +2 -0
  113. package/template/db/sql/01_users.sql +31 -0
  114. package/template/db/sql/02_todos.sql +20 -0
  115. package/template/docker/Dockerfile +13 -0
  116. package/template/docker/docker-compose.yaml +79 -0
  117. package/template/gitignore +42 -0
  118. package/template/jsconfig.json +18 -0
  119. package/template/package.json +88 -0
  120. package/template/postcss.config.js +9 -0
  121. package/template/src/cli/App.jsx +25 -0
  122. package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
  123. package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
  124. package/template/src/cli/components/stepper.jsx +408 -0
  125. package/template/src/cli/components/ui/avatar.jsx +36 -0
  126. package/template/src/cli/components/ui/badge.jsx +31 -0
  127. package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
  128. package/template/src/cli/components/ui/card.jsx +73 -0
  129. package/template/src/cli/components/ui/collapsible.jsx +16 -0
  130. package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
  131. package/template/src/cli/components/ui/field.jsx +217 -0
  132. package/template/src/cli/components/ui/input.jsx +19 -0
  133. package/template/src/cli/components/ui/label.jsx +17 -0
  134. package/template/src/cli/components/ui/pagination.jsx +99 -0
  135. package/template/src/cli/components/ui/patched/alert.jsx +56 -0
  136. package/template/src/cli/components/ui/patched/button.jsx +45 -0
  137. package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
  138. package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
  139. package/template/src/cli/components/ui/select.jsx +141 -0
  140. package/template/src/cli/components/ui/separator.jsx +21 -0
  141. package/template/src/cli/components/ui/sheet.jsx +115 -0
  142. package/template/src/cli/components/ui/skeleton.jsx +13 -0
  143. package/template/src/cli/components/ui/sonner.jsx +22 -0
  144. package/template/src/cli/components/ui/switch.jsx +25 -0
  145. package/template/src/cli/components/ui/table.jsx +88 -0
  146. package/template/src/cli/components/ui/textarea.jsx +16 -0
  147. package/template/src/cli/components/ui/tooltip.jsx +45 -0
  148. package/template/src/cli/config/store_keys.mjs +2 -0
  149. package/template/src/cli/context/data/DataContext.jsx +5 -0
  150. package/template/src/cli/context/data/DataProvider.jsx +44 -0
  151. package/template/src/cli/context/data/useBreads.mjs +15 -0
  152. package/template/src/cli/context/data/useDataContext.mjs +4 -0
  153. package/template/src/cli/context/session/SessionContext.mjs +4 -0
  154. package/template/src/cli/context/session/SessionProvider.jsx +31 -0
  155. package/template/src/cli/context/session/makePermissioner.mjs +34 -0
  156. package/template/src/cli/context/session/useSessionContext.mjs +6 -0
  157. package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
  158. package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
  159. package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
  160. package/template/src/cli/context/ui/UIContext.jsx +5 -0
  161. package/template/src/cli/context/ui/UIProvider.jsx +16 -0
  162. package/template/src/cli/context/ui/useUIContext.mjs +4 -0
  163. package/template/src/cli/context/util.mjs +17 -0
  164. package/template/src/cli/entry-cli.jsx +33 -0
  165. package/template/src/cli/hooks/useIsMobile.mjs +19 -0
  166. package/template/src/cli/hooks/useStoragedState.mjs +63 -0
  167. package/template/src/cli/index.html +29 -0
  168. package/template/src/cli/layout/app-sidebar.jsx +25 -0
  169. package/template/src/cli/layout/main-layout.jsx +63 -0
  170. package/template/src/cli/layout/nav-last-todos.jsx +72 -0
  171. package/template/src/cli/layout/nav-main.jsx +39 -0
  172. package/template/src/cli/layout/nav-user.jsx +105 -0
  173. package/template/src/cli/layout/prop-switcher.jsx +93 -0
  174. package/template/src/cli/lib/utils.mjs +10 -0
  175. package/template/src/cli/pages/Index.jsx +13 -0
  176. package/template/src/cli/pages/IndexOffline.jsx +13 -0
  177. package/template/src/cli/pages/IndexOnline.jsx +18 -0
  178. package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
  179. package/template/src/cli/pages/offline/Login.jsx +43 -0
  180. package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
  181. package/template/src/cli/pages/security/Security.jsx +39 -0
  182. package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
  183. package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
  184. package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
  185. package/template/src/cli/pages/todos/TodoList.jsx +60 -0
  186. package/template/src/cli/pages/todos/Todos.jsx +23 -0
  187. package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
  188. package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
  189. package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
  190. package/template/src/ns/models/Todo.mjs +29 -0
  191. package/template/src/ns/models/TodoList.mjs +8 -0
  192. package/template/src/ns/models/User.mjs +40 -0
  193. package/template/src/server/bot/check_today.mjs +10 -0
  194. package/template/src/server/io/cache/base.mjs +21 -0
  195. package/template/src/server/io/db/filter.mjs +92 -0
  196. package/template/src/server/io/db/todos/delete.mjs +29 -0
  197. package/template/src/server/io/db/todos/find.mjs +13 -0
  198. package/template/src/server/io/db/todos/read.mjs +83 -0
  199. package/template/src/server/io/db/todos/toggle.mjs +37 -0
  200. package/template/src/server/io/db/todos/upsave.mjs +32 -0
  201. package/template/src/server/io/db/triggers/user.mjs +13 -0
  202. package/template/src/server/io/db/users/auth.mjs +132 -0
  203. package/template/src/server/io/db/users/pwd.mjs +38 -0
  204. package/template/src/server/io/db/users/save.mjs +17 -0
  205. package/template/src/server/miolo/auth/basic.mjs +15 -0
  206. package/template/src/server/miolo/auth/guest.mjs +3 -0
  207. package/template/src/server/miolo/auth/passport.mjs +73 -0
  208. package/template/src/server/miolo/cache.mjs +11 -0
  209. package/template/src/server/miolo/cron/foo.mjs +7 -0
  210. package/template/src/server/miolo/cron/index.mjs +28 -0
  211. package/template/src/server/miolo/cron/invalidate.mjs +21 -0
  212. package/template/src/server/miolo/db.mjs +36 -0
  213. package/template/src/server/miolo/http.mjs +14 -0
  214. package/template/src/server/miolo/index.mjs +43 -0
  215. package/template/src/server/miolo/routes/crud.mjs +16 -0
  216. package/template/src/server/miolo/routes/index.mjs +8 -0
  217. package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
  218. package/template/src/server/miolo/ssr/loader.mjs +18 -0
  219. package/template/src/server/routes/index.mjs +66 -0
  220. package/template/src/server/routes/todos/mod.mjs +52 -0
  221. package/template/src/server/routes/todos/read.mjs +45 -0
  222. package/template/src/server/routes/todos/special.mjs +47 -0
  223. package/template/src/server/routes/users/user.mjs +54 -0
  224. package/template/src/server/server.mjs +10 -0
  225. package/template/src/server/utils/crypt.mjs +38 -0
  226. package/template/src/server/utils/io.mjs +15 -0
  227. package/template/src/server/utils/pwdfor.mjs +25 -0
  228. package/template/src/server/utils/schema.mjs +22 -0
  229. package/template/src/static/img/default/profile.png +0 -0
  230. package/template/src/static/img/favicon.ico +0 -0
  231. package/template/src/static/img/miolo_logo.png +0 -0
  232. package/template/src/static/img/miolo_name.png +0 -0
  233. package/template/src/static/public/manifest.json +21 -0
  234. package/template/src/static/public/sw.js +79 -0
  235. package/template/src/static/style/globals.css +156 -0
  236. package/template/src/static/style/json-tree.css +54 -0
  237. package/template/src/static/style/skeleton.css +49 -0
  238. package/bin/prod-build/build-client.mjs +0 -67
  239. package/bin/prod-build/build-server.mjs +0 -58
  240. package/bin/prod-run/restart.mjs +0 -9
  241. package/bin/prod-run/start.mjs +0 -15
  242. package/bin/prod-run/stop.mjs +0 -20
  243. package/src/engines/logger/verify.mjs +0 -22
  244. package/src/middleware/auth/credentials/index.mjs +0 -151
  245. package/src/middleware/auth/credentials/session/index.mjs +0 -24
  246. package/src/middleware/auth/credentials/session/store_koa_redis.mjs +0 -3
@@ -0,0 +1,433 @@
1
+ ---
2
+ name: miolo-ssr
3
+ description: Server-Side Rendering (SSR) patterns for miolo applications. Use when implementing data preloading on the server, configuring SSR loader, or using useSsrData hook for client-side hydration and remote data loading.
4
+ ---
5
+
6
+ # Miolo Server-Side Rendering (SSR)
7
+
8
+ Server-side data preloading and client-side hydration patterns for miolo applications.
9
+
10
+ ## Backend - SSR Loader
11
+
12
+ The SSR loader preloads data on the server before sending the initial HTML to the client.
13
+
14
+ ### Loader Configuration
15
+
16
+ **File:** `src/server/miolo/ssr/loader.mjs`
17
+
18
+ ```javascript
19
+ import { db_todo_read } from '#server/io/db/todos/read.mjs'
20
+ import { db_user_profile } from '#server/io/db/users/read.mjs'
21
+
22
+ const loader = async (ctx) => {
23
+ let todos = []
24
+ let profile = null
25
+
26
+ try {
27
+ // Load data using database functions
28
+ todos = await db_todo_read(ctx, {})
29
+ todos = todos.sort((a, b) => b.created_at - a.created_at)
30
+ } catch (_) {}
31
+
32
+ try {
33
+ if (ctx.session.user) {
34
+ profile = await db_user_profile(ctx, { id: ctx.session.user.id })
35
+ }
36
+ } catch (_) {}
37
+
38
+ // Return object with data
39
+ // Keys are important - they're used in client code
40
+ const data = {
41
+ todos, // Accessible as 'todos' in client
42
+ profile // Accessible as 'profile' in client
43
+ }
44
+
45
+ return data
46
+ }
47
+
48
+ export { loader }
49
+ ```
50
+
51
+ **Key points:**
52
+ - Export async function named `loader`
53
+ - Receives `ctx` with session and request data
54
+ - Returns object with data to preload
55
+ - **Object keys are important** - used as `<key>` in client `useSsrData()`
56
+ - Wrap in try/catch to handle errors gracefully
57
+
58
+ ### URL-Based Loading
59
+
60
+ Filter data based on current URL:
61
+
62
+ ```javascript
63
+ const loader = async (ctx) => {
64
+ const url = ctx.request.url
65
+
66
+ let data = {}
67
+
68
+ // Load different data based on route
69
+ if (url.startsWith('/dashboard')) {
70
+ try {
71
+ data.stats = await db_dashboard_stats(ctx)
72
+ data.recent = await db_recent_activity(ctx)
73
+ } catch (_) {}
74
+ } else if (url.startsWith('/profile')) {
75
+ try {
76
+ data.profile = await db_user_profile(ctx, { id: ctx.session.user.id })
77
+ data.settings = await db_user_settings(ctx, { id: ctx.session.user.id })
78
+ } catch (_) {}
79
+ }
80
+
81
+ return data
82
+ }
83
+ ```
84
+
85
+ **Access URL:**
86
+ - `ctx.request.url` - Current request URL
87
+ - Load only the data needed for the current page
88
+ - Reduces payload size and improves performance
89
+
90
+ ### User-Specific Loading
91
+
92
+ Load different data based on authentication:
93
+
94
+ ```javascript
95
+ const loader = async (ctx) => {
96
+ let data = {
97
+ publicData: []
98
+ }
99
+
100
+ // Always load public data
101
+ try {
102
+ data.publicData = await db_public_items(ctx)
103
+ } catch (_) {}
104
+
105
+ // Load user-specific data if authenticated
106
+ if (ctx.session.authenticated) {
107
+ try {
108
+ data.userItems = await db_user_items(ctx, {
109
+ user_id: ctx.session.user.id
110
+ })
111
+ data.notifications = await db_user_notifications(ctx, {
112
+ user_id: ctx.session.user.id
113
+ })
114
+ } catch (_) {}
115
+ }
116
+
117
+ return data
118
+ }
119
+ ```
120
+
121
+ ## Client-Side - useSsrData Hook
122
+
123
+ The `useSsrData` hook manages SSR-preloaded data and provides remote loading fallback.
124
+
125
+ ### Hook Signature
126
+
127
+ ```javascript
128
+ const [data, setData, refreshData] = useSsrData(
129
+ '<key>', // Key from SSR loader object
130
+ defaultValue, // Default value if no data
131
+ async (context, fetcher) => { // Remote loader (fallback)
132
+ // Load data from API if not in SSR
133
+ const response = await fetcher.get('/api/data')
134
+ return processData(response.data)
135
+ }
136
+ )
137
+ ```
138
+
139
+ **Parameters:**
140
+ 1. **`<key>`** (string) - Key name from SSR loader object
141
+ 2. **`defaultValue`** (any) - Default value when no data available
142
+ 3. **`remoteLoader`** (async function) - Fallback function to load data remotely
143
+
144
+ **Returns:** `[data, setData, refreshData]`
145
+ - **`data`** - Current data (React state)
146
+ - **`setData`** - Function to set data directly (like setState)
147
+ - **`refreshData`** - Function to re-execute remote loader
148
+
149
+ ### Access useSsrData
150
+
151
+ Available from two sources:
152
+
153
+ **1. From useMioloContext:**
154
+ ```javascript
155
+ import { useMioloContext } from 'miolo-react'
156
+
157
+ function MyComponent() {
158
+ const { useSsrData } = useMioloContext()
159
+
160
+ const [todos, setTodos, refreshTodos] = useSsrData('todos', [], async (context, fetcher) => {
161
+ const { data } = await fetcher.get('/api/todo/list')
162
+ return data
163
+ })
164
+
165
+ return <div>{todos.length} todos</div>
166
+ }
167
+ ```
168
+
169
+ **2. From useSessionContext:**
170
+ ```javascript
171
+ import useSessionContext from '#cli/context/session/useSessionContext.mjs'
172
+
173
+ function MyComponent() {
174
+ const { useSsrData } = useSessionContext()
175
+
176
+ const [profile, setProfile, refreshProfile] = useSsrData('profile', null, async (context, fetcher) => {
177
+ const { data } = await fetcher.get('/api/user/profile')
178
+ return data
179
+ })
180
+
181
+ return <div>{profile?.name}</div>
182
+ }
183
+ ```
184
+
185
+ ### Basic Usage Example
186
+
187
+ **DataProvider using useSsrData:**
188
+
189
+ ```javascript
190
+ import { useState } from 'react'
191
+ import useSessionContext from '#cli/context/session/useSessionContext.mjs'
192
+ import TodoList from '#ns/models/TodoList.mjs'
193
+ import DataContext from './DataContext.jsx'
194
+
195
+ const DataProvider = ({ children }) => {
196
+ const [status, setStatus] = useState('loaded')
197
+ const { useSsrData } = useSessionContext()
198
+
199
+ const [todos, _setTodos, refreshTodos] = useSsrData('todos', [], async (context, fetcher) => {
200
+ setStatus('loading')
201
+ const { data: nTodos } = await fetcher.get('/api/todo/list')
202
+ setStatus('loaded')
203
+ return new TodoList(nTodos)
204
+ })
205
+
206
+ return (
207
+ <DataContext.Provider value={{
208
+ todos,
209
+ refreshTodos,
210
+ loading: status !== 'loaded',
211
+ loaded: status === 'loaded'
212
+ }}>
213
+ {children}
214
+ </DataContext.Provider>
215
+ )
216
+ }
217
+
218
+ export default DataProvider
219
+ ```
220
+
221
+ **How it works:**
222
+ 1. **SSR hit** - Miolo finds `'todos'` in SSR loader, uses that data
223
+ 2. **SSR miss** - Key not found in SSR, executes remote loader
224
+ 3. **Client refresh** - Calling `refreshTodos()` re-executes remote loader
225
+
226
+ ### Remote Loader Function
227
+
228
+ The third parameter to `useSsrData` is only executed when:
229
+ - Data is not available in SSR loader (no matching key)
230
+ - User calls `refreshData()` to reload
231
+
232
+ ```javascript
233
+ const [items, setItems, refreshItems] = useSsrData(
234
+ 'items',
235
+ [],
236
+ async (context, fetcher) => {
237
+ // This runs ONLY if:
238
+ // 1. SSR loader didn't provide 'items' data
239
+ // 2. User calls refreshItems()
240
+
241
+ const { data } = await fetcher.get('/api/items/list')
242
+
243
+ // Can transform data before returning
244
+ return data.map(item => new ItemModel(item))
245
+ }
246
+ )
247
+ ```
248
+
249
+ **Remote loader parameters:**
250
+ - **`context`** - Application context
251
+ - **`fetcher`** - Authenticated fetch wrapper (see miolo-fetcher skill)
252
+
253
+ ### Setting Data Manually
254
+
255
+ Use `setData` to update state directly:
256
+
257
+ ```javascript
258
+ const [todos, setTodos, refreshTodos] = useSsrData('todos', [], remoteLoader)
259
+
260
+ const handleAddTodo = async (newTodo) => {
261
+ // Optimistic update
262
+ setTodos([...todos, newTodo])
263
+
264
+ // Save to backend
265
+ const response = await fetcher.post('/api/todo/save', newTodo)
266
+
267
+ if (!response.ok) {
268
+ // Rollback on error
269
+ await refreshTodos()
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Refreshing Data
275
+
276
+ Call `refreshData` to re-execute the remote loader:
277
+
278
+ ```javascript
279
+ const [notifications, setNotifications, refreshNotifications] = useSsrData(
280
+ 'notifications',
281
+ [],
282
+ async (context, fetcher) => {
283
+ const { data } = await fetcher.get('/api/notifications')
284
+ return data
285
+ }
286
+ )
287
+
288
+ // Manually refresh
289
+ const handleRefresh = () => {
290
+ refreshNotifications() // Re-executes remote loader
291
+ }
292
+
293
+ // Auto-refresh every 30 seconds
294
+ useEffect(() => {
295
+ const interval = setInterval(refreshNotifications, 30000)
296
+ return () => clearInterval(interval)
297
+ }, [])
298
+ ```
299
+
300
+ ### Complex Data Transformations
301
+
302
+ Transform data in remote loader:
303
+
304
+ ```javascript
305
+ import TodoList from '#ns/models/TodoList.mjs'
306
+
307
+ const [todos, setTodos, refreshTodos] = useSsrData(
308
+ 'todos',
309
+ new TodoList([]),
310
+ async (context, fetcher) => {
311
+ const { data: rawTodos } = await fetcher.get('/api/todo/list')
312
+
313
+ // Transform to model class
314
+ const todoList = new TodoList(rawTodos)
315
+
316
+ // Apply filters
317
+ todoList.filterByStatus('active')
318
+
319
+ // Sort
320
+ todoList.sortByDate('desc')
321
+
322
+ return todoList
323
+ }
324
+ )
325
+ ```
326
+
327
+ ### Multiple Data Sources
328
+
329
+ Load multiple independent data sources:
330
+
331
+ ```javascript
332
+ const DataProvider = ({ children }) => {
333
+ const { useSsrData } = useSessionContext()
334
+
335
+ const [todos, setTodos, refreshTodos] = useSsrData('todos', [], loadTodos)
336
+ const [profile, setProfile, refreshProfile] = useSsrData('profile', null, loadProfile)
337
+ const [stats, setStats, refreshStats] = useSsrData('stats', {}, loadStats)
338
+
339
+ return (
340
+ <DataContext.Provider value={{
341
+ todos, refreshTodos,
342
+ profile, refreshProfile,
343
+ stats, refreshStats
344
+ }}>
345
+ {children}
346
+ </DataContext.Provider>
347
+ )
348
+ }
349
+ ```
350
+
351
+ ## Best Practices
352
+
353
+ ### Backend (SSR Loader)
354
+
355
+ 1. **Wrap in try/catch** - Always handle errors gracefully, don't crash SSR
356
+ 2. **Return default values** - Provide sensible defaults (empty arrays, null, etc.)
357
+ 3. **Use meaningful keys** - Choose clear, descriptive names for data keys
358
+ 4. **Load only needed data** - Filter by URL to reduce payload size
359
+ 5. **Check authentication** - Load user-specific data only if authenticated
360
+ 6. **Keep it fast** - SSR loader blocks initial render, optimize queries
361
+
362
+ ### Client (useSsrData)
363
+
364
+ 1. **Provide default values** - Always specify sensible defaults
365
+ 2. **Transform in remote loader** - Process data before returning
366
+ 3. **Handle loading states** - Track loading status in remote loader
367
+ 4. **Use setData for optimistic updates** - Update UI immediately, sync later
368
+ 5. **Refresh on mutations** - Call refreshData after creating/updating data
369
+ 6. **Don't over-refresh** - Only refresh when data might have changed
370
+
371
+ ## Common Patterns
372
+
373
+ ### Loading State Management
374
+
375
+ ```javascript
376
+ const DataProvider = ({ children }) => {
377
+ const [status, setStatus] = useState('loaded')
378
+ const { useSsrData } = useSessionContext()
379
+
380
+ const [data, setData, refreshData] = useSsrData('data', [], async (context, fetcher) => {
381
+ setStatus('loading')
382
+ try {
383
+ const response = await fetcher.get('/api/data')
384
+ setStatus('loaded')
385
+ return response.data
386
+ } catch (error) {
387
+ setStatus('error')
388
+ return []
389
+ }
390
+ })
391
+
392
+ return (
393
+ <DataContext.Provider value={{
394
+ data,
395
+ refreshData,
396
+ loading: status === 'loading',
397
+ loaded: status === 'loaded',
398
+ error: status === 'error'
399
+ }}>
400
+ {children}
401
+ </DataContext.Provider>
402
+ )
403
+ }
404
+ ```
405
+
406
+ ### Conditional Loading
407
+
408
+ ```javascript
409
+ const loader = async (ctx) => {
410
+ const data = {}
411
+
412
+ // Only load heavy data for specific routes
413
+ if (ctx.request.url.includes('/admin')) {
414
+ try {
415
+ data.adminStats = await db_admin_stats(ctx)
416
+ } catch (_) {}
417
+ }
418
+
419
+ return data
420
+ }
421
+ ```
422
+
423
+ ## Related Skills
424
+
425
+ - **miolo-session-context** - Access `useSsrData` from session context
426
+ - **miolo-fetcher** - Fetcher usage in remote loaders (future skill)
427
+ - **miolo-react-patterns** - Context provider patterns
428
+
429
+ ## Examples from miolo-sample
430
+
431
+ See actual implementations:
432
+ - `src/server/miolo/ssr/loader.mjs` - SSR loader configuration
433
+ - `src/cli/context/data/DataProvider.jsx` - useSsrData usage
@@ -0,0 +1,18 @@
1
+ root = true
2
+ charset = utf-8
3
+
4
+ [*]
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+
8
+ [*.{js,jsx}]
9
+ indent_style = space
10
+ indent_size = 2
11
+
12
+ [*.{css,scss}]
13
+ indent_style = space
14
+ indent_size = 2
15
+
16
+ [{package.json,.travis.yml}]
17
+ indent_style = space
18
+ indent_size = 2
package/template/.env ADDED
@@ -0,0 +1,120 @@
1
+ #
2
+ # General
3
+ #
4
+
5
+ MIOLO_NAME=miolo-sample
6
+ MIOLO_INTRE_LOCALE=es
7
+
8
+ #
9
+ # HTTP
10
+ #
11
+
12
+ MIOLO_PORT=8001
13
+ MIOLO_HOSTNAME=localhost
14
+ MIOLO_HOSTNAME_DOCKER=0.0.0.0
15
+ MIOLO_HTTP_CORS=simple # true | false | simple. For {options}, use JS
16
+ MIOLO_HTTP_PROXY=false # true | false. For {options}, use JS
17
+
18
+ MIOLO_RATELIMIT_WHITELIST_IPS=172.22.0.1
19
+ MIOLO_REQUEST_LAZY=2 # seconds to consider lazy a request
20
+ MIOLO_REQUEST_SLOW=4 # seconds to consider slow a request
21
+ MIOLO_GEOIP_ENABLED=true
22
+ MIOLO_GEOIP_LOCAL_IPS=127.0.0.1,172.22.0.1,172.19.0.1
23
+
24
+ #
25
+ # If google auth
26
+ #
27
+ MIOLO_AUTH_GOOGLE_CLIENT_ID=123456
28
+ MIOLO_AUTH_GOOGLE_CLIENT_SECRET=123456
29
+ MIOLO_AUTH_GOOGLE_CALLBACK_URL=/auth/google/callback
30
+
31
+ #
32
+ # Session
33
+ #
34
+
35
+ MIOLO_SESSION_KEY='miolo-sample.sess'
36
+ MIOLO_SESSION_SALT=00000000-0000-0000-0000-000000000000
37
+ MIOLO_SESSION_SECRET=00000000-0000-0000-0000-000000000000
38
+ MIOLO_SESSION_MAX_AGE=864000000
39
+ MIOLO_SESSION_SECURE=false
40
+ MIOLO_SESSION_RENEW=true
41
+ MIOLO_SESSION_SAME_SITE=lax # lax | strict
42
+
43
+ #
44
+ # Database
45
+ #
46
+
47
+ MIOLO_DB_DIALECT=postgres
48
+ MIOLO_DB_DATABASE=miolo-sample
49
+ MIOLO_DB_DOCKER_HOST=postgres
50
+ MIOLO_DB_HOST=localhost
51
+ MIOLO_DB_PORT=5432
52
+ MIOLO_DB_USER=postgres
53
+ MIOLO_DB_PASSWORD=postgres
54
+ MIOLO_DB_POOL_MAX=5
55
+ MIOLO_DB_POOL_MIN=0
56
+ MIOLO_DB_POOL_IDLE_TIMEOUT_MS=10000
57
+
58
+ #
59
+ # Logging
60
+ #
61
+
62
+ MIOLO_LOG_LEVEL=verbose
63
+ MIOLO_LOG_CONSOLE_ENABLED=true
64
+ MIOLO_LOG_FILE_ENABLED=false
65
+ MIOLO_LOG_MAIL_ENABLED=false
66
+ #MIOLO_LOG_MAIL_LEVEL=warn
67
+ MIOLO_LOG_MAIL_FROM=miolo-sample@miolo-sample.com
68
+ MIOLO_LOG_MAIL_TO=miolo-sample@miolo-sample.com
69
+
70
+ #
71
+ # Mailer
72
+ #
73
+
74
+ MIOLO_MAILER_SILENT=true
75
+ MIOLO_MAILER_HOST=mail.miolo-sample.com
76
+ MIOLO_MAILER_PORT=25
77
+ MIOLO_MAILER_FROM=miolo-sample@miolo-sample.com
78
+ MIOLO_MAILER_TO=miolo-sample@miolo-sample.com
79
+
80
+ # MIOLO_MAILER_AUTH_METHOD=PLAIN # PLAIN / LOGIN
81
+ # # If LOGIN, you need to specify:
82
+ # MIOLO_MAILER_SMTP_USER=noreply@mail.com
83
+ # MIOLO_MAILER_SMTP_PASS=****
84
+
85
+ #
86
+ # Cache
87
+ #
88
+
89
+ MIOLO_REDIS_HOSTNAME=127.0.0.1
90
+ MIOLO_REDIS_HOSTNAME_DOCKER=redis
91
+ MIOLO_REDIS_PORT=6379
92
+
93
+ MIOLO_CACHE_TYPE=redis
94
+ MIOLO_CACHE_VERSION=1
95
+ MIOLO_CACHE_CALUSTRA_VERSION=1
96
+ MIOLO_CACHE_CALUSTRA_TTL=86400000
97
+ MIOLO_CACHE_SESSION_VERSION=1
98
+ MIOLO_CACHE_SESSION_TTL=864000000
99
+
100
+ #
101
+ # Others
102
+ #
103
+
104
+ MIOLO_DOTENVX_DEBUG=false
105
+
106
+ #
107
+ # Build
108
+ #
109
+
110
+ MIOLO_BUILD_HTML_FILE=./src/cli/index.html
111
+ MIOLO_BUILD_CLIENT_ENTRY=./src/cli/entry-cli.jsx
112
+ MIOLO_BUILD_CLIENT_DEST=./build/cli
113
+ MIOLO_BUILD_CLIENT_SUFFIX=iife.bundle.min
114
+ MIOLO_BUILD_SERVER_SSR_ENTRY=./src/server/miolo/ssr/entry-server.jsx
115
+ MIOLO_BUILD_SERVER_ENTRY=./src/server/server.mjs
116
+ MIOLO_BUILD_SERVER_DEST=./build/server
117
+ MIOLO_BUILD_SERVER_EXT=node.bundle.mjs
118
+ MIOLO_BUILD_CONFIG_ENTRY=./src/server/miolo/index.mjs
119
+ MIOLO_DEV_WATCH_ENABLED=true
120
+ MIOLO_DEV_WATCH_DIRS=./src/server
@@ -0,0 +1,63 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
3
+ "files": {
4
+ "includes": [
5
+ "**/*",
6
+ "!dist",
7
+ "!build",
8
+ "!lib",
9
+ "!libs",
10
+ "!arredemo"
11
+ ]
12
+ },
13
+ "assist": {
14
+ "actions": {
15
+ "source": {
16
+ "organizeImports": "on"
17
+ }
18
+ }
19
+ },
20
+ "linter": {
21
+ "enabled": true,
22
+ "rules": {
23
+ "recommended": true,
24
+ "complexity": {
25
+ "noStaticOnlyClass": "off"
26
+ },
27
+ "suspicious": {
28
+ "noVar": "warn",
29
+ "noConsole": "off",
30
+ "noAssignInExpressions": "off"
31
+ },
32
+ "style": {
33
+ "useTemplate": "off"
34
+ },
35
+ "correctness": {
36
+ "noUnusedVariables": "warn",
37
+ "useExhaustiveDependencies": "warn",
38
+ "useHookAtTopLevel": "error"
39
+ }
40
+ }
41
+ },
42
+ "formatter": {
43
+ "enabled": true,
44
+ "indentStyle": "space",
45
+ "indentWidth": 2,
46
+ "lineWidth": 100
47
+ },
48
+ "javascript": {
49
+ "parser": {
50
+ "unsafeParameterDecoratorsEnabled": true
51
+ },
52
+ "formatter": {
53
+ "quoteStyle": "double",
54
+ "semicolons": "asNeeded",
55
+ "trailingCommas": "none"
56
+ }
57
+ },
58
+ "css": {
59
+ "parser": {
60
+ "tailwindDirectives": true
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "new-york",
4
+ "rsc": false,
5
+ "tsx": false,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "src/public/style/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "#cli/components",
15
+ "utils": "#cli/lib/utils.mjs",
16
+ "ui": "#cli/components/ui",
17
+ "lib": "#cli/lib",
18
+ "hooks": "#cli/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }