diff --git a/examples/streaming/.gitignore b/examples/streaming/.gitignore
new file mode 100644
index 000000000..ad2da72f4
--- /dev/null
+++ b/examples/streaming/.gitignore
@@ -0,0 +1,11 @@
+/.wasp/
+
+# We ignore env files recognized and used by Wasp.
+.env.server
+.env.client
+
+# To be extra safe, we by default ignore any files with `.env` extension in them.
+# If this is too agressive for you, consider allowing specific files with `!` operator,
+# or modify/delete these two lines.
+*.env
+*.env.*
diff --git a/examples/streaming/.wasproot b/examples/streaming/.wasproot
new file mode 100644
index 000000000..ca2cfdb48
--- /dev/null
+++ b/examples/streaming/.wasproot
@@ -0,0 +1 @@
+File marking the root of Wasp project.
diff --git a/examples/streaming/main.wasp b/examples/streaming/main.wasp
new file mode 100644
index 000000000..a2c87e63c
--- /dev/null
+++ b/examples/streaming/main.wasp
@@ -0,0 +1,21 @@
+app streaming {
+ wasp: {
+ version: "^0.11.5"
+ },
+ title: "streaming"
+}
+
+route RootRoute { path: "/", to: MainPage }
+page MainPage {
+ component: import Main from "@client/MainPage.jsx"
+}
+
+api streamingText {
+ httpRoute: (GET, "/api/streaming-test"),
+ fn: import { getText } from "@server/streaming.js",
+}
+
+apiNamespace defaultMiddleware {
+ path: "/api",
+ middlewareConfigFn: import { getMiddlewareConfig } from "@server/streaming.js",
+}
\ No newline at end of file
diff --git a/examples/streaming/src/.waspignore b/examples/streaming/src/.waspignore
new file mode 100644
index 000000000..1c432f30d
--- /dev/null
+++ b/examples/streaming/src/.waspignore
@@ -0,0 +1,3 @@
+# Ignore editor tmp files
+**/*~
+**/#*#
diff --git a/examples/streaming/src/client/Main.css b/examples/streaming/src/client/Main.css
new file mode 100644
index 000000000..b6e7ed3f1
--- /dev/null
+++ b/examples/streaming/src/client/Main.css
@@ -0,0 +1,89 @@
+* {
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+}
+
+.container {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+main {
+ padding: 5rem 0;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+main p {
+ font-size: 1.2rem;
+}
+
+.logo {
+ margin-bottom: 2rem;
+}
+
+.logo img {
+ max-height: 200px;
+}
+
+.welcome-title {
+ font-weight: 500;
+}
+
+.welcome-subtitle {
+ font-weight: 400;
+ margin-bottom: 3rem;
+}
+
+.buttons {
+ display: flex;
+ flex-direction: row;
+}
+
+.buttons .button:not(:last-child) {
+ margin-right: 0.5rem;
+}
+
+.button {
+ border-radius: 3px;
+ font-size: 1.2rem;
+ padding: 1rem 2rem;
+ text-align: center;
+ font-weight: 700;
+ text-decoration: none;
+}
+
+.button-filled {
+ border: 2px solid #bf9900;
+ background-color: #bf9900;
+ color: #f4f4f4;
+}
+
+.button-outline {
+ border: 2px solid #8a9cff;
+ color: #8a9cff;
+ background-color: none;
+}
+
+code {
+ border-radius: 5px;
+ padding: 0.2rem;
+ background: #efefef;
+ font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
+ Bitstream Vera Sans Mono, Courier New, monospace;
+}
diff --git a/examples/streaming/src/client/MainPage.jsx b/examples/streaming/src/client/MainPage.jsx
new file mode 100644
index 000000000..93a197e63
--- /dev/null
+++ b/examples/streaming/src/client/MainPage.jsx
@@ -0,0 +1,57 @@
+import { useState, useEffect } from "react";
+import config from "@wasp/config";
+import "./Main.css";
+
+const MainPage = () => {
+ const { response } = useTextStream("/api/streaming-test");
+ return (
+
+
+ Streaming Demo
+
+ {response}
+
+
+
+ );
+};
+export default MainPage;
+
+function useTextStream(path) {
+ const [response, setResponse] = useState("");
+ useEffect(() => {
+ const controller = new AbortController();
+ fetchStream(
+ path,
+ (chunk) => {
+ setResponse((prev) => prev + chunk);
+ },
+ controller
+ );
+ return () => {
+ controller.abort();
+ };
+ }, []);
+
+ return {
+ response,
+ };
+}
+
+async function fetchStream(path, onData, controller) {
+ const response = await fetch(config.apiUrl + path, {
+ signal: controller.signal,
+ });
+ const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) {
+ return;
+ }
+ onData(value.toString());
+ }
+}
diff --git a/examples/streaming/src/client/tsconfig.json b/examples/streaming/src/client/tsconfig.json
new file mode 100644
index 000000000..d501a4193
--- /dev/null
+++ b/examples/streaming/src/client/tsconfig.json
@@ -0,0 +1,55 @@
+// =============================== IMPORTANT =================================
+//
+// This file is only used for Wasp IDE support. You can change it to configure
+// your IDE checks, but none of these options will affect the TypeScript
+// compiler. Proper TS compiler configuration in Wasp is coming soon :)
+{
+ "compilerOptions": {
+ // JSX support
+ "jsx": "preserve",
+ "strict": true,
+ // Allow default imports.
+ "esModuleInterop": true,
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "allowJs": true,
+ // Wasp needs the following settings enable IDE support in your source
+ // files. Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/web-app/",
+ "paths": {
+ // Resolve all "@wasp" imports to the generated source code.
+ "@wasp/*": [
+ "src/*"
+ ],
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": [
+ "../../.wasp/out/web-app/node_modules/@types"
+ ],
+ // Since this TS config is used only for IDE support and not for
+ // compilation, the following directory doesn't exist. We need to specify
+ // it to prevent this error:
+ // https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
+ "outDir": "phantom"
+ },
+ "exclude": [
+ "phantom"
+ ],
+}
\ No newline at end of file
diff --git a/examples/streaming/src/client/vite-env.d.ts b/examples/streaming/src/client/vite-env.d.ts
new file mode 100644
index 000000000..1623b9c79
--- /dev/null
+++ b/examples/streaming/src/client/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/streaming/src/client/waspLogo.png b/examples/streaming/src/client/waspLogo.png
new file mode 100644
index 000000000..d39a9443a
Binary files /dev/null and b/examples/streaming/src/client/waspLogo.png differ
diff --git a/examples/streaming/src/server/streaming.ts b/examples/streaming/src/server/streaming.ts
new file mode 100644
index 000000000..49834d28f
--- /dev/null
+++ b/examples/streaming/src/server/streaming.ts
@@ -0,0 +1,29 @@
+import { StreamingText } from "@wasp/apis/types";
+import { MiddlewareConfigFn } from "@wasp/middleware";
+
+// Custom API endpoint that returns a streaming text.
+export const getText: StreamingText = async (req, res, context) => {
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
+ res.setHeader("Transfer-Encoding", "chunked");
+
+ let counter = 1;
+ res.write("Hm, let me see...\n");
+ while (counter <= 10) {
+ // Send a chunk of data.
+ if (counter === 10) {
+ res.write(`and finally about ${counter}.`);
+ } else {
+ res.write(`let's talk about number ${counter} and `);
+ }
+ counter++;
+ // Wait for 1 second.
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ // End the response.
+ res.end();
+};
+
+// Returning the default config.
+export const getMiddlewareConfig: MiddlewareConfigFn = (config) => {
+ return config;
+};
diff --git a/examples/streaming/src/server/tsconfig.json b/examples/streaming/src/server/tsconfig.json
new file mode 100644
index 000000000..70a79b44e
--- /dev/null
+++ b/examples/streaming/src/server/tsconfig.json
@@ -0,0 +1,48 @@
+// =============================== IMPORTANT =================================
+//
+// This file is only used for Wasp IDE support. You can change it to configure
+// your IDE checks, but none of these options will affect the TypeScript
+// compiler. Proper TS compiler configuration in Wasp is coming soon :)
+{
+ "compilerOptions": {
+ // Allows default imports.
+ "esModuleInterop": true,
+ "allowJs": true,
+ "strict": true,
+ // Wasp needs the following settings enable IDE support in your source
+ // files. Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/server/",
+ "paths": {
+ // Resolve all "@wasp" imports to the generated source code.
+ "@wasp/*": [
+ "src/*"
+ ],
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": [
+ "../../.wasp/out/server/node_modules/@types"
+ ],
+ // Since this TS config is used only for IDE support and not for
+ // compilation, the following directory doesn't exist. We need to specify
+ // it to prevent this error:
+ // https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
+ "outDir": "phantom",
+ },
+ "exclude": [
+ "phantom"
+ ],
+}
\ No newline at end of file
diff --git a/examples/streaming/src/shared/tsconfig.json b/examples/streaming/src/shared/tsconfig.json
new file mode 100644
index 000000000..20fcac843
--- /dev/null
+++ b/examples/streaming/src/shared/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ // Enable default imports in TypeScript.
+ "esModuleInterop": true,
+ "allowJs": true,
+ // The following settings enable IDE support in user-provided source files.
+ // Editing them might break features like import autocompletion and
+ // definition lookup. Don't change them unless you know what you're doing.
+ //
+ // The relative path to the generated web app's root directory. This must be
+ // set to define the "paths" option.
+ "baseUrl": "../../.wasp/out/server/",
+ "paths": {
+ // Resolve all non-relative imports to the correct node module. Source:
+ // https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
+ "*": [
+ // Start by looking for the definiton inside the node modules root
+ // directory...
+ "node_modules/*",
+ // ... If that fails, try to find it inside definitely-typed type
+ // definitions.
+ "node_modules/@types/*"
+ ]
+ },
+ // Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
+ "typeRoots": ["../../.wasp/out/server/node_modules/@types"]
+ }
+}