@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.
- package/README.md +27 -3
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/templates/nextjs-auth-demo/.env.example +6 -0
- package/templates/nextjs-auth-demo/README.md +74 -0
- package/templates/nextjs-auth-demo/_gitignore +33 -0
- package/templates/nextjs-auth-demo/drizzle.config.ts +10 -0
- package/templates/nextjs-auth-demo/eslint.config.mjs +18 -0
- package/templates/nextjs-auth-demo/next-env.d.ts +6 -0
- package/templates/nextjs-auth-demo/next.config.ts +7 -0
- package/templates/nextjs-auth-demo/package.json +34 -0
- package/templates/nextjs-auth-demo/postcss.config.mjs +7 -0
- package/templates/nextjs-auth-demo/public/file.svg +1 -0
- package/templates/nextjs-auth-demo/public/globe.svg +1 -0
- package/templates/nextjs-auth-demo/public/next.svg +1 -0
- package/templates/nextjs-auth-demo/public/vercel.svg +1 -0
- package/templates/nextjs-auth-demo/public/window.svg +1 -0
- package/templates/nextjs-auth-demo/src/app/api/auth/[...all]/route.ts +4 -0
- package/templates/nextjs-auth-demo/src/app/api/pulse/verify/route.ts +54 -0
- package/templates/nextjs-auth-demo/src/app/auth/sign-in/page.tsx +131 -0
- package/templates/nextjs-auth-demo/src/app/auth/sign-up/page.tsx +153 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/chat/page.tsx +248 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/encrypted-chat/page.tsx +198 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/game-sync/page.tsx +192 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/queues/page.tsx +297 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/demos/watch-together/page.tsx +258 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/layout.tsx +109 -0
- package/templates/nextjs-auth-demo/src/app/dashboard/page.tsx +147 -0
- package/templates/nextjs-auth-demo/src/app/favicon.ico +0 -0
- package/templates/nextjs-auth-demo/src/app/globals.css +96 -0
- package/templates/nextjs-auth-demo/src/app/layout.tsx +27 -0
- package/templates/nextjs-auth-demo/src/app/page.tsx +254 -0
- package/templates/nextjs-auth-demo/src/lib/auth-client.ts +15 -0
- package/templates/nextjs-auth-demo/src/lib/auth.ts +13 -0
- package/templates/nextjs-auth-demo/src/lib/db.ts +5 -0
- package/templates/nextjs-auth-demo/src/lib/pulse.ts +45 -0
- package/templates/nextjs-auth-demo/tsconfig.json +34 -0
- package/templates/react-queue-demo/README.md +6 -7
- package/templates/react-queue-demo/src/App.tsx +34 -13
- package/src/index.ts +0 -115
|
@@ -36,7 +36,7 @@ const TTL_OPTIONS = [
|
|
|
36
36
|
]
|
|
37
37
|
|
|
38
38
|
export default function App() {
|
|
39
|
-
const [
|
|
39
|
+
const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'reconnecting' | 'disconnected'>('connecting')
|
|
40
40
|
const [messages, setMessages] = useState<DisplayMessage[]>([])
|
|
41
41
|
const [input, setInput] = useState('')
|
|
42
42
|
const [rtt, setRtt] = useState<number | null>(null)
|
|
@@ -47,35 +47,54 @@ export default function App() {
|
|
|
47
47
|
const connRef = useRef<PulseConnection | null>(null)
|
|
48
48
|
const queueRef = useRef<PulseQueue | null>(null)
|
|
49
49
|
|
|
50
|
+
const connected = connectionState === 'connected'
|
|
51
|
+
|
|
52
|
+
// Helper: drain persisted messages and refresh depth
|
|
53
|
+
const recoverMessages = useCallback(async (queue: PulseQueue) => {
|
|
54
|
+
try {
|
|
55
|
+
const recovered = await queue.drain()
|
|
56
|
+
if (recovered.length > 0) {
|
|
57
|
+
setMessages(recovered.map((m) => ({ ...m, state: 'pending' as const })))
|
|
58
|
+
}
|
|
59
|
+
await refreshDepth(queue)
|
|
60
|
+
} catch {
|
|
61
|
+
// Queue may not exist yet — that's fine, it'll be auto-created
|
|
62
|
+
}
|
|
63
|
+
}, [])
|
|
64
|
+
|
|
50
65
|
// Connect + auto-fetch existing messages on mount
|
|
66
|
+
// Auto-reconnect is enabled by default in the SDK — the protocol "just works"
|
|
51
67
|
useEffect(() => {
|
|
52
68
|
const pulse = new Pulse({ apiKey: 'dev' })
|
|
53
69
|
|
|
70
|
+
setConnectionState('connecting')
|
|
71
|
+
|
|
54
72
|
pulse.connect('ws://localhost:4001').then(async (connection) => {
|
|
55
73
|
connRef.current = connection
|
|
56
74
|
const queue = new PulseQueue(connection, QUEUE_NAME, 'demo-ui')
|
|
57
75
|
queueRef.current = queue
|
|
58
|
-
|
|
76
|
+
setConnectionState('connected')
|
|
59
77
|
|
|
60
78
|
// Live metrics
|
|
61
79
|
connection.on('metrics', (m: ConnectionMetrics) => setRtt(m.rtt))
|
|
62
80
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
// Connection lifecycle events
|
|
82
|
+
connection.on('disconnect', () => setConnectionState('reconnecting'))
|
|
83
|
+
connection.on('reconnecting', () => setConnectionState('reconnecting'))
|
|
84
|
+
connection.on('reconnected', async () => {
|
|
85
|
+
setConnectionState('connected')
|
|
86
|
+
// Re-drain persisted messages after reconnect
|
|
87
|
+
await recoverMessages(queue)
|
|
88
|
+
})
|
|
68
89
|
|
|
69
|
-
//
|
|
70
|
-
await
|
|
71
|
-
}).catch((err: Error) => {
|
|
72
|
-
console.error('Failed to connect:', err)
|
|
90
|
+
// Drain any persisted messages from a previous session
|
|
91
|
+
await recoverMessages(queue)
|
|
73
92
|
})
|
|
74
93
|
|
|
75
94
|
return () => {
|
|
76
95
|
connRef.current?.disconnect()
|
|
77
96
|
}
|
|
78
|
-
}, [])
|
|
97
|
+
}, [recoverMessages])
|
|
79
98
|
|
|
80
99
|
const refreshDepth = async (queue?: PulseQueue) => {
|
|
81
100
|
const q = queue || queueRef.current
|
|
@@ -162,8 +181,10 @@ export default function App() {
|
|
|
162
181
|
<h1>🔄 Pulse Queue Demo</h1>
|
|
163
182
|
<p style={{ color: '#888' }}>
|
|
164
183
|
Durable message queues with at-least-once delivery over PLP.
|
|
165
|
-
{connected ? (
|
|
184
|
+
{connectionState === 'connected' ? (
|
|
166
185
|
<span style={{ color: '#4ade80' }}> ● Connected</span>
|
|
186
|
+
) : connectionState === 'reconnecting' ? (
|
|
187
|
+
<span style={{ color: '#fbbf24' }}> ◌ Reconnecting...</span>
|
|
167
188
|
) : (
|
|
168
189
|
<span style={{ color: '#f87171' }}> ○ Connecting...</span>
|
|
169
190
|
)}
|
package/src/index.ts
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import * as p from '@clack/prompts';
|
|
3
|
-
import pc from 'picocolors';
|
|
4
|
-
import mri from 'mri';
|
|
5
|
-
import fs from 'fs';
|
|
6
|
-
import path from 'path';
|
|
7
|
-
import { fileURLToPath } from 'url';
|
|
8
|
-
|
|
9
|
-
const argv = mri(process.argv.slice(2));
|
|
10
|
-
|
|
11
|
-
// In CommonJS, __dirname is available, but since we compile with tsup to CJS, we can just use __dirname.
|
|
12
|
-
const TEMPLATES_DIR = path.join(__dirname, '../templates');
|
|
13
|
-
|
|
14
|
-
async function main() {
|
|
15
|
-
p.intro(pc.bgMagenta(pc.white(' Create Pulse App ')));
|
|
16
|
-
|
|
17
|
-
let targetDir = argv._[0];
|
|
18
|
-
|
|
19
|
-
const project = await p.group(
|
|
20
|
-
{
|
|
21
|
-
path: () => {
|
|
22
|
-
if (targetDir) return Promise.resolve(targetDir);
|
|
23
|
-
return p.text({
|
|
24
|
-
message: 'Project name:',
|
|
25
|
-
initialValue: 'pulse-app',
|
|
26
|
-
validate: (val) => {
|
|
27
|
-
if (!val || val.trim() === '') return 'Project name is required';
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
},
|
|
32
|
-
template: () => {
|
|
33
|
-
return p.select({
|
|
34
|
-
message: 'Pick a template:',
|
|
35
|
-
options: [
|
|
36
|
-
{ value: 'react-watch-together', label: 'Watch Together (React + TS)', hint: 'Synchronized video playback' },
|
|
37
|
-
{ value: 'react-all-features', label: 'All Features (React + TS)', hint: 'Chat, Video, Audio, RPC' },
|
|
38
|
-
{ value: 'react-queue-demo', label: 'Durable Queues (React + TS)', hint: 'Persistent queues with WAL/Postgres/Redis' },
|
|
39
|
-
{ value: 'vanilla-basic', label: 'Vanilla JS (Basic)', hint: 'Minimal setup' }
|
|
40
|
-
]
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
onCancel: () => {
|
|
46
|
-
p.cancel('Operation cancelled.');
|
|
47
|
-
process.exit(0);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
const destDir = path.resolve(process.cwd(), project.path);
|
|
53
|
-
if (!fs.existsSync(destDir)) {
|
|
54
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const s = p.spinner();
|
|
58
|
-
s.start(`Scaffolding project in ${pc.cyan(project.path)}...`);
|
|
59
|
-
|
|
60
|
-
const templateDir = path.join(TEMPLATES_DIR, project.template as string);
|
|
61
|
-
|
|
62
|
-
if (!fs.existsSync(templateDir)) {
|
|
63
|
-
s.stop(pc.red(`Template ${project.template} not found at ${templateDir}`));
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
copyDir(templateDir, destDir);
|
|
68
|
-
|
|
69
|
-
// Rename _gitignore to .gitignore
|
|
70
|
-
const gitignorePath = path.join(destDir, '_gitignore');
|
|
71
|
-
if (fs.existsSync(gitignorePath)) {
|
|
72
|
-
fs.renameSync(gitignorePath, path.join(destDir, '.gitignore'));
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Update package.json name to match the project path
|
|
76
|
-
const pkgPath = path.join(destDir, 'package.json');
|
|
77
|
-
if (fs.existsSync(pkgPath)) {
|
|
78
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
79
|
-
pkg.name = path.basename(destDir);
|
|
80
|
-
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
s.stop(`Scaffolded ${pc.cyan(project.template)} template in ${pc.cyan(project.path)}`);
|
|
84
|
-
|
|
85
|
-
p.note(
|
|
86
|
-
`cd ${project.path}\n` +
|
|
87
|
-
`npm install\n` +
|
|
88
|
-
`npm run dev`,
|
|
89
|
-
'Next steps'
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
p.outro(pc.magenta('Pulse is ready! 🚀'));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function copyDir(src: string, dest: string) {
|
|
96
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
97
|
-
|
|
98
|
-
for (const entry of entries) {
|
|
99
|
-
const srcPath = path.join(src, entry.name);
|
|
100
|
-
if (entry.name === 'node_modules' || entry.name === 'dist') continue;
|
|
101
|
-
|
|
102
|
-
const destPath = path.join(dest, entry.name);
|
|
103
|
-
|
|
104
|
-
if (entry.isDirectory()) {
|
|
105
|
-
if (!fs.existsSync(destPath)) {
|
|
106
|
-
fs.mkdirSync(destPath);
|
|
107
|
-
}
|
|
108
|
-
copyDir(srcPath, destPath);
|
|
109
|
-
} else {
|
|
110
|
-
fs.copyFileSync(srcPath, destPath);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
main().catch(console.error);
|