<html lang="en">
<head></head>
<body>

<form id="mainForm" method="post" action="https://stackblitz.com/run" target="_self">
<input type="hidden" name="project[files][README.md]" value="# Actor Actions

Demonstrates how to define and call actions on Rivet Actors for RPC-style communication between actors and clients.

## Getting Started

```sh
git clone https://github.com/rivet-dev/rivet.git
cd rivet/examples/actor-actions
npm install
npm run dev
```


## Features

- **Type-safe actor actions**: Define and call actions with full TypeScript type safety
- **Actor state management**: Initialize and persist actor state using `createState`
- **Cross-actor communication**: Create and interact with actors from within other actors
- **RPC-style patterns**: Call actor methods from client code with automatic type inference

## Implementation

This example demonstrates the fundamentals of defining and calling actor actions:

- **Actor Definition** ([`src/backend/registry.ts`](https://github.com/rivet-dev/rivet/tree/main/examples/actor-actions/src/backend/registry.ts)): Shows how to define actions with parameters, return values, and state management

## Resources

Read more about [actions](/docs/actors/actions), [state](/docs/actors/state), and [actor setup](/docs/setup).

## License

MIT
">
<input type="hidden" name="project[files][index.html]" value="&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;Quickstart: Actions&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif;
            margin: 0;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        .header {
            background: #28a745;
            color: white;
            padding: 30px;
            text-align: center;
            border-radius: 8px;
            margin-bottom: 30px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .header h1 {
            margin: 0 0 10px 0;
            font-size: 2em;
        }
        .header p {
            margin: 0;
            opacity: 0.9;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(450px, 1fr));
            gap: 20px;
            margin-bottom: 20px;
        }
        .section {
            background: white;
            border-radius: 8px;
            padding: 25px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .section h2 {
            margin: 0 0 20px 0;
            color: #28a745;
            font-size: 1.5em;
            border-bottom: 2px solid #28a745;
            padding-bottom: 10px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: 500;
            color: #333;
        }
        .form-group input {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }
        .button-group {
            display: flex;
            gap: 10px;
            margin-top: 20px;
        }
        button {
            padding: 12px 20px;
            background: #6c757d;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            flex: 1;
        }
        button.primary {
            background: #28a745;
        }
        button.primary:hover:not(:disabled) {
            background: #218838;
        }
        button.secondary {
            background: #007bff;
            width: 100%;
            margin-top: 10px;
        }
        button.secondary:hover:not(:disabled) {
            background: #0056b3;
        }
        button:disabled {
            background: #ccc;
            cursor: not-allowed;
        }
        button:hover:not(:disabled) {
            background: #5a6268;
        }
        .profile {
            margin-top: 25px;
            padding: 20px;
            background: #f8f9fa;
            border-radius: 8px;
            border-left: 4px solid #28a745;
        }
        .profile h3 {
            margin: 0 0 15px 0;
            color: #28a745;
            font-size: 1.2em;
        }
        .profile-field {
            margin-bottom: 10px;
            padding: 8px 0;
            border-bottom: 1px solid #e9ecef;
        }
        .profile-field:last-of-type {
            border-bottom: none;
        }
        .profile-field strong {
            color: #495057;
            margin-right: 8px;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;script type=&quot;module&quot; src=&quot;/frontend/main.tsx&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
">
<input type="hidden" name="project[files][package.json]" value="{&quot;name&quot;:&quot;actor-actions&quot;,&quot;version&quot;:&quot;2.0.21&quot;,&quot;private&quot;:true,&quot;type&quot;:&quot;module&quot;,&quot;scripts&quot;:{&quot;dev&quot;:&quot;vite&quot;,&quot;check-types&quot;:&quot;tsc --noEmit&quot;,&quot;test&quot;:&quot;vitest run&quot;,&quot;build&quot;:&quot;vite build &amp;&amp; vite build --mode server&quot;,&quot;start&quot;:&quot;srvx --static=public/ dist/server.js&quot;},&quot;devDependencies&quot;:{&quot;@types/node&quot;:&quot;^22.13.9&quot;,&quot;@types/react&quot;:&quot;^18.2.0&quot;,&quot;@types/react-dom&quot;:&quot;^18.2.0&quot;,&quot;@vitejs/plugin-react&quot;:&quot;^4.2.0&quot;,&quot;tsx&quot;:&quot;^3.12.7&quot;,&quot;typescript&quot;:&quot;^5.5.2&quot;,&quot;vite&quot;:&quot;^5.0.0&quot;,&quot;vitest&quot;:&quot;^3.1.1&quot;,&quot;vite-plugin-srvx&quot;:&quot;^1.0.0&quot;},&quot;dependencies&quot;:{&quot;@hono/node-server&quot;:&quot;^1.19.7&quot;,&quot;@hono/node-ws&quot;:&quot;^1.2.0&quot;,&quot;@rivetkit/react&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@bab56c93ffe3eb62e2e2db172c826e27721d88e8&quot;,&quot;hono&quot;:&quot;^4.11.3&quot;,&quot;react&quot;:&quot;^18.2.0&quot;,&quot;react-dom&quot;:&quot;^18.2.0&quot;,&quot;rivetkit&quot;:&quot;https://pkg.pr.new/rivet-dev/rivet/rivetkit@bab56c93ffe3eb62e2e2db172c826e27721d88e8&quot;,&quot;srvx&quot;:&quot;^0.10.0&quot;},&quot;template&quot;:{&quot;technologies&quot;:[&quot;typescript&quot;],&quot;tags&quot;:[],&quot;priority&quot;:1000,&quot;frontendPort&quot;:5173},&quot;license&quot;:&quot;MIT&quot;}">
<input type="hidden" name="project[files][tsconfig.build.json]" value="{
	&quot;extends&quot;: &quot;./tsconfig.json&quot;,
	&quot;compilerOptions&quot;: {
		&quot;noEmit&quot;: false,
		&quot;outDir&quot;: &quot;dist&quot;,
		&quot;rootDir&quot;: &quot;src&quot;
	},
	&quot;include&quot;: [&quot;src/**/*&quot;]
}
">
<input type="hidden" name="project[files][tsconfig.json]" value="{
	&quot;compilerOptions&quot;: {
		/* Visit https://aka.ms/tsconfig.json to read more about this file */

		/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
		&quot;target&quot;: &quot;esnext&quot;,
		/* Specify a set of bundled library declaration files that describe the target runtime environment. */
		&quot;lib&quot;: [&quot;esnext&quot;, &quot;dom&quot;],
		/* Specify what JSX code is generated. */
		&quot;jsx&quot;: &quot;react-jsx&quot;,

		/* Specify what module code is generated. */
		&quot;module&quot;: &quot;esnext&quot;,
		/* Specify how TypeScript looks up a file from a given module specifier. */
		&quot;moduleResolution&quot;: &quot;bundler&quot;,
		/* Specify type package names to be included without being referenced in a source file. */
		&quot;types&quot;: [&quot;node&quot;, &quot;vite/client&quot;],
		/* Enable importing .json files */
		&quot;resolveJsonModule&quot;: true,

		/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
		&quot;allowJs&quot;: true,
		/* Enable error reporting in type-checked JavaScript files. */
		&quot;checkJs&quot;: false,

		/* Disable emitting files from a compilation. */
		&quot;noEmit&quot;: true,

		/* Ensure that each file can be safely transpiled without relying on other imports. */
		&quot;isolatedModules&quot;: true,
		/* Allow &#39;import x from y&#39; when a module doesn&#39;t have a default export. */
		&quot;allowSyntheticDefaultImports&quot;: true,
		/* Ensure that casing is correct in imports. */
		&quot;forceConsistentCasingInFileNames&quot;: true,

		/* Enable all strict type-checking options. */
		&quot;strict&quot;: true,

		/* Skip type checking all .d.ts files. */
		&quot;skipLibCheck&quot;: true,
		&quot;allowImportingTsExtensions&quot;: true,
		&quot;rewriteRelativeImportExtensions&quot;: true
	},
	&quot;include&quot;: [&quot;src/**/*&quot;, &quot;tests/**/*&quot;]
}
">
<input type="hidden" name="project[files][turbo.json]" value="{
	&quot;$schema&quot;: &quot;https://turbo.build/schema.json&quot;,
	&quot;extends&quot;: [&quot;//&quot;]
}
">
<input type="hidden" name="project[files][vercel.json]" value="{
	&quot;framework&quot;: &quot;hono&quot;
}
">
<input type="hidden" name="project[files][vite.config.ts]" value="import { defineConfig } from &quot;vite&quot;;
import react from &quot;@vitejs/plugin-react&quot;;
import srvx from &quot;vite-plugin-srvx&quot;;

export default defineConfig({
	plugins: [react(), ...srvx({ entry: &quot;src/server.ts&quot; })],
});
">
<input type="hidden" name="project[files][vitest.config.ts]" value="import { defineConfig } from &quot;vitest/config&quot;;

export default defineConfig({
	server: {
		port: 5173,
	},
	test: {
		include: [&quot;tests/**/*.test.ts&quot;],
	},
});
">
<input type="hidden" name="project[files][frontend/App.tsx]" value="import { ActorError, createClient } from &quot;rivetkit/client&quot;;
import { useState } from &quot;react&quot;;
import type {
	CompanyProfile,
	EmployeeProfile,
	registry,
} from &quot;../src/actors.ts&quot;;

const client = createClient&lt;typeof registry&gt;(`${window.location.origin}/api/rivet`);

export function App() {
	const [companyEin, setCompanyEin] = useState(&quot;12-3456789&quot;);
	const [companyName, setCompanyName] = useState(&quot;Acme Corp&quot;);
	const [companyIndustry, setCompanyIndustry] = useState(&quot;Technology&quot;);
	const [companyProfile, setCompanyProfile] = useState&lt;CompanyProfile | null&gt;(
		null
	);

	const [employeeName, setEmployeeName] = useState(&quot;Jane Smith&quot;);
	const [employeeEmail, setEmployeeEmail] = useState(&quot;jane@acme.com&quot;);
	const [employeePosition, setEmployeePosition] = useState(&quot;Software Engineer&quot;);
	const [createdEmployee, setCreatedEmployee] =
		useState&lt;EmployeeProfile | null&gt;(null);
	const [employeeList, setEmployeeList] = useState&lt;string[]&gt;([]);

	const createCompany = async () =&gt; {
		try {
			// Create actor with input using EIN as key
			const company = await client.company.create([companyEin], {
				input: {
					name: companyName,
					industry: companyIndustry,
				},
			});

			// Get profile with full type safety
			const profile = await company.getProfile();
			setCompanyProfile(profile);
		} catch (error) {
			// Handle actor already exists error
			if (
				error instanceof ActorError &amp;&amp;
				error.group === &quot;actor&quot; &amp;&amp;
				error.code === &quot;already_exists&quot;
			) {
				alert(
					`Company with EIN &quot;${companyEin}&quot; already exists. Use &quot;Load Profile&quot; to view it.`
				);
			} else {
				// Re-throw other errors
				throw error;
			}
		}
	};

	const loadCompanyProfile = async () =&gt; {
		const company = client.company.get([companyEin]);
		const profile = await company.getProfile();
		setCompanyProfile(profile);

		// Also load the list of employees
		const employees = await company.getEmployees();
		setEmployeeList(employees);
	};

	const createEmployee = async () =&gt; {
		try {
			// Use the company&#39;s createEmployee action to spawn an employee actor
			const company = client.company.get([companyEin]);
			const employee = await company.createEmployee({
				name: employeeName,
				email: employeeEmail,
				position: employeePosition,
			});
			setCreatedEmployee(employee);

			// Refresh the employee list
			const employees = await company.getEmployees();
			setEmployeeList(employees);
		} catch (error) {
			// Handle actor already exists error
			if (
				error instanceof ActorError &amp;&amp;
				error.group === &quot;actor&quot; &amp;&amp;
				error.code === &quot;already_exists&quot;
			) {
				alert(
					`Employee with email &quot;${employeeEmail}&quot; already exists. Click on their name in the list to view their profile.`
				);
			} else {
				// Re-throw other errors
				throw error;
			}
		}
	};

	const loadEmployee = async (email: string) =&gt; {
		const employee = client.employee.get([email]);
		const profile = await employee.getProfile();
		setCreatedEmployee(profile);
	};


	return (
		&lt;div className=&quot;container&quot;&gt;
			&lt;div className=&quot;header&quot;&gt;
				&lt;h1&gt;Quickstart: Actions&lt;/h1&gt;
				&lt;p&gt;
					Demonstrates creating actors with input parameters and calling
					type-safe actions between actors
				&lt;/p&gt;
			&lt;/div&gt;

			&lt;div className=&quot;grid&quot;&gt;
				{/* Company Section */}
				&lt;div className=&quot;section&quot;&gt;
					&lt;h2&gt;Company Actor&lt;/h2&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;EIN (key):&lt;/label&gt;
						&lt;input
							type=&quot;text&quot;
							value={companyEin}
							onChange={(e) =&gt; setCompanyEin(e.target.value)}
							placeholder=&quot;Enter EIN&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;Company Name:&lt;/label&gt;
						&lt;input
							type=&quot;text&quot;
							value={companyName}
							onChange={(e) =&gt; setCompanyName(e.target.value)}
							placeholder=&quot;Enter company name&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;Industry:&lt;/label&gt;
						&lt;input
							type=&quot;text&quot;
							value={companyIndustry}
							onChange={(e) =&gt; setCompanyIndustry(e.target.value)}
							placeholder=&quot;Enter industry&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;button-group&quot;&gt;
						&lt;button
							onClick={createCompany}
							className=&quot;primary&quot;
						&gt;
							Create Company
						&lt;/button&gt;
						&lt;button
							onClick={loadCompanyProfile}
						&gt;
							Load Profile
						&lt;/button&gt;
					&lt;/div&gt;

					{companyProfile &amp;&amp; (
						&lt;div className=&quot;profile&quot;&gt;
							&lt;h3&gt;Company Profile&lt;/h3&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;ID:&lt;/strong&gt; {companyProfile.id}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Name:&lt;/strong&gt; {companyProfile.name}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Industry:&lt;/strong&gt; {companyProfile.industry}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Founded:&lt;/strong&gt;{&quot; &quot;}
								{new Date(companyProfile.foundedAt).toLocaleString()}
							&lt;/div&gt;
						&lt;/div&gt;
					)}

					{employeeList.length &gt; 0 &amp;&amp; (
						&lt;div className=&quot;profile&quot;&gt;
							&lt;h3&gt;Employees ({employeeList.length})&lt;/h3&gt;
							&lt;div style={{ display: &quot;flex&quot;, flexDirection: &quot;column&quot;, gap: &quot;8px&quot; }}&gt;
								{employeeList.map((email) =&gt; (
									&lt;button
										key={email}
										onClick={() =&gt; loadEmployee(email)}
										style={{
											padding: &quot;8px 12px&quot;,
											textAlign: &quot;left&quot;,
											cursor: &quot;pointer&quot;,
										}}
									&gt;
										{email}
									&lt;/button&gt;
								))}
							&lt;/div&gt;
						&lt;/div&gt;
					)}
				&lt;/div&gt;

				{/* Employee Section */}
				&lt;div className=&quot;section&quot;&gt;
					&lt;h2&gt;Employee Actor&lt;/h2&gt;
					&lt;p className=&quot;section-description&quot;&gt;
						Create employees through the company actor
					&lt;/p&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;Employee Email (key):&lt;/label&gt;
						&lt;input
							type=&quot;email&quot;
							value={employeeEmail}
							onChange={(e) =&gt; setEmployeeEmail(e.target.value)}
							placeholder=&quot;Enter email&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;Name:&lt;/label&gt;
						&lt;input
							type=&quot;text&quot;
							value={employeeName}
							onChange={(e) =&gt; setEmployeeName(e.target.value)}
							placeholder=&quot;Enter name&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;form-group&quot;&gt;
						&lt;label&gt;Position:&lt;/label&gt;
						&lt;input
							type=&quot;text&quot;
							value={employeePosition}
							onChange={(e) =&gt; setEmployeePosition(e.target.value)}
							placeholder=&quot;Enter position&quot;
						/&gt;
					&lt;/div&gt;
					&lt;div className=&quot;button-group&quot;&gt;
						&lt;button onClick={createEmployee} className=&quot;primary&quot;&gt;
							Create Employee via Company
						&lt;/button&gt;
					&lt;/div&gt;

					{createdEmployee &amp;&amp; (
						&lt;div className=&quot;profile&quot;&gt;
							&lt;h3&gt;Employee Profile&lt;/h3&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Employee ID:&lt;/strong&gt; {createdEmployee.employeeId}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Name:&lt;/strong&gt; {createdEmployee.name}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Email:&lt;/strong&gt; {createdEmployee.email}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Position:&lt;/strong&gt; {createdEmployee.position}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Company ID:&lt;/strong&gt; {createdEmployee.companyId}
							&lt;/div&gt;
							&lt;div className=&quot;profile-field&quot;&gt;
								&lt;strong&gt;Hired:&lt;/strong&gt;{&quot; &quot;}
								{new Date(createdEmployee.hiredAt).toLocaleString()}
							&lt;/div&gt;
						&lt;/div&gt;
					)}
				&lt;/div&gt;
			&lt;/div&gt;
		&lt;/div&gt;
	);
}
">
<input type="hidden" name="project[files][frontend/main.tsx]" value="import { StrictMode } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
import { App } from &quot;./App.tsx&quot;;

const root = document.getElementById(&quot;root&quot;);
if (!root) throw new Error(&quot;Root element not found&quot;);

createRoot(root).render(
	&lt;StrictMode&gt;
		&lt;App /&gt;
	&lt;/StrictMode&gt;
);
">
<input type="hidden" name="project[files][tests/actions.test.ts]" value="import { setupTest } from &quot;rivetkit/test&quot;;
import { describe, expect, test } from &quot;vitest&quot;;
import { registry } from &quot;../src/actors.ts&quot;;

describe(&quot;company and employee actors&quot;, () =&gt; {
	test(&quot;create company actor with input and get profile&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company with EIN as key
		const company = await client.company.create([&quot;12-3456789&quot;], {
			input: {
				name: &quot;Acme Corp&quot;,
				industry: &quot;Technology&quot;,
			},
		});

		// Get profile
		const profile = await company.getProfile();

		expect(profile).toMatchObject({
			id: expect.any(String),
			name: &quot;Acme Corp&quot;,
			industry: &quot;Technology&quot;,
			foundedAt: expect.any(Number),
		});
	});

	test(&quot;update company profile&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company
		const company = await client.company.create([&quot;23-4567890&quot;], {
			input: {
				name: &quot;Tech Startup&quot;,
				industry: &quot;Software&quot;,
			},
		});

		// Update profile
		const updatedProfile = await company.updateProfile({
			name: &quot;Tech Unicorn&quot;,
			industry: &quot;SaaS&quot;,
		});

		expect(updatedProfile.name).toBe(&quot;Tech Unicorn&quot;);
		expect(updatedProfile.industry).toBe(&quot;SaaS&quot;);

		// Verify changes persist
		const profile = await company.getProfile();
		expect(profile.name).toBe(&quot;Tech Unicorn&quot;);
		expect(profile.industry).toBe(&quot;SaaS&quot;);
	});

	test(&quot;company creates employee actor&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company
		const company = await client.company.create([&quot;34-5678901&quot;], {
			input: {
				name: &quot;Growing Corp&quot;,
				industry: &quot;Technology&quot;,
			},
		});

		// Company creates employee
		const employeeProfile = await company.createEmployee({
			name: &quot;Jane Smith&quot;,
			email: &quot;jane@growingcorp.com&quot;,
			position: &quot;Software Engineer&quot;,
		});

		expect(employeeProfile).toMatchObject({
			employeeId: expect.any(String),
			name: &quot;Jane Smith&quot;,
			email: &quot;jane@growingcorp.com&quot;,
			position: &quot;Software Engineer&quot;,
			companyId: expect.any(String),
			hiredAt: expect.any(Number),
		});

		// Verify employee is in company&#39;s list
		const employees = await company.getEmployees();
		expect(employees).toContain(&quot;jane@growingcorp.com&quot;);
	});

	test(&quot;get employee profile directly&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company and employee
		const company = await client.company.create([&quot;45-6789012&quot;], {
			input: {
				name: &quot;Test Corp&quot;,
				industry: &quot;Testing&quot;,
			},
		});

		await company.createEmployee({
			name: &quot;John Doe&quot;,
			email: &quot;john@testcorp.com&quot;,
			position: &quot;QA Engineer&quot;,
		});

		// Get employee directly using email key
		const employee = client.employee.get([&quot;john@testcorp.com&quot;]);
		const profile = await employee.getProfile();

		expect(profile).toMatchObject({
			name: &quot;John Doe&quot;,
			email: &quot;john@testcorp.com&quot;,
			position: &quot;QA Engineer&quot;,
		});
	});

	test(&quot;update employee profile&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company and employee
		const company = await client.company.create([&quot;56-7890123&quot;], {
			input: {
				name: &quot;Update Corp&quot;,
				industry: &quot;Technology&quot;,
			},
		});

		await company.createEmployee({
			name: &quot;Alice Johnson&quot;,
			email: &quot;alice@updatecorp.com&quot;,
			position: &quot;Junior Developer&quot;,
		});

		// Update employee profile
		const employee = client.employee.get([&quot;alice@updatecorp.com&quot;]);
		const updatedProfile = await employee.updateProfile({
			name: &quot;Alice Johnson-Smith&quot;,
			position: &quot;Senior Developer&quot;,
		});

		expect(updatedProfile.name).toBe(&quot;Alice Johnson-Smith&quot;);
		expect(updatedProfile.position).toBe(&quot;Senior Developer&quot;);
	});

	test(&quot;company tracks multiple employees&quot;, async (ctx) =&gt; {
		const { client } = await setupTest(ctx, registry);

		// Create company
		const company = await client.company.create([&quot;67-8901234&quot;], {
			input: {
				name: &quot;Multi Corp&quot;,
				industry: &quot;Technology&quot;,
			},
		});

		// Create multiple employees
		await company.createEmployee({
			name: &quot;Employee One&quot;,
			email: &quot;one@multicorp.com&quot;,
			position: &quot;Developer&quot;,
		});

		await company.createEmployee({
			name: &quot;Employee Two&quot;,
			email: &quot;two@multicorp.com&quot;,
			position: &quot;Designer&quot;,
		});

		await company.createEmployee({
			name: &quot;Employee Three&quot;,
			email: &quot;three@multicorp.com&quot;,
			position: &quot;Manager&quot;,
		});

		// Verify all employees are tracked
		const employees = await company.getEmployees();
		expect(employees).toHaveLength(3);
		expect(employees).toContain(&quot;one@multicorp.com&quot;);
		expect(employees).toContain(&quot;two@multicorp.com&quot;);
		expect(employees).toContain(&quot;three@multicorp.com&quot;);
	});
});
">
<input type="hidden" name="project[files][src/actors.ts]" value="import { actor, setup } from &quot;rivetkit&quot;;

export interface EmployeeInput {
	employeeId: string;
	name: string;
	email: string;
	position: string;
	companyId: string;
}

export interface EmployeeState {
	profile: EmployeeProfile;
}

export interface EmployeeProfile {
	employeeId: string;
	name: string;
	email: string;
	position: string;
	companyId: string;
	hiredAt: number;
}

export interface CompanyInput {
	name: string;
	industry: string;
}

export interface CompanyState {
	profile: CompanyProfile;
	employeeEmails: string[];
}

export interface CompanyProfile {
	id: string;
	name: string;
	industry: string;
	foundedAt: number;
}

export const employee = actor({
	// Initialize state from input
	createState: (_c, input: EmployeeInput): EmployeeState =&gt; ({
		profile: {
			employeeId: input.employeeId,
			name: input.name,
			email: input.email,
			position: input.position,
			companyId: input.companyId,
			hiredAt: Date.now(),
		},
	}),

	actions: {
		// Get employee profile
		getProfile: (c): EmployeeProfile =&gt; c.state.profile,

		// Update employee profile
		updateProfile: (
			c,
			updates: Partial&lt;Omit&lt;EmployeeInput, &quot;companyId&quot; | &quot;employeeId&quot;&gt;&gt;,
		) =&gt; {
			if (updates.name) c.state.profile.name = updates.name;
			if (updates.email) c.state.profile.email = updates.email;
			if (updates.position) c.state.profile.position = updates.position;
			return c.state.profile;
		},
	},
});

export const company = actor({
	// Initialize state from input: https://rivet.dev/docs/actors/input
	createState: (_c, input: CompanyInput): CompanyState =&gt; ({
		profile: {
			id: crypto.randomUUID(),
			name: input.name,
			industry: input.industry,
			foundedAt: Date.now(),
		},
		employeeEmails: [],
	}),

	actions: {
		// Fully type-safe profile retrieval: https://rivet.dev/docs/actors/actions
		getProfile: (c): CompanyProfile =&gt; c.state.profile,

		// Update company profile
		updateProfile: (c, updates: Partial&lt;CompanyInput&gt;) =&gt; {
			if (updates.name) c.state.profile.name = updates.name;
			if (updates.industry) c.state.profile.industry = updates.industry;
			return c.state.profile;
		},

		// Create an employee actor and track it
		createEmployee: async (
			c,
			employeeData: { name: string; email: string; position: string },
		): Promise&lt;EmployeeProfile&gt; =&gt; {
			const client = c.client&lt;typeof registry&gt;();

			// Generate a unique employee ID
			const employeeId = crypto.randomUUID();

			// Create employee actor using their email as the key
			await client.employee.create([employeeData.email], {
				input: {
					employeeId,
					name: employeeData.name,
					email: employeeData.email,
					position: employeeData.position,
					companyId: c.state.profile.id,
				},
			});
			c.state.employeeEmails.push(employeeData.email);

			// Return the employee profile
			return {
				employeeId,
				name: employeeData.name,
				email: employeeData.email,
				position: employeeData.position,
				companyId: c.state.profile.id,
				hiredAt: Date.now(),
			};
		},

		// Get all employee emails
		getEmployees: (c) =&gt; {
			return c.state.employeeEmails;
		},
	},
});

// Register actors for use: https://rivet.dev/docs/setup
export const registry = setup({
	use: { employee, company },
});
">
<input type="hidden" name="project[files][src/server.ts]" value="import { Hono } from &quot;hono&quot;;
import { registry } from &quot;./actors.ts&quot;;

const app = new Hono();
app.all(&quot;/api/rivet/*&quot;, (c) =&gt; registry.handler(c.req.raw));
export default app;
">
<input type="hidden" name="project[description]" value="generated by https://pkg.pr.new">
<input type="hidden" name="project[template]" value="node">
<input type="hidden" name="project[title]" value="actor-actions">
</form>
<script>document.getElementById("mainForm").submit();</script>

</body></html>