Skip to content

Commit 3bb11b4

Browse files
chore: wip
1 parent 2ba1f9d commit 3bb11b4

File tree

4 files changed

+119
-10
lines changed

4 files changed

+119
-10
lines changed

app/Actions/Buddy/BuddyProcessStreamAction.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ export default new Action({
2020

2121
async handle(request: RequestInstance) {
2222
try {
23-
const command = request.get<string>('command')
24-
const repo = request.get<string>('repo') || request.get<string>('repository')
25-
const driver = request.get<string>('driver')
23+
const command = request.get('command')
24+
const repo = request.get('repo') || request.get('repository')
25+
const driver = request.get('driver')
2626

2727
if (!command) {
2828
return new Response(JSON.stringify({ error: 'Command is required' }), {
@@ -63,15 +63,12 @@ export default new Action({
6363
const reader = stream.getReader()
6464

6565
try {
66-
while (true) {
67-
const { done, value } = await reader.read()
68-
if (done) break
69-
66+
let result = await reader.read()
67+
while (!result.done) {
7068
// Convert chunk to text and send as SSE data event
71-
const text = decoder.decode(value, { stream: true })
72-
// Escape newlines for SSE format
73-
const escapedText = text.replace(/\n/g, '\\n')
69+
const text = decoder.decode(result.value, { stream: true })
7470
controller.enqueue(encoder.encode(`event: chunk\ndata: ${JSON.stringify({ text })}\n\n`))
71+
result = await reader.read()
7572
}
7673

7774
// Wait for full response and apply changes
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import type { ChannelType, EmitOptions } from '@stacksjs/types'
2+
import { config } from '@stacksjs/config'
3+
import { log } from '@stacksjs/logging'
4+
import { RealtimeFactory } from './factory'
5+
6+
/**
7+
* Emit an event to a channel
8+
*
9+
* @example
10+
* // Simple emit to public channel
11+
* emit('orders', 'created', { id: 1, total: 99.99 })
12+
*
13+
* // Emit to private channel
14+
* emit('orders.123', 'updated', { status: 'shipped' }, { private: true })
15+
*
16+
* // Emit to presence channel
17+
* emit('chat.room.1', 'message', { text: 'Hello' }, { presence: true })
18+
*
19+
* // Exclude specific users
20+
* emit('chat.room.1', 'message', { text: 'Hello' }, { exclude: 'user-123' })
21+
*/
22+
export function emit<T = unknown>(
23+
channel: string,
24+
event: string,
25+
data?: T,
26+
options?: EmitOptions,
27+
): void {
28+
const driverType = options?.driver ?? config.realtime?.driver ?? 'bun'
29+
const driver = RealtimeFactory.getInstance().getDriver(driverType)
30+
31+
// Determine channel type
32+
let channelType: ChannelType = 'public'
33+
if (options?.presence) {
34+
channelType = 'presence'
35+
}
36+
else if (options?.private) {
37+
channelType = 'private'
38+
}
39+
40+
// Build the payload
41+
const payload = {
42+
data,
43+
exclude: options?.exclude
44+
? Array.isArray(options.exclude)
45+
? options.exclude
46+
: [options.exclude]
47+
: undefined,
48+
}
49+
50+
// Broadcast the event
51+
driver.broadcast(channel, event, payload, channelType)
52+
53+
log.debug(`[realtime] emit "${event}" to ${channelType}:${channel}`)
54+
}
55+
56+
/**
57+
* Emit an event to a specific user
58+
*
59+
* @example
60+
* emitToUser('user-123', 'notification', { message: 'You have a new order!' })
61+
*/
62+
export function emitToUser<T = unknown>(
63+
userId: string,
64+
event: string,
65+
data?: T,
66+
options?: Omit<EmitOptions, 'private'>,
67+
): void {
68+
emit(`user.${userId}`, event, data, { ...options, private: true })
69+
}
70+
71+
/**
72+
* Emit an event to multiple users
73+
*
74+
* @example
75+
* emitToUsers(['user-1', 'user-2'], 'announcement', { message: 'Server maintenance!' })
76+
*/
77+
export function emitToUsers<T = unknown>(
78+
userIds: string[],
79+
event: string,
80+
data?: T,
81+
options?: Omit<EmitOptions, 'private'>,
82+
): void {
83+
for (const userId of userIds) {
84+
emitToUser(userId, event, data, options)
85+
}
86+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './broadcast'
22
export * from './channel'
33
export * from './drivers'
4+
export * from './emit'
45
export * from './ws'

storage/framework/core/types/src/realtime.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,3 +557,28 @@ export const RealtimePresets = {
557557
},
558558
},
559559
} as const
560+
561+
/**
562+
* Options for the emit function
563+
*/
564+
export interface EmitOptions {
565+
/**
566+
* Make this a private channel (requires authentication)
567+
*/
568+
private?: boolean
569+
570+
/**
571+
* Make this a presence channel (tracks who's online)
572+
*/
573+
presence?: boolean
574+
575+
/**
576+
* Exclude specific user IDs from receiving this event
577+
*/
578+
exclude?: string | string[]
579+
580+
/**
581+
* Specify which driver to use (overrides default)
582+
*/
583+
driver?: DriverType
584+
}

0 commit comments

Comments
 (0)