Jake Shore 96e52666c5 MCPEngine full sync — studio scaffold, factory v2, server updates, state.json — 2026-02-12
=== NEW ===
- studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan)
- docs/FACTORY-V2.md — Factory v2 architecture doc
- docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report

=== UPDATED SERVERS ===
- fieldedge: Added jobs-tools, UI build script, main entry update
- lightspeed: Updated main + server entry points
- squarespace: Added collection-browser + page-manager apps
- toast: Added main + server entry points

=== INFRA ===
- infra/command-center/state.json — Updated pipeline state
- infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
2026-02-12 17:58:33 -05:00

90 lines
2.6 KiB
TypeScript

// SSE Helper — converts async generator of PipelineEvents to Server-Sent Events Response
import type { PipelineEvent } from '../types';
/**
* Create a Server-Sent Events Response from a PipelineEvent async generator.
* Compatible with Next.js App Router API routes.
*/
export function createSSEResponse(
generator: AsyncGenerator<PipelineEvent>
): Response {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
for await (const event of generator) {
const data = `data: ${JSON.stringify(event)}\n\n`;
controller.enqueue(encoder.encode(data));
}
// Send done event
controller.enqueue(encoder.encode('data: [DONE]\n\n'));
} catch (error) {
const errorEvent: PipelineEvent = {
type: 'error',
message: error instanceof Error ? error.message : String(error),
recoverable: false,
};
controller.enqueue(
encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`)
);
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no', // Disable nginx buffering
},
});
}
/**
* Create a named SSE event (with event: field).
* Useful when clients want to use EventSource.addEventListener().
*/
export function createNamedSSEResponse(
generator: AsyncGenerator<PipelineEvent>
): Response {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
for await (const event of generator) {
const eventType = event.type.replace(':', '_');
const line = `event: ${eventType}\ndata: ${JSON.stringify(event)}\n\n`;
controller.enqueue(encoder.encode(line));
}
controller.enqueue(encoder.encode('event: done\ndata: {}\n\n'));
} catch (error) {
const errorEvent: PipelineEvent = {
type: 'error',
message: error instanceof Error ? error.message : String(error),
recoverable: false,
};
controller.enqueue(
encoder.encode(`event: error\ndata: ${JSON.stringify(errorEvent)}\n\n`)
);
} finally {
controller.close();
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
Connection: 'keep-alive',
'X-Accel-Buffering': 'no',
},
});
}