@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/storage.ts ADDED
@@ -0,0 +1,404 @@
1
+ import { PERSISTED_SNAPSHOT_KEY } from "./constants";
2
+ import type { Caller } from "./types";
3
+
4
+ /**
5
+ * SQL schema for the actor-kit SQLite storage
6
+ */
7
+ const SQL_SCHEMA = `
8
+ -- Alarms table - supports one-time and recurring alarms
9
+ CREATE TABLE IF NOT EXISTS alarms (
10
+ id TEXT PRIMARY KEY,
11
+ type TEXT NOT NULL,
12
+ scheduled_at INTEGER NOT NULL,
13
+ repeat_interval INTEGER,
14
+ payload TEXT,
15
+ created_at INTEGER NOT NULL
16
+ );
17
+
18
+ -- Index for efficient due alarm queries
19
+ CREATE INDEX IF NOT EXISTS idx_alarms_scheduled_at ON alarms(scheduled_at);
20
+
21
+ -- Actor metadata (replaces KV keys: actorType, actorId, initialCaller, input)
22
+ CREATE TABLE IF NOT EXISTS actor_meta (
23
+ actor_id TEXT PRIMARY KEY,
24
+ actor_type TEXT NOT NULL,
25
+ initial_caller TEXT NOT NULL,
26
+ input TEXT NOT NULL,
27
+ created_at INTEGER NOT NULL,
28
+ updated_at INTEGER NOT NULL
29
+ );
30
+
31
+ -- Snapshots table (replaces PERSISTED_SNAPSHOT_KEY)
32
+ CREATE TABLE IF NOT EXISTS snapshots (
33
+ actor_id TEXT PRIMARY KEY,
34
+ snapshot TEXT NOT NULL,
35
+ checksum TEXT,
36
+ updated_at INTEGER NOT NULL
37
+ );
38
+ `;
39
+
40
+ /**
41
+ * Alarm record from the database
42
+ */
43
+ export interface AlarmRecord {
44
+ id: string;
45
+ type: string;
46
+ scheduled_at: number;
47
+ repeat_interval: number | null;
48
+ payload: string | null;
49
+ created_at: number;
50
+ }
51
+
52
+ /**
53
+ * Scheduled alarm options
54
+ */
55
+ export interface AlarmScheduleOptions {
56
+ id: string;
57
+ type: string;
58
+ scheduledAt: number;
59
+ repeatInterval?: number;
60
+ payload: Record<string, unknown>;
61
+ }
62
+
63
+ /**
64
+ * Actor metadata record
65
+ */
66
+ export interface ActorMetaRecord {
67
+ actor_id: string;
68
+ actor_type: string;
69
+ initial_caller: string; // JSON stringified Caller
70
+ input: string; // JSON stringified
71
+ created_at: number;
72
+ updated_at: number;
73
+ }
74
+
75
+ /**
76
+ * Actor metadata as an object
77
+ */
78
+ export interface ActorMeta {
79
+ actorId: string;
80
+ actorType: string;
81
+ initialCaller: Caller;
82
+ input: Record<string, unknown>;
83
+ }
84
+
85
+ /**
86
+ * Snapshot record
87
+ */
88
+ export interface SnapshotRecord {
89
+ actor_id: string;
90
+ snapshot: string; // JSON stringified
91
+ checksum: string | null;
92
+ updated_at: number;
93
+ }
94
+
95
+ /**
96
+ * Snapshot as an object
97
+ */
98
+ export interface Snapshot {
99
+ actorId: string;
100
+ snapshot: unknown;
101
+ checksum?: string;
102
+ }
103
+
104
+ /**
105
+ * SQLite storage wrapper for actor-kit Durable Objects
106
+ * Provides methods for managing alarms, actor metadata, and snapshots
107
+ */
108
+ export class ActorKitStorage {
109
+ private initialized = false;
110
+ private sql: DurableObjectStorage["sql"];
111
+
112
+ constructor(private storage: DurableObjectStorage) {
113
+ this.sql = storage.sql;
114
+ }
115
+
116
+ /**
117
+ * Initialize the database schema if not already done
118
+ */
119
+ async ensureInitialized(): Promise<void> {
120
+ if (this.initialized) return;
121
+
122
+ try {
123
+ // Execute schema creation - SQLite will ignore IF NOT EXISTS if tables exist
124
+ await this.sql.exec(SQL_SCHEMA);
125
+ this.initialized = true;
126
+ } catch (error) {
127
+ console.error("Failed to initialize database schema:", error);
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ // ==================== Alarms ====================
133
+
134
+ /**
135
+ * Get all alarms, optionally filtered by actor
136
+ */
137
+ async getAlarms(): Promise<AlarmRecord[]> {
138
+ await this.ensureInitialized();
139
+ const result = await this.sql.exec(
140
+ "SELECT id, type, scheduled_at, repeat_interval, payload, created_at FROM alarms ORDER BY scheduled_at ASC"
141
+ );
142
+ return (await this.parseRows(result)) as AlarmRecord[];
143
+ }
144
+
145
+ /**
146
+ * Get alarms that are due before a given timestamp
147
+ */
148
+ async getDueAlarms(before: number): Promise<AlarmRecord[]> {
149
+ await this.ensureInitialized();
150
+ const result = await this.sql.exec(
151
+ "SELECT id, type, scheduled_at, repeat_interval, payload, created_at FROM alarms WHERE scheduled_at <= ? ORDER BY scheduled_at ASC",
152
+ [before]
153
+ );
154
+ return (await this.parseRows(result)) as AlarmRecord[];
155
+ }
156
+
157
+ /**
158
+ * Get the earliest scheduled alarm
159
+ */
160
+ async getEarliestAlarm(): Promise<AlarmRecord | null> {
161
+ await this.ensureInitialized();
162
+ const result = await this.sql.exec(
163
+ "SELECT id, type, scheduled_at, repeat_interval, payload, created_at FROM alarms ORDER BY scheduled_at ASC LIMIT 1"
164
+ );
165
+ const rows = (await this.parseRows(result)) as AlarmRecord[];
166
+ return rows[0] || null;
167
+ }
168
+
169
+ /**
170
+ * Insert a new alarm
171
+ */
172
+ async insertAlarm(options: AlarmScheduleOptions): Promise<void> {
173
+ await this.ensureInitialized();
174
+ await this.sql.exec(
175
+ "INSERT INTO alarms (id, type, scheduled_at, repeat_interval, payload, created_at) VALUES (?, ?, ?, ?, ?, ?)",
176
+ [
177
+ options.id,
178
+ options.type,
179
+ options.scheduledAt,
180
+ options.repeatInterval ?? null,
181
+ JSON.stringify(options.payload),
182
+ Date.now(),
183
+ ]
184
+ );
185
+ }
186
+
187
+ /**
188
+ * Update an alarm's scheduled time (for recurring alarms)
189
+ */
190
+ async updateAlarm(options: AlarmScheduleOptions): Promise<void> {
191
+ await this.ensureInitialized();
192
+ await this.sql.exec(
193
+ "UPDATE alarms SET scheduled_at = ?, repeat_interval = ?, payload = ? WHERE id = ?",
194
+ [options.scheduledAt, options.repeatInterval ?? null, JSON.stringify(options.payload), options.id]
195
+ );
196
+ }
197
+
198
+ /**
199
+ * Delete an alarm by ID
200
+ */
201
+ async deleteAlarm(id: string): Promise<void> {
202
+ await this.ensureInitialized();
203
+ await this.sql.exec("DELETE FROM alarms WHERE id = ?", [id]);
204
+ }
205
+
206
+ /**
207
+ * Delete all alarms of a specific type
208
+ */
209
+ async deleteAlarmsByType(type: string): Promise<void> {
210
+ await this.ensureInitialized();
211
+ await this.sql.exec("DELETE FROM alarms WHERE type = ?", [type]);
212
+ }
213
+
214
+ // ==================== Actor Metadata ====================
215
+
216
+ /**
217
+ * Get actor metadata by ID
218
+ */
219
+ async getActorMeta(actorId?: string): Promise<ActorMeta | null> {
220
+ await this.ensureInitialized();
221
+
222
+ if (!actorId) {
223
+ // Get the first (and only) actor metadata
224
+ const result = await this.sql.exec(
225
+ "SELECT actor_id, actor_type, initial_caller, input, created_at, updated_at FROM actor_meta LIMIT 1"
226
+ );
227
+ const rows = (await this.parseRows(result)) as ActorMetaRecord[];
228
+ if (rows.length === 0) return null;
229
+
230
+ const row = rows[0];
231
+ return {
232
+ actorId: row.actor_id,
233
+ actorType: row.actor_type,
234
+ initialCaller: JSON.parse(row.initial_caller) as Caller,
235
+ input: JSON.parse(row.input),
236
+ };
237
+ }
238
+
239
+ const result = await this.sql.exec(
240
+ "SELECT actor_id, actor_type, initial_caller, input, created_at, updated_at FROM actor_meta WHERE actor_id = ?",
241
+ [actorId]
242
+ );
243
+ const rows = (await this.parseRows(result)) as ActorMetaRecord[];
244
+ if (rows.length === 0) return null;
245
+
246
+ const row = rows[0];
247
+ return {
248
+ actorId: row.actor_id,
249
+ actorType: row.actor_type,
250
+ initialCaller: JSON.parse(row.initial_caller) as Caller,
251
+ input: JSON.parse(row.input),
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Set actor metadata
257
+ */
258
+ async setActorMeta(meta: ActorMeta): Promise<void> {
259
+ await this.ensureInitialized();
260
+ const now = Date.now();
261
+ await this.sql.exec(
262
+ `INSERT INTO actor_meta (actor_id, actor_type, initial_caller, input, created_at, updated_at)
263
+ VALUES (?, ?, ?, ?, ?, ?)
264
+ ON CONFLICT(actor_id) DO UPDATE SET
265
+ actor_type = excluded.actor_type,
266
+ initial_caller = excluded.initial_caller,
267
+ input = excluded.input,
268
+ updated_at = excluded.updated_at`,
269
+ [
270
+ meta.actorId,
271
+ meta.actorType,
272
+ JSON.stringify(meta.initialCaller),
273
+ JSON.stringify(meta.input),
274
+ now,
275
+ now,
276
+ ]
277
+ );
278
+ }
279
+
280
+ /**
281
+ * Delete actor metadata
282
+ */
283
+ async deleteActorMeta(actorId: string): Promise<void> {
284
+ await this.ensureInitialized();
285
+ await this.sql.exec("DELETE FROM actor_meta WHERE actor_id = ?", [actorId]);
286
+ }
287
+
288
+ // ==================== Snapshots ====================
289
+
290
+ /**
291
+ * Get a snapshot by actor ID
292
+ */
293
+ async getSnapshot(actorId: string): Promise<Snapshot | null> {
294
+ await this.ensureInitialized();
295
+ const result = await this.sql.exec(
296
+ "SELECT actor_id, snapshot, checksum, updated_at FROM snapshots WHERE actor_id = ?",
297
+ [actorId]
298
+ );
299
+ const rows = (await this.parseRows(result)) as SnapshotRecord[];
300
+ if (rows.length === 0) return null;
301
+
302
+ const row = rows[0];
303
+ return {
304
+ actorId: row.actor_id,
305
+ snapshot: JSON.parse(row.snapshot),
306
+ checksum: row.checksum ?? undefined,
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Set a snapshot for an actor
312
+ */
313
+ async setSnapshot(actorId: string, snapshot: unknown, checksum?: string): Promise<void> {
314
+ await this.ensureInitialized();
315
+ await this.sql.exec(
316
+ `INSERT INTO snapshots (actor_id, snapshot, checksum, updated_at)
317
+ VALUES (?, ?, ?, ?)
318
+ ON CONFLICT(actor_id) DO UPDATE SET
319
+ snapshot = excluded.snapshot,
320
+ checksum = excluded.checksum,
321
+ updated_at = excluded.updated_at`,
322
+ [actorId, JSON.stringify(snapshot), checksum ?? null, Date.now()]
323
+ );
324
+ }
325
+
326
+ /**
327
+ * Delete a snapshot
328
+ */
329
+ async deleteSnapshot(actorId: string): Promise<void> {
330
+ await this.ensureInitialized();
331
+ await this.sql.exec("DELETE FROM snapshots WHERE actor_id = ?", [actorId]);
332
+ }
333
+
334
+ // ==================== Migration Helpers ====================
335
+
336
+ /**
337
+ * Migrate data from legacy KV storage to SQLite
338
+ * This is a one-time migration helper
339
+ */
340
+ async migrateFromKV(storage: DurableObjectStorage): Promise<void> {
341
+ await this.ensureInitialized();
342
+
343
+ // Migrate actor metadata
344
+ const [actorType, actorId, initialCallerString, inputString] = await Promise.all([
345
+ storage.get("actorType"),
346
+ storage.get("actorId"),
347
+ storage.get("initialCaller"),
348
+ storage.get("input"),
349
+ ]);
350
+
351
+ if (actorType && actorId && initialCallerString && inputString) {
352
+ await this.setActorMeta({
353
+ actorId: actorId as string,
354
+ actorType: actorType as string,
355
+ initialCaller: JSON.parse(initialCallerString as string) as Caller,
356
+ input: JSON.parse(inputString as string),
357
+ });
358
+ }
359
+
360
+ // Migrate persisted snapshot
361
+ const persistedSnapshot = await storage.get(PERSISTED_SNAPSHOT_KEY);
362
+ if (persistedSnapshot) {
363
+ const snapshot = JSON.parse(persistedSnapshot as string);
364
+ await this.setSnapshot(actorId as string, snapshot);
365
+ }
366
+ }
367
+
368
+ // ==================== Utility Methods ====================
369
+
370
+ /**
371
+ * Parse SQL result rows - handles both arrays and cursors
372
+ */
373
+ private async parseRows(result: any): Promise<unknown[]> {
374
+ // Check if result is an async iterable (cursor)
375
+ if (result && typeof result[Symbol.asyncIterator] === "function") {
376
+ const cursor = result as AsyncIterable<{ columns: string[]; results: (string | number | null)[][] }>;
377
+ const rows: unknown[] = [];
378
+ let columns: string[] = [];
379
+
380
+ for await (const batch of cursor) {
381
+ if (!columns) columns = batch.columns;
382
+ for (const row of batch.results) {
383
+ const obj: Record<string, unknown> = {};
384
+ columns.forEach((col, i) => {
385
+ obj[col] = row[i];
386
+ });
387
+ rows.push(obj);
388
+ }
389
+ }
390
+ return rows;
391
+ }
392
+
393
+ // Handle array format
394
+ if (Array.isArray(result) && result.length === 0) return [];
395
+ const { columns, rows } = result[0];
396
+ return rows.map((row: unknown[]) => {
397
+ const obj: Record<string, unknown> = {};
398
+ columns.forEach((col: string, i: number) => {
399
+ obj[col] = row[i];
400
+ });
401
+ return obj;
402
+ });
403
+ }
404
+ }
@@ -0,0 +1,42 @@
1
+ import type { AnyActorKitStateMachine, CallerSnapshotFrom } from "./types";
2
+
3
+ export { withActorKit } from "./withActorKit";
4
+
5
+ /**
6
+ * Configuration interface for actor-kit state machines in stories.
7
+ * Allows configuring multiple actors with different initial states.
8
+ *
9
+ * @template TMachine - Type of the actor-kit state machine
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * parameters: {
14
+ * actorKit: {
15
+ * session: {
16
+ * "session-123": {
17
+ * public: { userId: "123" },
18
+ * private: {},
19
+ * value: "ready"
20
+ * }
21
+ * }
22
+ * }
23
+ * }
24
+ * ```
25
+ */
26
+ export interface ActorKitParameters<TMachine extends AnyActorKitStateMachine> {
27
+ actorKit: {
28
+ [K: string]: {
29
+ [actorId: string]: CallerSnapshotFrom<TMachine>;
30
+ };
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Helper type for stories that use actor-kit state machines.
36
+ * Combines the story type with actor-kit parameters.
37
+ *
38
+ * @template TMachine - Type of the actor-kit state machine
39
+ */
40
+ export type StoryWithActorKit<TMachine extends AnyActorKitStateMachine> = {
41
+ parameters: ActorKitParameters<TMachine>;
42
+ };
package/src/test.ts ADDED
@@ -0,0 +1 @@
1
+ export { createActorKitMockClient } from "./createActorKitMockClient";