@jaypie/mcp 0.2.9 → 0.2.12

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.
@@ -0,0 +1,408 @@
1
+ ---
2
+ description: Complete guide to streaming in Jaypie - Lambda, Express, and SSE patterns
3
+ globs: packages/express/**, packages/lambda/**, packages/aws/**
4
+ ---
5
+
6
+ # Jaypie Streaming Guide
7
+
8
+ Jaypie provides three distinct streaming patterns for different deployment scenarios. This guide explains when to use each approach and how to implement streaming correctly.
9
+
10
+ ## Quick Reference
11
+
12
+ | Handler | Package | Express Required | Use Case |
13
+ |---------|---------|------------------|----------|
14
+ | `lambdaStreamHandler` | `@jaypie/lambda` | No | Pure Lambda Function URL streaming |
15
+ | `expressStreamHandler` | `@jaypie/express` | Yes | Express routes with SSE |
16
+ | `createLambdaStreamHandler` | `@jaypie/express` | Yes | Express app deployed to Lambda with streaming |
17
+
18
+ ### Decision Guide
19
+
20
+ - **Building a pure Lambda function?** Use `lambdaStreamHandler`
21
+ - **Building an Express app for non-Lambda deployment?** Use `expressStreamHandler`
22
+ - **Building an Express app that runs on Lambda?** Use `createLambdaStreamHandler`
23
+
24
+ ## Streaming Utilities
25
+
26
+ All streaming utilities are exported from `@jaypie/aws` and re-exported through `jaypie`:
27
+
28
+ | Function | Purpose |
29
+ |----------|---------|
30
+ | `createLambdaStream(stream, writer)` | Pipe async iterable to Lambda response writer |
31
+ | `createExpressStream(stream, res)` | Pipe async iterable to Express response with SSE headers |
32
+ | `JaypieStream` | Wrapper class with `.toLambda()` and `.toExpress()` methods |
33
+ | `createJaypieStream(source)` | Factory function for JaypieStream |
34
+ | `formatSSE(chunk)` | Format a chunk as SSE event string |
35
+ | `streamToSSE(stream)` | Convert async iterable to SSE-formatted strings |
36
+
37
+ ### Types
38
+
39
+ ```typescript
40
+ import type {
41
+ ExpressStreamResponse,
42
+ LambdaStreamWriter,
43
+ SSEEvent,
44
+ StreamChunk,
45
+ } from "jaypie";
46
+ ```
47
+
48
+ ## Pattern 1: lambdaStreamHandler
49
+
50
+ Use for pure Lambda functions without Express. Requires AWS Lambda Response Streaming via Function URL.
51
+
52
+ ### Basic Usage
53
+
54
+ ```typescript
55
+ import { lambdaStreamHandler } from "jaypie";
56
+ import type { StreamHandlerContext } from "@jaypie/lambda";
57
+
58
+ const streamWorker = lambdaStreamHandler(
59
+ async (event: unknown, context: StreamHandlerContext) => {
60
+ const { responseStream } = context;
61
+
62
+ responseStream.write("event: start\ndata: {}\n\n");
63
+
64
+ for (let i = 0; i < 5; i++) {
65
+ responseStream.write(`event: data\ndata: {"count": ${i}}\n\n`);
66
+ }
67
+
68
+ responseStream.write("event: done\ndata: {}\n\n");
69
+ // Handler automatically calls responseStream.end()
70
+ },
71
+ {
72
+ name: "streamWorker",
73
+ contentType: "text/event-stream",
74
+ }
75
+ );
76
+
77
+ // Wrap with AWS streamifyResponse for Lambda
78
+ declare const awslambda: { streamifyResponse: <T>(handler: T) => T };
79
+ export const handler = awslambda.streamifyResponse(streamWorker);
80
+ ```
81
+
82
+ ### With LLM Streaming
83
+
84
+ ```typescript
85
+ import { lambdaStreamHandler, createLambdaStream, Llm } from "jaypie";
86
+ import type { StreamHandlerContext } from "@jaypie/lambda";
87
+
88
+ interface PromptEvent {
89
+ prompt?: string;
90
+ }
91
+
92
+ const llmStreamHandler = lambdaStreamHandler(
93
+ async (event: PromptEvent, context: StreamHandlerContext) => {
94
+ const llm = new Llm("anthropic");
95
+ const stream = llm.stream(event.prompt || "Hello");
96
+
97
+ // createLambdaStream pipes chunks as SSE events
98
+ await createLambdaStream(stream, context.responseStream);
99
+ },
100
+ {
101
+ name: "llmStream",
102
+ secrets: ["ANTHROPIC_API_KEY"],
103
+ }
104
+ );
105
+
106
+ declare const awslambda: { streamifyResponse: <T>(handler: T) => T };
107
+ export const handler = awslambda.streamifyResponse(llmStreamHandler);
108
+ ```
109
+
110
+ ### Handler Options
111
+
112
+ ```typescript
113
+ import type { LambdaStreamHandlerOptions } from "@jaypie/lambda";
114
+
115
+ const options: LambdaStreamHandlerOptions = {
116
+ name: "myStreamHandler", // Handler name for logging
117
+ contentType: "text/event-stream", // Response content type (default)
118
+ chaos: "low", // Chaos testing level
119
+ secrets: ["API_KEY"], // AWS secrets to load into process.env
120
+ setup: [], // Setup function(s)
121
+ teardown: [], // Teardown function(s)
122
+ validate: [], // Validation function(s)
123
+ throw: false, // Re-throw errors instead of SSE error
124
+ unavailable: false, // Return 503 if true
125
+ };
126
+ ```
127
+
128
+ ### CDK Configuration
129
+
130
+ ```typescript
131
+ import { JaypieLambda } from "@jaypie/constructs";
132
+ import { FunctionUrlAuthType, InvokeMode } from "aws-cdk-lib/aws-lambda";
133
+
134
+ const streamingLambda = new JaypieLambda(this, "StreamingFunction", {
135
+ code: "dist",
136
+ handler: "streamWorker.handler",
137
+ timeout: Duration.minutes(5),
138
+ });
139
+
140
+ // Enable Lambda Response Streaming
141
+ streamingLambda.addFunctionUrl({
142
+ authType: FunctionUrlAuthType.NONE,
143
+ invokeMode: InvokeMode.RESPONSE_STREAM,
144
+ });
145
+ ```
146
+
147
+ ## Pattern 2: expressStreamHandler
148
+
149
+ Use for Express applications not running on Lambda. Sets SSE headers automatically.
150
+
151
+ ### Basic Usage
152
+
153
+ ```typescript
154
+ import { expressStreamHandler } from "jaypie";
155
+ import type { Request, Response } from "express";
156
+
157
+ const streamRoute = expressStreamHandler(async (req: Request, res: Response) => {
158
+ // Write SSE events directly to response
159
+ res.write("event: message\ndata: {\"text\": \"Hello\"}\n\n");
160
+ res.write("event: message\ndata: {\"text\": \"World\"}\n\n");
161
+ // Handler automatically ends the stream
162
+ });
163
+
164
+ app.get("/stream", streamRoute);
165
+ ```
166
+
167
+ ### With LLM Streaming
168
+
169
+ ```typescript
170
+ import { expressStreamHandler, createExpressStream, Llm } from "jaypie";
171
+
172
+ const llmStreamRoute = expressStreamHandler(async (req: Request, res: Response) => {
173
+ const llm = new Llm("anthropic");
174
+ const stream = llm.stream(req.body.prompt);
175
+
176
+ // createExpressStream pipes LLM chunks as SSE events
177
+ await createExpressStream(stream, res);
178
+ });
179
+
180
+ app.post("/chat", llmStreamRoute);
181
+ ```
182
+
183
+ ### Handler Options
184
+
185
+ ```typescript
186
+ import type { ExpressStreamHandlerOptions } from "jaypie";
187
+
188
+ const options: ExpressStreamHandlerOptions = {
189
+ name: "myStreamHandler", // Handler name for logging
190
+ contentType: "text/event-stream", // Default SSE content type
191
+ chaos: "low", // Chaos testing level
192
+ secrets: ["API_KEY"], // Secrets to load
193
+ setup: [], // Setup function(s)
194
+ teardown: [], // Teardown function(s)
195
+ validate: [], // Validation function(s)
196
+ locals: {}, // Values to set on req.locals
197
+ unavailable: false, // Return 503 if true
198
+ };
199
+ ```
200
+
201
+ ### SSE Headers
202
+
203
+ `expressStreamHandler` automatically sets these headers:
204
+
205
+ - `Content-Type: text/event-stream`
206
+ - `Cache-Control: no-cache`
207
+ - `Connection: keep-alive`
208
+ - `X-Accel-Buffering: no` (disables nginx buffering)
209
+
210
+ ## Pattern 3: createLambdaStreamHandler
211
+
212
+ Use for Express applications deployed to AWS Lambda with streaming support.
213
+
214
+ ### Basic Usage
215
+
216
+ ```typescript
217
+ import express from "express";
218
+ import { createLambdaStreamHandler, expressStreamHandler } from "jaypie";
219
+
220
+ const app = express();
221
+
222
+ app.get("/stream", expressStreamHandler(async (req, res) => {
223
+ res.write("event: message\ndata: {\"text\": \"Hello\"}\n\n");
224
+ res.write("event: message\ndata: {\"text\": \"World\"}\n\n");
225
+ }));
226
+
227
+ // Export streaming Lambda handler for Express app
228
+ export const handler = createLambdaStreamHandler(app);
229
+ ```
230
+
231
+ ### Combined Buffered and Streaming
232
+
233
+ When you need both regular endpoints and streaming endpoints:
234
+
235
+ ```typescript
236
+ import express from "express";
237
+ import {
238
+ createLambdaHandler,
239
+ createLambdaStreamHandler,
240
+ expressHandler,
241
+ expressStreamHandler,
242
+ cors,
243
+ } from "jaypie";
244
+
245
+ const app = express();
246
+ app.use(express.json());
247
+ app.use(cors());
248
+
249
+ // Standard buffered route
250
+ app.get("/api/data", expressHandler(async (req, res) => {
251
+ return { data: "buffered response" };
252
+ }));
253
+
254
+ // SSE streaming route
255
+ app.get("/api/stream", expressStreamHandler(async (req, res) => {
256
+ for (let i = 0; i < 5; i++) {
257
+ res.write(`event: update\ndata: {"count": ${i}}\n\n`);
258
+ }
259
+ }));
260
+
261
+ // Choose based on needs:
262
+ // - createLambdaHandler for buffered-only
263
+ // - createLambdaStreamHandler for streaming support
264
+ export const handler = createLambdaStreamHandler(app);
265
+ ```
266
+
267
+ ## Streaming Utilities Deep Dive
268
+
269
+ ### JaypieStream Class
270
+
271
+ Wraps an async iterable for convenient streaming to different targets:
272
+
273
+ ```typescript
274
+ import { JaypieStream, createJaypieStream, Llm } from "jaypie";
275
+
276
+ const llm = new Llm("anthropic");
277
+ const stream = createJaypieStream(llm.stream("Hello"));
278
+
279
+ // Pipe to Lambda
280
+ await stream.toLambda(context.responseStream);
281
+
282
+ // Or pipe to Express
283
+ await stream.toExpress(res);
284
+
285
+ // Or iterate directly
286
+ for await (const chunk of stream) {
287
+ console.log(chunk);
288
+ }
289
+
290
+ // Or convert to SSE strings
291
+ for await (const sseEvent of stream.toSSE()) {
292
+ console.log(sseEvent);
293
+ }
294
+ ```
295
+
296
+ ### Manual SSE Formatting
297
+
298
+ ```typescript
299
+ import { formatSSE } from "jaypie";
300
+
301
+ const chunk = { type: "message", content: "Hello" };
302
+ const sseString = formatSSE(chunk);
303
+ // "event: message\ndata: {\"type\":\"message\",\"content\":\"Hello\"}\n\n"
304
+ ```
305
+
306
+ ### Converting Async Iterables
307
+
308
+ ```typescript
309
+ import { streamToSSE } from "jaypie";
310
+
311
+ async function* myGenerator() {
312
+ yield { type: "start", data: {} };
313
+ yield { type: "data", value: 42 };
314
+ yield { type: "end", data: {} };
315
+ }
316
+
317
+ for await (const sseEvent of streamToSSE(myGenerator())) {
318
+ responseStream.write(sseEvent);
319
+ }
320
+ ```
321
+
322
+ ## Error Handling
323
+
324
+ Errors in streaming handlers are written as SSE error events:
325
+
326
+ ```typescript
327
+ // Format: event: error\ndata: {"errors":[{"status":500,"title":"Internal Error"}]}\n\n
328
+ ```
329
+
330
+ For `lambdaStreamHandler`, set `throw: true` to re-throw errors instead of writing to stream:
331
+
332
+ ```typescript
333
+ const handler = lambdaStreamHandler(
334
+ async (event, context) => {
335
+ throw new InternalError("Something went wrong");
336
+ },
337
+ {
338
+ throw: true, // Re-throw instead of SSE error
339
+ }
340
+ );
341
+ ```
342
+
343
+ ## Testing Streaming Handlers
344
+
345
+ ### Mocking for Unit Tests
346
+
347
+ ```typescript
348
+ import { describe, expect, it, vi } from "vitest";
349
+
350
+ vi.mock("jaypie", async () => {
351
+ const testkit = await import("@jaypie/testkit/mock");
352
+ return testkit;
353
+ });
354
+
355
+ import { handler } from "./streamWorker.js";
356
+
357
+ describe("Stream Handler", () => {
358
+ it("writes expected events", async () => {
359
+ const writes: string[] = [];
360
+ const mockWriter = {
361
+ write: vi.fn((chunk) => writes.push(chunk)),
362
+ end: vi.fn(),
363
+ };
364
+
365
+ const event = { prompt: "test" };
366
+ const context = { responseStream: mockWriter };
367
+
368
+ await handler(event, context);
369
+
370
+ expect(mockWriter.write).toHaveBeenCalled();
371
+ expect(writes.some(w => w.includes("event:"))).toBe(true);
372
+ expect(mockWriter.end).toHaveBeenCalled();
373
+ });
374
+ });
375
+ ```
376
+
377
+ ### Integration Testing
378
+
379
+ Use the Docker setup in `packages/express/docker/` for local Lambda streaming tests.
380
+
381
+ ## TypeScript Types
382
+
383
+ ```typescript
384
+ // From @jaypie/lambda
385
+ import type {
386
+ LambdaStreamHandlerOptions,
387
+ StreamHandlerContext,
388
+ ResponseStream,
389
+ AwsStreamingHandler,
390
+ } from "@jaypie/lambda";
391
+
392
+ // From @jaypie/express (or jaypie)
393
+ import type {
394
+ ExpressStreamHandlerOptions,
395
+ ExpressStreamHandlerLocals,
396
+ JaypieStreamHandlerSetup,
397
+ JaypieStreamHandlerTeardown,
398
+ JaypieStreamHandlerValidate,
399
+ } from "jaypie";
400
+
401
+ // From @jaypie/aws (or jaypie)
402
+ import type {
403
+ ExpressStreamResponse,
404
+ LambdaStreamWriter,
405
+ SSEEvent,
406
+ StreamChunk,
407
+ } from "jaypie";
408
+ ```
@@ -9,10 +9,14 @@ Templates for creating an Express subpackage that runs on AWS Lambda in a Jaypie
9
9
  ## index.ts
10
10
 
11
11
  ```typescript
12
- import serverlessExpress from "@codegenie/serverless-express";
12
+ import { createLambdaHandler } from "jaypie";
13
13
  import app from "./src/app.js";
14
14
 
15
- export default serverlessExpress({ app });
15
+ // Lambda handler for Function URL
16
+ export const handler = createLambdaHandler(app);
17
+
18
+ // For streaming responses (SSE), use:
19
+ // export const handler = createLambdaStreamHandler(app);
16
20
 
17
21
  if (process.env.NODE_ENV === "development") {
18
22
  app.listen(8080);