@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.
- package/LICENSE.md +7 -0
- package/README.md +2042 -0
- package/dist/browser.d.ts +384 -0
- package/dist/browser.js +2 -0
- package/dist/browser.js.map +1 -0
- package/dist/index.d.ts +644 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +416 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +1 -0
- package/dist/src/alarms.d.ts +47 -0
- package/dist/src/alarms.d.ts.map +1 -0
- package/dist/src/browser.d.ts +2 -0
- package/dist/src/browser.d.ts.map +1 -0
- package/dist/src/constants.d.ts +12 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/createAccessToken.d.ts +9 -0
- package/dist/src/createAccessToken.d.ts.map +1 -0
- package/dist/src/createActorFetch.d.ts +18 -0
- package/dist/src/createActorFetch.d.ts.map +1 -0
- package/dist/src/createActorKitClient.d.ts +13 -0
- package/dist/src/createActorKitClient.d.ts.map +1 -0
- package/dist/src/createActorKitContext.d.ts +29 -0
- package/dist/src/createActorKitContext.d.ts.map +1 -0
- package/dist/src/createActorKitMockClient.d.ts +11 -0
- package/dist/src/createActorKitMockClient.d.ts.map +1 -0
- package/dist/src/createActorKitRouter.d.ts +4 -0
- package/dist/src/createActorKitRouter.d.ts.map +1 -0
- package/dist/src/createMachineServer.d.ts +20 -0
- package/dist/src/createMachineServer.d.ts.map +1 -0
- package/dist/src/durable-object-system.d.ts +36 -0
- package/dist/src/durable-object-system.d.ts.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/react.d.ts +2 -0
- package/dist/src/react.d.ts.map +1 -0
- package/dist/src/schemas.d.ts +312 -0
- package/dist/src/schemas.d.ts.map +1 -0
- package/dist/src/server.d.ts +3 -0
- package/dist/src/server.d.ts.map +1 -0
- package/dist/src/storage.d.ts +64 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storybook.d.ts +13 -0
- package/dist/src/storybook.d.ts.map +1 -0
- package/dist/src/test.d.ts +2 -0
- package/dist/src/test.d.ts.map +1 -0
- package/dist/src/types.d.ts +181 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/src/withActorKit.d.ts +9 -0
- package/dist/src/withActorKit.d.ts.map +1 -0
- package/dist/src/worker.d.ts +3 -0
- package/dist/src/worker.d.ts.map +1 -0
- package/package.json +87 -0
- package/src/alarms.ts +237 -0
- package/src/browser.ts +1 -0
- package/src/constants.ts +31 -0
- package/src/createAccessToken.ts +29 -0
- package/src/createActorFetch.ts +111 -0
- package/src/createActorKitClient.ts +224 -0
- package/src/createActorKitContext.tsx +228 -0
- package/src/createActorKitMockClient.ts +138 -0
- package/src/createActorKitRouter.ts +149 -0
- package/src/createMachineServer.ts +844 -0
- package/src/durable-object-system.ts +212 -0
- package/src/global.d.ts +7 -0
- package/src/index.ts +6 -0
- package/src/react.ts +1 -0
- package/src/schemas.ts +95 -0
- package/src/server.ts +3 -0
- package/src/storage.ts +404 -0
- package/src/storybook.ts +42 -0
- package/src/test.ts +1 -0
- package/src/types.ts +334 -0
- package/src/utils.ts +171 -0
- package/src/withActorKit.tsx +103 -0
- 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