diff --git a/README.md b/README.md
index bf3d02b7595c..ab86020af845 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,8 @@ The [`pulse`](./pulse) folder contains examples of projects using [Prisma Pulse]
| ---------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| [`starter`](./pulse/starter) | A Prisma Pulse starter app |
| [`email-with-resend`](./pulse/email-with-resend) | An example app to send emails to new users using Prisma Pulse and Resend |
+| [`fullstack-leaderboard`](./pulse/fullstack-leaderboard) | An example app to send emails to new users using Prisma Pulse and Resend |
+| [`fullstack-simple-chat`](./pulse/fullstack-simple-chat) | An example app to send emails to new users using Prisma Pulse and Resend |
## Prisma ORM (TypeScript)
diff --git a/pulse/email-with-resend/README.md b/pulse/email-with-resend/README.md
index 0e05454ac718..0590dd0b0632 100644
--- a/pulse/email-with-resend/README.md
+++ b/pulse/email-with-resend/README.md
@@ -1,7 +1,10 @@
# Prisma Pulse Example: Send Onboarding Email with Resend
-This repository contains an example app that uses Prisma Pulse to detect when new users are added to the database and sends them an onboarding email using [Resend](https://resend.com/).
+This repository contains an example app that uses Prisma Pulse to detect when new users are added to the database and sends them an onboarding email:
+- [Prisma Pulse](https://www.prisma.io/data-platform/pulse) to get real-time updates from the database
+- [Resend](https://resend.com/) to send emails
+- [PostgreSQL](https://www.postgresql.org/) as the database
## Prerequisites
@@ -9,7 +12,7 @@ To successfully run the project, you will need the following:
- The **connection string** of a Pulse-ready database (if you don't have one yet, you can configure your database following the instructions in our [docs](https://www.prisma.io/docs/pulse/database-setup) or [use a Railway template](https://railway.app/template/pulse-pg?referralCode=VQ09uv))
- A **Pulse API key** which you can get by enabling Pulse in a project in your [Prisma Data Platform](https://pris.ly/pdp) account (learn more in the [docs](https://www.prisma.io/docs/platform/concepts/environments#api-keys))
-- A **Resend API Key** which you can get from your [Resend account](https://resend.com/api-keys)
+- A **Resend API Key** which you can get from your [Resend account](https://resend.com/api-keys) (note that if you're on the [free plan](https://resend.com/blog/new-free-tier), you'll only be able to send emails to exactly _one_ email address per day)
## Getting started
@@ -54,7 +57,7 @@ You now have an empty `User` table in your database.
### 4. Start the Pulse subscription
-Run the [script](./index.ts) that contains the code to subscribe to database events:
+Run the [script](./index.ts) that contains the code to stream database events:
```
npm run dev
@@ -71,6 +74,8 @@ The following instructions use [Prisma Studio](https://www.prisma.io/studio) to
3. You will receive a new email in the inbox of the specified `email`. For example: When a new user with an email of `datta@prisma.io` and a name of `Ankur` is created, the email inbox for `datta@prisma.io` should have received the following email (the copy for this email is hardcoded in [`./script.ts`](./script.ts#L33)):

+> **Note**: If you can see the event logged to the terminal but didn't receive an email, it may be that you have maxed out the receiver mail addresses on the [free tier](https://resend.com/blog/new-free-tier) for the day.
+
## Resources
- [Pulse examples](https://pris.ly/pulse-examples)
diff --git a/pulse/email-with-resend/package.json b/pulse/email-with-resend/package.json
index 8eadb2f0a13e..cbdd28551908 100644
--- a/pulse/email-with-resend/package.json
+++ b/pulse/email-with-resend/package.json
@@ -6,7 +6,7 @@
},
"dependencies": {
"@prisma/client": "5.14.0",
- "@prisma/extension-pulse": "1.0.2",
+ "@prisma/extension-pulse": "dev",
"@types/node": "20.12.12",
"dotenv": "16.4.5",
"resend": "3.2.0"
@@ -16,4 +16,4 @@
"ts-node": "10.9.2",
"typescript": "5.4.5"
}
-}
\ No newline at end of file
+}
diff --git a/pulse/email-with-resend/script.ts b/pulse/email-with-resend/script.ts
index 2d9c2d8e311c..acdbc36642d9 100644
--- a/pulse/email-with-resend/script.ts
+++ b/pulse/email-with-resend/script.ts
@@ -42,22 +42,20 @@ const sendUserCreationEmail = async ({ email, name }: UserEmail) => {
return await resendClient.emails.send(emailOptions);
};
-// Subscribe to user creation events and send emails
-const emailSubscriber = async () => {
- const subscription = await prisma.user.subscribe({
- create: {},
+// Stream user creation events and send emails
+const emailStream = async () => {
+ const stream = await prisma.user.stream({
+ name: 'all-created-users', // Add `name` so that we never lose events
+ create: {}
});
process.on("exit", (code) => {
- console.log("Closing Prisma Pulse Subscription.");
- subscription.stop();
+ console.log("Closing Prisma Pulse Stream.");
+ stream.stop();
});
- if (subscription instanceof Error) {
- throw subscription;
- }
- for await (const event of subscription) {
+ for await (const event of stream) {
console.log("Received event:", event);
const { email, name } = event.created;
@@ -72,7 +70,7 @@ const emailSubscriber = async () => {
// Main function
async function main() {
- await emailSubscriber();
+ await emailStream();
}
// Run the main function
diff --git a/pulse/fullstack-leaderboard/.eslintrc.json b/pulse/fullstack-leaderboard/.eslintrc.json
new file mode 100644
index 000000000000..bffb357a7122
--- /dev/null
+++ b/pulse/fullstack-leaderboard/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": "next/core-web-vitals"
+}
diff --git a/pulse/fullstack-leaderboard/.gitignore b/pulse/fullstack-leaderboard/.gitignore
new file mode 100644
index 000000000000..1d94587008ac
--- /dev/null
+++ b/pulse/fullstack-leaderboard/.gitignore
@@ -0,0 +1,38 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+.yarn/install-state.gz
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# local env files
+.env*.local
+.env
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+dist
diff --git a/pulse/fullstack-leaderboard/README.md b/pulse/fullstack-leaderboard/README.md
new file mode 100644
index 000000000000..ac48a474b8a1
--- /dev/null
+++ b/pulse/fullstack-leaderboard/README.md
@@ -0,0 +1,118 @@
+# Prisma Pulse Example: Fullstack Leaderboard (Next.js)
+
+
+
+This repository contains an example app that uses Prisma Pulse in a fullstack application to display and update a real-time leaderboard:
+
+- [Next.js](https://nextjs.org/) (_frontend_) with a [custom server](https://nextjs.org/docs/pages/building-your-application/configuring/custom-server) (_backend_)
+- [React Flip Move](https://github.com/joshwcomeau/react-flip-move) for animating React components
+- [socket.io](https://socket.io/) for the websocket connection between client and server
+- [Prisma Pulse](https://www.prisma.io/data-platform/pulse) to get real-time updates from the database
+- [PostgreSQL](https://www.postgresql.org/) as the database
+
+> **Note**: The custom server is required because Pulse requires a long-running connection to the database. As an alternative to the custom server included in this app, you can also build your own server using a library/framework like Express, Fastify or NestJS.
+
+
+## Prerequisites
+
+To successfully run the project, you will need the following:
+
+- The **connection string** of a Pulse-ready database (if you don't have one yet, you can configure your database following the instructions in our [docs](https://www.prisma.io/docs/pulse/database-setup) or [use a Railway template](https://railway.app/template/pulse-pg?referralCode=VQ09uv))
+- A **Pulse API key** which you can get by enabling Pulse in a project in your [Prisma Data Platform](https://pris.ly/pdp) account (learn more in the [docs](https://www.prisma.io/docs/platform/concepts/environments#api-keys))
+
+## Getting started
+
+### 1. Clone the respository
+
+Clone the repository, navigate into it and install dependencies:
+
+```
+git clone git@github.com:prisma/prisma-examples.git --depth=1
+cd prisma-examples/pulse/fullstack-leaderboard
+npm install
+```
+
+### 2. Configure environment variables
+
+Create a `.env` in the root of the project directory:
+
+```bash
+touch .env
+```
+
+Now, open the `.env` file and update the `DATABASE_URL` and `PULSE_API_KEY` environment variables with the values of your connection string and your Pulse API keys:
+
+```bash
+# .env
+DATABASE_URL="__YOUR_DATABASE_CONNECTION_STRING__"
+PULSE_API_KEY="__YOUR_PULSE_API_KEY__"
+```
+
+Note that `__YOUR_DATABASE_CONNECTION_STRING__` and `__YOUR_PULSE_API_KEY__` are **placeholder values that you must replace** with the values of your own connection string and Pulse API key.
+
+### 3. Run a database migration to create the `Player` table
+
+The [Prisma schema file](./prisma/schema.prisma) in this project contains a single `Player` model. You can map this model to the database and create the corresponding `Player` table using the following command:
+
+```
+npx prisma migrate dev --name init
+```
+
+You now have a table called `Player` in your database.
+
+Next, run the following command to [seed](./prisma/seed.ts) the database with some dummy data:
+
+```
+npx prisma db seed
+```
+
+The is invoked the [seed script](./prisma/seed.ts) and created three `Player` records in the database.
+
+
+### 4. Start the server
+
+Make sure you're inside the [`server`](./server) directory and start the long-running server that streams changes from the database:
+
+```
+npm run server
+```
+
+The server will accept WebSocket connections at `http://localhost:3001`.
+
+Next, run the Next.js app:
+
+```
+npm run dev
+```
+
+You can open the app at [`http://localhost:3000`](http://localhost:3000).
+
+Every new tab/window you open in your browser and point to that URL will instantiate its own WebSocket connection to the long-running server.
+
+### 5. Use the app
+
+Click on the buttons at the bottom to increase the score of a player and see how the leaderboard updates in real-time.
+
+## Deployment
+
+Because the app requires a [custom server](https://nextjs.org/docs/pages/building-your-application/configuring/custom-server) to enable the WebSocket connections, you need to deploy the frontend and the [backend](./src/server.ts) separately.
+
+### Deploying on Railway
+
+In the following, you find instructions to deploy the app via [Railway](https://railway.app). In order to deploy successfully, you need:
+- a Railway account
+- the Railway CLI installed on your machine
+
+
+#### Deploying the frontend
+
+Create a new
+
+#### Deploying the backend
+
+## Resources
+
+- [Pulse examples](https://pris.ly/pulse-examples)
+- [Pulse documentation](https://pris.ly/pulse-docs)
+- [Pulse announcement blog post](https://pris.ly/gh/pulse-ga)
+- [Prisma Discord](https://pris.ly/discord)
diff --git a/pulse/fullstack-leaderboard/leaderboard.gif b/pulse/fullstack-leaderboard/leaderboard.gif
new file mode 100644
index 000000000000..8a4cbc062381
Binary files /dev/null and b/pulse/fullstack-leaderboard/leaderboard.gif differ
diff --git a/pulse/fullstack-leaderboard/leaderboard.png b/pulse/fullstack-leaderboard/leaderboard.png
new file mode 100644
index 000000000000..4ee289879224
Binary files /dev/null and b/pulse/fullstack-leaderboard/leaderboard.png differ
diff --git a/pulse/fullstack-leaderboard/next.config.mjs b/pulse/fullstack-leaderboard/next.config.mjs
new file mode 100644
index 000000000000..4678774e6d60
--- /dev/null
+++ b/pulse/fullstack-leaderboard/next.config.mjs
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+export default nextConfig;
diff --git a/pulse/fullstack-leaderboard/package.json b/pulse/fullstack-leaderboard/package.json
new file mode 100644
index 000000000000..6fe7f5cc0d2e
--- /dev/null
+++ b/pulse/fullstack-leaderboard/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "pulse-leaderboard",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "server": "ts-node --project tsconfig.server.json src/server.ts",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@prisma/client": "^5.14.0",
+ "@prisma/extension-pulse": "^1.1.0",
+ "@types/socket.io": "^3.0.2",
+ "next": "14.2.3",
+ "react": "^18",
+ "react-dom": "^18",
+ "react-flip-move": "^3.0.5",
+ "socket.io": "^4.7.5",
+ "socket.io-client": "^4.7.5"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "eslint": "^8",
+ "eslint-config-next": "14.2.3",
+ "postcss": "^8",
+ "prisma": "^5.14.0",
+ "tailwindcss": "^3.4.1",
+ "ts-node": "^10.9.2",
+ "typescript": "^5"
+ },
+ "prisma": {
+ "seed": "ts-node --project tsconfig.server.json prisma/seed.ts"
+ }
+}
diff --git a/pulse/fullstack-leaderboard/postcss.config.mjs b/pulse/fullstack-leaderboard/postcss.config.mjs
new file mode 100644
index 000000000000..1a69fd2a450a
--- /dev/null
+++ b/pulse/fullstack-leaderboard/postcss.config.mjs
@@ -0,0 +1,8 @@
+/** @type {import('postcss-load-config').Config} */
+const config = {
+ plugins: {
+ tailwindcss: {},
+ },
+};
+
+export default config;
diff --git a/pulse/fullstack-leaderboard/prisma/schema.prisma b/pulse/fullstack-leaderboard/prisma/schema.prisma
new file mode 100644
index 000000000000..d61d0d3b9683
--- /dev/null
+++ b/pulse/fullstack-leaderboard/prisma/schema.prisma
@@ -0,0 +1,20 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
+// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model Player {
+ id Int @id @default(autoincrement())
+ username String @unique
+ points Int @default(0)
+}
diff --git a/pulse/fullstack-leaderboard/prisma/seed.ts b/pulse/fullstack-leaderboard/prisma/seed.ts
new file mode 100644
index 000000000000..6c654ee67d35
--- /dev/null
+++ b/pulse/fullstack-leaderboard/prisma/seed.ts
@@ -0,0 +1,22 @@
+import { PrismaClient } from "@prisma/client";
+
+const prisma = new PrismaClient();
+
+async function main() {
+ const players = await prisma.player.createMany({
+ data: [
+ {
+ username: "Marc",
+ },
+ {
+ username: "Ankur",
+ },
+ {
+ username: "Jon",
+ },
+ ],
+ });
+ console.log(`Seeded the database with ${players.count} players.`);
+}
+
+main();
diff --git a/pulse/fullstack-leaderboard/public/next.svg b/pulse/fullstack-leaderboard/public/next.svg
new file mode 100644
index 000000000000..5174b28c565c
--- /dev/null
+++ b/pulse/fullstack-leaderboard/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pulse/fullstack-leaderboard/public/vercel.svg b/pulse/fullstack-leaderboard/public/vercel.svg
new file mode 100644
index 000000000000..d2f84222734f
--- /dev/null
+++ b/pulse/fullstack-leaderboard/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pulse/fullstack-leaderboard/src/app/actions.ts b/pulse/fullstack-leaderboard/src/app/actions.ts
new file mode 100644
index 000000000000..9ecb8cef27ec
--- /dev/null
+++ b/pulse/fullstack-leaderboard/src/app/actions.ts
@@ -0,0 +1,11 @@
+"use server";
+import prisma from "@/lib/prisma";
+
+export async function addPoints({ points, playerId }: { points: number; playerId: number }) {
+ console.log(`addPoints, `, points, playerId);
+ const updatedPlayer = await prisma.player.update({
+ where: { id: playerId },
+ data: { points: { increment: points } },
+ });
+ console.log(`Player ${updatedPlayer.username} now has ${updatedPlayer.points}.`);
+}
diff --git a/pulse/fullstack-leaderboard/src/app/favicon.ico b/pulse/fullstack-leaderboard/src/app/favicon.ico
new file mode 100644
index 000000000000..718d6fea4835
Binary files /dev/null and b/pulse/fullstack-leaderboard/src/app/favicon.ico differ
diff --git a/pulse/fullstack-leaderboard/src/app/globals.css b/pulse/fullstack-leaderboard/src/app/globals.css
new file mode 100644
index 000000000000..af1af3ed1116
--- /dev/null
+++ b/pulse/fullstack-leaderboard/src/app/globals.css
@@ -0,0 +1,46 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+:root {
+ --foreground-rgb: 0, 0, 0;
+ --background-start-rgb: 214, 219, 220;
+ --background-end-rgb: 255, 255, 255;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --foreground-rgb: 255, 255, 255;
+ --background-start-rgb: 0, 0, 0;
+ --background-end-rgb: 0, 0, 0;
+ }
+}
+
+body {
+ color: rgb(var(--foreground-rgb));
+ background: linear-gradient(
+ to bottom,
+ transparent,
+ rgb(var(--background-end-rgb))
+ )
+ rgb(var(--background-start-rgb));
+}
+
+@layer utilities {
+ .text-balance {
+ text-wrap: balance;
+ }
+}
+
+@keyframes blink {
+ 0%, 100% {
+ background-color: white;
+ }
+ 50% {
+ background-color: #16A394; /*16A394*/
+ }
+}
+
+.blink {
+ animation: blink 1s ease-out;
+}
diff --git a/pulse/fullstack-leaderboard/src/app/layout.tsx b/pulse/fullstack-leaderboard/src/app/layout.tsx
new file mode 100644
index 000000000000..3314e4780a0c
--- /dev/null
+++ b/pulse/fullstack-leaderboard/src/app/layout.tsx
@@ -0,0 +1,22 @@
+import type { Metadata } from "next";
+import { Inter } from "next/font/google";
+import "./globals.css";
+
+const inter = Inter({ subsets: ["latin"] });
+
+export const metadata: Metadata = {
+ title: "Create Next App",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/pulse/fullstack-simple-chat/client/next.config.js b/pulse/fullstack-simple-chat/client/next.config.js
new file mode 100644
index 000000000000..53e2dc06756d
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/next.config.js
@@ -0,0 +1,6 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ reactStrictMode: false
+}
+
+module.exports = nextConfig
diff --git a/pulse/fullstack-simple-chat/client/package.json b/pulse/fullstack-simple-chat/client/package.json
new file mode 100644
index 000000000000..1938786aa170
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "client",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.0.0",
+ "react": "^18",
+ "react-dom": "^18",
+ "socket.io-client": "^4.7.2"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "autoprefixer": "^10",
+ "eslint": "^8",
+ "eslint-config-next": "14.0.0",
+ "postcss": "^8",
+ "tailwindcss": "^3",
+ "typescript": "^5"
+ }
+}
diff --git a/pulse/fullstack-simple-chat/client/postcss.config.js b/pulse/fullstack-simple-chat/client/postcss.config.js
new file mode 100644
index 000000000000..33ad091d26d8
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/pulse/fullstack-simple-chat/client/public/next.svg b/pulse/fullstack-simple-chat/client/public/next.svg
new file mode 100644
index 000000000000..5174b28c565c
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pulse/fullstack-simple-chat/client/public/vercel.svg b/pulse/fullstack-simple-chat/client/public/vercel.svg
new file mode 100644
index 000000000000..d2f84222734f
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/pulse/fullstack-simple-chat/client/tailwind.config.ts b/pulse/fullstack-simple-chat/client/tailwind.config.ts
new file mode 100644
index 000000000000..c7ead804652e
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/tailwind.config.ts
@@ -0,0 +1,20 @@
+import type { Config } from 'tailwindcss'
+
+const config: Config = {
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ theme: {
+ extend: {
+ backgroundImage: {
+ 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
+ 'gradient-conic':
+ 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
+ },
+ },
+ },
+ plugins: [],
+}
+export default config
diff --git a/pulse/fullstack-simple-chat/client/tsconfig.json b/pulse/fullstack-simple-chat/client/tsconfig.json
new file mode 100644
index 000000000000..3b412f2f55d9
--- /dev/null
+++ b/pulse/fullstack-simple-chat/client/tsconfig.json
@@ -0,0 +1,28 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "noImplicitAny": false,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/pulse/fullstack-simple-chat/server/.gitignore b/pulse/fullstack-simple-chat/server/.gitignore
new file mode 100644
index 000000000000..11ddd8dbe662
--- /dev/null
+++ b/pulse/fullstack-simple-chat/server/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+# Keep environment variables out of version control
+.env
diff --git a/pulse/fullstack-simple-chat/server/package.json b/pulse/fullstack-simple-chat/server/package.json
new file mode 100644
index 000000000000..7328fc714c85
--- /dev/null
+++ b/pulse/fullstack-simple-chat/server/package.json
@@ -0,0 +1,26 @@
+{
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.ts",
+ "scripts": {
+ "dev": "ts-node src/index.ts",
+ "build": "npx tsc"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "@prisma/client": "^5.5.2",
+ "@prisma/extension-pulse": "^1.1.0",
+ "cors": "^2.8.5",
+ "express": "^4.18.2",
+ "socket.io": "^4.7.2"
+ },
+ "devDependencies": {
+ "@types/cors": "^2.8.13",
+ "@types/express": "^4.17.17",
+ "@types/node": "^20.4.9",
+ "prisma": "^5.5.2",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.1.6"
+ }
+}
diff --git a/pulse/fullstack-simple-chat/server/prisma/schema.prisma b/pulse/fullstack-simple-chat/server/prisma/schema.prisma
new file mode 100644
index 000000000000..9884ca939159
--- /dev/null
+++ b/pulse/fullstack-simple-chat/server/prisma/schema.prisma
@@ -0,0 +1,19 @@
+// This is your Prisma schema file,
+// learn more about it in the docs: https://pris.ly/d/prisma-schema
+
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model Message {
+ id Int @id @default(autoincrement())
+ createdAt DateTime @default(now())
+ updatedAt DateTime @updatedAt
+ text String?
+ senderSocketId String?
+}
diff --git a/pulse/fullstack-simple-chat/server/src/index.ts b/pulse/fullstack-simple-chat/server/src/index.ts
new file mode 100644
index 000000000000..deaf3e1b6918
--- /dev/null
+++ b/pulse/fullstack-simple-chat/server/src/index.ts
@@ -0,0 +1,61 @@
+import * as socket from "socket.io";
+import { Server } from "socket.io";
+import express, { Request, Response } from "express";
+import http from "http";
+import cors from "cors";
+import { PrismaClient } from "@prisma/client";
+import { withPulse } from "@prisma/extension-pulse";
+
+const prisma = new PrismaClient().$extends(
+ withPulse({
+ apiKey: process.env.PULSE_API_KEY || "",
+ })
+);
+
+const app = express();
+app.use(cors());
+
+app.get(`/messages`, async (_: Request, res: Response) => {
+ const messages = await prisma.message.findMany();
+ res.json(messages);
+});
+
+const server = http.createServer(app);
+
+const io = new socket.Server(server, {
+ cors: { origin: true },
+});
+
+io.on(`connection`, async (socket) => {
+ console.log(`User connected: ${socket.id}`);
+
+ socket.on(`disconnect`, () => {
+ console.log(`User disconnected: ${socket.id}`);
+ });
+
+ socket.on(`chat-message`, async (text) => {
+ console.log(`Received message: ${text} (${socket.id})`);
+ await prisma.message.create({
+ data: {
+ text,
+ senderSocketId: socket.id,
+ },
+ });
+ });
+});
+
+server.listen(4000, async () => {
+ console.log(`Server running on http://localhost:4000`);
+ await streamChatMessages(io);
+});
+
+async function streamChatMessages(io: Server) {
+ console.log(`Stream new messages with Prisma Client ...`);
+ const stream = await prisma.message.stream({ create: {} });
+
+ // Handle Prisma stream events
+ for await (const event of stream) {
+ console.log(`New event from Pulse: `, event);
+ io.sockets.emit("chat-message", event.created);
+ }
+}
diff --git a/pulse/fullstack-simple-chat/server/tsconfig.json b/pulse/fullstack-simple-chat/server/tsconfig.json
new file mode 100644
index 000000000000..afe0bc82e135
--- /dev/null
+++ b/pulse/fullstack-simple-chat/server/tsconfig.json
@@ -0,0 +1,109 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+ "outDir": "./dist",
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ // "resolveJsonModule": true, /* Enable importing .json files. */
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
diff --git a/pulse/starter/README.md b/pulse/starter/README.md
index fbb76da34781..9edc4f5eed99 100644
--- a/pulse/starter/README.md
+++ b/pulse/starter/README.md
@@ -1,6 +1,6 @@
# Prisma Pulse Example: Starter
-This repository has been created to help you get started with [Pulse](https://prisma.io/pulse). You will be able to use this project with any Pulse-ready PostgreSQL database. This project comes with a basic [`schema.prisma`](./prisma/schema.prisma) file as well as a Pulse subscription found in the [`index.ts`](./index.ts) file.
+This repository has been created to help you get started with [Pulse](https://prisma.io/pulse). You will be able to use this project with any Pulse-ready PostgreSQL database. This project comes with a basic [`schema.prisma`](./prisma/schema.prisma) file as well as a Pulse stream found in the [`index.ts`](./index.ts) file.
## Prerequisites
@@ -49,29 +49,25 @@ npx prisma migrate dev --name init
You now have an empty `User` table in your database.
-### 4. Start the Pulse subscription
+### 4. Start the Pulse stream
-Run the [script](./index.ts) that contains the code to subscribe to database events:
+Run the [script](./index.ts) that contains the code to stream database events:
```bash
npx ts-node index.ts
```
-This will run a basic subscription on the `User` table. Whenever a record is created, updated or deleted in that table, an event will fire and the script will execute a `console.log` statement with details of the event it received.
+This will create a basic stream on the `User` table. Whenever a record is created, updated or deleted in that table, an event will fire and the script will execute a `console.log` statement with details of the event it received.
-The code can be found in the [`index.ts`](./index.ts) file. To learn more about the Pulse API and how to use it, check out our [documentation](https://www.prisma.io/docs/data-platform/pulse/api-reference#subscribe).
+The code can be found in the [`index.ts`](./index.ts) file. To learn more about the Pulse API and how to use it, check out our [documentation](https://www.prisma.io/docs/data-platform/pulse/api-reference#stream).
-Pulse subscription on the `User` table
+Pulse stream on the `User` table
```ts
async function main() {
- const subscription = await prisma.user.subscribe()
+ const stream = await prisma.user.stream()
- if (subscription instanceof Error) {
- throw subscription
- }
-
- for await (const event of subscription) {
+ for await (const event of stream) {
console.log('just received an event:', event)
}
}
@@ -79,7 +75,7 @@ async function main() {
-### 5. Test the subscription
+### 5. Test the stream
The following instructions use [Prisma Studio](https://www.prisma.io/studio) to create a new record in the `User` table. However, you can use any other method to write to the `User` table (e.g. a SQL client like `psql` or [TablePlus](https://tableplus.com/)) in order to trigger a database change event in Pulse.
diff --git a/pulse/starter/index.ts b/pulse/starter/index.ts
index 21c5b534d059..0a6dd7ddbb5e 100644
--- a/pulse/starter/index.ts
+++ b/pulse/starter/index.ts
@@ -12,17 +12,13 @@ const prisma = new PrismaClient().$extends(
)
async function main() {
- const subscription = await prisma.user.subscribe()
+ const stream = await prisma.user.stream()
process.on('exit', (code) => {
- subscription.stop()
+ stream.stop()
})
- if (subscription instanceof Error) {
- throw subscription
- }
-
- for await (const event of subscription) {
+ for await (const event of stream) {
console.log('just received an event:', event)
}
}
diff --git a/pulse/starter/package.json b/pulse/starter/package.json
index 20ba01ddac05..4dc29db7a5b8 100644
--- a/pulse/starter/package.json
+++ b/pulse/starter/package.json
@@ -12,11 +12,11 @@
"license": "MIT",
"dependencies": {
"@prisma/client": "5.14.0",
- "@prisma/extension-pulse": "1.0.2",
+ "@prisma/extension-pulse": "dev",
"@types/node": "20.12.12",
"dotenv": "16.4.5",
"prisma": "5.14.0",
"ts-node": "10.9.2",
"typescript": "5.4.5"
}
-}
\ No newline at end of file
+}