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.
- package/dist/index.js +98 -20
- package/dist/index.js.map +1 -1
- package/dist/templates/app/auth-controller.d.ts +2 -0
- package/dist/templates/app/auth-controller.d.ts.map +1 -0
- package/dist/templates/app/auth-controller.js +51 -0
- package/dist/templates/app/auth-controller.js.map +1 -0
- package/dist/templates/app/mcp-echo-server.d.ts +2 -0
- package/dist/templates/app/mcp-echo-server.d.ts.map +1 -0
- package/dist/templates/app/mcp-echo-server.js +13 -0
- package/dist/templates/app/mcp-echo-server.js.map +1 -0
- package/dist/templates/app/mcp-echo-tool.d.ts +2 -0
- package/dist/templates/app/mcp-echo-tool.d.ts.map +1 -0
- package/dist/templates/app/mcp-echo-tool.js +20 -0
- package/dist/templates/app/mcp-echo-tool.js.map +1 -0
- package/dist/templates/app/service-provider.d.ts +3 -0
- package/dist/templates/app/service-provider.d.ts.map +1 -0
- package/dist/templates/app/service-provider.js +48 -0
- package/dist/templates/app/service-provider.js.map +1 -0
- package/dist/templates/app/user-model.d.ts +2 -0
- package/dist/templates/app/user-model.d.ts.map +1 -0
- package/dist/templates/app/user-model.js +19 -0
- package/dist/templates/app/user-model.js.map +1 -0
- package/dist/templates/bootstrap/app.d.ts +3 -0
- package/dist/templates/bootstrap/app.d.ts.map +1 -0
- package/dist/templates/bootstrap/app.js +41 -0
- package/dist/templates/bootstrap/app.js.map +1 -0
- package/dist/templates/bootstrap/providers.d.ts +3 -0
- package/dist/templates/bootstrap/providers.d.ts.map +1 -0
- package/dist/templates/bootstrap/providers.js +27 -0
- package/dist/templates/bootstrap/providers.js.map +1 -0
- package/dist/templates/configs/ai.d.ts +2 -0
- package/dist/templates/configs/ai.d.ts.map +1 -0
- package/dist/templates/configs/ai.js +32 -0
- package/dist/templates/configs/ai.js.map +1 -0
- package/dist/templates/configs/app.d.ts +2 -0
- package/dist/templates/configs/app.d.ts.map +1 -0
- package/dist/templates/configs/app.js +12 -0
- package/dist/templates/configs/app.js.map +1 -0
- package/dist/templates/configs/auth.d.ts +3 -0
- package/dist/templates/configs/auth.d.ts.map +1 -0
- package/dist/templates/configs/auth.js +16 -0
- package/dist/templates/configs/auth.js.map +1 -0
- package/dist/templates/configs/cache.d.ts +2 -0
- package/dist/templates/configs/cache.d.ts.map +1 -0
- package/dist/templates/configs/cache.js +28 -0
- package/dist/templates/configs/cache.js.map +1 -0
- package/dist/templates/configs/crypt.d.ts +2 -0
- package/dist/templates/configs/crypt.d.ts.map +1 -0
- package/dist/templates/configs/crypt.js +16 -0
- package/dist/templates/configs/crypt.js.map +1 -0
- package/dist/templates/configs/database.d.ts +3 -0
- package/dist/templates/configs/database.d.ts.map +1 -0
- package/dist/templates/configs/database.js +28 -0
- package/dist/templates/configs/database.js.map +1 -0
- package/dist/templates/configs/hash.d.ts +2 -0
- package/dist/templates/configs/hash.d.ts.map +1 -0
- package/dist/templates/configs/hash.js +12 -0
- package/dist/templates/configs/hash.js.map +1 -0
- package/dist/templates/configs/horizon.d.ts +2 -0
- package/dist/templates/configs/horizon.d.ts.map +1 -0
- package/dist/templates/configs/horizon.js +30 -0
- package/dist/templates/configs/horizon.js.map +1 -0
- package/dist/templates/configs/index.d.ts +3 -0
- package/dist/templates/configs/index.d.ts.map +1 -0
- package/dist/templates/configs/index.js +92 -0
- package/dist/templates/configs/index.js.map +1 -0
- package/dist/templates/configs/localization.d.ts +2 -0
- package/dist/templates/configs/localization.d.ts.map +1 -0
- package/dist/templates/configs/localization.js +13 -0
- package/dist/templates/configs/localization.js.map +1 -0
- package/dist/templates/configs/log.d.ts +2 -0
- package/dist/templates/configs/log.d.ts.map +1 -0
- package/dist/templates/configs/log.js +40 -0
- package/dist/templates/configs/log.js.map +1 -0
- package/dist/templates/configs/mail.d.ts +2 -0
- package/dist/templates/configs/mail.d.ts.map +1 -0
- package/dist/templates/configs/mail.js +33 -0
- package/dist/templates/configs/mail.js.map +1 -0
- package/dist/templates/configs/passport.d.ts +2 -0
- package/dist/templates/configs/passport.d.ts.map +1 -0
- package/dist/templates/configs/passport.js +22 -0
- package/dist/templates/configs/passport.js.map +1 -0
- package/dist/templates/configs/pennant.d.ts +2 -0
- package/dist/templates/configs/pennant.d.ts.map +1 -0
- package/dist/templates/configs/pennant.js +16 -0
- package/dist/templates/configs/pennant.js.map +1 -0
- package/dist/templates/configs/pulse.d.ts +2 -0
- package/dist/templates/configs/pulse.d.ts.map +1 -0
- package/dist/templates/configs/pulse.js +21 -0
- package/dist/templates/configs/pulse.js.map +1 -0
- package/dist/templates/configs/queue.d.ts +2 -0
- package/dist/templates/configs/queue.d.ts.map +1 -0
- package/dist/templates/configs/queue.js +28 -0
- package/dist/templates/configs/queue.js.map +1 -0
- package/dist/templates/configs/sanctum.d.ts +2 -0
- package/dist/templates/configs/sanctum.d.ts.map +1 -0
- package/dist/templates/configs/sanctum.js +19 -0
- package/dist/templates/configs/sanctum.js.map +1 -0
- package/dist/templates/configs/server.d.ts +2 -0
- package/dist/templates/configs/server.d.ts.map +1 -0
- package/dist/templates/configs/server.js +15 -0
- package/dist/templates/configs/server.js.map +1 -0
- package/dist/templates/configs/session.d.ts +2 -0
- package/dist/templates/configs/session.d.ts.map +1 -0
- package/dist/templates/configs/session.js +26 -0
- package/dist/templates/configs/session.js.map +1 -0
- package/dist/templates/configs/socialite.d.ts +2 -0
- package/dist/templates/configs/socialite.d.ts.map +1 -0
- package/dist/templates/configs/socialite.js +27 -0
- package/dist/templates/configs/socialite.js.map +1 -0
- package/dist/templates/configs/storage.d.ts +2 -0
- package/dist/templates/configs/storage.d.ts.map +1 -0
- package/dist/templates/configs/storage.js +35 -0
- package/dist/templates/configs/storage.js.map +1 -0
- package/dist/templates/configs/sync.d.ts +3 -0
- package/dist/templates/configs/sync.d.ts.map +1 -0
- package/dist/templates/configs/sync.js +17 -0
- package/dist/templates/configs/sync.js.map +1 -0
- package/dist/templates/configs/telescope.d.ts +2 -0
- package/dist/templates/configs/telescope.d.ts.map +1 -0
- package/dist/templates/configs/telescope.js +25 -0
- package/dist/templates/configs/telescope.js.map +1 -0
- package/dist/templates/css/index.d.ts +3 -0
- package/dist/templates/css/index.d.ts.map +1 -0
- package/dist/templates/css/index.js +140 -0
- package/dist/templates/css/index.js.map +1 -0
- package/dist/templates/css/plain.d.ts +2 -0
- package/dist/templates/css/plain.d.ts.map +1 -0
- package/dist/templates/css/plain.js +373 -0
- package/dist/templates/css/plain.js.map +1 -0
- package/dist/templates/css/tailwind.d.ts +2 -0
- package/dist/templates/css/tailwind.d.ts.map +1 -0
- package/dist/templates/css/tailwind.js +176 -0
- package/dist/templates/css/tailwind.js.map +1 -0
- package/dist/templates/demos/avatar.d.ts +3 -0
- package/dist/templates/demos/avatar.d.ts.map +1 -0
- package/dist/templates/demos/avatar.js +182 -0
- package/dist/templates/demos/avatar.js.map +1 -0
- package/dist/templates/demos/cache.d.ts +3 -0
- package/dist/templates/demos/cache.d.ts.map +1 -0
- package/dist/templates/demos/cache.js +99 -0
- package/dist/templates/demos/cache.js.map +1 -0
- package/dist/templates/demos/contact.d.ts +3 -0
- package/dist/templates/demos/contact.d.ts.map +1 -0
- package/dist/templates/demos/contact.js +106 -0
- package/dist/templates/demos/contact.js.map +1 -0
- package/dist/templates/demos/fibonacci.d.ts +7 -0
- package/dist/templates/demos/fibonacci.d.ts.map +1 -0
- package/dist/templates/demos/fibonacci.js +172 -0
- package/dist/templates/demos/fibonacci.js.map +1 -0
- package/dist/templates/demos/http.d.ts +3 -0
- package/dist/templates/demos/http.d.ts.map +1 -0
- package/dist/templates/demos/http.js +117 -0
- package/dist/templates/demos/http.js.map +1 -0
- package/dist/templates/demos/index-view.d.ts +3 -0
- package/dist/templates/demos/index-view.d.ts.map +1 -0
- package/dist/templates/demos/index-view.js +144 -0
- package/dist/templates/demos/index-view.js.map +1 -0
- package/dist/templates/demos/localization.d.ts +4 -0
- package/dist/templates/demos/localization.d.ts.map +1 -0
- package/dist/templates/demos/localization.js +130 -0
- package/dist/templates/demos/localization.js.map +1 -0
- package/dist/templates/demos/mail.d.ts +4 -0
- package/dist/templates/demos/mail.d.ts.map +1 -0
- package/dist/templates/demos/mail.js +127 -0
- package/dist/templates/demos/mail.js.map +1 -0
- package/dist/templates/demos/notifications.d.ts +4 -0
- package/dist/templates/demos/notifications.d.ts.map +1 -0
- package/dist/templates/demos/notifications.js +133 -0
- package/dist/templates/demos/notifications.js.map +1 -0
- package/dist/templates/demos/pennant.d.ts +8 -0
- package/dist/templates/demos/pennant.d.ts.map +1 -0
- package/dist/templates/demos/pennant.js +138 -0
- package/dist/templates/demos/pennant.js.map +1 -0
- package/dist/templates/demos/queue.d.ts +4 -0
- package/dist/templates/demos/queue.d.ts.map +1 -0
- package/dist/templates/demos/queue.js +107 -0
- package/dist/templates/demos/queue.js.map +1 -0
- package/dist/templates/demos/registry.d.ts +13 -0
- package/dist/templates/demos/registry.d.ts.map +1 -0
- package/dist/templates/demos/registry.js +26 -0
- package/dist/templates/demos/registry.js.map +1 -0
- package/dist/templates/demos/rudder-socket.d.ts +2 -0
- package/dist/templates/demos/rudder-socket.d.ts.map +1 -0
- package/dist/templates/demos/rudder-socket.js +95 -0
- package/dist/templates/demos/rudder-socket.js.map +1 -0
- package/dist/templates/demos/sync.d.ts +2 -0
- package/dist/templates/demos/sync.d.ts.map +1 -0
- package/dist/templates/demos/sync.js +97 -0
- package/dist/templates/demos/sync.js.map +1 -0
- package/dist/templates/demos/system-info.d.ts +3 -0
- package/dist/templates/demos/system-info.d.ts.map +1 -0
- package/dist/templates/demos/system-info.js +142 -0
- package/dist/templates/demos/system-info.js.map +1 -0
- package/dist/templates/demos/todos.d.ts +6 -0
- package/dist/templates/demos/todos.d.ts.map +1 -0
- package/dist/templates/demos/todos.js +246 -0
- package/dist/templates/demos/todos.js.map +1 -0
- package/dist/templates/demos/ws.d.ts +2 -0
- package/dist/templates/demos/ws.d.ts.map +1 -0
- package/dist/templates/demos/ws.js +106 -0
- package/dist/templates/demos/ws.js.map +1 -0
- package/dist/templates/env.d.ts +7 -0
- package/dist/templates/env.d.ts.map +1 -0
- package/dist/templates/env.js +113 -0
- package/dist/templates/env.js.map +1 -0
- package/dist/templates/package-json.d.ts +3 -0
- package/dist/templates/package-json.d.ts.map +1 -0
- package/dist/templates/package-json.js +193 -0
- package/dist/templates/package-json.js.map +1 -0
- package/dist/templates/package-managers.d.ts +14 -0
- package/dist/templates/package-managers.d.ts.map +1 -0
- package/dist/templates/package-managers.js +49 -0
- package/dist/templates/package-managers.js.map +1 -0
- package/dist/templates/pages/ai-chat.d.ts +7 -0
- package/dist/templates/pages/ai-chat.d.ts.map +1 -0
- package/dist/templates/pages/ai-chat.js +285 -0
- package/dist/templates/pages/ai-chat.js.map +1 -0
- package/dist/templates/pages/demo.d.ts +4 -0
- package/dist/templates/pages/demo.d.ts.map +1 -0
- package/dist/templates/pages/demo.js +71 -0
- package/dist/templates/pages/demo.js.map +1 -0
- package/dist/templates/pages/error.d.ts +7 -0
- package/dist/templates/pages/error.d.ts.map +1 -0
- package/dist/templates/pages/error.js +148 -0
- package/dist/templates/pages/error.js.map +1 -0
- package/dist/templates/pages/index.d.ts +9 -0
- package/dist/templates/pages/index.d.ts.map +1 -0
- package/dist/templates/pages/index.js +311 -0
- package/dist/templates/pages/index.js.map +1 -0
- package/dist/templates/prisma/auth.d.ts +2 -0
- package/dist/templates/prisma/auth.d.ts.map +1 -0
- package/dist/templates/prisma/auth.js +22 -0
- package/dist/templates/prisma/auth.js.map +1 -0
- package/dist/templates/prisma/base.d.ts +3 -0
- package/dist/templates/prisma/base.d.ts.map +1 -0
- package/dist/templates/prisma/base.js +14 -0
- package/dist/templates/prisma/base.js.map +1 -0
- package/dist/templates/prisma/config.d.ts +3 -0
- package/dist/templates/prisma/config.d.ts.map +1 -0
- package/dist/templates/prisma/config.js +15 -0
- package/dist/templates/prisma/config.js.map +1 -0
- package/dist/templates/prisma/notification.d.ts +2 -0
- package/dist/templates/prisma/notification.d.ts.map +1 -0
- package/dist/templates/prisma/notification.js +16 -0
- package/dist/templates/prisma/notification.js.map +1 -0
- package/dist/templates/prisma/passport.d.ts +2 -0
- package/dist/templates/prisma/passport.d.ts.map +1 -0
- package/dist/templates/prisma/passport.js +69 -0
- package/dist/templates/prisma/passport.js.map +1 -0
- package/dist/templates/routes/api.d.ts +3 -0
- package/dist/templates/routes/api.d.ts.map +1 -0
- package/dist/templates/routes/api.js +166 -0
- package/dist/templates/routes/api.js.map +1 -0
- package/dist/templates/routes/console.d.ts +2 -0
- package/dist/templates/routes/console.d.ts.map +1 -0
- package/dist/templates/routes/console.js +22 -0
- package/dist/templates/routes/console.js.map +1 -0
- package/dist/templates/routes/web.d.ts +4 -0
- package/dist/templates/routes/web.d.ts.map +1 -0
- package/dist/templates/routes/web.js +155 -0
- package/dist/templates/routes/web.js.map +1 -0
- package/dist/templates/server.d.ts +2 -0
- package/dist/templates/server.d.ts.map +1 -0
- package/dist/templates/server.js +10 -0
- package/dist/templates/server.js.map +1 -0
- package/dist/templates/tsconfig.d.ts +3 -0
- package/dist/templates/tsconfig.d.ts.map +1 -0
- package/dist/templates/tsconfig.js +33 -0
- package/dist/templates/tsconfig.js.map +1 -0
- package/dist/templates/views/welcome.d.ts +6 -0
- package/dist/templates/views/welcome.d.ts.map +1 -0
- package/dist/templates/views/welcome.js +396 -0
- package/dist/templates/views/welcome.js.map +1 -0
- package/dist/templates/vite.d.ts +3 -0
- package/dist/templates/vite.d.ts.map +1 -0
- package/dist/templates/vite.js +61 -0
- package/dist/templates/vite.js.map +1 -0
- package/dist/templates.d.ts +27 -17
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +158 -3778
- package/dist/templates.js.map +1 -1
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|