@methodacting/actor-kit 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +2042 -0
  3. package/dist/browser.d.ts +384 -0
  4. package/dist/browser.js +2 -0
  5. package/dist/browser.js.map +1 -0
  6. package/dist/index.d.ts +644 -0
  7. package/dist/index.js +2 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/react.d.ts +416 -0
  10. package/dist/react.js +2 -0
  11. package/dist/react.js.map +1 -0
  12. package/dist/src/alarms.d.ts +47 -0
  13. package/dist/src/alarms.d.ts.map +1 -0
  14. package/dist/src/browser.d.ts +2 -0
  15. package/dist/src/browser.d.ts.map +1 -0
  16. package/dist/src/constants.d.ts +12 -0
  17. package/dist/src/constants.d.ts.map +1 -0
  18. package/dist/src/createAccessToken.d.ts +9 -0
  19. package/dist/src/createAccessToken.d.ts.map +1 -0
  20. package/dist/src/createActorFetch.d.ts +18 -0
  21. package/dist/src/createActorFetch.d.ts.map +1 -0
  22. package/dist/src/createActorKitClient.d.ts +13 -0
  23. package/dist/src/createActorKitClient.d.ts.map +1 -0
  24. package/dist/src/createActorKitContext.d.ts +29 -0
  25. package/dist/src/createActorKitContext.d.ts.map +1 -0
  26. package/dist/src/createActorKitMockClient.d.ts +11 -0
  27. package/dist/src/createActorKitMockClient.d.ts.map +1 -0
  28. package/dist/src/createActorKitRouter.d.ts +4 -0
  29. package/dist/src/createActorKitRouter.d.ts.map +1 -0
  30. package/dist/src/createMachineServer.d.ts +20 -0
  31. package/dist/src/createMachineServer.d.ts.map +1 -0
  32. package/dist/src/durable-object-system.d.ts +36 -0
  33. package/dist/src/durable-object-system.d.ts.map +1 -0
  34. package/dist/src/index.d.ts +7 -0
  35. package/dist/src/index.d.ts.map +1 -0
  36. package/dist/src/react.d.ts +2 -0
  37. package/dist/src/react.d.ts.map +1 -0
  38. package/dist/src/schemas.d.ts +312 -0
  39. package/dist/src/schemas.d.ts.map +1 -0
  40. package/dist/src/server.d.ts +3 -0
  41. package/dist/src/server.d.ts.map +1 -0
  42. package/dist/src/storage.d.ts +64 -0
  43. package/dist/src/storage.d.ts.map +1 -0
  44. package/dist/src/storybook.d.ts +13 -0
  45. package/dist/src/storybook.d.ts.map +1 -0
  46. package/dist/src/test.d.ts +2 -0
  47. package/dist/src/test.d.ts.map +1 -0
  48. package/dist/src/types.d.ts +181 -0
  49. package/dist/src/types.d.ts.map +1 -0
  50. package/dist/src/utils.d.ts +30 -0
  51. package/dist/src/utils.d.ts.map +1 -0
  52. package/dist/src/withActorKit.d.ts +9 -0
  53. package/dist/src/withActorKit.d.ts.map +1 -0
  54. package/dist/src/worker.d.ts +3 -0
  55. package/dist/src/worker.d.ts.map +1 -0
  56. package/package.json +87 -0
  57. package/src/alarms.ts +237 -0
  58. package/src/browser.ts +1 -0
  59. package/src/constants.ts +31 -0
  60. package/src/createAccessToken.ts +29 -0
  61. package/src/createActorFetch.ts +111 -0
  62. package/src/createActorKitClient.ts +224 -0
  63. package/src/createActorKitContext.tsx +228 -0
  64. package/src/createActorKitMockClient.ts +138 -0
  65. package/src/createActorKitRouter.ts +149 -0
  66. package/src/createMachineServer.ts +844 -0
  67. package/src/durable-object-system.ts +212 -0
  68. package/src/global.d.ts +7 -0
  69. package/src/index.ts +6 -0
  70. package/src/react.ts +1 -0
  71. package/src/schemas.ts +95 -0
  72. package/src/server.ts +3 -0
  73. package/src/storage.ts +404 -0
  74. package/src/storybook.ts +42 -0
  75. package/src/test.ts +1 -0
  76. package/src/types.ts +334 -0
  77. package/src/utils.ts +171 -0
  78. package/src/withActorKit.tsx +103 -0
  79. package/src/worker.ts +2 -0
package/src/types.ts ADDED
@@ -0,0 +1,334 @@
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import { Operation } from "fast-json-patch";
3
+ import type {
4
+ AnyEventObject,
5
+ AnyStateMachine,
6
+ SnapshotFrom,
7
+ StateMachine,
8
+ StateValueFrom,
9
+ } from "xstate";
10
+ import type { z } from "zod";
11
+ import type {
12
+ AnyEventSchema,
13
+ CallerSchema,
14
+ RequestInfoSchema,
15
+ SystemEventSchema,
16
+ } from "./schemas";
17
+ import type { AlarmManager } from "./alarms";
18
+ import type { ActorKitStorage } from "./storage";
19
+
20
+ export type EnvWithDurableObjects = {
21
+ ACTOR_KIT_SECRET: string;
22
+ [key: string]: DurableObjectNamespace<ActorServer<any>> | unknown;
23
+ };
24
+
25
+ export type AnyEvent = z.infer<typeof AnyEventSchema>;
26
+
27
+ export interface ActorServerMethods<TMachine extends BaseActorKitStateMachine> {
28
+ fetch(request: Request): Promise<Response>;
29
+ spawn(props: {
30
+ actorType: string;
31
+ actorId: string;
32
+ caller: Caller;
33
+ input: Record<string, unknown>;
34
+ }): void;
35
+ send(event: ClientEventFrom<TMachine> | ServiceEventFrom<TMachine>): void;
36
+ getSnapshot(
37
+ caller: Caller,
38
+ options?: {
39
+ waitForEvent?: ClientEventFrom<TMachine>;
40
+ waitForState?: StateValueFrom<TMachine>;
41
+ timeout?: number;
42
+ errorOnWaitTimeout?: boolean;
43
+ }
44
+ ): Promise<{
45
+ checksum: string;
46
+ snapshot: CallerSnapshotFrom<TMachine>;
47
+ }>;
48
+ }
49
+
50
+ export type ActorServer<TMachine extends AnyActorKitStateMachine> =
51
+ DurableObject & ActorServerMethods<TMachine>;
52
+ export type AnyActorServer = ActorServer<any>;
53
+
54
+ export type Caller = z.infer<typeof CallerSchema>;
55
+ export type RequestInfo = z.infer<typeof RequestInfoSchema>;
56
+
57
+ export type ActorKitInputProps = {
58
+ id: string;
59
+ caller: Caller;
60
+ storage: DurableObjectStorage;
61
+ [key: string]: unknown;
62
+ };
63
+
64
+ export type CallerType = "client" | "system" | "service";
65
+
66
+ type EventObject = {
67
+ type: string;
68
+ };
69
+
70
+ type EventSchemaUnion = z.ZodDiscriminatedUnion<
71
+ "type",
72
+ [
73
+ z.ZodObject<z.ZodRawShape & { type: z.ZodString }>,
74
+ ...z.ZodObject<z.ZodRawShape & { type: z.ZodString }>[]
75
+ ]
76
+ >;
77
+
78
+ export type EventSchemas = {
79
+ client: EventSchemaUnion;
80
+ service: EventSchemaUnion;
81
+ };
82
+
83
+ export type BaseActorKitContext<
84
+ TPublicProps extends { [key: string]: unknown },
85
+ TPrivateProps extends { [key: string]: unknown }
86
+ > = {
87
+ public: TPublicProps;
88
+ private: Record<string, TPrivateProps>;
89
+ };
90
+
91
+ export type ActorKitStateMachine<
92
+ TEvent extends BaseActorKitEvent<EnvWithDurableObjects>,
93
+ TInput extends {
94
+ id: string;
95
+ caller: Caller;
96
+ storage: DurableObjectStorage;
97
+ },
98
+ TContext extends BaseActorKitContext<any, any> & {
99
+ [key: string]: unknown;
100
+ }
101
+ > = StateMachine<
102
+ TContext,
103
+ TEvent & EventObject,
104
+ any,
105
+ any,
106
+ any,
107
+ any,
108
+ any,
109
+ any,
110
+ any,
111
+ TInput,
112
+ any,
113
+ any,
114
+ any,
115
+ any
116
+ >;
117
+
118
+ export type BaseActorKitInput<TEnv = EnvWithDurableObjects> = {
119
+ id: string;
120
+ caller: Caller;
121
+ env: TEnv;
122
+ storage: DurableObjectStorage;
123
+ };
124
+
125
+ export type WithActorKitInput<
126
+ TInputProps extends { [key: string]: unknown },
127
+ TEnv extends EnvWithDurableObjects
128
+ > = TInputProps & BaseActorKitInput<TEnv>;
129
+
130
+ export type AnyActorKitStateMachine = ActorKitStateMachine<any, any, any>;
131
+
132
+ type AnyActorKitEvent = (
133
+ | WithActorKitEvent<AnyEventObject, "client">
134
+ | WithActorKitEvent<AnyEventObject, "service">
135
+ | ActorKitSystemEvent
136
+ ) &
137
+ BaseActorKitEvent<EnvWithDurableObjects>;
138
+
139
+ type AnyActorKitInput = WithActorKitInput<
140
+ { [key: string]: unknown },
141
+ EnvWithDurableObjects
142
+ > & {
143
+ storage: DurableObjectStorage;
144
+ };
145
+
146
+ type AnyActorKitContext = {
147
+ public: { [key: string]: unknown };
148
+ private: Record<string, { [key: string]: unknown }>;
149
+ };
150
+
151
+ export type BaseActorKitStateMachine = ActorKitStateMachine<
152
+ AnyActorKitEvent,
153
+ AnyActorKitInput,
154
+ AnyActorKitContext
155
+ >;
156
+
157
+ export type ExtraContext = {
158
+ requestId: string;
159
+ };
160
+
161
+ export interface BaseActorKitEvent<TEnv extends EnvWithDurableObjects> {
162
+ caller: Caller;
163
+ storage: DurableObjectStorage;
164
+ requestInfo?: RequestInfo;
165
+ env: TEnv;
166
+ }
167
+
168
+ export type ActorKitSystemEvent = z.infer<typeof SystemEventSchema>;
169
+
170
+ // Utility type to merge custom event types with the base event
171
+ export type WithActorKitEvent<
172
+ T extends { type: string },
173
+ C extends CallerType
174
+ > = T & BaseActorKitEvent<EnvWithDurableObjects> & { caller: { type: C } };
175
+
176
+ export type WithActorKitContext<
177
+ TExtraProps extends { [key: string]: unknown },
178
+ TPrivateProps extends { [key: string]: unknown },
179
+ TPublicProps extends { [key: string]: unknown }
180
+ > = TExtraProps & {
181
+ public: TPublicProps;
182
+ private: Record<string, TPrivateProps>;
183
+ };
184
+
185
+ export type CallerSnapshotFrom<TMachine extends AnyStateMachine> = {
186
+ public: SnapshotFrom<TMachine> extends { context: { public: infer P } }
187
+ ? P
188
+ : unknown;
189
+ private: SnapshotFrom<TMachine> extends {
190
+ context: { private: Partial<Record<string, infer PR>> };
191
+ }
192
+ ? PR
193
+ : unknown;
194
+ value: SnapshotFrom<TMachine> extends { value: infer V } ? V : unknown;
195
+ };
196
+
197
+ export type ClientEventFrom<T extends AnyActorKitStateMachine> =
198
+ T extends StateMachine<
199
+ any,
200
+ infer TEvent,
201
+ any,
202
+ any,
203
+ any,
204
+ any,
205
+ any,
206
+ any,
207
+ any,
208
+ any,
209
+ any,
210
+ any,
211
+ any,
212
+ any
213
+ >
214
+ ? TEvent extends WithActorKitEvent<infer E, "client">
215
+ ? Omit<E, keyof BaseActorKitEvent<EnvWithDurableObjects>>
216
+ : never
217
+ : never;
218
+
219
+ export type ServiceEventFrom<T extends AnyActorKitStateMachine> =
220
+ T extends StateMachine<
221
+ any,
222
+ infer TEvent,
223
+ any,
224
+ any,
225
+ any,
226
+ any,
227
+ any,
228
+ any,
229
+ any,
230
+ any,
231
+ any,
232
+ any,
233
+ any,
234
+ any
235
+ >
236
+ ? TEvent extends WithActorKitEvent<infer E, "service">
237
+ ? Omit<E, keyof BaseActorKitEvent<EnvWithDurableObjects>>
238
+ : never
239
+ : never;
240
+
241
+ // Helper type to convert from SCREAMING_SNAKE_CASE to kebab-case
242
+ export type ScreamingSnakeToKebab<S extends string> =
243
+ S extends `${infer T}_${infer U}`
244
+ ? `${Lowercase<T>}-${ScreamingSnakeToKebab<U>}`
245
+ : Lowercase<S>;
246
+
247
+ export type DurableObjectActor<TMachine extends AnyActorKitStateMachine> =
248
+ ActorServer<TMachine>;
249
+
250
+ type CamelToSnakeCase<S extends string> = S extends `${infer T}${infer U}`
251
+ ? U extends Uncapitalize<U>
252
+ ? `${Lowercase<T>}${CamelToSnakeCase<U>}`
253
+ : `${Lowercase<T>}_${CamelToSnakeCase<U>}`
254
+ : S;
255
+
256
+ type KebabToCamelCase<S extends string> = S extends `${infer T}-${infer U}`
257
+ ? `${T}${Capitalize<KebabToCamelCase<U>>}`
258
+ : S;
259
+
260
+ export type KebabToScreamingSnake<S extends string> = Uppercase<
261
+ CamelToSnakeCase<KebabToCamelCase<S>>
262
+ >;
263
+ export interface MatchesProps<TMachine extends AnyActorKitStateMachine> {
264
+ state: StateValueFrom<TMachine>;
265
+ and?: StateValueFrom<TMachine>;
266
+ or?: StateValueFrom<TMachine>;
267
+ not?: boolean;
268
+ initialValueOverride?: boolean;
269
+ }
270
+
271
+ export type MachineFromServer<T> = T extends ActorServer<infer M> ? M : never;
272
+
273
+ export type ActorKitEmittedEvent = {
274
+ operations: Operation[];
275
+ checksum: string;
276
+ };
277
+ // | {
278
+ // snapshot: CallerSnapshotFrom<TMachine>;
279
+ // checksum: string;
280
+ // };
281
+
282
+ export type ActorKitClient<TMachine extends AnyActorKitStateMachine> = {
283
+ connect: () => Promise<void>;
284
+ disconnect: () => void;
285
+ send: (event: ClientEventFrom<TMachine>) => void;
286
+ getState: () => CallerSnapshotFrom<TMachine>;
287
+ subscribe: (
288
+ listener: (state: CallerSnapshotFrom<TMachine>) => void
289
+ ) => () => void;
290
+ waitFor: (
291
+ predicateFn: (state: CallerSnapshotFrom<TMachine>) => boolean,
292
+ timeoutMs?: number
293
+ ) => Promise<void>;
294
+ };
295
+
296
+ // First define a helper to extract the event type from a machine
297
+ type ExtractEventType<TMachine> = TMachine extends ActorKitStateMachine<
298
+ infer TEvent,
299
+ any,
300
+ any
301
+ >
302
+ ? TEvent
303
+ : never;
304
+
305
+ // Then extract the env type from the event
306
+ type ExtractEnvType<TEvent> = TEvent extends { env: infer TEnv } ? TEnv : never;
307
+
308
+ // Finally, our InferEnvFromMachine type that ensures EnvWithDurableObjects
309
+ export type EnvFromMachine<TMachine extends AnyActorKitStateMachine> =
310
+ ExtractEnvType<ExtractEventType<TMachine>> extends never
311
+ ? EnvWithDurableObjects
312
+ : ExtractEnvType<ExtractEventType<TMachine>> & EnvWithDurableObjects;
313
+
314
+ // ==================== Alarm & Storage Types ====================
315
+
316
+ /**
317
+ * Options for creating a MachineServer with alarm support
318
+ */
319
+ export interface MachineServerOptions {
320
+ persisted?: boolean;
321
+ /**
322
+ * Enable alarm-based scheduling for XState delayed events
323
+ * @default true
324
+ */
325
+ enableAlarms?: boolean;
326
+ }
327
+
328
+ /**
329
+ * Internal services available to the machine server
330
+ */
331
+ export interface MachineServerServices {
332
+ storage: ActorKitStorage;
333
+ alarmManager: AlarmManager | null;
334
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,171 @@
1
+ import { jwtVerify, SignJWT } from "jose";
2
+ import { PERSISTED_SNAPSHOT_KEY } from "./constants";
3
+ import { CallerStringSchema } from "./schemas";
4
+ import { Caller } from "./types";
5
+
6
+ // Define log levels
7
+ export enum LogLevel {
8
+ ERROR = 0,
9
+ WARN = 1,
10
+ INFO = 2,
11
+ DEBUG = 3,
12
+ }
13
+
14
+ // Declare a global variable to control debug level
15
+ declare global {
16
+ var DEBUG_LEVEL: LogLevel | undefined;
17
+ }
18
+
19
+ // Set the current log level based on the global DEBUG_LEVEL variable
20
+ // Default to INFO if not set
21
+ const getCurrentLogLevel = (): LogLevel => {
22
+ return globalThis.DEBUG_LEVEL !== undefined
23
+ ? globalThis.DEBUG_LEVEL
24
+ : LogLevel.INFO;
25
+ };
26
+
27
+ /**
28
+ * Debug logging function
29
+ * @param message The message to log
30
+ * @param level The log level (default: DEBUG)
31
+ * @param data Additional data to log (optional)
32
+ */
33
+ export function debug(
34
+ message: string,
35
+ level: LogLevel = LogLevel.DEBUG,
36
+ data?: any
37
+ ) {
38
+ const currentLogLevel = getCurrentLogLevel();
39
+ if (level <= currentLogLevel) {
40
+ const timestamp = new Date().toISOString();
41
+ const logMessage = `[${timestamp}] ${LogLevel[level]}: ${message}`;
42
+
43
+ if (data) {
44
+ console.log(logMessage, data);
45
+ } else {
46
+ console.log(logMessage);
47
+ }
48
+
49
+ // You can extend this to send logs to a service if needed
50
+ // For example:
51
+ // if (typeof fetch !== 'undefined') {
52
+ // fetch('https://your-logging-service.com', {
53
+ // method: 'POST',
54
+ // body: JSON.stringify({ message: logMessage, level, data }),
55
+ // });
56
+ // }
57
+ }
58
+ }
59
+
60
+ // Convenience methods for different log levels
61
+ export const logError = (message: string, data?: any) =>
62
+ debug(message, LogLevel.ERROR, data);
63
+ export const logWarn = (message: string, data?: any) =>
64
+ debug(message, LogLevel.WARN, data);
65
+ export const logInfo = (message: string, data?: any) =>
66
+ debug(message, LogLevel.INFO, data);
67
+
68
+ export const json = <T>(data: T, status = 200) =>
69
+ Response.json(data, { status });
70
+
71
+ export const ok = () => json({ ok: true });
72
+
73
+ export const error = (err: string | { message: string }, status = 500) => {
74
+ console.error("Error response", err);
75
+ return json(
76
+ {
77
+ ok: false,
78
+ error: typeof err === "string" ? err : err.message ?? "Unknown error",
79
+ },
80
+ status
81
+ );
82
+ };
83
+
84
+ export const notFound = () => error("Not found", 404);
85
+
86
+ /**
87
+ * Parses query parameters from a URL string.
88
+ * @param url The URL to parse.
89
+ * @returns A URLSearchParams object containing the parsed query parameters.
90
+ */
91
+ export const parseQueryParams = (url: string) => {
92
+ const index = url.indexOf("?");
93
+ const search = index !== -1 ? url.substring(index + 1) : "";
94
+ return new URLSearchParams(search);
95
+ };
96
+
97
+ export function assert<T>(
98
+ expression: T,
99
+ errorMessage: string
100
+ ): asserts expression {
101
+ if (!expression) {
102
+ const error = new Error(errorMessage);
103
+ const stack = error.stack?.split("\n");
104
+
105
+ // Find the line in the stack trace that corresponds to where the assert was called.
106
+ // This is typically the third line in the stack, but this may vary depending on the JS environment.
107
+ const assertLine =
108
+ stack && stack.length >= 3 ? stack[2] : "unknown location";
109
+
110
+ throw new Error(`${errorMessage} (Assert failed at ${assertLine?.trim()})`);
111
+ }
112
+ }
113
+
114
+ export async function getCallerFromRequest(
115
+ request: Request,
116
+ actorType: string,
117
+ actorId: string,
118
+ secret: string
119
+ ): Promise<Caller> {
120
+ let accessToken: string;
121
+ if (request.headers.get("Upgrade") !== "websocket") {
122
+ const authHeader = request.headers.get("Authorization");
123
+ const stringPart = authHeader?.split(" ")[1];
124
+ assert(stringPart, "Expected authorization header to be set");
125
+ accessToken = stringPart;
126
+ } else {
127
+ const searchParams = new URLSearchParams(request.url.split("?")[1]);
128
+ const paramString = searchParams.get("accessToken");
129
+ assert(paramString, "expected accessToken when connecting to socket");
130
+ accessToken = paramString;
131
+ }
132
+
133
+ return parseAccessTokenForCaller({
134
+ accessToken,
135
+ type: actorType,
136
+ id: actorId,
137
+ secret,
138
+ });
139
+ }
140
+
141
+ export async function parseAccessTokenForCaller({
142
+ accessToken,
143
+ type,
144
+ id,
145
+ secret,
146
+ }: {
147
+ accessToken: string;
148
+ type: string;
149
+ id: string;
150
+ secret: string;
151
+ }): Promise<Caller> {
152
+ const verified = await jwtVerify(
153
+ accessToken,
154
+ new TextEncoder().encode(secret)
155
+ );
156
+ if (!verified.payload.jti) {
157
+ throw new Error("Expected JTI on accessToken");
158
+ }
159
+ if (verified.payload.jti !== id) {
160
+ throw new Error(`Expected JTI on accessToken to match actor id: ${id}`);
161
+ }
162
+ if (!verified.payload.aud) {
163
+ throw new Error(
164
+ `Expected accessToken audience to match actor type: ${type}`
165
+ );
166
+ }
167
+ if (!verified.payload.sub) {
168
+ throw new Error("Expected accessToken to have subject");
169
+ }
170
+ return CallerStringSchema.parse(verified.payload.sub);
171
+ }
@@ -0,0 +1,103 @@
1
+ import type { StoryContext, StoryFn } from "@storybook/react";
2
+ import React from "react";
3
+ import { createActorKitContext } from "./createActorKitContext";
4
+ import { createActorKitMockClient } from "./createActorKitMockClient";
5
+ import type { AnyActorKitStateMachine, CallerSnapshotFrom } from "./types";
6
+ import { ActorKitParameters } from "./storybook";
7
+
8
+ /**
9
+ * Storybook decorator that sets up actor-kit state machines.
10
+ *
11
+ * There are two main patterns for testing with actor-kit:
12
+ *
13
+ * 1. Static Stories (Use parameters.actorKit + within):
14
+ * - Use this decorator with parameters.actorKit
15
+ * - Use `within(canvasElement)` in play functions
16
+ * - Good for simple stories that don't need state manipulation
17
+ *
18
+ * 2. Interactive Stories (Use mount + direct client):
19
+ * - Don't use this decorator
20
+ * - Create client manually and use mount in play function
21
+ * - Good for stories that need to manipulate state
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * // Pattern 1: Static Story
26
+ * export const Static: Story = {
27
+ * parameters: {
28
+ * actorKit: {
29
+ * session: {
30
+ * "session-123": { ... }
31
+ * }
32
+ * }
33
+ * },
34
+ * play: async ({ canvasElement }) => {
35
+ * const canvas = within(canvasElement);
36
+ * // Test UI state...
37
+ * }
38
+ * };
39
+ *
40
+ * // Pattern 2: Interactive Story
41
+ * export const Interactive: Story = {
42
+ * play: async ({ canvasElement, mount }) => {
43
+ * const client = createActorKitMockClient({...});
44
+ * const canvas = within(canvasElement);
45
+ *
46
+ * await mount(
47
+ * <Context.ProviderFromClient client={client}>
48
+ * <Component />
49
+ * </Context.ProviderFromClient>
50
+ * );
51
+ *
52
+ * // Now you can manipulate client state...
53
+ * client.produce((draft) => { ... });
54
+ * }
55
+ * };
56
+ * ```
57
+ */
58
+ export const withActorKit = <TMachine extends AnyActorKitStateMachine>({
59
+ actorType,
60
+ context,
61
+ }: {
62
+ actorType: string;
63
+ context: ReturnType<typeof createActorKitContext<TMachine>>;
64
+ }) => {
65
+ return (Story: StoryFn, storyContext: StoryContext): React.ReactElement => {
66
+ const actorKitParams = storyContext.parameters?.actorKit as
67
+ | ActorKitParameters<TMachine>["actorKit"]
68
+ | undefined;
69
+
70
+ // If no params provided, just render the story without any providers
71
+ if (!actorKitParams?.[actorType]) {
72
+ return <Story />;
73
+ }
74
+
75
+ // Create nested providers for each actor ID
76
+ const actorSnapshots = actorKitParams[actorType];
77
+
78
+ // Recursively nest providers
79
+ const createNestedProviders = (
80
+ actorIds: string[],
81
+ index: number,
82
+ children: React.ReactNode
83
+ ): React.ReactElement => {
84
+ if (index >= actorIds.length) {
85
+ return children as React.ReactElement;
86
+ }
87
+
88
+ const actorId = actorIds[index];
89
+ const snapshot = actorSnapshots[actorId];
90
+ const client = createActorKitMockClient<TMachine>({
91
+ initialSnapshot: snapshot,
92
+ });
93
+
94
+ return (
95
+ <context.ProviderFromClient client={client}>
96
+ {createNestedProviders(actorIds, index + 1, children)}
97
+ </context.ProviderFromClient>
98
+ );
99
+ };
100
+
101
+ return createNestedProviders(Object.keys(actorSnapshots), 0, <Story />);
102
+ };
103
+ };
package/src/worker.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { createActorKitRouter } from "./createActorKitRouter";
2
+ export { createMachineServer } from "./createMachineServer";