diff --git a/examples/sst-nextjs/.gitignore b/examples/sst-nextjs/.gitignore
new file mode 100644
index 00000000..70137258
--- /dev/null
+++ b/examples/sst-nextjs/.gitignore
@@ -0,0 +1,42 @@
+# 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
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+# sst
+.sst
+
+# open-next
+.open-next
\ No newline at end of file
diff --git a/examples/sst-nextjs/README.md b/examples/sst-nextjs/README.md
new file mode 100644
index 00000000..c4033664
--- /dev/null
+++ b/examples/sst-nextjs/README.md
@@ -0,0 +1,36 @@
+This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
+
+## Getting Started
+
+First, run the development server:
+
+```bash
+npm run dev
+# or
+yarn dev
+# or
+pnpm dev
+# or
+bun dev
+```
+
+Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
+
+You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
+
+This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
+
+## Learn More
+
+To learn more about Next.js, take a look at the following resources:
+
+- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
+- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
+
+You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
+
+## Deploy on Vercel
+
+The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
+
+Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
diff --git a/examples/sst-nextjs/next.config.js b/examples/sst-nextjs/next.config.js
new file mode 100644
index 00000000..658404ac
--- /dev/null
+++ b/examples/sst-nextjs/next.config.js
@@ -0,0 +1,4 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {};
+
+module.exports = nextConfig;
diff --git a/examples/sst-nextjs/package.json b/examples/sst-nextjs/package.json
new file mode 100644
index 00000000..36d3b90d
--- /dev/null
+++ b/examples/sst-nextjs/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "sst-nextjs",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "sst bind next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "@eventual/core": "workspace:^",
+ "@eventual/client": "workspace:^",
+ "@eventual/aws-client": "workspace:^",
+ "react": "^18",
+ "react-dom": "^18",
+ "next": "14.0.4"
+ },
+ "devDependencies": {
+ "typescript": "^5",
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "autoprefixer": "^10.0.1",
+ "postcss": "^8",
+ "tailwindcss": "^3.3.0",
+ "sst": "^2.39.2",
+ "aws-cdk-lib": "2.110.1",
+ "@eventual/aws-cdk": "workspace:^",
+ "constructs": "10.3.0"
+ }
+}
diff --git a/examples/sst-nextjs/postcss.config.js b/examples/sst-nextjs/postcss.config.js
new file mode 100644
index 00000000..12a703d9
--- /dev/null
+++ b/examples/sst-nextjs/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/examples/sst-nextjs/public/next.svg b/examples/sst-nextjs/public/next.svg
new file mode 100644
index 00000000..5174b28c
--- /dev/null
+++ b/examples/sst-nextjs/public/next.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/sst-nextjs/public/vercel.svg b/examples/sst-nextjs/public/vercel.svg
new file mode 100644
index 00000000..d2f84222
--- /dev/null
+++ b/examples/sst-nextjs/public/vercel.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/sst-nextjs/src/app/api/start/route.tsx b/examples/sst-nextjs/src/app/api/start/route.tsx
new file mode 100644
index 00000000..c279aa06
--- /dev/null
+++ b/examples/sst-nextjs/src/app/api/start/route.tsx
@@ -0,0 +1,12 @@
+import { client } from "@/server/client";
+import { NextResponse } from "next/server";
+
+export default async function handler() {
+ const executionHandle = await client.tickTock.startExecution();
+
+ return new NextResponse(
+ JSON.stringify({
+ executionId: executionHandle.executionId,
+ })
+ );
+}
diff --git a/examples/sst-nextjs/src/app/favicon.ico b/examples/sst-nextjs/src/app/favicon.ico
new file mode 100644
index 00000000..718d6fea
Binary files /dev/null and b/examples/sst-nextjs/src/app/favicon.ico differ
diff --git a/examples/sst-nextjs/src/app/globals.css b/examples/sst-nextjs/src/app/globals.css
new file mode 100644
index 00000000..fd81e885
--- /dev/null
+++ b/examples/sst-nextjs/src/app/globals.css
@@ -0,0 +1,27 @@
+@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));
+}
diff --git a/examples/sst-nextjs/src/app/layout.tsx b/examples/sst-nextjs/src/app/layout.tsx
new file mode 100644
index 00000000..323bd9c9
--- /dev/null
+++ b/examples/sst-nextjs/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,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+
{children}
+
+ );
+}
diff --git a/examples/sst-nextjs/src/app/page.tsx b/examples/sst-nextjs/src/app/page.tsx
new file mode 100644
index 00000000..992bc443
--- /dev/null
+++ b/examples/sst-nextjs/src/app/page.tsx
@@ -0,0 +1,113 @@
+import Image from "next/image";
+
+export default function Home() {
+ return (
+
+
+
+ Get started by editing
+ src/app/page.tsx
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/sst-nextjs/src/server/client.ts b/examples/sst-nextjs/src/server/client.ts
new file mode 100644
index 00000000..a5410b1d
--- /dev/null
+++ b/examples/sst-nextjs/src/server/client.ts
@@ -0,0 +1,6 @@
+import type * as server from ".";
+import { AWSServiceClient } from "@eventual/aws-client";
+
+export const client = new AWSServiceClient({
+ serviceUrl: process.env.SERVICE_URL!,
+});
diff --git a/examples/sst-nextjs/src/server/event.ts b/examples/sst-nextjs/src/server/event.ts
new file mode 100644
index 00000000..d4819e54
--- /dev/null
+++ b/examples/sst-nextjs/src/server/event.ts
@@ -0,0 +1,9 @@
+import { event } from "@eventual/core";
+
+export const tick = event<{
+ time: number;
+}>("tick");
+
+export const tock = event<{
+ time: number;
+}>("tick");
diff --git a/examples/sst-nextjs/src/server/index.ts b/examples/sst-nextjs/src/server/index.ts
new file mode 100644
index 00000000..eb52b1a9
--- /dev/null
+++ b/examples/sst-nextjs/src/server/index.ts
@@ -0,0 +1,3 @@
+export * from "./event";
+export * from "./socket";
+export * from "./workflow";
diff --git a/examples/sst-nextjs/src/server/socket.ts b/examples/sst-nextjs/src/server/socket.ts
new file mode 100644
index 00000000..6d8c936b
--- /dev/null
+++ b/examples/sst-nextjs/src/server/socket.ts
@@ -0,0 +1,8 @@
+import { socket } from "@eventual/core";
+
+// expose a websocket endpoint for
+export const tickTockFeed = socket("tickTockFeed", {
+ $connect: () => {},
+ $disconnect: () => {},
+ $default: () => {},
+});
diff --git a/examples/sst-nextjs/src/server/workflow.ts b/examples/sst-nextjs/src/server/workflow.ts
new file mode 100644
index 00000000..7938cca2
--- /dev/null
+++ b/examples/sst-nextjs/src/server/workflow.ts
@@ -0,0 +1,24 @@
+import { duration, workflow } from "@eventual/core";
+import { tick, tock } from "./event";
+
+export const tickTock = workflow("tickTock", async (input?: string) => {
+ let i = 0;
+ while (true) {
+ // emit the tick event
+ await tick.emit({
+ time: Date.now(),
+ });
+
+ // put the workflow to sleep for 1 minute
+ await duration(1, "minute");
+
+ // emit the tock event
+ await tock.emit({
+ time: Date.now(),
+ });
+
+ if (i === 100) {
+ break;
+ }
+ }
+});
diff --git a/examples/sst-nextjs/sst-env.d.ts b/examples/sst-nextjs/sst-env.d.ts
new file mode 100644
index 00000000..3e233439
--- /dev/null
+++ b/examples/sst-nextjs/sst-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/sst-nextjs/sst.config.ts b/examples/sst-nextjs/sst.config.ts
new file mode 100644
index 00000000..732af1e4
--- /dev/null
+++ b/examples/sst-nextjs/sst.config.ts
@@ -0,0 +1,20 @@
+import { SSTConfig } from "sst";
+import { NextjsSite } from "sst/constructs";
+
+export default {
+ config(_input) {
+ return {
+ name: "sst-nextjs",
+ region: "us-east-1",
+ };
+ },
+ stacks(app) {
+ app.stack(function Site({ stack }) {
+ const site = new NextjsSite(stack, "site");
+
+ stack.addOutputs({
+ SiteUrl: site.url,
+ });
+ });
+ },
+} satisfies SSTConfig;
diff --git a/examples/sst-nextjs/tailwind.config.ts b/examples/sst-nextjs/tailwind.config.ts
new file mode 100644
index 00000000..e9a0944e
--- /dev/null
+++ b/examples/sst-nextjs/tailwind.config.ts
@@ -0,0 +1,20 @@
+import type { Config } from "tailwindcss";
+
+const config: Config = {
+ content: [
+ "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./src/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/examples/sst-nextjs/tsconfig.json b/examples/sst-nextjs/tsconfig.json
new file mode 100644
index 00000000..e59724b2
--- /dev/null
+++ b/examples/sst-nextjs/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "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,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/@eventual/client/src/http-eventual-client.ts b/packages/@eventual/client/src/http-eventual-client.ts
index e81be1e6..e8eec308 100644
--- a/packages/@eventual/client/src/http-eventual-client.ts
+++ b/packages/@eventual/client/src/http-eventual-client.ts
@@ -15,9 +15,10 @@ import {
type SendTaskFailureRequest,
type SendTaskHeartbeatResponse,
type SendTaskSuccessRequest,
- type StartExecutionRequest,
+ type DirectStartExecutionRequest,
type Transaction,
type Workflow,
+ WorkflowOutput,
} from "@eventual/core";
import type {
EventualService,
@@ -49,8 +50,8 @@ export class HttpEventualClient implements EventualServiceClient {
}
public async startExecution(
- request: StartExecutionRequest
- ): Promise> {
+ request: DirectStartExecutionRequest
+ ): Promise>> {
// serialize the workflow object to a string
const workflow =
typeof request.workflow === "string"
diff --git a/packages/@eventual/client/src/service-client.ts b/packages/@eventual/client/src/service-client.ts
index 5b0f89be..59f6e337 100644
--- a/packages/@eventual/client/src/service-client.ts
+++ b/packages/@eventual/client/src/service-client.ts
@@ -1,4 +1,14 @@
-import { Command } from "@eventual/core";
+import type {
+ Command,
+ Event,
+ Execution,
+ ExecutionHandle,
+ SendSignalProps,
+ Signal,
+ Socket,
+ StartExecutionRequest,
+ Workflow,
+} from "@eventual/core";
import {
HttpServiceClient,
HttpServiceClientProps,
@@ -104,14 +114,30 @@ type ServiceClientName = T extends { name: infer Name extends string }
? Name
: never;
-type ServiceClientMethod = T extends Command<
- any,
- infer Input,
- infer Output,
- any,
- any,
- any
->
+type ServiceClientMethod = T extends Workflow
+ ? {
+ startExecution: [undefined] extends [Input]
+ ? (
+ req?: StartExecutionRequest
+ ) => Promise>
+ : (
+ req: StartExecutionRequest
+ ) => Promise>;
+ getHandle: (executionId: string) => Promise>;
+ getStatus(executionId: string): Promise>;
+ sendSignal(
+ executionId: string,
+ signal: string | Signal,
+ ...args: SendSignalProps
+ ): Promise;
+ }
+ : T extends Event
+ ? {
+ emit: (payload: Payload) => Promise;
+ }
+ : T extends Socket
+ ? never
+ : T extends Command
? [Input] extends [undefined]
? {
(
@@ -134,7 +160,9 @@ type ServiceClientMethod = T extends Command<
type KeysWhereNameIsSame = {
[k in keyof Service]: k extends Extract["name"]
? // we only want commands to show up
- Service[k] extends { kind: "Command" }
+ Service[k] extends {
+ kind: "Command" | "Workflow" | "Event";
+ }
? k
: never
: never;
diff --git a/packages/@eventual/core-runtime/src/clients/runtime-service-clients.ts b/packages/@eventual/core-runtime/src/clients/runtime-service-clients.ts
index 630f56f2..a4641826 100644
--- a/packages/@eventual/core-runtime/src/clients/runtime-service-clients.ts
+++ b/packages/@eventual/core-runtime/src/clients/runtime-service-clients.ts
@@ -16,9 +16,10 @@ import {
SendTaskHeartbeatRequest,
SendTaskHeartbeatResponse,
SendTaskSuccessRequest,
- StartExecutionRequest,
+ DirectStartExecutionRequest,
Transaction,
Workflow,
+ WorkflowOutput,
} from "@eventual/core";
import type { WorkflowProvider } from "../providers/workflow-provider.js";
import type { ExecutionHistoryStateStore } from "../stores/execution-history-state-store.js";
@@ -73,8 +74,8 @@ export class RuntimeFallbackServiceClient implements EventualServiceClient {
}
public async startExecution(
- request: StartExecutionRequest
- ): Promise> {
+ request: DirectStartExecutionRequest
+ ): Promise>> {
if (!this.props.workflowClient) {
return this.fallbackServiceClient.startExecution(request);
}
diff --git a/packages/@eventual/core-runtime/src/clients/workflow-client.ts b/packages/@eventual/core-runtime/src/clients/workflow-client.ts
index 4a45fb89..db9e14dd 100644
--- a/packages/@eventual/core-runtime/src/clients/workflow-client.ts
+++ b/packages/@eventual/core-runtime/src/clients/workflow-client.ts
@@ -5,7 +5,7 @@ import {
FailedExecution,
FailExecutionRequest,
InProgressExecution,
- StartExecutionRequest,
+ DirectStartExecutionRequest,
SucceededExecution,
SucceedExecutionRequest,
Workflow,
@@ -52,8 +52,8 @@ export class WorkflowClient {
timeout,
...request
}:
- | StartExecutionRequest
- | StartChildExecutionRequest): Promise {
+ | DirectStartExecutionRequest
+ | DirectStartChildExecutionRequest): Promise {
if (
typeof workflow === "string" &&
!this.workflowProvider.workflowExists(workflow)
@@ -191,8 +191,8 @@ export class WorkflowClient {
}
}
-export interface StartChildExecutionRequest
- extends StartExecutionRequest,
+export interface DirectStartChildExecutionRequest
+ extends DirectStartExecutionRequest,
WorkflowExecutionOptions {
parentExecutionId: ExecutionID;
/**
diff --git a/packages/@eventual/core-runtime/test/workflow-executor.test.ts b/packages/@eventual/core-runtime/test/workflow-executor.test.ts
index 642569ae..448b22a0 100644
--- a/packages/@eventual/core-runtime/test/workflow-executor.test.ts
+++ b/packages/@eventual/core-runtime/test/workflow-executor.test.ts
@@ -66,17 +66,15 @@ const context: WorkflowContext = {
const workflow = (() => {
let n = 0;
- return (
- handler: WorkflowHandler
- ) => {
- return _workflow(`wf${n++}`, handler);
+ return (handler: H) => {
+ return _workflow(`wf${n++}`, handler);
};
})();
const myTask = task("my-task", async () => {});
const myTask0 = task("my-task-0", async () => {});
const myTask2 = task("my-task-2", async () => {});
-const handleErrorTask = task("handle-error", async () => {});
+const handleErrorTask = task("handle-error", async (_err?: any) => {});
const processItemTask = task("processItem", (_item?: string) => {});
const beforeTask = task("before", (_v: string) => {});
const insideTask = task("inside", (_v: string) => {
@@ -2016,14 +2014,11 @@ describe("signals", () => {
mySignalHappened++;
}
);
- const myOtherSignalHandler = onSignal(
- "MyOtherSignal",
- async function (payload) {
- myOtherSignalHappened++;
- await myTask(payload);
- myOtherSignalCompleted++;
- }
- );
+ const myOtherSignalHandler = onSignal("MyOtherSignal", async function () {
+ myOtherSignalHappened++;
+ await myTask();
+ myOtherSignalCompleted++;
+ });
await time(testTime);
@@ -3513,7 +3508,7 @@ describe("using then, catch, finally", () => {
x++;
return myTask0();
}),
- cwf("workflow1", undefined).catch(() => {
+ cwf().catch(() => {
x++;
return myTask0();
}),
diff --git a/packages/@eventual/core/src/execution.ts b/packages/@eventual/core/src/execution.ts
index bcebcdad..b65849da 100644
--- a/packages/@eventual/core/src/execution.ts
+++ b/packages/@eventual/core/src/execution.ts
@@ -9,7 +9,6 @@ import { isOrchestratorWorker } from "./internal/service-type.js";
import { SignalTargetType } from "./internal/signal.js";
import { EventualServiceClient } from "./service-client.js";
import type { SendSignalProps, Signal } from "./signals.js";
-import type { Workflow, WorkflowOutput } from "./workflow.js";
export enum ExecutionStatus {
IN_PROGRESS = "IN_PROGRESS",
@@ -86,7 +85,7 @@ export function isSucceededExecution(
* Note: This object should be usable within a workflow. It should only contain deterministic logic
* {@link EventualCall}s or {@link EventualProperty}s via the {@link EventualHook}.
*/
-export class ExecutionHandle {
+export class ExecutionHandle