docs: document the fetchStream method of the JS SDK (#13125)
This commit is contained in:
@@ -72,7 +72,9 @@ The response of this API route has the status code `201`.
|
||||
|
||||
To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
|
||||
|
||||
For example, to create an API route that returns an event stream:
|
||||
### Example: Server-Sent Events (SSE)
|
||||
|
||||
For example, to create an API route that returns server-sent events (SSE), you can set the `Content-Type` header to `text/event-stream`:
|
||||
|
||||
export const streamHighlights = [
|
||||
["7", "writeHead", "Set the response's headers."],
|
||||
@@ -97,9 +99,14 @@ export const GET = async (
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write("Streaming data...\n")
|
||||
res.write("data: Streaming data...\n\n")
|
||||
}, 3000)
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
|
||||
req.on("end", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
@@ -114,6 +121,12 @@ The `writeHead` method accepts two parameters:
|
||||
|
||||
This API route opens a stream by setting the `Content-Type` to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
|
||||
|
||||
### Tip: Fetching Stream with JS SDK
|
||||
|
||||
The JS SDK has a `fetchStream` method that you can use to fetch data from an API route that returns a stream.
|
||||
|
||||
Learn more in the [JS SDK](!resources!/js-sdk) documentation.
|
||||
|
||||
---
|
||||
|
||||
## Do More with Responses
|
||||
|
||||
@@ -57,7 +57,7 @@ export const generatedEditDates = {
|
||||
"app/learn/debugging-and-testing/testing-tools/unit-tests/module-example/page.mdx": "2024-09-02T11:04:27.232Z",
|
||||
"app/learn/debugging-and-testing/testing-tools/unit-tests/page.mdx": "2024-09-02T11:03:26.997Z",
|
||||
"app/learn/fundamentals/modules/service-constraints/page.mdx": "2025-03-18T15:12:46.006Z",
|
||||
"app/learn/fundamentals/api-routes/responses/page.mdx": "2025-07-25T15:17:39.986Z",
|
||||
"app/learn/fundamentals/api-routes/responses/page.mdx": "2025-08-01T14:15:34.787Z",
|
||||
"app/learn/fundamentals/api-routes/validation/page.mdx": "2025-03-24T06:52:47.896Z",
|
||||
"app/learn/fundamentals/api-routes/errors/page.mdx": "2025-06-19T16:09:08.563Z",
|
||||
"app/learn/fundamentals/admin/constraints/page.mdx": "2025-07-21T08:20:43.223Z",
|
||||
|
||||
@@ -8685,7 +8685,9 @@ The response of this API route has the status code `201`.
|
||||
|
||||
To return response data other than a JSON object, use the `writeHead` method of the `MedusaResponse` object. It allows you to set the response headers, including the content type.
|
||||
|
||||
For example, to create an API route that returns an event stream:
|
||||
### Example: Server-Sent Events (SSE)
|
||||
|
||||
For example, to create an API route that returns server-sent events (SSE), you can set the `Content-Type` header to `text/event-stream`:
|
||||
|
||||
```ts highlights={streamHighlights}
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
@@ -8701,9 +8703,14 @@ export const GET = async (
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write("Streaming data...\n")
|
||||
res.write("data: Streaming data...\n\n")
|
||||
}, 3000)
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
|
||||
req.on("end", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
@@ -8718,6 +8725,12 @@ The `writeHead` method accepts two parameters:
|
||||
|
||||
This API route opens a stream by setting the `Content-Type` to `text/event-stream`. It then simulates a stream by creating an interval that writes the stream data every three seconds.
|
||||
|
||||
### Tip: Fetching Stream with JS SDK
|
||||
|
||||
The JS SDK has a `fetchStream` method that you can use to fetch data from an API route that returns a stream.
|
||||
|
||||
Learn more in the [JS SDK](https://docs.medusajs.com/resources/js-sdk/index.html.md) documentation.
|
||||
|
||||
***
|
||||
|
||||
## Do More with Responses
|
||||
@@ -25026,7 +25039,7 @@ Users of any actor type (admin, customer, or custom actor type) can request to r
|
||||
|
||||
In this guide, you'll implement a subscriber that handles the `auth.password_reset` event to send an email notification to the user with instructions on how to reset their password.
|
||||
|
||||
After adding the subscriber, you will have a complete reset password flow you can utilize using the Medusa admin, storefront, or API routes.
|
||||
After adding the subscriber, you will have a complete reset password flow you can utilize using the Medusa Admin, storefront, or API routes.
|
||||
|
||||
***
|
||||
|
||||
@@ -43706,6 +43719,151 @@ The method returns a Promise that, when resolved, has the data returned by the r
|
||||
|
||||
***
|
||||
|
||||
## Stream Server-Sent Events
|
||||
|
||||
The JS SDK supports streaming server-sent events (SSE) using the `client.fetchStream` method. This method is useful when you want to receive real-time updates from the server.
|
||||
|
||||
For example, consider you have the following custom API route at `src/api/admin/stream/route.ts`:
|
||||
|
||||
```ts title="src/api/admin/stream/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write("data: Streaming data...\n\n")
|
||||
}, 3000)
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
|
||||
req.on("end", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then, you can use the `client.fetchStream` method in a UI route to receive the streaming data:
|
||||
|
||||
```tsx title="src/admin/route/stream/page.tsx"
|
||||
import { defineRouteConfig } from "@medusajs/admin-sdk"
|
||||
import { Container, Heading, Button, Text } from "@medusajs/ui"
|
||||
import { useState } from "react"
|
||||
import { sdk } from "../../lib/sdk"
|
||||
|
||||
const StreamTestPage = () => {
|
||||
const [messages, setMessages] = useState<string[]>([])
|
||||
const [isStreaming, setIsStreaming] = useState(false)
|
||||
const [abortStream, setAbortStream] = useState<(() => void) | null>(null)
|
||||
|
||||
const startStream = async () => {
|
||||
setIsStreaming(true)
|
||||
setMessages([])
|
||||
|
||||
const { stream, abort } = await sdk.client.fetchStream("/admin/stream")
|
||||
|
||||
if (!stream) {
|
||||
console.error("Failed to start stream")
|
||||
setIsStreaming(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Store the abort function for the abort button
|
||||
setAbortStream(() => abort)
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
// Since the server sends plain text, convert to string
|
||||
const message = typeof chunk === "string" ? chunk : (chunk.data || String(chunk))
|
||||
setMessages((prev) => [...prev, message.trim()])
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't log abort errors as they're expected when user clicks abort
|
||||
if (error instanceof Error && error.name !== "AbortError") {
|
||||
console.error("Stream error:", error)
|
||||
}
|
||||
} finally {
|
||||
setIsStreaming(false)
|
||||
setAbortStream(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAbort = () => {
|
||||
if (abortStream) {
|
||||
abortStream()
|
||||
setIsStreaming(false)
|
||||
setAbortStream(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="p-6">
|
||||
<Heading level="h1" className="mb-6">
|
||||
fetchStream Example
|
||||
</Heading>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={startStream}
|
||||
disabled={isStreaming}
|
||||
variant="primary"
|
||||
>
|
||||
{isStreaming ? "Streaming..." : "Start Stream"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleAbort}
|
||||
disabled={!isStreaming}
|
||||
variant="secondary"
|
||||
>
|
||||
Abort Stream
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border rounded p-4 h-64 overflow-y-auto bg-ui-bg-subtle">
|
||||
{messages.length === 0 ? (
|
||||
<Text className="text-ui-fg-muted">No messages yet...</Text>
|
||||
) : (
|
||||
messages.map((msg, index) => (
|
||||
<div key={index} className="mb-2 text-sm">
|
||||
{msg}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config = defineRouteConfig({
|
||||
label: "Stream Test",
|
||||
})
|
||||
|
||||
export default StreamTestPage
|
||||
```
|
||||
|
||||
`fetchStream` accepts the same parameters as `fetch`, but it returns an object having two properties:
|
||||
|
||||
- `stream`: An [AsyncGenerator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) that you can use to iterate over the streaming data.
|
||||
- `abort`: A function that you can call to abort the stream. This is useful when you want to stop receiving data from the server.
|
||||
|
||||
In this example, when the user clicks the "Start Stream" button, you start the stream and listen for incoming data. The data is received as chunks, which you can process and display in the UI.
|
||||
|
||||
***
|
||||
|
||||
## Handle Errors
|
||||
|
||||
If an error occurs in a request, the JS SDK throws a `FetchError` object. This object has the following properties:
|
||||
|
||||
@@ -398,6 +398,151 @@ The method returns a Promise that, when resolved, has the data returned by the r
|
||||
|
||||
---
|
||||
|
||||
## Stream Server-Sent Events
|
||||
|
||||
The JS SDK supports streaming server-sent events (SSE) using the `client.fetchStream` method. This method is useful when you want to receive real-time updates from the server.
|
||||
|
||||
For example, consider you have the following custom API route at `src/api/admin/stream/route.ts`:
|
||||
|
||||
```ts title="src/api/admin/stream/route.ts"
|
||||
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.writeHead(200, {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
Connection: "keep-alive",
|
||||
})
|
||||
|
||||
const interval = setInterval(() => {
|
||||
res.write("data: Streaming data...\n\n")
|
||||
}, 3000)
|
||||
|
||||
req.on("close", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
|
||||
req.on("end", () => {
|
||||
clearInterval(interval)
|
||||
res.end()
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Then, you can use the `client.fetchStream` method in a UI route to receive the streaming data:
|
||||
|
||||
```tsx title="src/admin/route/stream/page.tsx"
|
||||
import { defineRouteConfig } from "@medusajs/admin-sdk"
|
||||
import { Container, Heading, Button, Text } from "@medusajs/ui"
|
||||
import { useState } from "react"
|
||||
import { sdk } from "../../lib/sdk"
|
||||
|
||||
const StreamTestPage = () => {
|
||||
const [messages, setMessages] = useState<string[]>([])
|
||||
const [isStreaming, setIsStreaming] = useState(false)
|
||||
const [abortStream, setAbortStream] = useState<(() => void) | null>(null)
|
||||
|
||||
const startStream = async () => {
|
||||
setIsStreaming(true)
|
||||
setMessages([])
|
||||
|
||||
const { stream, abort } = await sdk.client.fetchStream("/admin/stream")
|
||||
|
||||
if (!stream) {
|
||||
console.error("Failed to start stream")
|
||||
setIsStreaming(false)
|
||||
return
|
||||
}
|
||||
|
||||
// Store the abort function for the abort button
|
||||
setAbortStream(() => abort)
|
||||
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
// Since the server sends plain text, convert to string
|
||||
const message = typeof chunk === "string" ? chunk : (chunk.data || String(chunk))
|
||||
setMessages((prev) => [...prev, message.trim()])
|
||||
}
|
||||
} catch (error) {
|
||||
// Don't log abort errors as they're expected when user clicks abort
|
||||
if (error instanceof Error && error.name !== "AbortError") {
|
||||
console.error("Stream error:", error)
|
||||
}
|
||||
} finally {
|
||||
setIsStreaming(false)
|
||||
setAbortStream(null)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAbort = () => {
|
||||
if (abortStream) {
|
||||
abortStream()
|
||||
setIsStreaming(false)
|
||||
setAbortStream(null)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Container className="p-6">
|
||||
<Heading level="h1" className="mb-6">
|
||||
fetchStream Example
|
||||
</Heading>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={startStream}
|
||||
disabled={isStreaming}
|
||||
variant="primary"
|
||||
>
|
||||
{isStreaming ? "Streaming..." : "Start Stream"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={handleAbort}
|
||||
disabled={!isStreaming}
|
||||
variant="secondary"
|
||||
>
|
||||
Abort Stream
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="border rounded p-4 h-64 overflow-y-auto bg-ui-bg-subtle">
|
||||
{messages.length === 0 ? (
|
||||
<Text className="text-ui-fg-muted">No messages yet...</Text>
|
||||
) : (
|
||||
messages.map((msg, index) => (
|
||||
<div key={index} className="mb-2 text-sm">
|
||||
{msg}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config = defineRouteConfig({
|
||||
label: "Stream Test",
|
||||
})
|
||||
|
||||
export default StreamTestPage
|
||||
```
|
||||
|
||||
`fetchStream` accepts the same parameters as `fetch`, but it returns an object having two properties:
|
||||
|
||||
- `stream`: An [AsyncGenerator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) that you can use to iterate over the streaming data.
|
||||
- `abort`: A function that you can call to abort the stream. This is useful when you want to stop receiving data from the server.
|
||||
|
||||
In this example, when the user clicks the "Start Stream" button, you start the stream and listen for incoming data. The data is received as chunks, which you can process and display in the UI.
|
||||
|
||||
---
|
||||
|
||||
## Handle Errors
|
||||
|
||||
If an error occurs in a request, the JS SDK throws a `FetchError` object. This object has the following properties:
|
||||
|
||||
@@ -2168,7 +2168,7 @@ export const generatedEditDates = {
|
||||
"app/commerce-modules/store/links-to-other-modules/page.mdx": "2025-04-17T16:03:16.419Z",
|
||||
"app/examples/page.mdx": "2025-07-16T09:53:26.163Z",
|
||||
"app/medusa-cli/commands/build/page.mdx": "2024-11-11T11:00:49.665Z",
|
||||
"app/js-sdk/page.mdx": "2025-05-26T15:08:16.590Z",
|
||||
"app/js-sdk/page.mdx": "2025-08-01T14:17:07.509Z",
|
||||
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.apiKey/page.mdx": "2025-05-20T07:51:40.924Z",
|
||||
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.campaign/page.mdx": "2025-05-20T07:51:40.925Z",
|
||||
"references/js_sdk/admin/Admin/properties/js_sdk.admin.Admin.claim/page.mdx": "2025-06-25T10:11:46.945Z",
|
||||
|
||||
Reference in New Issue
Block a user