@emeryld/manager 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +96 -0
  2. package/dist/create-package/cli-args.js +78 -0
  3. package/dist/create-package/prompts.js +138 -0
  4. package/dist/create-package/shared/configs.js +309 -0
  5. package/dist/create-package/shared/constants.js +5 -0
  6. package/dist/create-package/shared/fs-utils.js +69 -0
  7. package/dist/create-package/tasks.js +89 -0
  8. package/dist/create-package/types.js +1 -0
  9. package/dist/create-package/variant-info.js +67 -0
  10. package/dist/create-package/variants/client/expo-react-native/lib-files.js +168 -0
  11. package/dist/create-package/variants/client/expo-react-native/package-files.js +94 -0
  12. package/dist/create-package/variants/client/expo-react-native/scaffold.js +59 -0
  13. package/dist/create-package/variants/client/expo-react-native/ui-files.js +215 -0
  14. package/dist/create-package/variants/client/vite-react/health-page.js +251 -0
  15. package/dist/create-package/variants/client/vite-react/lib-files.js +176 -0
  16. package/dist/create-package/variants/client/vite-react/package-files.js +79 -0
  17. package/dist/create-package/variants/client/vite-react/scaffold.js +68 -0
  18. package/dist/create-package/variants/client/vite-react/ui-files.js +154 -0
  19. package/dist/create-package/variants/fullstack/files.js +129 -0
  20. package/dist/create-package/variants/fullstack/index.js +86 -0
  21. package/dist/create-package/variants/fullstack/utils.js +241 -0
  22. package/dist/llm-pack.js +2 -0
  23. package/dist/robot/cli/prompts.js +84 -27
  24. package/dist/robot/cli/settings.js +131 -56
  25. package/dist/robot/config.js +123 -50
  26. package/dist/robot/coordinator.js +10 -105
  27. package/dist/robot/extractors/classes.js +14 -13
  28. package/dist/robot/extractors/components.js +17 -10
  29. package/dist/robot/extractors/constants.js +9 -6
  30. package/dist/robot/extractors/functions.js +11 -8
  31. package/dist/robot/extractors/shared.js +6 -1
  32. package/dist/robot/extractors/types.js +5 -8
  33. package/dist/robot/llm-pack.js +1226 -0
  34. package/dist/robot/pack/builder.js +374 -0
  35. package/dist/robot/pack/cli.js +65 -0
  36. package/dist/robot/pack/exemplars.js +573 -0
  37. package/dist/robot/pack/globs.js +119 -0
  38. package/dist/robot/pack/selection.js +44 -0
  39. package/dist/robot/pack/symbols.js +309 -0
  40. package/dist/robot/pack/type-registry.js +285 -0
  41. package/dist/robot/pack/types.js +48 -0
  42. package/dist/robot/pack/utils.js +36 -0
  43. package/dist/robot/serializer.js +97 -0
  44. package/dist/robot/v2/cli.js +86 -0
  45. package/dist/robot/v2/globs.js +103 -0
  46. package/dist/robot/v2/parser/bundles.js +55 -0
  47. package/dist/robot/v2/parser/candidates.js +63 -0
  48. package/dist/robot/v2/parser/exemplars.js +114 -0
  49. package/dist/robot/v2/parser/exports.js +57 -0
  50. package/dist/robot/v2/parser/symbols.js +179 -0
  51. package/dist/robot/v2/parser.js +114 -0
  52. package/dist/robot/v2/types.js +42 -0
  53. package/dist/utils/export.js +39 -18
  54. package/package.json +2 -1
@@ -0,0 +1,215 @@
1
+ export function nativeAppTsx() {
2
+ return `import React from 'react'
3
+ import { SafeAreaView, ScrollView, StatusBar, StyleSheet, Text } from 'react-native'
4
+ import { QueryClientProvider } from '@tanstack/react-query'
5
+ import { queryClient, hasContract } from './src/queryClient'
6
+ import { AppSocketProvider } from './src/socket'
7
+ import { HealthScreen } from './src/screens/HealthScreen'
8
+
9
+ export default function App() {
10
+ return (
11
+ <QueryClientProvider client={queryClient}>
12
+ <AppSocketProvider>
13
+ <StatusBar barStyle="dark-content" />
14
+ <SafeAreaView style={styles.container}>
15
+ {!hasContract ? (
16
+ <Text style={styles.notice}>
17
+ Add your contract import in src/queryClient.ts and src/socket.tsx to enable
18
+ API + socket helpers.
19
+ </Text>
20
+ ) : null}
21
+ <ScrollView contentInsetAdjustmentBehavior="automatic">
22
+ <HealthScreen />
23
+ </ScrollView>
24
+ </SafeAreaView>
25
+ </AppSocketProvider>
26
+ </QueryClientProvider>
27
+ )
28
+ }
29
+
30
+ const styles = StyleSheet.create({
31
+ container: {
32
+ flex: 1,
33
+ padding: 16,
34
+ backgroundColor: '#f4f5fb',
35
+ },
36
+ notice: {
37
+ backgroundColor: '#fff7ed',
38
+ borderColor: '#fed7aa',
39
+ borderWidth: 1,
40
+ borderRadius: 8,
41
+ padding: 12,
42
+ marginBottom: 12,
43
+ color: '#9a3412',
44
+ },
45
+ })
46
+ `;
47
+ }
48
+ export function nativeHealthScreen() {
49
+ return `import React from 'react'
50
+ import {
51
+ Button,
52
+ StyleSheet,
53
+ Text,
54
+ TextInput,
55
+ View,
56
+ } from 'react-native'
57
+ import { healthGet, healthPost, hasContract } from '../queryClient'
58
+ import { roomMeta, useSocketClient, useSocketConnection, socketReady } from '../socket'
59
+
60
+ const HEALTH_ROOMS = ['health']
61
+
62
+ function useHealthSocket(event: string, onMessage: (payload: Record<string, unknown>) => void) {
63
+ useSocketConnection({
64
+ event,
65
+ rooms: HEALTH_ROOMS,
66
+ joinMeta: roomMeta,
67
+ leaveMeta: roomMeta,
68
+ onMessage,
69
+ })
70
+ }
71
+
72
+ function useLogs() {
73
+ const [logs, setLogs] = React.useState<string[]>([])
74
+ return {
75
+ logs,
76
+ push: (msg: string) =>
77
+ setLogs((prev) => ['[' + new Date().toLocaleTimeString() + '] ' + msg, ...prev].slice(0, 60)),
78
+ clear: () => setLogs([]),
79
+ }
80
+ }
81
+
82
+ export function HealthScreen() {
83
+ const [echo, setEcho] = React.useState('hello rrroute')
84
+ const httpGet = healthGet.useEndpoint()
85
+ const httpPost = healthPost.useEndpoint()
86
+ const socket = useSocketClient()
87
+ const { logs, push, clear } = useLogs()
88
+
89
+ useHealthSocket('health:connected', (payload) => {
90
+ push('connected ' + String(payload.socketId))
91
+ })
92
+
93
+ useHealthSocket('health:pong', (payload) => {
94
+ const echo = payload.echo ? ' (echo: ' + String(payload.echo) + ')' : ''
95
+ push('pong at ' + String(payload.at) + echo)
96
+ })
97
+
98
+ return (
99
+ <View style={styles.card}>
100
+ <Text style={styles.title}>RRRoutes health sandbox</Text>
101
+ <View style={styles.section}>
102
+ <Text style={styles.heading}>HTTP</Text>
103
+ {!hasContract ? (
104
+ <Text style={styles.muted}>
105
+ Wire up a contract to enable typed HTTP calls.
106
+ </Text>
107
+ ) : (
108
+ <>
109
+ <Button title="GET /health" onPress={() => httpGet.refetch()} />
110
+ <View style={{ height: 12 }} />
111
+ <TextInput
112
+ style={styles.input}
113
+ value={echo}
114
+ onChangeText={setEcho}
115
+ placeholder="echo payload"
116
+ />
117
+ <Button
118
+ title="POST /health"
119
+ onPress={() =>
120
+ httpPost
121
+ .mutateAsync({ echo })
122
+ .then(() => push('POST /health ok'))
123
+ }
124
+ />
125
+ <Text style={styles.label}>GET data</Text>
126
+ <Text style={styles.code}>
127
+ {JSON.stringify(httpGet.data ?? {}, null, 2)}
128
+ </Text>
129
+ <Text style={styles.label}>POST data</Text>
130
+ <Text style={styles.code}>
131
+ {JSON.stringify(httpPost.data ?? {}, null, 2)}
132
+ </Text>
133
+ </>
134
+ )}
135
+ </View>
136
+
137
+ <View style={styles.section}>
138
+ <Text style={styles.heading}>Socket</Text>
139
+ {!socketReady ? (
140
+ <Text style={styles.muted}>
141
+ Wire up socket events via your contract to enable live updates.
142
+ </Text>
143
+ ) : (
144
+ <>
145
+ <View style={styles.row}>
146
+ <Button title="Connect" onPress={() => socket.connect()} />
147
+ <Button title="Disconnect" onPress={() => socket.disconnect()} />
148
+ </View>
149
+ <View style={{ height: 8 }} />
150
+ <Button
151
+ title="Emit ping"
152
+ onPress={() =>
153
+ socket.emit('health:ping', { note: 'ping from app' })
154
+ }
155
+ />
156
+ <View style={{ height: 8 }} />
157
+ <View style={styles.row}>
158
+ <Button
159
+ title="Join room"
160
+ onPress={() => socket.joinRooms(HEALTH_ROOMS, roomMeta)}
161
+ />
162
+ <Button
163
+ title="Leave room"
164
+ onPress={() => socket.leaveRooms(HEALTH_ROOMS, roomMeta)}
165
+ />
166
+ </View>
167
+ </>
168
+ )}
169
+ </View>
170
+
171
+ <View style={styles.section}>
172
+ <Text style={styles.heading}>Socket logs</Text>
173
+ <Button title="Clear logs" onPress={clear} />
174
+ <Text style={styles.code}>
175
+ {logs.length === 0 ? 'No messages yet' : logs.join('\\n')}
176
+ </Text>
177
+ </View>
178
+ </View>
179
+ )
180
+ }
181
+
182
+ const styles = StyleSheet.create({
183
+ card: {
184
+ backgroundColor: '#fff',
185
+ borderRadius: 12,
186
+ padding: 16,
187
+ gap: 12,
188
+ shadowColor: '#000',
189
+ shadowOpacity: 0.05,
190
+ shadowRadius: 8,
191
+ },
192
+ title: { fontSize: 20, fontWeight: '700' },
193
+ section: { gap: 8 },
194
+ heading: { fontWeight: '600', fontSize: 16 },
195
+ row: { flexDirection: 'row', gap: 8, justifyContent: 'space-between' },
196
+ input: {
197
+ borderWidth: 1,
198
+ borderColor: '#d0d4de',
199
+ borderRadius: 8,
200
+ padding: 8,
201
+ },
202
+ label: { marginTop: 6, fontWeight: '600' },
203
+ code: {
204
+ backgroundColor: '#0f172a',
205
+ color: '#e2e8f0',
206
+ padding: 8,
207
+ borderRadius: 8,
208
+ fontFamily: 'Courier',
209
+ },
210
+ muted: {
211
+ color: '#6b7280',
212
+ },
213
+ })
214
+ `;
215
+ }
@@ -0,0 +1,251 @@
1
+ export function HealthPage() {
2
+ return `import React from 'react'
3
+ import {
4
+ Bar,
5
+ BarChart,
6
+ ResponsiveContainer,
7
+ Tooltip,
8
+ XAxis,
9
+ YAxis,
10
+ } from 'recharts'
11
+ import { healthGet, healthPost, hasContract } from '../lib/queryClient'
12
+ import {
13
+ roomMeta,
14
+ socketReady,
15
+ useSocketClient,
16
+ useSocketConnection,
17
+ } from '../lib/socket'
18
+
19
+ const HEALTH_ROOMS = ['health']
20
+
21
+ function now() {
22
+ return new Date().toLocaleTimeString()
23
+ }
24
+
25
+ function useHealthSocket(event: string, onMessage: (payload: Record<string, unknown>) => void) {
26
+ useSocketConnection({
27
+ event,
28
+ rooms: HEALTH_ROOMS,
29
+ joinMeta: roomMeta,
30
+ leaveMeta: roomMeta,
31
+ onMessage,
32
+ })
33
+ }
34
+
35
+ function useLogs() {
36
+ const [logs, setLogs] = React.useState<string[]>([])
37
+ return {
38
+ logs,
39
+ push: (msg: string) =>
40
+ setLogs((prev) => ['[' + now() + '] ' + msg, ...prev].slice(0, 60)),
41
+ clear: () => setLogs([]),
42
+ }
43
+ }
44
+
45
+ type HttpSectionProps = {
46
+ echo: string
47
+ onEchoChange: (value: string) => void
48
+ onLog: (message: string) => void
49
+ httpGet: ReturnType<typeof healthGet.useEndpoint>
50
+ httpPost: ReturnType<typeof healthPost.useEndpoint>
51
+ }
52
+
53
+ function HttpSection({ echo, onEchoChange, onLog, httpGet, httpPost }: HttpSectionProps) {
54
+ return (
55
+ <div className="card">
56
+ <h2>HTTP endpoints</h2>
57
+ {!hasContract ? (
58
+ <p className="muted">Wire up a contract to enable HTTP calls.</p>
59
+ ) : (
60
+ <>
61
+ <p>GET and POST against the shared contract.</p>
62
+ <div>
63
+ <button onClick={() => httpGet.refetch()}>GET /health</button>
64
+ {httpGet.error ? (
65
+ <p style={{ color: 'crimson', marginTop: 6 }}>
66
+ GET error: {String(httpGet.error)}
67
+ </p>
68
+ ) : null}
69
+ </div>
70
+ <div style={{ marginTop: 12 }}>
71
+ <input
72
+ value={echo}
73
+ onChange={(e) => onEchoChange(e.target.value)}
74
+ placeholder="echo payload"
75
+ style={{ width: '100%', padding: '8px' }}
76
+ />
77
+ <div style={{ marginTop: 8 }}>
78
+ <button
79
+ onClick={() =>
80
+ httpPost.mutateAsync({ echo }).then(() => onLog('POST /health ok'))
81
+ }
82
+ >
83
+ POST /health
84
+ </button>
85
+ </div>
86
+ {httpPost.error ? (
87
+ <p style={{ color: 'crimson', marginTop: 6 }}>
88
+ POST error: {String(httpPost.error)}
89
+ </p>
90
+ ) : null}
91
+ </div>
92
+ <div style={{ marginTop: 12 }}>
93
+ <strong>GET data</strong>
94
+ <pre>{JSON.stringify(httpGet.data, null, 2) ?? 'none'}</pre>
95
+ <strong>POST data</strong>
96
+ <pre>{JSON.stringify(httpPost.data, null, 2) ?? 'none'}</pre>
97
+ </div>
98
+ </>
99
+ )}
100
+ </div>
101
+ )
102
+ }
103
+
104
+ type SocketSectionProps = {
105
+ socket: ReturnType<typeof useSocketClient>
106
+ onLog: (message: string) => void
107
+ hasError: boolean
108
+ }
109
+
110
+ function SocketSection({ socket, onLog, hasError }: SocketSectionProps) {
111
+ return (
112
+ <div className="card">
113
+ <h2>Socket</h2>
114
+ {!socketReady ? (
115
+ <p className="muted">
116
+ Wire up socket events via your contract to enable live updates.
117
+ </p>
118
+ ) : (
119
+ <>
120
+ <p>Connect, ping, and watch lifecycle events.</p>
121
+ <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
122
+ <button onClick={() => socket.connect()}>Connect</button>
123
+ <button className="secondary" onClick={() => socket.disconnect()}>
124
+ Disconnect
125
+ </button>
126
+ <button onClick={() => socket.emit('health:ping', { note: 'ping from client' })}>
127
+ Emit ping
128
+ </button>
129
+ <button
130
+ className="secondary"
131
+ onClick={() => socket.joinRooms(HEALTH_ROOMS, roomMeta)}
132
+ >
133
+ Join room
134
+ </button>
135
+ <button
136
+ className="secondary"
137
+ onClick={() => socket.leaveRooms(HEALTH_ROOMS, roomMeta)}
138
+ >
139
+ Leave room
140
+ </button>
141
+ </div>
142
+ {hasError ? (
143
+ <p style={{ color: 'crimson', marginTop: 8 }}>
144
+ Check server logs; errors will also show above.
145
+ </p>
146
+ ) : null}
147
+ </>
148
+ )}
149
+ </div>
150
+ )
151
+ }
152
+
153
+ type LogsSectionProps = {
154
+ logs: string[]
155
+ onClear: () => void
156
+ }
157
+
158
+ function LogsSection({ logs, onClear }: LogsSectionProps) {
159
+ return (
160
+ <div className="card">
161
+ <h2>Socket logs</h2>
162
+ <button className="secondary" onClick={onClear}>
163
+ Clear
164
+ </button>
165
+ <div className="logs">
166
+ {logs.length === 0 ? 'No messages yet' : logs.join('\\n')}
167
+ </div>
168
+ </div>
169
+ )
170
+ }
171
+
172
+ type ChartSectionProps = {
173
+ chartData: { name: string; value: number }[]
174
+ }
175
+
176
+ function ChartSection({ chartData }: ChartSectionProps) {
177
+ return (
178
+ <div className="card chart-card">
179
+ <h2>Live state</h2>
180
+ <div className="chart-container">
181
+ <ResponsiveContainer width="100%" height="100%">
182
+ <BarChart data={chartData}>
183
+ <XAxis dataKey="name" />
184
+ <YAxis />
185
+ <Tooltip />
186
+ <Bar dataKey="value" fill="#2563eb" radius={[4, 4, 0, 0]} />
187
+ </BarChart>
188
+ </ResponsiveContainer>
189
+ </div>
190
+ </div>
191
+ )
192
+ }
193
+
194
+ export function HealthPage() {
195
+ const [echo, setEcho] = React.useState('hello rrroute')
196
+ const httpGet = healthGet.useEndpoint()
197
+ const httpPost = healthPost.useEndpoint()
198
+ const socket = useSocketClient()
199
+ const { logs, push, clear } = useLogs()
200
+
201
+ const chartData = React.useMemo(
202
+ () => [
203
+ { name: 'Logs', value: logs.length },
204
+ { name: 'Socket ready', value: socketReady ? 1 : 0 },
205
+ {
206
+ name: 'HTTP errors',
207
+ value: Number(Boolean(httpGet.error || httpPost.error)),
208
+ },
209
+ ],
210
+ [logs.length, socketReady, httpGet.error, httpPost.error],
211
+ )
212
+
213
+ useHealthSocket('health:connected', (payload) => {
214
+ push('socket connected (' + String(payload.socketId) + ')')
215
+ })
216
+
217
+ useHealthSocket('health:pong', (payload) => {
218
+ const echo = payload.echo ? ' (echo: ' + String(payload.echo) + ')' : ''
219
+ push('pong at ' + String(payload.at) + echo)
220
+ })
221
+
222
+ return (
223
+ <div className="health">
224
+ <h1>RRRoutes health sandbox</h1>
225
+ {!hasContract ? (
226
+ <p className="notice">
227
+ Add your contract import in <code>src/lib/queryClient.ts</code> and{' '}
228
+ <code>src/lib/socket.tsx</code> to call the real API and socket events.
229
+ </p>
230
+ ) : null}
231
+ <div className="grid">
232
+ <HttpSection
233
+ echo={echo}
234
+ onEchoChange={setEcho}
235
+ onLog={push}
236
+ httpGet={httpGet}
237
+ httpPost={httpPost}
238
+ />
239
+ <SocketSection
240
+ socket={socket}
241
+ onLog={push}
242
+ hasError={Boolean(httpGet.error || httpPost.error)}
243
+ />
244
+ <LogsSection logs={logs} onClear={clear} />
245
+ <ChartSection chartData={chartData} />
246
+ </div>
247
+ </div>
248
+ )
249
+ }
250
+ `;
251
+ }
@@ -0,0 +1,176 @@
1
+ export function queryClient(contractImport) {
2
+ if (contractImport) {
3
+ return `import { QueryClient } from '@tanstack/react-query'
4
+ import { createRouteClient } from '@emeryld/rrroutes-client'
5
+ import { registry } from '${contractImport}'
6
+
7
+ const baseUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:4000'
8
+
9
+ export const queryClient = new QueryClient()
10
+
11
+ export const routeClient = createRouteClient({
12
+ baseUrl,
13
+ queryClient,
14
+ environment: import.meta.env.MODE === 'production' ? 'production' : 'development',
15
+ })
16
+
17
+ export const healthGet = routeClient.build(registry.byKey['GET /api/health'])
18
+ export const healthPost = routeClient.build(registry.byKey['POST /api/health'])
19
+ export const hasContract = true as const
20
+ `;
21
+ }
22
+ return `import { QueryClient } from '@tanstack/react-query'
23
+ import { createRouteClient } from '@emeryld/rrroutes-client'
24
+
25
+ const baseUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:4000'
26
+
27
+ export const queryClient = new QueryClient()
28
+
29
+ export const routeClient = createRouteClient({
30
+ baseUrl,
31
+ queryClient,
32
+ environment: import.meta.env.MODE === 'production' ? 'production' : 'development',
33
+ })
34
+
35
+ type RouteEndpoint = ReturnType<typeof routeClient.build>
36
+
37
+ function placeholderUseEndpoint() {
38
+ const error = new Error('Add your contract import to src/lib/queryClient.ts')
39
+ return {
40
+ data: undefined,
41
+ error,
42
+ refetch: () => undefined,
43
+ mutateAsync: async () => Promise.reject(error),
44
+ isPending: false,
45
+ }
46
+ }
47
+
48
+ const placeholderEndpoint = {
49
+ useEndpoint: placeholderUseEndpoint,
50
+ } as unknown as RouteEndpoint
51
+
52
+ export const healthGet: RouteEndpoint = placeholderEndpoint
53
+ export const healthPost: RouteEndpoint = placeholderEndpoint
54
+ export const hasContract = false as const
55
+ `;
56
+ }
57
+ export function socketProvider(contractImport) {
58
+ if (contractImport) {
59
+ return `import React from 'react'
60
+ import { io, type Socket } from 'socket.io-client'
61
+ import {
62
+ buildSocketProvider,
63
+ type SocketClientOptions,
64
+ } from '@emeryld/rrroutes-client'
65
+ import { socketConfig, socketEvents } from '${contractImport}'
66
+
67
+ const socketUrl = import.meta.env.VITE_SOCKET_URL ?? 'http://localhost:4000'
68
+ const socketPath = import.meta.env.VITE_SOCKET_PATH ?? '/socket.io'
69
+
70
+ const sysEvents: SocketClientOptions<
71
+ typeof socketEvents,
72
+ typeof socketConfig
73
+ >['sys'] = {
74
+ 'sys:connect': async ({ socket }) => {
75
+ console.info('socket connected', socket.id)
76
+ },
77
+ 'sys:disconnect': async ({ reason }) => {
78
+ console.info('socket disconnected', reason)
79
+ },
80
+ 'sys:reconnect': async ({ attempt, socket }) => {
81
+ console.info('socket reconnect', attempt, socket?.id)
82
+ },
83
+ 'sys:connect_error': async ({ error }) => {
84
+ console.warn('socket connect error', error)
85
+ },
86
+ 'sys:ping': () => ({
87
+ note: 'client-heartbeat',
88
+ sentAt: new Date().toISOString(),
89
+ }),
90
+ 'sys:pong': async ({ payload }) => {
91
+ console.info('socket pong', payload)
92
+ },
93
+ 'sys:room_join': async ({ rooms }) => {
94
+ console.info('joining rooms', rooms)
95
+ return true
96
+ },
97
+ 'sys:room_leave': async ({ rooms }) => {
98
+ console.info('leaving rooms', rooms)
99
+ return true
100
+ },
101
+ }
102
+
103
+ const baseOptions: Omit<
104
+ SocketClientOptions<typeof socketEvents, typeof socketConfig>,
105
+ 'socket'
106
+ > = {
107
+ config: socketConfig,
108
+ sys: sysEvents,
109
+ environment: import.meta.env.MODE === 'production' ? 'production' : 'development',
110
+ heartbeat: { intervalMs: 15_000, timeoutMs: 7_500 },
111
+ debug: {
112
+ connection: true,
113
+ heartbeat: true,
114
+ },
115
+ }
116
+
117
+ const { SocketProvider, useSocketClient, useSocketConnection } =
118
+ buildSocketProvider({
119
+ events: socketEvents,
120
+ options: baseOptions,
121
+ })
122
+
123
+ function getSocket(): Promise<Socket> {
124
+ return Promise.resolve(
125
+ io(socketUrl, {
126
+ path: socketPath,
127
+ transports: ['websocket'],
128
+ }),
129
+ )
130
+ }
131
+
132
+ export const roomMeta = { room: 'health' }
133
+ export const socketReady = true as const
134
+
135
+ export function AppSocketProvider(props: React.PropsWithChildren) {
136
+ return (
137
+ <SocketProvider
138
+ getSocket={getSocket}
139
+ destroyLeaveMeta={roomMeta}
140
+ fallback={<p>Connecting socket…</p>}
141
+ >
142
+ {props.children}
143
+ </SocketProvider>
144
+ )
145
+ }
146
+
147
+ export { useSocketClient, useSocketConnection }
148
+ `;
149
+ }
150
+ return `import React from 'react'
151
+
152
+ export const roomMeta = { room: 'health' }
153
+ export const socketReady = false as const
154
+
155
+ const stubSocket = {
156
+ connect: () =>
157
+ console.info('Socket disabled; add your contract import in src/lib/socket.tsx.'),
158
+ disconnect: () => undefined,
159
+ emit: () => undefined,
160
+ joinRooms: () => undefined,
161
+ leaveRooms: () => undefined,
162
+ }
163
+
164
+ export function AppSocketProvider(props: React.PropsWithChildren) {
165
+ return <>{props.children}</>
166
+ }
167
+
168
+ export function useSocketClient() {
169
+ return stubSocket
170
+ }
171
+
172
+ export function useSocketConnection() {
173
+ // no-op when sockets are not configured
174
+ }
175
+ `;
176
+ }