@sansavision/create-pulse 0.4.0 → 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 (48) hide show
  1. package/README.md +27 -3
  2. package/dist/index.js +3 -1
  3. package/package.json +2 -2
  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-all-features/package.json +2 -2
  39. package/templates/react-all-features/src/App.tsx +20 -39
  40. package/templates/react-all-features/src/components/EncryptedChat.tsx +8 -8
  41. package/templates/react-all-features/src/components/GameSync.tsx +38 -23
  42. package/templates/react-all-features/src/components/ServerMetrics.tsx +20 -15
  43. package/templates/react-queue-demo/README.md +6 -7
  44. package/templates/react-queue-demo/package.json +1 -1
  45. package/templates/react-queue-demo/src/App.tsx +229 -62
  46. package/templates/react-watch-together/package.json +2 -2
  47. package/templates/react-watch-together/src/App.tsx +18 -40
  48. package/src/index.ts +0 -115
@@ -34,7 +34,7 @@ export default function App() {
34
34
  }]);
35
35
  }, []);
36
36
 
37
- const handleSyncMessage = useCallback((msg: any) => {
37
+ const handleSyncMessage = useCallback((msg: { action: string; time?: number; ts?: number }) => {
38
38
  if (activeFeature !== 'watch') return;
39
39
 
40
40
  // Track delivery delay
@@ -73,41 +73,22 @@ export default function App() {
73
73
  const msg = { action, time, ts: sendTs };
74
74
  broadcast(msg);
75
75
 
76
- // Single-tab simulation: defer peerTime update by the measured relay
77
- // round-trip so that drift is visible and realistic. In a real
78
- // multi-tab setup the relay echo handler (handleSyncMessage) updates
79
- // peerTime — but since the relay does not echo back to the sender,
80
- // we simulate it here with the actual measured delay.
81
- const simulatedDelay = delayHistoryRef.current.length > 0
82
- ? delayHistoryRef.current[delayHistoryRef.current.length - 1]
83
- : 4; // sensible default ~4ms first message
84
-
85
- setTimeout(() => {
86
- if (action === 'tick') {
87
- setPeerTime(time);
88
- } else if (action === 'play') {
89
- setIsPlaying(true);
90
- setPeerTime(time);
91
- } else if (action === 'pause') {
92
- setIsPlaying(false);
93
- setPeerTime(time);
94
- } else if (action === 'seek') {
95
- setPeerTime(time);
96
- }
97
- }, simulatedDelay);
98
-
99
- // Measure delay after the event loop processes the send
100
- setTimeout(() => {
101
- const delay = Date.now() - sendTs;
102
- setLastDelay(delay);
103
- delayHistoryRef.current = [...delayHistoryRef.current.slice(-29), delay];
104
- }, 0);
76
+ // Single-tab simulation: update peerTime immediately (in a real
77
+ // multi-tab setup the relay echo handler updates peerTime).
78
+ if (action === 'tick') {
79
+ setPeerTime(time);
80
+ } else if (action === 'play') {
81
+ setIsPlaying(true);
82
+ setPeerTime(time);
83
+ } else if (action === 'pause') {
84
+ setIsPlaying(false);
85
+ setPeerTime(time);
86
+ } else if (action === 'seek') {
87
+ setPeerTime(time);
88
+ }
105
89
  }, [broadcast, logEvent]);
106
90
 
107
- // Computed delay stats
108
- const avgDelay = delayHistoryRef.current.length > 0
109
- ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
110
- : null;
91
+
111
92
 
112
93
  return (
113
94
  <div className="min-h-screen">
@@ -315,14 +296,14 @@ export default function App() {
315
296
  <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
316
297
  <span className="text-textMuted">Relay RTT</span>
317
298
  <span className="text-textMain font-semibold tracking-wider">
318
- {metrics ? `${Math.round(metrics.rtt)}ms` : avgDelay !== null ? `~${avgDelay * 2}ms` : '—'}
299
+ {metrics ? `${Math.round(metrics.rtt)}ms` : '—'}
319
300
  </span>
320
301
  </div>
321
302
  <div className="flex justify-between items-center p-3 rounded-lg bg-black/30 border border-pulseBorder">
322
303
  <span className="text-textMuted">Msg Delay</span>
323
304
  <span className={`font-semibold tracking-wider ${lastDelay !== null && lastDelay > 50 ? 'text-brandAmber' : 'text-brandEmerald'}`}>
324
305
  {lastDelay !== null ? `${lastDelay}ms` : '—'}
325
- {avgDelay !== null && <span className="text-textDim text-[10px] ml-1">avg {avgDelay}ms</span>}
306
+ <span className="text-textDim text-[10px] ml-1">(relay echo)</span>
326
307
  </span>
327
308
  </div>
328
309
  </div>
@@ -364,7 +345,7 @@ export default function App() {
364
345
  <h2 className="text-3xl font-bold tracking-tight mb-2">Encrypted Chat</h2>
365
346
  <div className="text-textMuted mb-6 text-sm">Real Web Crypto API (ECDH P-256 + AES-GCM) powering E2E encryption over Pulse Relay.</div>
366
347
 
367
- <EncryptedChat useChannel={useChannel} logEvent={logEvent} />
348
+ <EncryptedChat useChannel={useChannel} logEvent={logEvent} relayRtt={metrics?.rtt} />
368
349
  </div>
369
350
  )}
370
351
 
@@ -379,7 +360,7 @@ export default function App() {
379
360
  <h2 className="text-3xl font-bold tracking-tight mb-2">Server Metrics Dashboard</h2>
380
361
  <div className="text-textMuted mb-6 text-sm">Receiving live server telemetry via Pulse STREAM_DATA and rendering realtime charts.</div>
381
362
 
382
- <ServerMetrics useChannel={useChannel} logEvent={logEvent} />
363
+ <ServerMetrics useChannel={useChannel} logEvent={logEvent} relayRtt={metrics?.rtt} />
383
364
  </div>
384
365
  )}
385
366
 
@@ -394,7 +375,7 @@ export default function App() {
394
375
  <h2 className="text-3xl font-bold tracking-tight mb-2">Game State Sync</h2>
395
376
  <div className="text-textMuted mb-6 text-sm">Sending 60Hz tick payloads over Pulse relay and interpolating remote peer states smoothly via Canvas API.</div>
396
377
 
397
- <GameSync useChannel={useChannel} logEvent={logEvent} />
378
+ <GameSync useChannel={useChannel} logEvent={logEvent} relayRtt={metrics?.rtt} />
398
379
  </div>
399
380
  )}
400
381
  </main>
@@ -64,9 +64,12 @@ export const WebCrypto = {
64
64
  },
65
65
  };
66
66
 
67
+ import { PulseStream } from '@sansavision/pulse-sdk';
68
+
67
69
  interface EncryptedChatProps {
68
- useChannel: any;
70
+ useChannel: (channelName: string, onMessage?: (data: Record<string, unknown>) => void) => { stream: PulseStream | null; broadcast: (data: Record<string, unknown>) => void };
69
71
  logEvent: (action: string) => void;
72
+ relayRtt?: number;
70
73
  }
71
74
 
72
75
  interface ChatMessage {
@@ -75,7 +78,7 @@ interface ChatMessage {
75
78
  encrypted?: string; // base64 ciphertext for display
76
79
  }
77
80
 
78
- export function EncryptedChat({ useChannel, logEvent }: EncryptedChatProps) {
81
+ export function EncryptedChat({ useChannel, logEvent, relayRtt }: EncryptedChatProps) {
79
82
  const [status, setStatus] = useState('Generating keypairs...');
80
83
  const [aliceKeyStr, setAliceKeyStr] = useState('generating...');
81
84
  const [bobKeyStr, setBobKeyStr] = useState('generating...');
@@ -125,7 +128,8 @@ export function EncryptedChat({ useChannel, logEvent }: EncryptedChatProps) {
125
128
  }, [logEvent]);
126
129
 
127
130
  // Handle incoming messages from the relay (cross-tab scenario)
128
- const handleMessage = useCallback(async (msg: any) => {
131
+ const handleMessage = useCallback(async (data: Record<string, unknown>) => {
132
+ const msg = data as { type: string; sender: string; ciphertext: string };
129
133
  if (msg.type !== 'e2e') return;
130
134
 
131
135
  try {
@@ -196,10 +200,6 @@ export function EncryptedChat({ useChannel, logEvent }: EncryptedChatProps) {
196
200
  }
197
201
  };
198
202
 
199
- const avgDelay = delayHistoryRef.current.length > 0
200
- ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
201
- : null;
202
-
203
203
  return (
204
204
  <div className="space-y-6 max-w-5xl mx-auto">
205
205
  {/* Crypto status banner */}
@@ -225,7 +225,7 @@ export function EncryptedChat({ useChannel, logEvent }: EncryptedChatProps) {
225
225
  <div className="glass-panel px-4 py-3 flex items-center justify-between">
226
226
  <div className="flex items-center gap-2 text-xs text-textMuted"><Clock className="w-3.5 h-3.5" />Relay RTT</div>
227
227
  <span className="text-sm font-bold font-mono text-textMain">
228
- {avgDelay !== null ? `~${avgDelay * 2}ms` : '—'}
228
+ {relayRtt !== undefined ? `${Math.round(relayRtt)}ms` : '—'}
229
229
  </span>
230
230
  </div>
231
231
  </div>
@@ -1,11 +1,36 @@
1
1
  import React, { useEffect, useRef, useState, useCallback } from 'react';
2
+ import { PulseStream } from '@sansavision/pulse-sdk';
3
+
4
+ interface PlayerState {
5
+ x: number;
6
+ y: number;
7
+ color: string;
8
+ label: string;
9
+ }
10
+
11
+ interface Collectible {
12
+ x: number;
13
+ y: number;
14
+ color: string;
15
+ collected: boolean;
16
+ id: number;
17
+ }
18
+
19
+ interface SyncMessage {
20
+ p1: { x: number; y: number };
21
+ p2: { x: number; y: number };
22
+ score: { p1: number; p2: number };
23
+ seq: number;
24
+ ts?: number;
25
+ }
2
26
 
3
27
  interface GameSyncProps {
4
- useChannel: any; // from usePulse
28
+ useChannel: (channelName: string, onMessage?: (data: Record<string, unknown>) => void) => { stream: PulseStream | null; broadcast: (data: Record<string, unknown>) => void };
5
29
  logEvent: (action: string) => void;
30
+ relayRtt?: number;
6
31
  }
7
32
 
8
- export function GameSync({ useChannel, logEvent }: GameSyncProps) {
33
+ export function GameSync({ useChannel, logEvent, relayRtt }: GameSyncProps) {
9
34
  const localCanvasRef = useRef<HTMLCanvasElement>(null);
10
35
  const remoteCanvasRef = useRef<HTMLCanvasElement>(null);
11
36
 
@@ -15,8 +40,8 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
15
40
  const [lastDelay, setLastDelay] = useState<number | null>(null);
16
41
  const delayHistoryRef = useRef<number[]>([]);
17
42
 
18
- const localPlayerRef = useRef({ x: 0.3, y: 0.5, color: '#7C3AED', label: 'P1' });
19
- const remotePlayerRef = useRef({ x: 0.7, y: 0.5, color: '#10B981', label: 'P2' });
43
+ const localPlayerRef = useRef<PlayerState>({ x: 0.3, y: 0.5, color: '#7C3AED', label: 'P1' });
44
+ const remotePlayerRef = useRef<PlayerState>({ x: 0.7, y: 0.5, color: '#10B981', label: 'P2' });
20
45
  const remoteViewRef = useRef({
21
46
  p1: { x: 0.3, y: 0.5, color: '#7C3AED', label: 'P1' },
22
47
  p2: { x: 0.7, y: 0.5, color: '#10B981', label: 'P2' }
@@ -29,7 +54,7 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
29
54
  const aiRef = useRef({ angle: 0, speed: 0.003 });
30
55
 
31
56
  // Collectibles
32
- const collectiblesRef = useRef(
57
+ const collectiblesRef = useRef<Collectible[]>(
33
58
  Array.from({ length: 5 }).map((_, i) => ({
34
59
  x: 0.1 + Math.random() * 0.8,
35
60
  y: 0.1 + Math.random() * 0.8,
@@ -54,7 +79,8 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
54
79
  }, []);
55
80
 
56
81
  // Sync handler (incoming data)
57
- const handleSyncMessage = useCallback((msg: any) => {
82
+ const handleSyncMessage = useCallback((data: Record<string, unknown>) => {
83
+ const msg = data as unknown as SyncMessage;
58
84
  if (msg.seq) {
59
85
  const state = msg;
60
86
  // Interpolate towards the actual synced state
@@ -102,7 +128,7 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
102
128
  ctx.textAlign = "start";
103
129
  };
104
130
 
105
- const drawArena = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, p1: any, p2: any, isRemote: boolean) => {
131
+ const drawArena = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, p1: PlayerState, p2: PlayerState, isRemote: boolean) => {
106
132
  const w = canvas.width;
107
133
  const h = canvas.height;
108
134
 
@@ -118,7 +144,7 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
118
144
  ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
119
145
  }
120
146
 
121
- collectiblesRef.current.forEach((c: any) => {
147
+ collectiblesRef.current.forEach((c) => {
122
148
  if (c.collected) return;
123
149
  ctx.beginPath();
124
150
  ctx.arc(c.x * w, c.y * h, 8, 0, Math.PI * 2);
@@ -142,11 +168,6 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
142
168
  }
143
169
  };
144
170
 
145
- // Computed avg delay
146
- const avgDelay = delayHistoryRef.current.length > 0
147
- ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
148
- : null;
149
-
150
171
  // Game loop
151
172
  useEffect(() => {
152
173
  let animationId: number;
@@ -171,7 +192,7 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
171
192
  remoteP.x = 0.5 + Math.cos(aiRef.current.angle) * 0.3;
172
193
  remoteP.y = 0.5 + Math.sin(aiRef.current.angle * 1.3) * 0.25;
173
194
 
174
- collectiblesRef.current.forEach((c: any) => {
195
+ collectiblesRef.current.forEach((c) => {
175
196
  if (c.collected) return;
176
197
  const d1 = Math.hypot(localP.x - c.x, localP.y - c.y);
177
198
  const d2 = Math.hypot(remoteP.x - c.x, remoteP.y - c.y);
@@ -179,8 +200,8 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
179
200
  if (d2 < 0.04) { c.collected = true; scoreRef.current.p2++; }
180
201
  });
181
202
 
182
- if (collectiblesRef.current.every((c: any) => c.collected)) {
183
- collectiblesRef.current.forEach((c: any) => {
203
+ if (collectiblesRef.current.every((c) => c.collected)) {
204
+ collectiblesRef.current.forEach((c) => {
184
205
  c.x = 0.1 + Math.random() * 0.8;
185
206
  c.y = 0.1 + Math.random() * 0.8;
186
207
  c.collected = false;
@@ -208,12 +229,6 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
208
229
  broadcast(payload);
209
230
 
210
231
  // Delay is measured in handleSyncMessage when the relay echoes back.
211
- // Also measure locally via deferred setTimeout for single-tab demos.
212
- setTimeout(() => {
213
- const delay = Date.now() - sendTs;
214
- setLastDelay(delay);
215
- delayHistoryRef.current = [...delayHistoryRef.current.slice(-29), delay];
216
- }, 0);
217
232
 
218
233
  // Interpolate fallback for when broadcast doesn't echo locally fast enough
219
234
  const rv = remoteViewRef.current;
@@ -286,7 +301,7 @@ export function GameSync({ useChannel, logEvent }: GameSyncProps) {
286
301
  </div>
287
302
  <div className="glass-panel p-4 text-center">
288
303
  <div className="text-2xl font-bold font-mono text-brandEmerald">
289
- {avgDelay !== null ? `~${avgDelay * 2}ms` : '—'}
304
+ {relayRtt !== undefined ? `${Math.round(relayRtt)}ms` : '—'}
290
305
  </div>
291
306
  <div className="text-[10px] text-textDim uppercase tracking-widest mt-1">Relay RTT</div>
292
307
  </div>
@@ -1,11 +1,22 @@
1
- import React, { useEffect, useState, useRef, useCallback } from 'react';
1
+ import { useEffect, useState, useRef, useCallback } from 'react';
2
+ import { PulseStream } from '@sansavision/pulse-sdk';
3
+
4
+ interface ServerMetricsPayload {
5
+ type: string;
6
+ cpu: number;
7
+ mem: number;
8
+ net: number;
9
+ disk: number;
10
+ ts?: number;
11
+ }
2
12
 
3
13
  interface ServerMetricsProps {
4
- useChannel: any; // from usePulse
14
+ useChannel: (channelName: string, onMessage?: (data: Record<string, unknown>) => void) => { stream: PulseStream | null; broadcast: (data: Record<string, unknown>) => void };
5
15
  logEvent: (action: string) => void;
16
+ relayRtt?: number; // Real PLP RTT from parent ConnectionMetrics
6
17
  }
7
18
 
8
- export function ServerMetrics({ useChannel, logEvent }: ServerMetricsProps) {
19
+ export function ServerMetrics({ useChannel, logEvent, relayRtt }: ServerMetricsProps) {
9
20
  const [stats, setStats] = useState({ cpu: 0, mem: 0, net: 0, disk: 0 });
10
21
  const cpuCanvas = useRef<HTMLCanvasElement>(null);
11
22
  const memCanvas = useRef<HTMLCanvasElement>(null);
@@ -54,7 +65,8 @@ export function ServerMetrics({ useChannel, logEvent }: ServerMetricsProps) {
54
65
  ctx.fill();
55
66
  };
56
67
 
57
- const handleMessage = useCallback((metrics: any) => {
68
+ const handleMessage = useCallback((data: Record<string, unknown>) => {
69
+ const metrics = data as unknown as ServerMetricsPayload;
58
70
  if (metrics.type === "server_metrics") {
59
71
  setStats({ cpu: metrics.cpu, mem: metrics.mem, net: metrics.net, disk: metrics.disk });
60
72
 
@@ -101,21 +113,14 @@ export function ServerMetrics({ useChannel, logEvent }: ServerMetricsProps) {
101
113
  // Normally the server does this, here we do it so the demo "just works" locally
102
114
  broadcast(fakeMetrics);
103
115
 
104
- // Measure delay via deferred setTimeout for single-tab demos
105
- setTimeout(() => {
106
- const delay = Date.now() - sendTs;
107
- setLastDelay(delay);
108
- delayHistoryRef.current = [...delayHistoryRef.current.slice(-29), delay];
109
- }, 0);
116
+ // Single-tab: this self-echo arrives almost instantly.
117
+ // The "Msg Delay" shown is the relay round-trip observed for
118
+ // the broadcast echo it is NOT a fabricated value.
110
119
  }, 1000);
111
120
 
112
121
  return () => clearInterval(interval);
113
122
  }, [broadcast]);
114
123
 
115
- const avgDelay = delayHistoryRef.current.length > 0
116
- ? Math.round(delayHistoryRef.current.reduce((a: number, b: number) => a + b, 0) / delayHistoryRef.current.length)
117
- : null;
118
-
119
124
  return (
120
125
  <div className="space-y-6 max-w-5xl mx-auto">
121
126
  <div className="grid grid-cols-4 gap-4">
@@ -136,7 +141,7 @@ export function ServerMetrics({ useChannel, logEvent }: ServerMetricsProps) {
136
141
  <div className="glass-panel p-6 text-center">
137
142
  <div className="text-[10px] text-textDim uppercase tracking-widest mb-2 font-semibold">Relay RTT</div>
138
143
  <div className="text-3xl font-bold text-brandEmerald font-mono">
139
- {avgDelay !== null ? `~${avgDelay * 2}ms` : '—'}
144
+ {relayRtt !== undefined ? `${Math.round(relayRtt)}ms` : '—'}
140
145
  </div>
141
146
  </div>
142
147
  </div>
@@ -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.
@@ -8,7 +8,7 @@
8
8
  "preview": "vite preview"
9
9
  },
10
10
  "dependencies": {
11
- "@sansavision/pulse-sdk": "^0.4.0",
11
+ "@sansavision/pulse-sdk": "^0.4.1",
12
12
  "react": "^19.0.0",
13
13
  "react-dom": "^19.0.0"
14
14
  },