create-rudder-app 0.3.1 → 0.5.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 (283) hide show
  1. package/dist/index.js +98 -20
  2. package/dist/index.js.map +1 -1
  3. package/dist/templates/app/auth-controller.d.ts +2 -0
  4. package/dist/templates/app/auth-controller.d.ts.map +1 -0
  5. package/dist/templates/app/auth-controller.js +51 -0
  6. package/dist/templates/app/auth-controller.js.map +1 -0
  7. package/dist/templates/app/mcp-echo-server.d.ts +2 -0
  8. package/dist/templates/app/mcp-echo-server.d.ts.map +1 -0
  9. package/dist/templates/app/mcp-echo-server.js +13 -0
  10. package/dist/templates/app/mcp-echo-server.js.map +1 -0
  11. package/dist/templates/app/mcp-echo-tool.d.ts +2 -0
  12. package/dist/templates/app/mcp-echo-tool.d.ts.map +1 -0
  13. package/dist/templates/app/mcp-echo-tool.js +20 -0
  14. package/dist/templates/app/mcp-echo-tool.js.map +1 -0
  15. package/dist/templates/app/service-provider.d.ts +3 -0
  16. package/dist/templates/app/service-provider.d.ts.map +1 -0
  17. package/dist/templates/app/service-provider.js +48 -0
  18. package/dist/templates/app/service-provider.js.map +1 -0
  19. package/dist/templates/app/user-model.d.ts +2 -0
  20. package/dist/templates/app/user-model.d.ts.map +1 -0
  21. package/dist/templates/app/user-model.js +19 -0
  22. package/dist/templates/app/user-model.js.map +1 -0
  23. package/dist/templates/bootstrap/app.d.ts +3 -0
  24. package/dist/templates/bootstrap/app.d.ts.map +1 -0
  25. package/dist/templates/bootstrap/app.js +41 -0
  26. package/dist/templates/bootstrap/app.js.map +1 -0
  27. package/dist/templates/bootstrap/providers.d.ts +3 -0
  28. package/dist/templates/bootstrap/providers.d.ts.map +1 -0
  29. package/dist/templates/bootstrap/providers.js +27 -0
  30. package/dist/templates/bootstrap/providers.js.map +1 -0
  31. package/dist/templates/configs/ai.d.ts +2 -0
  32. package/dist/templates/configs/ai.d.ts.map +1 -0
  33. package/dist/templates/configs/ai.js +32 -0
  34. package/dist/templates/configs/ai.js.map +1 -0
  35. package/dist/templates/configs/app.d.ts +2 -0
  36. package/dist/templates/configs/app.d.ts.map +1 -0
  37. package/dist/templates/configs/app.js +12 -0
  38. package/dist/templates/configs/app.js.map +1 -0
  39. package/dist/templates/configs/auth.d.ts +3 -0
  40. package/dist/templates/configs/auth.d.ts.map +1 -0
  41. package/dist/templates/configs/auth.js +16 -0
  42. package/dist/templates/configs/auth.js.map +1 -0
  43. package/dist/templates/configs/cache.d.ts +2 -0
  44. package/dist/templates/configs/cache.d.ts.map +1 -0
  45. package/dist/templates/configs/cache.js +28 -0
  46. package/dist/templates/configs/cache.js.map +1 -0
  47. package/dist/templates/configs/crypt.d.ts +2 -0
  48. package/dist/templates/configs/crypt.d.ts.map +1 -0
  49. package/dist/templates/configs/crypt.js +16 -0
  50. package/dist/templates/configs/crypt.js.map +1 -0
  51. package/dist/templates/configs/database.d.ts +3 -0
  52. package/dist/templates/configs/database.d.ts.map +1 -0
  53. package/dist/templates/configs/database.js +28 -0
  54. package/dist/templates/configs/database.js.map +1 -0
  55. package/dist/templates/configs/hash.d.ts +2 -0
  56. package/dist/templates/configs/hash.d.ts.map +1 -0
  57. package/dist/templates/configs/hash.js +12 -0
  58. package/dist/templates/configs/hash.js.map +1 -0
  59. package/dist/templates/configs/horizon.d.ts +2 -0
  60. package/dist/templates/configs/horizon.d.ts.map +1 -0
  61. package/dist/templates/configs/horizon.js +30 -0
  62. package/dist/templates/configs/horizon.js.map +1 -0
  63. package/dist/templates/configs/index.d.ts +3 -0
  64. package/dist/templates/configs/index.d.ts.map +1 -0
  65. package/dist/templates/configs/index.js +92 -0
  66. package/dist/templates/configs/index.js.map +1 -0
  67. package/dist/templates/configs/localization.d.ts +2 -0
  68. package/dist/templates/configs/localization.d.ts.map +1 -0
  69. package/dist/templates/configs/localization.js +13 -0
  70. package/dist/templates/configs/localization.js.map +1 -0
  71. package/dist/templates/configs/log.d.ts +2 -0
  72. package/dist/templates/configs/log.d.ts.map +1 -0
  73. package/dist/templates/configs/log.js +40 -0
  74. package/dist/templates/configs/log.js.map +1 -0
  75. package/dist/templates/configs/mail.d.ts +2 -0
  76. package/dist/templates/configs/mail.d.ts.map +1 -0
  77. package/dist/templates/configs/mail.js +33 -0
  78. package/dist/templates/configs/mail.js.map +1 -0
  79. package/dist/templates/configs/passport.d.ts +2 -0
  80. package/dist/templates/configs/passport.d.ts.map +1 -0
  81. package/dist/templates/configs/passport.js +22 -0
  82. package/dist/templates/configs/passport.js.map +1 -0
  83. package/dist/templates/configs/pennant.d.ts +2 -0
  84. package/dist/templates/configs/pennant.d.ts.map +1 -0
  85. package/dist/templates/configs/pennant.js +16 -0
  86. package/dist/templates/configs/pennant.js.map +1 -0
  87. package/dist/templates/configs/pulse.d.ts +2 -0
  88. package/dist/templates/configs/pulse.d.ts.map +1 -0
  89. package/dist/templates/configs/pulse.js +21 -0
  90. package/dist/templates/configs/pulse.js.map +1 -0
  91. package/dist/templates/configs/queue.d.ts +2 -0
  92. package/dist/templates/configs/queue.d.ts.map +1 -0
  93. package/dist/templates/configs/queue.js +28 -0
  94. package/dist/templates/configs/queue.js.map +1 -0
  95. package/dist/templates/configs/sanctum.d.ts +2 -0
  96. package/dist/templates/configs/sanctum.d.ts.map +1 -0
  97. package/dist/templates/configs/sanctum.js +19 -0
  98. package/dist/templates/configs/sanctum.js.map +1 -0
  99. package/dist/templates/configs/server.d.ts +2 -0
  100. package/dist/templates/configs/server.d.ts.map +1 -0
  101. package/dist/templates/configs/server.js +15 -0
  102. package/dist/templates/configs/server.js.map +1 -0
  103. package/dist/templates/configs/session.d.ts +2 -0
  104. package/dist/templates/configs/session.d.ts.map +1 -0
  105. package/dist/templates/configs/session.js +26 -0
  106. package/dist/templates/configs/session.js.map +1 -0
  107. package/dist/templates/configs/socialite.d.ts +2 -0
  108. package/dist/templates/configs/socialite.d.ts.map +1 -0
  109. package/dist/templates/configs/socialite.js +27 -0
  110. package/dist/templates/configs/socialite.js.map +1 -0
  111. package/dist/templates/configs/storage.d.ts +2 -0
  112. package/dist/templates/configs/storage.d.ts.map +1 -0
  113. package/dist/templates/configs/storage.js +35 -0
  114. package/dist/templates/configs/storage.js.map +1 -0
  115. package/dist/templates/configs/sync.d.ts +3 -0
  116. package/dist/templates/configs/sync.d.ts.map +1 -0
  117. package/dist/templates/configs/sync.js +17 -0
  118. package/dist/templates/configs/sync.js.map +1 -0
  119. package/dist/templates/configs/telescope.d.ts +2 -0
  120. package/dist/templates/configs/telescope.d.ts.map +1 -0
  121. package/dist/templates/configs/telescope.js +25 -0
  122. package/dist/templates/configs/telescope.js.map +1 -0
  123. package/dist/templates/css/index.d.ts +3 -0
  124. package/dist/templates/css/index.d.ts.map +1 -0
  125. package/dist/templates/css/index.js +140 -0
  126. package/dist/templates/css/index.js.map +1 -0
  127. package/dist/templates/css/plain.d.ts +2 -0
  128. package/dist/templates/css/plain.d.ts.map +1 -0
  129. package/dist/templates/css/plain.js +373 -0
  130. package/dist/templates/css/plain.js.map +1 -0
  131. package/dist/templates/css/tailwind.d.ts +2 -0
  132. package/dist/templates/css/tailwind.d.ts.map +1 -0
  133. package/dist/templates/css/tailwind.js +176 -0
  134. package/dist/templates/css/tailwind.js.map +1 -0
  135. package/dist/templates/demos/avatar.d.ts +3 -0
  136. package/dist/templates/demos/avatar.d.ts.map +1 -0
  137. package/dist/templates/demos/avatar.js +182 -0
  138. package/dist/templates/demos/avatar.js.map +1 -0
  139. package/dist/templates/demos/cache.d.ts +3 -0
  140. package/dist/templates/demos/cache.d.ts.map +1 -0
  141. package/dist/templates/demos/cache.js +99 -0
  142. package/dist/templates/demos/cache.js.map +1 -0
  143. package/dist/templates/demos/contact.d.ts +3 -0
  144. package/dist/templates/demos/contact.d.ts.map +1 -0
  145. package/dist/templates/demos/contact.js +106 -0
  146. package/dist/templates/demos/contact.js.map +1 -0
  147. package/dist/templates/demos/fibonacci.d.ts +7 -0
  148. package/dist/templates/demos/fibonacci.d.ts.map +1 -0
  149. package/dist/templates/demos/fibonacci.js +172 -0
  150. package/dist/templates/demos/fibonacci.js.map +1 -0
  151. package/dist/templates/demos/http.d.ts +3 -0
  152. package/dist/templates/demos/http.d.ts.map +1 -0
  153. package/dist/templates/demos/http.js +117 -0
  154. package/dist/templates/demos/http.js.map +1 -0
  155. package/dist/templates/demos/index-view.d.ts +3 -0
  156. package/dist/templates/demos/index-view.d.ts.map +1 -0
  157. package/dist/templates/demos/index-view.js +144 -0
  158. package/dist/templates/demos/index-view.js.map +1 -0
  159. package/dist/templates/demos/localization.d.ts +4 -0
  160. package/dist/templates/demos/localization.d.ts.map +1 -0
  161. package/dist/templates/demos/localization.js +130 -0
  162. package/dist/templates/demos/localization.js.map +1 -0
  163. package/dist/templates/demos/mail.d.ts +4 -0
  164. package/dist/templates/demos/mail.d.ts.map +1 -0
  165. package/dist/templates/demos/mail.js +127 -0
  166. package/dist/templates/demos/mail.js.map +1 -0
  167. package/dist/templates/demos/notifications.d.ts +4 -0
  168. package/dist/templates/demos/notifications.d.ts.map +1 -0
  169. package/dist/templates/demos/notifications.js +133 -0
  170. package/dist/templates/demos/notifications.js.map +1 -0
  171. package/dist/templates/demos/pennant.d.ts +8 -0
  172. package/dist/templates/demos/pennant.d.ts.map +1 -0
  173. package/dist/templates/demos/pennant.js +138 -0
  174. package/dist/templates/demos/pennant.js.map +1 -0
  175. package/dist/templates/demos/queue.d.ts +4 -0
  176. package/dist/templates/demos/queue.d.ts.map +1 -0
  177. package/dist/templates/demos/queue.js +107 -0
  178. package/dist/templates/demos/queue.js.map +1 -0
  179. package/dist/templates/demos/registry.d.ts +13 -0
  180. package/dist/templates/demos/registry.d.ts.map +1 -0
  181. package/dist/templates/demos/registry.js +26 -0
  182. package/dist/templates/demos/registry.js.map +1 -0
  183. package/dist/templates/demos/rudder-socket.d.ts +2 -0
  184. package/dist/templates/demos/rudder-socket.d.ts.map +1 -0
  185. package/dist/templates/demos/rudder-socket.js +95 -0
  186. package/dist/templates/demos/rudder-socket.js.map +1 -0
  187. package/dist/templates/demos/sync.d.ts +2 -0
  188. package/dist/templates/demos/sync.d.ts.map +1 -0
  189. package/dist/templates/demos/sync.js +97 -0
  190. package/dist/templates/demos/sync.js.map +1 -0
  191. package/dist/templates/demos/system-info.d.ts +3 -0
  192. package/dist/templates/demos/system-info.d.ts.map +1 -0
  193. package/dist/templates/demos/system-info.js +142 -0
  194. package/dist/templates/demos/system-info.js.map +1 -0
  195. package/dist/templates/demos/todos.d.ts +6 -0
  196. package/dist/templates/demos/todos.d.ts.map +1 -0
  197. package/dist/templates/demos/todos.js +246 -0
  198. package/dist/templates/demos/todos.js.map +1 -0
  199. package/dist/templates/demos/ws.d.ts +2 -0
  200. package/dist/templates/demos/ws.d.ts.map +1 -0
  201. package/dist/templates/demos/ws.js +106 -0
  202. package/dist/templates/demos/ws.js.map +1 -0
  203. package/dist/templates/env.d.ts +7 -0
  204. package/dist/templates/env.d.ts.map +1 -0
  205. package/dist/templates/env.js +113 -0
  206. package/dist/templates/env.js.map +1 -0
  207. package/dist/templates/package-json.d.ts +3 -0
  208. package/dist/templates/package-json.d.ts.map +1 -0
  209. package/dist/templates/package-json.js +193 -0
  210. package/dist/templates/package-json.js.map +1 -0
  211. package/dist/templates/package-managers.d.ts +14 -0
  212. package/dist/templates/package-managers.d.ts.map +1 -0
  213. package/dist/templates/package-managers.js +49 -0
  214. package/dist/templates/package-managers.js.map +1 -0
  215. package/dist/templates/pages/ai-chat.d.ts +7 -0
  216. package/dist/templates/pages/ai-chat.d.ts.map +1 -0
  217. package/dist/templates/pages/ai-chat.js +285 -0
  218. package/dist/templates/pages/ai-chat.js.map +1 -0
  219. package/dist/templates/pages/demo.d.ts +4 -0
  220. package/dist/templates/pages/demo.d.ts.map +1 -0
  221. package/dist/templates/pages/demo.js +71 -0
  222. package/dist/templates/pages/demo.js.map +1 -0
  223. package/dist/templates/pages/error.d.ts +7 -0
  224. package/dist/templates/pages/error.d.ts.map +1 -0
  225. package/dist/templates/pages/error.js +148 -0
  226. package/dist/templates/pages/error.js.map +1 -0
  227. package/dist/templates/pages/index.d.ts +9 -0
  228. package/dist/templates/pages/index.d.ts.map +1 -0
  229. package/dist/templates/pages/index.js +311 -0
  230. package/dist/templates/pages/index.js.map +1 -0
  231. package/dist/templates/prisma/auth.d.ts +2 -0
  232. package/dist/templates/prisma/auth.d.ts.map +1 -0
  233. package/dist/templates/prisma/auth.js +22 -0
  234. package/dist/templates/prisma/auth.js.map +1 -0
  235. package/dist/templates/prisma/base.d.ts +3 -0
  236. package/dist/templates/prisma/base.d.ts.map +1 -0
  237. package/dist/templates/prisma/base.js +14 -0
  238. package/dist/templates/prisma/base.js.map +1 -0
  239. package/dist/templates/prisma/config.d.ts +3 -0
  240. package/dist/templates/prisma/config.d.ts.map +1 -0
  241. package/dist/templates/prisma/config.js +15 -0
  242. package/dist/templates/prisma/config.js.map +1 -0
  243. package/dist/templates/prisma/notification.d.ts +2 -0
  244. package/dist/templates/prisma/notification.d.ts.map +1 -0
  245. package/dist/templates/prisma/notification.js +16 -0
  246. package/dist/templates/prisma/notification.js.map +1 -0
  247. package/dist/templates/prisma/passport.d.ts +2 -0
  248. package/dist/templates/prisma/passport.d.ts.map +1 -0
  249. package/dist/templates/prisma/passport.js +69 -0
  250. package/dist/templates/prisma/passport.js.map +1 -0
  251. package/dist/templates/routes/api.d.ts +3 -0
  252. package/dist/templates/routes/api.d.ts.map +1 -0
  253. package/dist/templates/routes/api.js +166 -0
  254. package/dist/templates/routes/api.js.map +1 -0
  255. package/dist/templates/routes/console.d.ts +2 -0
  256. package/dist/templates/routes/console.d.ts.map +1 -0
  257. package/dist/templates/routes/console.js +22 -0
  258. package/dist/templates/routes/console.js.map +1 -0
  259. package/dist/templates/routes/web.d.ts +4 -0
  260. package/dist/templates/routes/web.d.ts.map +1 -0
  261. package/dist/templates/routes/web.js +155 -0
  262. package/dist/templates/routes/web.js.map +1 -0
  263. package/dist/templates/server.d.ts +2 -0
  264. package/dist/templates/server.d.ts.map +1 -0
  265. package/dist/templates/server.js +10 -0
  266. package/dist/templates/server.js.map +1 -0
  267. package/dist/templates/tsconfig.d.ts +3 -0
  268. package/dist/templates/tsconfig.d.ts.map +1 -0
  269. package/dist/templates/tsconfig.js +33 -0
  270. package/dist/templates/tsconfig.js.map +1 -0
  271. package/dist/templates/views/welcome.d.ts +6 -0
  272. package/dist/templates/views/welcome.d.ts.map +1 -0
  273. package/dist/templates/views/welcome.js +396 -0
  274. package/dist/templates/views/welcome.js.map +1 -0
  275. package/dist/templates/vite.d.ts +3 -0
  276. package/dist/templates/vite.d.ts.map +1 -0
  277. package/dist/templates/vite.js +61 -0
  278. package/dist/templates/vite.js.map +1 -0
  279. package/dist/templates.d.ts +27 -17
  280. package/dist/templates.d.ts.map +1 -1
  281. package/dist/templates.js +158 -3778
  282. package/dist/templates.js.map +1 -1
  283. package/package.json +2 -2
@@ -0,0 +1,182 @@
1
+ // Avatar demo — image upload + resize via @rudderjs/image, persisted to the
2
+ // `public` Storage disk so the resized URL is browser-reachable.
3
+ export function demosAvatarView() {
4
+ return `import { useState, useRef } from 'react'
5
+ import '@/index.css'
6
+
7
+ interface ProcessedImage {
8
+ url: string
9
+ format: string
10
+ width: number
11
+ height: number
12
+ size: number
13
+ }
14
+
15
+ interface UploadResponse {
16
+ original: { format: string; width: number; height: number; size: number }
17
+ processed: ProcessedImage
18
+ }
19
+
20
+ export default function AvatarResize() {
21
+ const inputRef = useRef<HTMLInputElement>(null)
22
+ const [preview, setPreview] = useState<string | null>(null)
23
+ const [result, setResult ] = useState<UploadResponse | null>(null)
24
+ const [loading, setLoading] = useState(false)
25
+ const [error, setError ] = useState<string | null>(null)
26
+
27
+ function onPick(e: React.ChangeEvent<HTMLInputElement>) {
28
+ const file = e.target.files?.[0]
29
+ if (!file) return
30
+ setError(null)
31
+ setResult(null)
32
+ const reader = new FileReader()
33
+ reader.onload = () => setPreview(reader.result as string)
34
+ reader.readAsDataURL(file)
35
+ }
36
+
37
+ async function upload() {
38
+ if (!preview) return
39
+ setLoading(true); setError(null)
40
+ try {
41
+ const res = await fetch('/api/avatar', {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json' },
44
+ body: JSON.stringify({ image: preview }),
45
+ })
46
+ const data = await res.json() as UploadResponse | { message: string }
47
+ if (!res.ok) throw new Error((data as { message: string }).message ?? 'Upload failed')
48
+ setResult(data as UploadResponse)
49
+ } catch (e) {
50
+ setError((e as Error).message)
51
+ } finally {
52
+ setLoading(false)
53
+ }
54
+ }
55
+
56
+ function reset() {
57
+ setPreview(null); setResult(null); setError(null)
58
+ if (inputRef.current) inputRef.current.value = ''
59
+ }
60
+
61
+ return (
62
+ <div className="page">
63
+ <nav className="page-nav">
64
+ <div className="brand">
65
+ <span className="brand-dot" />
66
+ RudderJS
67
+ </div>
68
+ <div className="nav-right">
69
+ <a href="/demos" className="nav-link">Demos</a>
70
+ <a href="/" className="nav-link">Home</a>
71
+ </div>
72
+ </nav>
73
+
74
+ <section className="hero">
75
+ <h1 className="hero-title">Avatar Resize</h1>
76
+ <p className="hero-lead">
77
+ Upload any image — the server resizes it to 256×256, converts to WebP at quality 85,
78
+ and saves to the <code className="inline-code">public</code> storage disk via{' '}
79
+ <code className="inline-code">@rudderjs/image</code>.
80
+ </p>
81
+ </section>
82
+
83
+ <section className="feature-section">
84
+ <div className="form-card">
85
+ <input
86
+ ref={inputRef}
87
+ type="file"
88
+ accept="image/*"
89
+ onChange={onPick}
90
+ className="form-input"
91
+ style={{ marginBottom: '1rem' }}
92
+ />
93
+
94
+ {preview && !result && (
95
+ <>
96
+ <div style={{ marginBottom: '1rem' }}>
97
+ <p className="form-label">Original</p>
98
+ <img src={preview} alt="preview" style={{ maxWidth: '256px', maxHeight: '256px', borderRadius: '0.5rem', border: '1px solid var(--border, #e5e7eb)' }} />
99
+ </div>
100
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
101
+ <button className="form-submit" onClick={upload} disabled={loading} style={{ flex: 1 }}>
102
+ {loading ? 'Processing…' : 'Resize & upload'}
103
+ </button>
104
+ <button onClick={reset} disabled={loading} style={{ padding: '0.5rem 1rem', borderRadius: '0.375rem', border: '1px solid var(--border, #e5e7eb)', background: 'transparent', cursor: 'pointer' }}>
105
+ Reset
106
+ </button>
107
+ </div>
108
+ </>
109
+ )}
110
+
111
+ {error && (
112
+ <p className="form-error" style={{ marginTop: '1rem' }}>{error}</p>
113
+ )}
114
+
115
+ {result && (
116
+ <>
117
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginTop: '1rem' }}>
118
+ <div>
119
+ <p className="form-label">Original</p>
120
+ {preview && (
121
+ <img src={preview} alt="original" style={{ maxWidth: '100%', borderRadius: '0.5rem', border: '1px solid var(--border, #e5e7eb)' }} />
122
+ )}
123
+ <p className="feature-desc" style={{ fontSize: '0.75rem' }}>
124
+ {result.original.format} · {result.original.width}×{result.original.height} ·{' '}
125
+ {(result.original.size / 1024).toFixed(1)} KB
126
+ </p>
127
+ </div>
128
+ <div>
129
+ <p className="form-label">Resized (WebP, 256×256)</p>
130
+ <img src={result.processed.url} alt="resized" style={{ maxWidth: '100%', borderRadius: '0.5rem', border: '1px solid var(--border, #e5e7eb)' }} />
131
+ <p className="feature-desc" style={{ fontSize: '0.75rem' }}>
132
+ {result.processed.format} · {result.processed.width}×{result.processed.height} ·{' '}
133
+ {(result.processed.size / 1024).toFixed(1)} KB ·{' '}
134
+ {Math.round((1 - result.processed.size / result.original.size) * 100)}% smaller
135
+ </p>
136
+ </div>
137
+ </div>
138
+ <div style={{ marginTop: '1rem' }}>
139
+ <button onClick={reset} style={{ padding: '0.5rem 1rem', borderRadius: '0.375rem', border: '1px solid var(--border, #e5e7eb)', background: 'transparent', cursor: 'pointer' }}>
140
+ Try another
141
+ </button>
142
+ </div>
143
+ </>
144
+ )}
145
+ </div>
146
+ </section>
147
+ </div>
148
+ )
149
+ }
150
+ `;
151
+ }
152
+ export function demosAvatarApiBlock() {
153
+ return `// POST /api/avatar — resize an uploaded image to 256x256 webp via @rudderjs/image,
154
+ // then write to the 'public' Storage disk so the URL is browser-reachable.
155
+ router.post('/api/avatar', async (req, res) => {
156
+ const { image: dataUrl } = (req.body ?? {}) as { image?: string }
157
+ if (!dataUrl || !dataUrl.startsWith('data:image/')) {
158
+ return res.status(422).json({ message: 'Body must be { image: "data:image/...;base64,..." }' })
159
+ }
160
+ const base64 = dataUrl.split(',', 2)[1] ?? ''
161
+ const input = Buffer.from(base64, 'base64')
162
+
163
+ const { image } = await import('@rudderjs/image')
164
+ const { Storage } = await import('@rudderjs/storage')
165
+
166
+ const original = await image(input).metadata()
167
+ const buf = await image(input).resize(256, 256).format('webp').quality(85).toBuffer()
168
+ const meta = await image(buf).metadata()
169
+
170
+ const filename = \`avatars/\${Date.now()}-\${Math.random().toString(36).slice(2, 8)}.webp\`
171
+ await Storage.disk('public').put(filename, buf)
172
+
173
+ res.json({
174
+ original: { format: original.format, width: original.width, height: original.height, size: input.length },
175
+ processed: {
176
+ url: Storage.disk('public').url(filename),
177
+ format: meta.format, width: meta.width, height: meta.height, size: buf.length,
178
+ },
179
+ })
180
+ })`;
181
+ }
182
+ //# sourceMappingURL=avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"avatar.js","sourceRoot":"","sources":["../../../src/templates/demos/avatar.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,iEAAiE;AAEjE,MAAM,UAAU,eAAe;IAC7B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkJR,CAAA;AACD,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BN,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function demosCacheView(): string;
2
+ export declare function demosCacheApiBlock(): string;
3
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/templates/demos/cache.ts"],"names":[],"mappings":"AAGA,wBAAgB,cAAc,IAAI,MAAM,CA8EvC;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAgB3C"}
@@ -0,0 +1,99 @@
1
+ // Cache counter demo — `Cache.get` + `Cache.set` round-trip with no TTL.
2
+ // Cache is a Tier A package, so this demo is always available.
3
+ export function demosCacheView() {
4
+ return `import { useState } from 'react'
5
+ import '@/index.css'
6
+
7
+ interface CacheResponse {
8
+ views: number
9
+ key: string
10
+ }
11
+
12
+ export default function CacheDemo() {
13
+ const [data, setData] = useState<CacheResponse | null>(null)
14
+ const [loading, setLoading] = useState(false)
15
+
16
+ async function bump() {
17
+ setLoading(true)
18
+ try {
19
+ const res = await fetch('/api/cache/views', { method: 'POST' })
20
+ setData(await res.json() as CacheResponse)
21
+ } finally {
22
+ setLoading(false)
23
+ }
24
+ }
25
+
26
+ async function clear() {
27
+ setLoading(true)
28
+ try {
29
+ await fetch('/api/cache/views', { method: 'DELETE' })
30
+ setData({ views: 0, key: 'demos:views' })
31
+ } finally {
32
+ setLoading(false)
33
+ }
34
+ }
35
+
36
+ return (
37
+ <div className="page">
38
+ <nav className="page-nav">
39
+ <div className="brand">
40
+ <span className="brand-dot" />
41
+ RudderJS
42
+ </div>
43
+ <div className="nav-right">
44
+ <a href="/demos" className="nav-link">Demos</a>
45
+ <a href="/" className="nav-link">Home</a>
46
+ </div>
47
+ </nav>
48
+
49
+ <section className="hero">
50
+ <h1 className="hero-title">Cache counter</h1>
51
+ <p className="hero-lead">
52
+ Click "Bump" to read the current value via <code className="inline-code">Cache.get</code>,
53
+ increment it, and write it back via <code className="inline-code">Cache.set</code>.
54
+ Default driver is in-memory; swap it via <code className="inline-code">config/cache.ts</code>.
55
+ </p>
56
+ </section>
57
+
58
+ <section className="feature-section" style={{ maxWidth: '32rem', margin: '0 auto' }}>
59
+ <div className="form-card" style={{ textAlign: 'center' }}>
60
+ <p className="form-label" style={{ marginBottom: '0.5rem' }}>Views recorded</p>
61
+ <p style={{ fontSize: '3rem', fontWeight: 700, margin: '0.5rem 0' }}>
62
+ {data?.views ?? '—'}
63
+ </p>
64
+ <p className="feature-desc" style={{ fontSize: '0.7rem', marginBottom: '1rem' }}>
65
+ key: <code>{data?.key ?? 'demos:views'}</code>
66
+ </p>
67
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
68
+ <button className="form-submit" onClick={bump} disabled={loading} style={{ flex: 1 }}>
69
+ {loading ? '…' : 'Bump'}
70
+ </button>
71
+ <button onClick={clear} disabled={loading} style={{ padding: '0.5rem 1rem', borderRadius: '0.375rem', border: '1px solid var(--border, #e5e7eb)', background: 'transparent', cursor: 'pointer' }}>
72
+ Clear
73
+ </button>
74
+ </div>
75
+ </div>
76
+ </section>
77
+ </div>
78
+ )
79
+ }
80
+ `;
81
+ }
82
+ export function demosCacheApiBlock() {
83
+ return `// POST /api/cache/views — read+increment+write (no TTL); DELETE to forget the key.
84
+ router.post('/api/cache/views', async (_req, res) => {
85
+ const { Cache } = await import('@rudderjs/cache')
86
+ const KEY = 'demos:views'
87
+ const current = (await Cache.get<number>(KEY)) ?? 0
88
+ const next = current + 1
89
+ await Cache.set(KEY, next)
90
+ res.json({ views: next, key: KEY })
91
+ })
92
+
93
+ router.delete('/api/cache/views', async (_req, res) => {
94
+ const { Cache } = await import('@rudderjs/cache')
95
+ await Cache.forget('demos:views')
96
+ res.json({ views: 0, key: 'demos:views' })
97
+ })`;
98
+ }
99
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/templates/demos/cache.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,+DAA+D;AAE/D,MAAM,UAAU,cAAc;IAC5B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4ER,CAAA;AACD,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;GAcN,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { TemplateContext } from '../../templates.js';
2
+ export declare function demosContactView(ctx: TemplateContext): string;
3
+ //# sourceMappingURL=contact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contact.d.ts","sourceRoot":"","sources":["../../../src/templates/demos/contact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAyG7D"}
@@ -0,0 +1,106 @@
1
+ export function demosContactView(ctx) {
2
+ // CSRF is only enforced when @rudderjs/auth is installed (CsrfMiddleware
3
+ // wraps the /api/contact route in routes/api.ts). Without auth, the
4
+ // unprotected form just succeeds.
5
+ const sendsCsrf = ctx.packages.auth ? 'true' : 'false';
6
+ return `import '@/index.css'
7
+ import { useState } from 'react'
8
+ import { getCsrfToken } from '@rudderjs/middleware'
9
+
10
+ interface FormFields { name: string; email: string; message: string }
11
+ interface FormErrors { name?: string; email?: string; message?: string }
12
+
13
+ export default function ContactDemo() {
14
+ const [fields, setFields] = useState<FormFields>({ name: '', email: '', message: '' })
15
+ const [errors, setErrors] = useState<FormErrors>({})
16
+ const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle')
17
+ const [message, setMessage] = useState('')
18
+
19
+ function setField(key: keyof FormFields, value: string) {
20
+ setFields(f => ({ ...f, [key]: value }))
21
+ if (errors[key]) setErrors(e => ({ ...e, [key]: undefined }))
22
+ }
23
+
24
+ async function submit(e: React.FormEvent) {
25
+ e.preventDefault()
26
+ setStatus('loading')
27
+ setErrors({})
28
+
29
+ const headers: Record<string, string> = { 'Content-Type': 'application/json' }
30
+ if (${sendsCsrf}) headers['X-CSRF-Token'] = getCsrfToken()
31
+
32
+ const res = await fetch('/api/contact', {
33
+ method: 'POST',
34
+ headers,
35
+ body: JSON.stringify(fields),
36
+ })
37
+ const data = await res.json() as { ok?: boolean; message?: string; errors?: FormErrors }
38
+
39
+ if (res.ok) {
40
+ setStatus('success')
41
+ setMessage(data.message ?? 'Thanks!')
42
+ setFields({ name: '', email: '', message: '' })
43
+ } else if (res.status === 422) {
44
+ setStatus('error')
45
+ setErrors(data.errors ?? {})
46
+ } else {
47
+ setStatus('error')
48
+ setMessage(\`\${res.status} — \${data.message ?? 'Request failed.'}\`)
49
+ }
50
+ }
51
+
52
+ return (
53
+ <div className="page">
54
+ <nav className="page-nav">
55
+ <div className="brand">
56
+ <span className="brand-dot" />
57
+ RudderJS
58
+ </div>
59
+ <div className="nav-right">
60
+ <a href="/demos" className="nav-link">← Demos</a>
61
+ </div>
62
+ </nav>
63
+
64
+ <section className="hero">
65
+ <h1 className="hero-title">Contact</h1>
66
+ <p className="hero-lead">
67
+ POSTs to <code className="inline-code">/api/contact</code>${ctx.packages.auth
68
+ ? ' with an X-CSRF-Token header.'
69
+ : '. Add @rudderjs/auth to require CSRF.'}{' '}
70
+ Server-side validated with Zod.
71
+ </p>
72
+ </section>
73
+
74
+ <section className="feature-section" style={{ maxWidth: '32rem', margin: '0 auto' }}>
75
+ <form onSubmit={submit} className="form-card">
76
+ <div>
77
+ <label className="form-label" htmlFor="name">Name</label>
78
+ <input id="name" className="form-input" value={fields.name}
79
+ onChange={e => setField('name', e.target.value)} />
80
+ {errors.name && <p className="form-error">{errors.name}</p>}
81
+ </div>
82
+ <div>
83
+ <label className="form-label" htmlFor="email">Email</label>
84
+ <input id="email" type="email" className="form-input" value={fields.email}
85
+ onChange={e => setField('email', e.target.value)} />
86
+ {errors.email && <p className="form-error">{errors.email}</p>}
87
+ </div>
88
+ <div>
89
+ <label className="form-label" htmlFor="message">Message</label>
90
+ <textarea id="message" rows={4} className="form-input" value={fields.message}
91
+ onChange={e => setField('message', e.target.value)} />
92
+ {errors.message && <p className="form-error">{errors.message}</p>}
93
+ </div>
94
+ <button type="submit" className="form-submit" disabled={status === 'loading'}>
95
+ {status === 'loading' ? 'Sending…' : 'Send message'}
96
+ </button>
97
+ {status === 'success' && <p className="form-success">{message}</p>}
98
+ {status === 'error' && message && <p className="form-error">{message}</p>}
99
+ </form>
100
+ </section>
101
+ </div>
102
+ )
103
+ }
104
+ `;
105
+ }
106
+ //# sourceMappingURL=contact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contact.js","sourceRoot":"","sources":["../../../src/templates/demos/contact.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,GAAoB;IACnD,yEAAyE;IACzE,oEAAoE;IACpE,kCAAkC;IAClC,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;IAEtD,OAAO;;;;;;;;;;;;;;;;;;;;;;;;UAwBC,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sEAqCmD,GAAG,CAAC,QAAQ,CAAC,IAAI;QAC/E,CAAC,CAAC,+BAA+B;QACjC,CAAC,CAAC,uCAAuC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmChD,CAAA;AACD,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare function demosFibonacciView(): string;
2
+ /**
3
+ * Returns the inline lines that live in routes/api.ts to handle GET /api/fib.
4
+ * Inlined (not a separate Service) to keep the demo single-file readable.
5
+ */
6
+ export declare function demosFibonacciApiBlock(): string;
7
+ //# sourceMappingURL=fibonacci.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fibonacci.d.ts","sourceRoot":"","sources":["../../../src/templates/demos/fibonacci.ts"],"names":[],"mappings":"AAEA,wBAAgB,kBAAkB,IAAI,MAAM,CAiI3C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAmC/C"}
@@ -0,0 +1,172 @@
1
+ // Fibonacci demo — sequential vs worker-pool parallel via @rudderjs/concurrency.
2
+ export function demosFibonacciView() {
3
+ return `import { useState } from 'react'
4
+ import '@/index.css'
5
+
6
+ interface FibResponse {
7
+ n: number
8
+ count: number
9
+ result: number
10
+ sequentialMs: number
11
+ parallelMs: number
12
+ workers: number
13
+ }
14
+
15
+ export default function FibonacciDemo() {
16
+ const [n, setN ] = useState(36)
17
+ const [count, setCount ] = useState(4)
18
+ const [data, setData ] = useState<FibResponse | null>(null)
19
+ const [loading, setLoading] = useState(false)
20
+ const [error, setError ] = useState<string | null>(null)
21
+
22
+ async function run() {
23
+ setLoading(true); setError(null); setData(null)
24
+ try {
25
+ const res = await fetch(\`/api/fib?n=\${n}&count=\${count}\`)
26
+ const body = await res.json() as FibResponse | { message: string }
27
+ if (!res.ok) throw new Error((body as { message: string }).message ?? 'Failed')
28
+ setData(body as FibResponse)
29
+ } catch (e) {
30
+ setError((e as Error).message)
31
+ } finally {
32
+ setLoading(false)
33
+ }
34
+ }
35
+
36
+ return (
37
+ <div className="page">
38
+ <nav className="page-nav">
39
+ <div className="brand">
40
+ <span className="brand-dot" />
41
+ RudderJS
42
+ </div>
43
+ <div className="nav-right">
44
+ <a href="/demos" className="nav-link">Demos</a>
45
+ <a href="/" className="nav-link">Home</a>
46
+ </div>
47
+ </nav>
48
+
49
+ <section className="hero">
50
+ <h1 className="hero-title">Worker Threads</h1>
51
+ <p className="hero-lead">
52
+ Compute <code className="inline-code">fib(n)</code> N times — sequentially on the main thread, then
53
+ in parallel via <code className="inline-code">@rudderjs/concurrency</code>'s worker pool. Watch the
54
+ parallel cost stay flat as N grows (until you saturate workers).
55
+ </p>
56
+ </section>
57
+
58
+ <section className="feature-section">
59
+ <div className="form-card">
60
+ <div style={{ display: 'flex', gap: '1rem', marginBottom: '1rem' }}>
61
+ <div style={{ flex: 1 }}>
62
+ <p className="form-label">n (Fibonacci index)</p>
63
+ <input
64
+ className="form-input"
65
+ type="number"
66
+ min={20}
67
+ max={42}
68
+ value={n}
69
+ onChange={e => setN(Number(e.target.value))}
70
+ />
71
+ </div>
72
+ <div style={{ flex: 1 }}>
73
+ <p className="form-label">count (parallel calls)</p>
74
+ <input
75
+ className="form-input"
76
+ type="number"
77
+ min={1}
78
+ max={16}
79
+ value={count}
80
+ onChange={e => setCount(Number(e.target.value))}
81
+ />
82
+ </div>
83
+ </div>
84
+
85
+ <button
86
+ className="form-submit"
87
+ onClick={run}
88
+ disabled={loading}
89
+ style={{ marginBottom: '1rem' }}
90
+ >
91
+ {loading ? 'Computing…' : \`Run \${count} × fib(\${n})\`}
92
+ </button>
93
+
94
+ {error && (
95
+ <p className="form-error">{error}</p>
96
+ )}
97
+
98
+ {data && (
99
+ <>
100
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginBottom: '0.75rem' }}>
101
+ <div style={{ padding: '0.75rem', borderRadius: '0.375rem', border: '1px solid var(--border, #e5e7eb)' }}>
102
+ <p className="form-label" style={{ marginBottom: '0.25rem' }}>Sequential (main thread)</p>
103
+ <p style={{ fontSize: '1.25rem', fontWeight: 600, margin: 0 }}>{data.sequentialMs}ms</p>
104
+ <p className="feature-desc" style={{ fontSize: '0.7rem', marginTop: '0.25rem' }}>
105
+ {data.count} × fib({data.n}) one after another, blocking the event loop
106
+ </p>
107
+ </div>
108
+ <div style={{ padding: '0.75rem', borderRadius: '0.375rem', border: '1px solid var(--border, #e5e7eb)' }}>
109
+ <p className="form-label" style={{ marginBottom: '0.25rem' }}>Parallel ({data.workers} workers)</p>
110
+ <p style={{ fontSize: '1.25rem', fontWeight: 600, margin: 0 }}>{data.parallelMs}ms</p>
111
+ <p className="feature-desc" style={{ fontSize: '0.7rem', marginTop: '0.25rem' }}>
112
+ same {data.count} tasks via <code>Concurrency.run([...])</code>
113
+ </p>
114
+ </div>
115
+ </div>
116
+
117
+ <p className="feature-desc" style={{ fontSize: '0.85rem' }}>
118
+ Result: <code>fib({data.n}) = {data.result.toLocaleString()}</code>
119
+ {' · '}
120
+ <strong>{Math.round((data.sequentialMs / Math.max(data.parallelMs, 1)) * 10) / 10}× faster</strong>
121
+ {' '}via worker pool
122
+ </p>
123
+ </>
124
+ )}
125
+ </div>
126
+ </section>
127
+ </div>
128
+ )
129
+ }
130
+ `;
131
+ }
132
+ /**
133
+ * Returns the inline lines that live in routes/api.ts to handle GET /api/fib.
134
+ * Inlined (not a separate Service) to keep the demo single-file readable.
135
+ */
136
+ export function demosFibonacciApiBlock() {
137
+ return `// GET /api/fib?n=36&count=4 — sequential vs worker-pool parallel via @rudderjs/concurrency.
138
+ router.get('/api/fib', async (req, res) => {
139
+ const n = Math.max(1, Math.min(42, Number((req.query as Record<string, string>)['n'] ?? 36)))
140
+ const count = Math.max(1, Math.min(16, Number((req.query as Record<string, string>)['count'] ?? 4)))
141
+
142
+ const { Concurrency } = await import('@rudderjs/concurrency')
143
+ const { cpus } = await import('node:os')
144
+
145
+ // The task body must be self-contained — closures don't capture variables across the
146
+ // worker boundary. Inline 'fib' and bind 'n' via a Function-style template.
147
+ const buildTask = (val: number): (() => number) => {
148
+ const src = \`
149
+ function fib(k) { return k < 2 ? k : fib(k - 1) + fib(k - 2) }
150
+ return fib(\${val})
151
+ \`
152
+ return new Function(src) as () => number
153
+ }
154
+
155
+ const seqStart = Date.now()
156
+ let result = 0
157
+ for (let i = 0; i < count; i++) result = buildTask(n)()
158
+ const sequentialMs = Date.now() - seqStart
159
+
160
+ const parStart = Date.now()
161
+ const tasks = Array.from({ length: count }, () => buildTask(n))
162
+ const results = await Concurrency.run(tasks)
163
+ const parallelMs = Date.now() - parStart
164
+ result = results[0] ?? result
165
+
166
+ res.json({
167
+ n, count, result, sequentialMs, parallelMs,
168
+ workers: Math.min(count, cpus().length),
169
+ })
170
+ })`;
171
+ }
172
+ //# sourceMappingURL=fibonacci.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fibonacci.js","sourceRoot":"","sources":["../../../src/templates/demos/fibonacci.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,MAAM,UAAU,kBAAkB;IAChC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HR,CAAA;AACD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCN,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare function demosHttpView(): string;
2
+ export declare function demosHttpApiBlock(): string;
3
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../../src/templates/demos/http.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,IAAI,MAAM,CAyFtC;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAwB1C"}