@sansavision/create-pulse 0.4.1 → 0.4.2

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 (40) hide show
  1. package/README.md +27 -3
  2. package/dist/index.js +3 -1
  3. package/package.json +1 -1
  4. package/templates/nextjs-auth-demo/.env.example +6 -0
  5. package/templates/nextjs-auth-demo/README.md +74 -0
  6. package/templates/nextjs-auth-demo/_gitignore +33 -0
  7. package/templates/nextjs-auth-demo/drizzle.config.ts +10 -0
  8. package/templates/nextjs-auth-demo/eslint.config.mjs +18 -0
  9. package/templates/nextjs-auth-demo/next-env.d.ts +6 -0
  10. package/templates/nextjs-auth-demo/next.config.ts +7 -0
  11. package/templates/nextjs-auth-demo/package.json +34 -0
  12. package/templates/nextjs-auth-demo/postcss.config.mjs +7 -0
  13. package/templates/nextjs-auth-demo/public/file.svg +1 -0
  14. package/templates/nextjs-auth-demo/public/globe.svg +1 -0
  15. package/templates/nextjs-auth-demo/public/next.svg +1 -0
  16. package/templates/nextjs-auth-demo/public/vercel.svg +1 -0
  17. package/templates/nextjs-auth-demo/public/window.svg +1 -0
  18. package/templates/nextjs-auth-demo/src/app/api/auth/[...all]/route.ts +4 -0
  19. package/templates/nextjs-auth-demo/src/app/api/pulse/verify/route.ts +54 -0
  20. package/templates/nextjs-auth-demo/src/app/auth/sign-in/page.tsx +131 -0
  21. package/templates/nextjs-auth-demo/src/app/auth/sign-up/page.tsx +153 -0
  22. package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +248 -0
  23. package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +198 -0
  24. package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +192 -0
  25. package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +297 -0
  26. package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +258 -0
  27. package/templates/nextjs-auth-demo/src/app/dashboard/layout.tsx +109 -0
  28. package/templates/nextjs-auth-demo/src/app/dashboard/page.tsx +147 -0
  29. package/templates/nextjs-auth-demo/src/app/favicon.ico +0 -0
  30. package/templates/nextjs-auth-demo/src/app/globals.css +96 -0
  31. package/templates/nextjs-auth-demo/src/app/layout.tsx +27 -0
  32. package/templates/nextjs-auth-demo/src/app/page.tsx +254 -0
  33. package/templates/nextjs-auth-demo/src/lib/auth-client.ts +15 -0
  34. package/templates/nextjs-auth-demo/src/lib/auth.ts +13 -0
  35. package/templates/nextjs-auth-demo/src/lib/db.ts +5 -0
  36. package/templates/nextjs-auth-demo/src/lib/pulse.ts +45 -0
  37. package/templates/nextjs-auth-demo/tsconfig.json +34 -0
  38. package/templates/react-queue-demo/README.md +6 -7
  39. package/templates/react-queue-demo/src/App.tsx +34 -13
  40. package/src/index.ts +0 -115
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useSession } from "@/lib/auth-client";
5
+ import {
6
+ MessageSquare,
7
+ Video,
8
+ Database,
9
+ Gamepad2,
10
+ Lock,
11
+ ArrowRight,
12
+ Shield,
13
+ Wifi,
14
+ Activity,
15
+ } from "lucide-react";
16
+
17
+ const demos = [
18
+ {
19
+ href: "/dashboard/demos/chat",
20
+ icon: MessageSquare,
21
+ title: "Real-time Chat",
22
+ desc: "Multi-user rooms with presence, typing indicators, and auth identity. Open a second tab with a different user to see live messaging.",
23
+ color: "from-blue-500 to-blue-600",
24
+ badge: "Pub/Sub",
25
+ },
26
+ {
27
+ href: "/dashboard/demos/watch-together",
28
+ icon: Video,
29
+ title: "Watch Together",
30
+ desc: "Synchronized video playback. Play, pause, and seek in perfect sync across all connected users.",
31
+ color: "from-pink-500 to-rose-600",
32
+ badge: "Broadcast",
33
+ },
34
+ {
35
+ href: "/dashboard/demos/queues",
36
+ icon: Database,
37
+ title: "Durable Queues",
38
+ desc: "Publish messages, simulate going offline, come back online — messages persist and are delivered. Full ACK/NACK flow.",
39
+ color: "from-amber-500 to-orange-600",
40
+ badge: "Queue",
41
+ },
42
+ {
43
+ href: "/dashboard/demos/game-sync",
44
+ icon: Gamepad2,
45
+ title: "Game State Sync",
46
+ desc: "Shared game board with real-time state sync. Multiple users can interact with the same game state simultaneously.",
47
+ color: "from-green-500 to-emerald-600",
48
+ badge: "State",
49
+ },
50
+ {
51
+ href: "/dashboard/demos/encrypted-chat",
52
+ icon: Lock,
53
+ title: "E2E Encrypted Chat",
54
+ desc: "End-to-end encrypted messaging where the relay cannot read content. Key exchange visualization included.",
55
+ color: "from-purple-500 to-violet-600",
56
+ badge: "P2P",
57
+ },
58
+ ];
59
+
60
+ export default function DashboardPage() {
61
+ const { data: session } = useSession();
62
+
63
+ return (
64
+ <div className="p-8">
65
+ {/* Header */}
66
+ <div className="mb-8">
67
+ <h1 className="text-3xl font-bold mb-2">
68
+ Welcome, {session?.user?.name?.split(" ")[0] || "there"} 👋
69
+ </h1>
70
+ <p className="text-slate-400 text-lg">
71
+ Choose a demo to explore Pulse&apos;s capabilities with authenticated
72
+ connections.
73
+ </p>
74
+ </div>
75
+
76
+ {/* Connection status */}
77
+ <div className="glass rounded-xl p-5 mb-8 flex items-center gap-6">
78
+ <div className="flex items-center gap-2">
79
+ <Shield className="w-4 h-4 text-green-400" />
80
+ <span className="text-sm text-green-400 font-medium">
81
+ Authenticated
82
+ </span>
83
+ </div>
84
+ <div className="flex items-center gap-2">
85
+ <Wifi className="w-4 h-4 text-cyan-400" />
86
+ <span className="text-sm text-slate-400">
87
+ Relay:{" "}
88
+ <code className="text-cyan-400 bg-cyan-500/10 px-1.5 py-0.5 rounded text-xs">
89
+ {process.env.NEXT_PUBLIC_PULSE_URL || "ws://localhost:4001"}
90
+ </code>
91
+ </span>
92
+ </div>
93
+ <div className="flex items-center gap-2">
94
+ <Activity className="w-4 h-4 text-purple-400" />
95
+ <span className="text-sm text-slate-400">
96
+ Auth Mode:{" "}
97
+ <code className="text-purple-400 bg-purple-500/10 px-1.5 py-0.5 rounded text-xs">
98
+ webhook
99
+ </code>
100
+ </span>
101
+ </div>
102
+ </div>
103
+
104
+ {/* Tip */}
105
+ <div className="glass rounded-xl p-5 mb-8 border-l-4 border-purple-500">
106
+ <h3 className="text-sm font-semibold text-purple-400 mb-1">
107
+ 💡 Multi-User Tip
108
+ </h3>
109
+ <p className="text-sm text-slate-400">
110
+ Open a second browser tab (or incognito window), create a different
111
+ account, and interact simultaneously. Watch messages, video sync, and
112
+ game state flow in real-time between users.
113
+ </p>
114
+ </div>
115
+
116
+ {/* Demo cards */}
117
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
118
+ {demos.map((demo) => (
119
+ <Link key={demo.href} href={demo.href}>
120
+ <div className="demo-card glass rounded-2xl p-6 h-full cursor-pointer group">
121
+ <div className="flex items-start justify-between mb-4">
122
+ <div
123
+ className={`w-12 h-12 rounded-xl bg-gradient-to-br ${demo.color} flex items-center justify-center`}
124
+ >
125
+ <demo.icon className="w-6 h-6 text-white" />
126
+ </div>
127
+ <span className="text-xs font-mono px-2 py-1 rounded-md bg-slate-800 text-slate-400 border border-slate-700">
128
+ {demo.badge}
129
+ </span>
130
+ </div>
131
+ <h3 className="text-lg font-semibold mb-2 group-hover:text-purple-400 transition-colors">
132
+ {demo.title}
133
+ </h3>
134
+ <p className="text-sm text-slate-400 leading-relaxed mb-4">
135
+ {demo.desc}
136
+ </p>
137
+ <div className="flex items-center gap-1 text-sm text-purple-400 font-medium">
138
+ Open Demo
139
+ <ArrowRight className="w-3.5 h-3.5 group-hover:translate-x-1 transition-transform" />
140
+ </div>
141
+ </div>
142
+ </Link>
143
+ ))}
144
+ </div>
145
+ </div>
146
+ );
147
+ }
@@ -0,0 +1,96 @@
1
+ @import "tailwindcss";
2
+
3
+ :root {
4
+ --color-primary: #7c3aed;
5
+ --color-primary-light: #a78bfa;
6
+ --color-accent: #06b6d4;
7
+ --color-surface: #0f172a;
8
+ --color-surface-elevated: #1e293b;
9
+ --color-surface-hover: #334155;
10
+ --color-text: #f1f5f9;
11
+ --color-text-muted: #94a3b8;
12
+ --color-border: #334155;
13
+ --color-danger: #ef4444;
14
+ --color-success: #10b981;
15
+ --color-warning: #f59e0b;
16
+ }
17
+
18
+ * {
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ body {
23
+ font-family: "Inter", "SF Pro Display", -apple-system, system-ui, sans-serif;
24
+ background: var(--color-surface);
25
+ color: var(--color-text);
26
+ -webkit-font-smoothing: antialiased;
27
+ -moz-osx-font-smoothing: grayscale;
28
+ }
29
+
30
+ /* Smooth scrollbar */
31
+ ::-webkit-scrollbar {
32
+ width: 6px;
33
+ }
34
+ ::-webkit-scrollbar-track {
35
+ background: transparent;
36
+ }
37
+ ::-webkit-scrollbar-thumb {
38
+ background: var(--color-border);
39
+ border-radius: 3px;
40
+ }
41
+
42
+ /* Glassmorphism utility */
43
+ .glass {
44
+ background: rgba(30, 41, 59, 0.7);
45
+ backdrop-filter: blur(16px);
46
+ -webkit-backdrop-filter: blur(16px);
47
+ border: 1px solid rgba(148, 163, 184, 0.1);
48
+ }
49
+
50
+ /* Gradient text */
51
+ .gradient-text {
52
+ background: linear-gradient(135deg, #7c3aed, #06b6d4, #10b981);
53
+ -webkit-background-clip: text;
54
+ -webkit-text-fill-color: transparent;
55
+ background-clip: text;
56
+ }
57
+
58
+ /* Pulse animation */
59
+ @keyframes pulse-glow {
60
+ 0%, 100% { box-shadow: 0 0 20px rgba(124, 58, 237, 0.3); }
61
+ 50% { box-shadow: 0 0 40px rgba(124, 58, 237, 0.6); }
62
+ }
63
+
64
+ .pulse-glow {
65
+ animation: pulse-glow 2s ease-in-out infinite;
66
+ }
67
+
68
+ /* Status indicator */
69
+ @keyframes status-blink {
70
+ 0%, 100% { opacity: 1; }
71
+ 50% { opacity: 0.5; }
72
+ }
73
+
74
+ .status-online {
75
+ width: 8px;
76
+ height: 8px;
77
+ border-radius: 50%;
78
+ background: var(--color-success);
79
+ animation: status-blink 2s ease-in-out infinite;
80
+ }
81
+
82
+ .status-offline {
83
+ width: 8px;
84
+ height: 8px;
85
+ border-radius: 50%;
86
+ background: var(--color-danger);
87
+ }
88
+
89
+ /* Card hover effect */
90
+ .demo-card {
91
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
92
+ }
93
+ .demo-card:hover {
94
+ transform: translateY(-4px);
95
+ box-shadow: 0 20px 60px rgba(124, 58, 237, 0.15);
96
+ }
@@ -0,0 +1,27 @@
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ export const metadata: Metadata = {
5
+ title: "Pulse — Real-Time Protocol Demo",
6
+ description:
7
+ "Investor-ready demo showcasing PLP (Pulse Line Protocol) with auth, real-time chat, synchronized video, durable queues, and encrypted messaging.",
8
+ keywords: ["pulse", "real-time", "websocket", "protocol", "auth"],
9
+ };
10
+
11
+ export default function RootLayout({
12
+ children,
13
+ }: {
14
+ children: React.ReactNode;
15
+ }) {
16
+ return (
17
+ <html lang="en" className="dark">
18
+ <head>
19
+ <link
20
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
21
+ rel="stylesheet"
22
+ />
23
+ </head>
24
+ <body className="min-h-screen antialiased">{children}</body>
25
+ </html>
26
+ );
27
+ }
@@ -0,0 +1,254 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import {
5
+ Zap,
6
+ Shield,
7
+ Wifi,
8
+ MessageSquare,
9
+ Video,
10
+ Database,
11
+ Gamepad2,
12
+ Lock,
13
+ ArrowRight,
14
+ Globe,
15
+ Clock,
16
+ Activity,
17
+ } from "lucide-react";
18
+
19
+ export default function LandingPage() {
20
+ return (
21
+ <div className="min-h-screen">
22
+ {/* Nav */}
23
+ <nav className="fixed top-0 left-0 right-0 z-50 glass">
24
+ <div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between">
25
+ <div className="flex items-center gap-2">
26
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-purple-500 to-cyan-500 flex items-center justify-center">
27
+ <Zap className="w-5 h-5 text-white" />
28
+ </div>
29
+ <span className="text-xl font-bold">Pulse</span>
30
+ </div>
31
+ <div className="flex items-center gap-4">
32
+ <Link
33
+ href="/auth/sign-in"
34
+ className="px-4 py-2 text-sm text-slate-300 hover:text-white transition-colors"
35
+ >
36
+ Sign In
37
+ </Link>
38
+ <Link
39
+ href="/auth/sign-up"
40
+ className="px-5 py-2 text-sm bg-purple-600 hover:bg-purple-500 rounded-lg font-medium transition-all hover:shadow-lg hover:shadow-purple-500/25"
41
+ >
42
+ Get Started
43
+ </Link>
44
+ </div>
45
+ </div>
46
+ </nav>
47
+
48
+ {/* Hero */}
49
+ <section className="relative pt-32 pb-20 px-6 overflow-hidden">
50
+ {/* Background gradient orbs */}
51
+ <div className="absolute top-20 left-1/4 w-96 h-96 bg-purple-500/20 rounded-full blur-[128px]" />
52
+ <div className="absolute top-40 right-1/4 w-96 h-96 bg-cyan-500/20 rounded-full blur-[128px]" />
53
+
54
+ <div className="max-w-5xl mx-auto text-center relative z-10">
55
+ <div className="inline-flex items-center gap-2 px-4 py-1.5 rounded-full border border-purple-500/30 bg-purple-500/10 text-purple-300 text-sm mb-8">
56
+ <Activity className="w-3.5 h-3.5" />
57
+ <span>PLP — Pulse Line Protocol v1</span>
58
+ </div>
59
+
60
+ <h1 className="text-5xl md:text-7xl font-extrabold tracking-tight leading-tight mb-6">
61
+ <span className="gradient-text">The Real-Time Protocol</span>
62
+ <br />
63
+ <span className="text-slate-200">for Modern Applications</span>
64
+ </h1>
65
+
66
+ <p className="text-xl text-slate-400 max-w-2xl mx-auto mb-10 leading-relaxed">
67
+ Sub-millisecond latency, end-to-end encryption, durable queues, and
68
+ external auth — all in one binary. Built with Rust, powered by QUIC.
69
+ </p>
70
+
71
+ <div className="flex items-center justify-center gap-4">
72
+ <Link
73
+ href="/auth/sign-up"
74
+ className="inline-flex items-center gap-2 px-8 py-3.5 bg-gradient-to-r from-purple-600 to-purple-500 hover:from-purple-500 hover:to-purple-400 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25 pulse-glow"
75
+ >
76
+ Try the Demo
77
+ <ArrowRight className="w-5 h-5" />
78
+ </Link>
79
+ <a
80
+ href="https://github.com/Sansa-Organisation/pulse"
81
+ target="_blank"
82
+ rel="noopener"
83
+ className="px-8 py-3.5 rounded-xl font-semibold text-lg border border-slate-700 hover:border-slate-500 hover:bg-slate-800/50 transition-all"
84
+ >
85
+ GitHub
86
+ </a>
87
+ </div>
88
+
89
+ {/* Stats */}
90
+ <div className="grid grid-cols-3 gap-8 max-w-xl mx-auto mt-16">
91
+ {[
92
+ { label: "Latency", value: "<1ms", icon: Clock },
93
+ { label: "Connections", value: "100K+", icon: Globe },
94
+ { label: "Uptime", value: "99.99%", icon: Activity },
95
+ ].map((stat) => (
96
+ <div key={stat.label} className="text-center">
97
+ <stat.icon className="w-5 h-5 text-purple-400 mx-auto mb-2" />
98
+ <div className="text-2xl font-bold">{stat.value}</div>
99
+ <div className="text-sm text-slate-500">{stat.label}</div>
100
+ </div>
101
+ ))}
102
+ </div>
103
+ </div>
104
+ </section>
105
+
106
+ {/* Features */}
107
+ <section className="py-20 px-6">
108
+ <div className="max-w-6xl mx-auto">
109
+ <div className="text-center mb-16">
110
+ <h2 className="text-3xl md:text-4xl font-bold mb-4">
111
+ Everything you need, out of the box
112
+ </h2>
113
+ <p className="text-slate-400 text-lg max-w-xl mx-auto">
114
+ One protocol, five powerful modes. Each demo below is fully functional
115
+ and uses real PLP connections.
116
+ </p>
117
+ </div>
118
+
119
+ <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
120
+ {[
121
+ {
122
+ icon: MessageSquare,
123
+ title: "Real-time Chat",
124
+ desc: "Multi-user rooms with typing indicators, presence, and message history.",
125
+ color: "from-blue-500 to-blue-600",
126
+ },
127
+ {
128
+ icon: Video,
129
+ title: "Watch Together",
130
+ desc: "Synchronized video playback — play, pause, seek in perfect sync.",
131
+ color: "from-pink-500 to-rose-600",
132
+ },
133
+ {
134
+ icon: Database,
135
+ title: "Durable Queues",
136
+ desc: "Persist messages, simulate offline, come back online — nothing lost.",
137
+ color: "from-amber-500 to-orange-600",
138
+ },
139
+ {
140
+ icon: Gamepad2,
141
+ title: "Game State Sync",
142
+ desc: "Real-time shared state with conflict-free resolution.",
143
+ color: "from-green-500 to-emerald-600",
144
+ },
145
+ {
146
+ icon: Lock,
147
+ title: "E2E Encrypted Chat",
148
+ desc: "End-to-end encryption with relay-blind transport.",
149
+ color: "from-purple-500 to-violet-600",
150
+ },
151
+ {
152
+ icon: Shield,
153
+ title: "Auth Context",
154
+ desc: "External auth integration — Better Auth, Clerk, Auth0, and more.",
155
+ color: "from-cyan-500 to-teal-600",
156
+ },
157
+ ].map((feature) => (
158
+ <div
159
+ key={feature.title}
160
+ className="demo-card glass rounded-2xl p-6"
161
+ >
162
+ <div
163
+ className={`w-12 h-12 rounded-xl bg-gradient-to-br ${feature.color} flex items-center justify-center mb-4`}
164
+ >
165
+ <feature.icon className="w-6 h-6 text-white" />
166
+ </div>
167
+ <h3 className="text-lg font-semibold mb-2">{feature.title}</h3>
168
+ <p className="text-slate-400 text-sm leading-relaxed">
169
+ {feature.desc}
170
+ </p>
171
+ </div>
172
+ ))}
173
+ </div>
174
+ </div>
175
+ </section>
176
+
177
+ {/* Architecture */}
178
+ <section className="py-20 px-6">
179
+ <div className="max-w-4xl mx-auto">
180
+ <div className="glass rounded-2xl p-8 md:p-12">
181
+ <h2 className="text-2xl font-bold mb-6 text-center">
182
+ How Auth Works
183
+ </h2>
184
+ <div className="flex flex-col md:flex-row items-center gap-6">
185
+ {[
186
+ {
187
+ step: 1,
188
+ icon: Shield,
189
+ title: "Sign In",
190
+ desc: "User authenticates via Better Auth (email/password)",
191
+ },
192
+ {
193
+ step: 2,
194
+ icon: Wifi,
195
+ title: "Connect",
196
+ desc: "SDK sends bearer token in PLP CONNECT handshake",
197
+ },
198
+ {
199
+ step: 3,
200
+ icon: Zap,
201
+ title: "Verify",
202
+ desc: "Relay calls webhook → Better Auth validates session",
203
+ },
204
+ ].map((item, i) => (
205
+ <div key={item.step} className="flex-1 text-center">
206
+ <div className="w-10 h-10 rounded-full bg-purple-600 text-white font-bold flex items-center justify-center mx-auto mb-3">
207
+ {item.step}
208
+ </div>
209
+ <item.icon className="w-6 h-6 text-purple-400 mx-auto mb-2" />
210
+ <h4 className="font-semibold mb-1">{item.title}</h4>
211
+ <p className="text-sm text-slate-400">{item.desc}</p>
212
+ {i < 2 && (
213
+ <ArrowRight className="w-5 h-5 text-slate-600 mx-auto mt-4 hidden md:block rotate-0" />
214
+ )}
215
+ </div>
216
+ ))}
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </section>
221
+
222
+ {/* CTA */}
223
+ <section className="py-20 px-6">
224
+ <div className="max-w-3xl mx-auto text-center">
225
+ <h2 className="text-3xl md:text-4xl font-bold mb-4">
226
+ Ready to experience it?
227
+ </h2>
228
+ <p className="text-slate-400 text-lg mb-8">
229
+ Create an account and explore every feature live. Open two browser
230
+ tabs to see multi-user sync in action.
231
+ </p>
232
+ <Link
233
+ href="/auth/sign-up"
234
+ className="inline-flex items-center gap-2 px-10 py-4 bg-gradient-to-r from-purple-600 to-cyan-600 hover:from-purple-500 hover:to-cyan-500 rounded-xl font-semibold text-lg transition-all hover:shadow-xl hover:shadow-purple-500/25"
235
+ >
236
+ Create Account
237
+ <ArrowRight className="w-5 h-5" />
238
+ </Link>
239
+ </div>
240
+ </section>
241
+
242
+ {/* Footer */}
243
+ <footer className="border-t border-slate-800 py-8 px-6">
244
+ <div className="max-w-6xl mx-auto flex items-center justify-between text-sm text-slate-500">
245
+ <span>© 2026 Sansa Vision. Pulse Protocol.</span>
246
+ <div className="flex items-center gap-1">
247
+ <div className="status-online" />
248
+ <span>Relay Live</span>
249
+ </div>
250
+ </div>
251
+ </footer>
252
+ </div>
253
+ );
254
+ }
@@ -0,0 +1,15 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+
3
+ export const authClient = createAuthClient({
4
+ baseURL: typeof window !== "undefined" ? window.location.origin : "http://localhost:3000",
5
+ fetchOptions: {
6
+ onSuccess: (ctx) => {
7
+ const authToken = ctx.response.headers.get("set-auth-token");
8
+ if (authToken && typeof window !== "undefined") {
9
+ localStorage.setItem("pulse_bearer_token", authToken);
10
+ }
11
+ },
12
+ },
13
+ });
14
+
15
+ export const { signIn, signUp, signOut, useSession } = authClient;
@@ -0,0 +1,13 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
+ import { bearer } from "better-auth/plugins";
4
+ import { db } from "./db";
5
+
6
+ export const auth = betterAuth({
7
+ database: drizzleAdapter(db, { provider: "sqlite" }),
8
+ emailAndPassword: {
9
+ enabled: true,
10
+ },
11
+ plugins: [bearer()],
12
+ trustedOrigins: [process.env.BETTER_AUTH_URL || "http://localhost:3000"],
13
+ });
@@ -0,0 +1,5 @@
1
+ import Database from "better-sqlite3";
2
+ import { drizzle } from "drizzle-orm/better-sqlite3";
3
+
4
+ const sqlite = new Database("./sqlite.db");
5
+ export const db = drizzle(sqlite);
@@ -0,0 +1,45 @@
1
+ "use client";
2
+
3
+ import { Pulse } from "@sansavision/pulse-sdk";
4
+
5
+ let pulseInstance: Pulse | null = null;
6
+
7
+ export function getPulse(): Pulse {
8
+ if (!pulseInstance) {
9
+ pulseInstance = new Pulse({ apiKey: "demo" });
10
+ }
11
+ return pulseInstance;
12
+ }
13
+
14
+ /**
15
+ * Get the bearer token stored by Better Auth for Pulse auth.
16
+ */
17
+ export function getPulseToken(): string | null {
18
+ if (typeof window === "undefined") return null;
19
+ return localStorage.getItem("pulse_bearer_token");
20
+ }
21
+
22
+ /**
23
+ * Connect to the Pulse relay with authentication.
24
+ * Uses the Better Auth bearer token for the PLP CONNECT handshake.
25
+ */
26
+ export async function connectWithAuth() {
27
+ const pulse = getPulse();
28
+ const token = getPulseToken();
29
+ const url = process.env.NEXT_PUBLIC_PULSE_URL || "ws://localhost:4001";
30
+
31
+ const conn = await pulse.connect(url, {
32
+ encoding: "json",
33
+ autoReconnect: true,
34
+ ...(token
35
+ ? {
36
+ auth: {
37
+ token,
38
+ provider: "better-auth",
39
+ },
40
+ }
41
+ : {}),
42
+ });
43
+
44
+ return conn;
45
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }
@@ -1,6 +1,6 @@
1
1
  # Pulse Queue Demo
2
2
 
3
- A demo app showcasing **Pulse v0.4.0** durable message queues.
3
+ A demo app showcasing **Pulse v0.4.5** durable message queues.
4
4
 
5
5
  ## Features
6
6
 
@@ -20,16 +20,16 @@ In a separate terminal, start Pulse with your preferred queue backend:
20
20
 
21
21
  ```bash
22
22
  # In-memory (default, ephemeral)
23
- pulse dev
23
+ pulse serve
24
24
 
25
25
  # WAL (Write-Ahead Log — crash-resilient)
26
- PULSE_QUEUE_BACKEND=wal pulse dev
26
+ pulse serve --queue-storage wal
27
27
 
28
28
  # PostgreSQL
29
- PULSE_QUEUE_BACKEND=postgres PULSE_QUEUE_POSTGRES_URL=postgres://user:pass@localhost/pulse pulse dev
29
+ pulse serve --queue-storage postgres --database-url postgres://user:pass@localhost/pulse
30
30
 
31
31
  # Redis
32
- PULSE_QUEUE_BACKEND=redis PULSE_QUEUE_REDIS_URL=redis://localhost:6379 pulse dev
32
+ pulse serve --queue-storage redis --redis-url redis://localhost:6379
33
33
  ```
34
34
 
35
35
  ## Encryption at Rest
@@ -37,8 +37,7 @@ PULSE_QUEUE_BACKEND=redis PULSE_QUEUE_REDIS_URL=redis://localhost:6379 pulse dev
37
37
  Add encryption to any backend:
38
38
 
39
39
  ```bash
40
- export PULSE_QUEUE_KEY=$(openssl rand -hex 32)
41
- PULSE_QUEUE_BACKEND=wal pulse dev
40
+ pulse serve --queue-storage wal --queue-encryption-key $(openssl rand -hex 32)
42
41
  ```
43
42
 
44
43
  All payloads are encrypted with ChaCha20-Poly1305 before reaching storage.