@cloudwerk/durable-object 0.0.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Squirrelsoft Dev Tools
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,736 @@
1
+ import { ZodType } from 'zod';
2
+
3
+ /**
4
+ * @cloudwerk/durable-object - Type Definitions
5
+ *
6
+ * Core types for Durable Objects with native Cloudflare RPC support.
7
+ */
8
+
9
+ /**
10
+ * Type that can be either a value or a Promise of that value.
11
+ * Used throughout the durable-object package for async-friendly APIs.
12
+ */
13
+ type Awaitable<T> = T | Promise<T>;
14
+ /**
15
+ * Supported file extensions for durable object definitions.
16
+ */
17
+ type SupportedExtension = '.ts' | '.tsx' | '.js' | '.jsx';
18
+ /**
19
+ * Context available to all durable object handlers.
20
+ * Provides access to storage, SQL, and WebSocket management.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * export default defineDurableObject<CounterState>({
25
+ * async init(ctx) {
26
+ * // Access SQLite storage
27
+ * ctx.sql.run(`CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, action TEXT)`)
28
+ * return { value: 0 }
29
+ * }
30
+ * })
31
+ * ```
32
+ */
33
+ interface DurableObjectContext {
34
+ /** Persistent key-value storage */
35
+ storage: DurableObjectStorage;
36
+ /** SQLite storage (if enabled) */
37
+ sql: SqlStorage;
38
+ /** Unique identifier for this durable object instance */
39
+ id: DurableObjectId;
40
+ /**
41
+ * Get all WebSocket connections, optionally filtered by tag.
42
+ * @param tag - Optional tag to filter WebSockets
43
+ */
44
+ getWebSockets(tag?: string): WebSocket[];
45
+ /**
46
+ * Accept a WebSocket connection with optional tags for grouping.
47
+ * @param ws - The WebSocket to accept
48
+ * @param tags - Optional tags for filtering later
49
+ */
50
+ acceptWebSocket(ws: WebSocket, tags?: string[]): void;
51
+ }
52
+ /**
53
+ * Handler context with state access.
54
+ * `this` is bound to this context in all handler methods.
55
+ *
56
+ * @typeParam TState - The state type for this durable object
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * export default defineDurableObject<CounterState>({
61
+ * methods: {
62
+ * async increment(amount = 1) {
63
+ * this.state.value += amount // Access state via this
64
+ * this.ctx.sql.run(`INSERT INTO logs (action) VALUES ('increment')`)
65
+ * return this.state.value
66
+ * }
67
+ * }
68
+ * })
69
+ * ```
70
+ */
71
+ interface DurableObjectHandlerContext<TState = unknown> {
72
+ /** Current state of the durable object */
73
+ state: TState;
74
+ /** Context with storage, SQL, and WebSocket access */
75
+ ctx: DurableObjectContext;
76
+ }
77
+ /**
78
+ * Durable Object storage interface for key-value persistence.
79
+ */
80
+ interface DurableObjectStorage {
81
+ get<T = unknown>(key: string): Promise<T | undefined>;
82
+ get<T = unknown>(keys: string[]): Promise<Map<string, T>>;
83
+ list<T = unknown>(options?: DurableObjectStorageListOptions): Promise<Map<string, T>>;
84
+ put<T>(key: string, value: T): Promise<void>;
85
+ put<T>(entries: Record<string, T>): Promise<void>;
86
+ delete(key: string): Promise<boolean>;
87
+ delete(keys: string[]): Promise<number>;
88
+ deleteAll(): Promise<void>;
89
+ transaction<T>(closure: (txn: DurableObjectTransaction) => Promise<T>): Promise<T>;
90
+ getAlarm(): Promise<number | null>;
91
+ setAlarm(scheduledTime: number | Date): Promise<void>;
92
+ deleteAlarm(): Promise<void>;
93
+ sync(): Promise<void>;
94
+ }
95
+ /**
96
+ * Options for listing storage keys.
97
+ */
98
+ interface DurableObjectStorageListOptions {
99
+ start?: string;
100
+ startAfter?: string;
101
+ end?: string;
102
+ prefix?: string;
103
+ reverse?: boolean;
104
+ limit?: number;
105
+ }
106
+ /**
107
+ * Transaction interface for atomic operations.
108
+ */
109
+ interface DurableObjectTransaction {
110
+ get<T = unknown>(key: string): Promise<T | undefined>;
111
+ get<T = unknown>(keys: string[]): Promise<Map<string, T>>;
112
+ list<T = unknown>(options?: DurableObjectStorageListOptions): Promise<Map<string, T>>;
113
+ put<T>(key: string, value: T): Promise<void>;
114
+ put<T>(entries: Record<string, T>): Promise<void>;
115
+ delete(key: string): Promise<boolean>;
116
+ delete(keys: string[]): Promise<number>;
117
+ rollback(): void;
118
+ }
119
+ /**
120
+ * SQLite storage interface for relational data.
121
+ */
122
+ interface SqlStorage {
123
+ exec(query: string): SqlStorageCursor;
124
+ run(query: string, ...bindings: unknown[]): void;
125
+ }
126
+ /**
127
+ * Cursor for iterating SQL results.
128
+ */
129
+ interface SqlStorageCursor {
130
+ [Symbol.iterator](): IterableIterator<Record<string, unknown>>;
131
+ toArray(): Record<string, unknown>[];
132
+ one(): Record<string, unknown> | null;
133
+ raw<T extends unknown[] = unknown[]>(): IterableIterator<T>;
134
+ columnNames: string[];
135
+ rowsRead: number;
136
+ rowsWritten: number;
137
+ }
138
+ /**
139
+ * Unique identifier for a durable object instance.
140
+ */
141
+ interface DurableObjectId {
142
+ toString(): string;
143
+ equals(other: DurableObjectId): boolean;
144
+ name?: string;
145
+ }
146
+ /**
147
+ * Namespace for accessing durable object stubs.
148
+ */
149
+ interface DurableObjectNamespace<T = unknown> {
150
+ idFromName(name: string): DurableObjectId;
151
+ idFromString(id: string): DurableObjectId;
152
+ newUniqueId(): DurableObjectId;
153
+ get(id: DurableObjectId): DurableObjectStub<T>;
154
+ }
155
+ /**
156
+ * Stub for interacting with a durable object instance.
157
+ * Methods defined in `defineDurableObject({ methods })` are directly callable via native RPC.
158
+ */
159
+ interface DurableObjectStub<T = unknown> {
160
+ id: DurableObjectId;
161
+ name?: string;
162
+ fetch(request: Request): Promise<Response>;
163
+ }
164
+ /**
165
+ * Configuration for defineDurableObject().
166
+ *
167
+ * @typeParam TState - The state type managed by this durable object
168
+ * @typeParam TEnv - The environment bindings type
169
+ *
170
+ * @example
171
+ * ```typescript
172
+ * interface CounterState {
173
+ * value: number
174
+ * }
175
+ *
176
+ * export default defineDurableObject<CounterState>({
177
+ * sqlite: true,
178
+ *
179
+ * async init(ctx) {
180
+ * ctx.sql.run(`CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY)`)
181
+ * return { value: 0 }
182
+ * },
183
+ *
184
+ * methods: {
185
+ * async increment(amount = 1) {
186
+ * this.state.value += amount
187
+ * return this.state.value
188
+ * },
189
+ *
190
+ * async getValue() {
191
+ * return this.state.value
192
+ * },
193
+ * },
194
+ *
195
+ * async fetch(request) {
196
+ * return new Response(`Counter: ${this.state.value}`)
197
+ * },
198
+ * })
199
+ * ```
200
+ */
201
+ interface DurableObjectConfig<TState = unknown, TEnv = unknown> {
202
+ /**
203
+ * Optional name override.
204
+ * By default, the name is derived from the filename.
205
+ * - `app/objects/counter.ts` -> `counter`
206
+ * - `app/objects/chat-room.ts` -> `chatRoom`
207
+ */
208
+ name?: string;
209
+ /**
210
+ * Enable SQLite storage for this durable object.
211
+ * When true, `ctx.sql` will be available for SQL operations.
212
+ * @default false
213
+ */
214
+ sqlite?: boolean;
215
+ /**
216
+ * Optional Zod schema for runtime validation of state.
217
+ */
218
+ schema?: ZodType<TState>;
219
+ /**
220
+ * Initialize state on first access to this durable object.
221
+ * Called once when the durable object is first created or hydrated.
222
+ *
223
+ * @param ctx - Context with storage, SQL, and WebSocket access
224
+ * @param env - Environment bindings
225
+ * @returns Initial state or Promise resolving to initial state
226
+ */
227
+ init?: (ctx: DurableObjectContext, env: TEnv) => Awaitable<TState>;
228
+ /**
229
+ * RPC methods callable directly on the durable object stub.
230
+ * These become native Cloudflare RPC methods (no HTTP overhead).
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * methods: {
235
+ * async increment(amount = 1) {
236
+ * this.state.value += amount
237
+ * return this.state.value
238
+ * }
239
+ * }
240
+ *
241
+ * // Usage:
242
+ * const stub = env.COUNTER.get(id)
243
+ * const value = await stub.increment(5) // Direct RPC call
244
+ * ```
245
+ */
246
+ methods?: {
247
+ [name: string]: (this: DurableObjectHandlerContext<TState>, ...args: unknown[]) => Awaitable<unknown>;
248
+ };
249
+ /**
250
+ * HTTP request handler.
251
+ * Called when the durable object receives an HTTP request via stub.fetch().
252
+ *
253
+ * @param request - The incoming HTTP request
254
+ * @param env - Environment bindings
255
+ * @returns Response or Promise resolving to Response
256
+ */
257
+ fetch?: (this: DurableObjectHandlerContext<TState>, request: Request, env: TEnv) => Awaitable<Response>;
258
+ /**
259
+ * WebSocket message handler.
260
+ * Called when a WebSocket message is received.
261
+ *
262
+ * @param ws - The WebSocket that sent the message
263
+ * @param message - The message content (string or binary)
264
+ */
265
+ webSocketMessage?: (this: DurableObjectHandlerContext<TState>, ws: WebSocket, message: string | ArrayBuffer) => Awaitable<void>;
266
+ /**
267
+ * WebSocket close handler.
268
+ * Called when a WebSocket connection closes.
269
+ *
270
+ * @param ws - The WebSocket that closed
271
+ * @param code - Close code
272
+ * @param reason - Close reason
273
+ */
274
+ webSocketClose?: (this: DurableObjectHandlerContext<TState>, ws: WebSocket, code: number, reason: string) => Awaitable<void>;
275
+ /**
276
+ * WebSocket error handler.
277
+ * Called when a WebSocket error occurs.
278
+ *
279
+ * @param ws - The WebSocket with the error
280
+ * @param error - The error that occurred
281
+ */
282
+ webSocketError?: (this: DurableObjectHandlerContext<TState>, ws: WebSocket, error: Error) => Awaitable<void>;
283
+ /**
284
+ * Alarm handler.
285
+ * Called when a scheduled alarm fires.
286
+ * Set alarms using `ctx.storage.setAlarm()`.
287
+ *
288
+ * @example
289
+ * ```typescript
290
+ * {
291
+ * async init(ctx) {
292
+ * // Schedule alarm for 1 hour from now
293
+ * await ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)
294
+ * return { lastCleanup: Date.now() }
295
+ * },
296
+ *
297
+ * async alarm() {
298
+ * // Perform periodic cleanup
299
+ * this.state.lastCleanup = Date.now()
300
+ * // Reschedule for next hour
301
+ * await this.ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)
302
+ * }
303
+ * }
304
+ * ```
305
+ */
306
+ alarm?: (this: DurableObjectHandlerContext<TState>) => Awaitable<void>;
307
+ }
308
+ /**
309
+ * A defined durable object, returned by defineDurableObject().
310
+ *
311
+ * @typeParam TState - The state type managed by this durable object
312
+ * @typeParam TEnv - The environment bindings type
313
+ */
314
+ interface DurableObjectDefinition<TState = unknown, TEnv = unknown> {
315
+ /** Internal marker identifying this as a durable object definition */
316
+ readonly __brand: 'cloudwerk-durable-object';
317
+ /** Object name (derived from filename or explicitly set) */
318
+ readonly name: string | undefined;
319
+ /** Whether SQLite storage is enabled */
320
+ readonly sqlite: boolean;
321
+ /** Zod schema for state validation (if provided) */
322
+ readonly schema: ZodType<TState> | undefined;
323
+ /** The full configuration object */
324
+ readonly config: DurableObjectConfig<TState, TEnv>;
325
+ }
326
+ /**
327
+ * A scanned durable object file from the app/objects/ directory.
328
+ */
329
+ interface ScannedDurableObject {
330
+ /** Relative path from app/objects/ (e.g., 'counter.ts') */
331
+ relativePath: string;
332
+ /** Absolute filesystem path */
333
+ absolutePath: string;
334
+ /** File name without extension (e.g., 'counter') */
335
+ name: string;
336
+ /** File extension (e.g., '.ts') */
337
+ extension: SupportedExtension;
338
+ }
339
+ /**
340
+ * Result of scanning the app/objects/ directory.
341
+ */
342
+ interface DurableObjectScanResult {
343
+ /** All discovered durable object files */
344
+ durableObjects: ScannedDurableObject[];
345
+ }
346
+ /**
347
+ * A compiled durable object entry in the manifest.
348
+ */
349
+ interface DurableObjectEntry {
350
+ /** Object name derived from filename (e.g., 'counter', 'chatRoom') */
351
+ name: string;
352
+ /** Binding name for wrangler.toml (e.g., 'COUNTER', 'CHAT_ROOM') */
353
+ bindingName: string;
354
+ /** Class name for the generated DO class (e.g., 'Counter', 'ChatRoom') */
355
+ className: string;
356
+ /** Relative path to the definition file */
357
+ filePath: string;
358
+ /** Absolute path to the definition file */
359
+ absolutePath: string;
360
+ /** Path to the generated DO class file */
361
+ generatedPath: string;
362
+ /** Whether SQLite storage is enabled */
363
+ sqlite: boolean;
364
+ /** Whether fetch handler is defined */
365
+ hasFetch: boolean;
366
+ /** Whether WebSocket handlers are defined */
367
+ hasWebSocket: boolean;
368
+ /** Whether alarm handler is defined */
369
+ hasAlarm: boolean;
370
+ /** RPC method names extracted from methods config */
371
+ methodNames: string[];
372
+ }
373
+ /**
374
+ * Validation error for a durable object definition.
375
+ */
376
+ interface DurableObjectValidationError {
377
+ /** Durable object file path */
378
+ file: string;
379
+ /** Error message */
380
+ message: string;
381
+ /** Error code for programmatic handling */
382
+ code: 'NO_HANDLER' | 'INVALID_CONFIG' | 'DUPLICATE_NAME' | 'INVALID_NAME' | 'INVALID_DEFINITION';
383
+ }
384
+ /**
385
+ * Validation warning for a durable object definition.
386
+ */
387
+ interface DurableObjectValidationWarning {
388
+ /** Durable object file path */
389
+ file: string;
390
+ /** Warning message */
391
+ message: string;
392
+ /** Warning code */
393
+ code: 'NO_INIT' | 'NO_METHODS' | 'SQLITE_WITHOUT_INIT';
394
+ }
395
+ /**
396
+ * Complete durable object manifest generated during build.
397
+ */
398
+ interface DurableObjectManifest {
399
+ /** All compiled durable object entries */
400
+ durableObjects: DurableObjectEntry[];
401
+ /** Validation errors (object won't be registered) */
402
+ errors: DurableObjectValidationError[];
403
+ /** Validation warnings (object will be registered with warning) */
404
+ warnings: DurableObjectValidationWarning[];
405
+ /** When the manifest was generated */
406
+ generatedAt: Date;
407
+ /** Root directory of the app */
408
+ rootDir: string;
409
+ }
410
+ /**
411
+ * A Cloudflare Durable Object migration for wrangler.toml.
412
+ */
413
+ interface DurableObjectMigration {
414
+ /** Migration tag (e.g., 'v1', 'v2') */
415
+ tag: string;
416
+ /** New classes to create */
417
+ new_classes?: string[];
418
+ /** New classes that use SQLite storage */
419
+ new_sqlite_classes?: string[];
420
+ /** Classes to rename */
421
+ renamed_classes?: Array<{
422
+ from: string;
423
+ to: string;
424
+ }>;
425
+ /** Classes to delete */
426
+ deleted_classes?: string[];
427
+ }
428
+ /**
429
+ * Options for building the durable object manifest.
430
+ */
431
+ interface BuildDurableObjectManifestOptions {
432
+ /** Application name for prefixing (e.g., 'cloudwerk') */
433
+ appName?: string;
434
+ /** Whether to validate definitions at build time */
435
+ validate?: boolean;
436
+ /** Output directory for generated files */
437
+ outputDir?: string;
438
+ }
439
+
440
+ /**
441
+ * @cloudwerk/durable-object - Error Classes
442
+ *
443
+ * Custom error classes for Durable Object operations.
444
+ */
445
+ /**
446
+ * Base error class for durable object-related errors.
447
+ */
448
+ declare class DurableObjectError extends Error {
449
+ /** Error code for programmatic handling */
450
+ readonly code: string;
451
+ constructor(code: string, message: string, options?: ErrorOptions);
452
+ }
453
+ /**
454
+ * Error thrown when durable object configuration is invalid.
455
+ *
456
+ * @example
457
+ * ```typescript
458
+ * // Thrown when no handlers are defined
459
+ * export default defineDurableObject({
460
+ * // Missing methods, fetch, alarm, or WebSocket handlers
461
+ * })
462
+ * ```
463
+ */
464
+ declare class DurableObjectConfigError extends DurableObjectError {
465
+ /** The configuration field that is invalid */
466
+ readonly field?: string;
467
+ constructor(message: string, field?: string);
468
+ }
469
+ /**
470
+ * Error thrown when no handler is defined for a durable object.
471
+ */
472
+ declare class DurableObjectNoHandlerError extends DurableObjectError {
473
+ constructor(objectName: string);
474
+ }
475
+ /**
476
+ * Error thrown when accessing a durable object outside of request context.
477
+ */
478
+ declare class DurableObjectContextError extends DurableObjectError {
479
+ constructor();
480
+ }
481
+ /**
482
+ * Error thrown when a durable object binding is not found.
483
+ *
484
+ * @example
485
+ * ```typescript
486
+ * import { durableObjects } from '@cloudwerk/bindings'
487
+ *
488
+ * // Throws if COUNTER binding doesn't exist in environment
489
+ * const counter = durableObjects.counter
490
+ * ```
491
+ */
492
+ declare class DurableObjectNotFoundError extends DurableObjectError {
493
+ /** The durable object name that was not found */
494
+ readonly objectName: string;
495
+ /** Available durable object names */
496
+ readonly availableObjects: string[];
497
+ constructor(objectName: string, availableObjects: string[]);
498
+ }
499
+ /**
500
+ * Error thrown when state initialization fails.
501
+ */
502
+ declare class DurableObjectStateError extends DurableObjectError {
503
+ /** The durable object name */
504
+ readonly objectName: string;
505
+ constructor(objectName: string, message: string, options?: ErrorOptions);
506
+ }
507
+ /**
508
+ * Error thrown when state validation fails.
509
+ */
510
+ declare class DurableObjectSchemaValidationError extends DurableObjectError {
511
+ /** The validation errors from Zod */
512
+ readonly validationErrors: unknown[];
513
+ constructor(message: string, validationErrors: unknown[]);
514
+ }
515
+ /**
516
+ * Error thrown when an RPC method call fails.
517
+ */
518
+ declare class DurableObjectRPCError extends DurableObjectError {
519
+ /** The durable object name */
520
+ readonly objectName: string;
521
+ /** The method that was called */
522
+ readonly methodName: string;
523
+ constructor(objectName: string, methodName: string, message: string, options?: ErrorOptions);
524
+ }
525
+ /**
526
+ * Error thrown when an RPC method is not found.
527
+ */
528
+ declare class DurableObjectMethodNotFoundError extends DurableObjectError {
529
+ /** The durable object name */
530
+ readonly objectName: string;
531
+ /** The method that was not found */
532
+ readonly methodName: string;
533
+ /** Available method names */
534
+ readonly availableMethods: string[];
535
+ constructor(objectName: string, methodName: string, availableMethods: string[]);
536
+ }
537
+ /**
538
+ * Error thrown when an alarm operation fails.
539
+ */
540
+ declare class DurableObjectAlarmError extends DurableObjectError {
541
+ /** The durable object name */
542
+ readonly objectName: string;
543
+ constructor(objectName: string, message: string, options?: ErrorOptions);
544
+ }
545
+ /**
546
+ * Error thrown when a WebSocket operation fails.
547
+ */
548
+ declare class DurableObjectWebSocketError extends DurableObjectError {
549
+ /** The durable object name */
550
+ readonly objectName: string;
551
+ constructor(objectName: string, message: string, options?: ErrorOptions);
552
+ }
553
+
554
+ /**
555
+ * @cloudwerk/durable-object - defineDurableObject()
556
+ *
557
+ * Factory function for creating Durable Object definitions with native RPC support.
558
+ */
559
+
560
+ /**
561
+ * Define a Durable Object with native Cloudflare RPC support.
562
+ *
563
+ * This function creates a durable object definition that will be automatically
564
+ * discovered and registered by Cloudwerk during build. The build process generates
565
+ * a class extending Cloudflare's DurableObject with native RPC methods.
566
+ *
567
+ * @typeParam TState - The state type managed by this durable object
568
+ * @typeParam TEnv - The environment bindings type
569
+ * @param config - Durable object configuration
570
+ * @returns Durable object definition
571
+ *
572
+ * @example
573
+ * ```typescript
574
+ * // app/objects/counter.ts
575
+ * import { defineDurableObject } from '@cloudwerk/durable-object'
576
+ *
577
+ * interface CounterState {
578
+ * value: number
579
+ * }
580
+ *
581
+ * export default defineDurableObject<CounterState>({
582
+ * sqlite: true,
583
+ *
584
+ * async init(ctx) {
585
+ * ctx.sql.run(`CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, action TEXT)`)
586
+ * return { value: 0 }
587
+ * },
588
+ *
589
+ * methods: {
590
+ * async increment(amount = 1) {
591
+ * this.state.value += amount
592
+ * this.ctx.sql.run(`INSERT INTO logs (action) VALUES ('increment')`)
593
+ * return this.state.value
594
+ * },
595
+ *
596
+ * async getValue() {
597
+ * return this.state.value
598
+ * },
599
+ * },
600
+ * })
601
+ * ```
602
+ *
603
+ * @example
604
+ * ```typescript
605
+ * // Usage in route handler:
606
+ * import { durableObjects } from '@cloudwerk/bindings'
607
+ *
608
+ * export async function POST(request: Request, { params }: Context) {
609
+ * const id = durableObjects.Counter.idFromName(params.id)
610
+ * const stub = durableObjects.Counter.get(id)
611
+ *
612
+ * // Native RPC - direct method calls!
613
+ * const value = await stub.increment(5)
614
+ *
615
+ * return Response.json({ value })
616
+ * }
617
+ * ```
618
+ *
619
+ * @example
620
+ * ```typescript
621
+ * // With HTTP fetch handler
622
+ * import { defineDurableObject } from '@cloudwerk/durable-object'
623
+ *
624
+ * export default defineDurableObject<{ count: number }>({
625
+ * init: () => ({ count: 0 }),
626
+ *
627
+ * async fetch(request) {
628
+ * const url = new URL(request.url)
629
+ *
630
+ * if (url.pathname === '/increment') {
631
+ * this.state.count++
632
+ * }
633
+ *
634
+ * return new Response(String(this.state.count))
635
+ * },
636
+ * })
637
+ * ```
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * // With WebSocket support
642
+ * import { defineDurableObject } from '@cloudwerk/durable-object'
643
+ *
644
+ * interface ChatState {
645
+ * messages: string[]
646
+ * }
647
+ *
648
+ * export default defineDurableObject<ChatState>({
649
+ * init: () => ({ messages: [] }),
650
+ *
651
+ * async fetch(request) {
652
+ * const upgradeHeader = request.headers.get('Upgrade')
653
+ * if (upgradeHeader !== 'websocket') {
654
+ * return new Response('Expected WebSocket', { status: 426 })
655
+ * }
656
+ *
657
+ * const pair = new WebSocketPair()
658
+ * this.ctx.acceptWebSocket(pair[1])
659
+ *
660
+ * return new Response(null, { status: 101, webSocket: pair[0] })
661
+ * },
662
+ *
663
+ * async webSocketMessage(ws, message) {
664
+ * this.state.messages.push(String(message))
665
+ *
666
+ * // Broadcast to all connected clients
667
+ * for (const client of this.ctx.getWebSockets()) {
668
+ * client.send(String(message))
669
+ * }
670
+ * },
671
+ * })
672
+ * ```
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * // With alarm handler
677
+ * import { defineDurableObject } from '@cloudwerk/durable-object'
678
+ *
679
+ * interface CleanupState {
680
+ * lastCleanup: number
681
+ * }
682
+ *
683
+ * export default defineDurableObject<CleanupState>({
684
+ * async init(ctx) {
685
+ * // Schedule first alarm for 1 hour from now
686
+ * await ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)
687
+ * return { lastCleanup: Date.now() }
688
+ * },
689
+ *
690
+ * async alarm() {
691
+ * // Perform periodic cleanup
692
+ * this.state.lastCleanup = Date.now()
693
+ *
694
+ * // Reschedule for next hour
695
+ * await this.ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)
696
+ * },
697
+ *
698
+ * methods: {
699
+ * getLastCleanup() {
700
+ * return this.state.lastCleanup
701
+ * },
702
+ * },
703
+ * })
704
+ * ```
705
+ */
706
+ declare function defineDurableObject<TState = unknown, TEnv = unknown>(config: DurableObjectConfig<TState, TEnv>): DurableObjectDefinition<TState, TEnv>;
707
+ /**
708
+ * Check if a value is a durable object definition created by defineDurableObject().
709
+ *
710
+ * @param value - Value to check
711
+ * @returns true if value is a DurableObjectDefinition
712
+ */
713
+ declare function isDurableObjectDefinition(value: unknown): value is DurableObjectDefinition;
714
+ /**
715
+ * Extract method names from a durable object definition.
716
+ *
717
+ * @param definition - The durable object definition
718
+ * @returns Array of method names
719
+ */
720
+ declare function getMethodNames(definition: DurableObjectDefinition): string[];
721
+ /**
722
+ * Check if a durable object definition has any handlers.
723
+ *
724
+ * @param definition - The durable object definition
725
+ * @returns true if at least one handler is defined
726
+ */
727
+ declare function hasHandlers(definition: DurableObjectDefinition): boolean;
728
+ /**
729
+ * Check if a durable object definition has WebSocket support.
730
+ *
731
+ * @param definition - The durable object definition
732
+ * @returns true if any WebSocket handler is defined
733
+ */
734
+ declare function hasWebSocketSupport(definition: DurableObjectDefinition): boolean;
735
+
736
+ export { type Awaitable, type BuildDurableObjectManifestOptions, DurableObjectAlarmError, type DurableObjectConfig, DurableObjectConfigError, type DurableObjectContext, DurableObjectContextError, type DurableObjectDefinition, type DurableObjectEntry, DurableObjectError, type DurableObjectHandlerContext, type DurableObjectId, type DurableObjectManifest, DurableObjectMethodNotFoundError, type DurableObjectMigration, type DurableObjectNamespace, DurableObjectNoHandlerError, DurableObjectNotFoundError, DurableObjectRPCError, type DurableObjectScanResult, DurableObjectSchemaValidationError, DurableObjectStateError, type DurableObjectStorage, type DurableObjectStorageListOptions, type DurableObjectStub, type DurableObjectTransaction, type DurableObjectValidationError, type DurableObjectValidationWarning, DurableObjectWebSocketError, type ScannedDurableObject, type SqlStorage, type SqlStorageCursor, type SupportedExtension, defineDurableObject, getMethodNames, hasHandlers, hasWebSocketSupport, isDurableObjectDefinition };
package/dist/index.js ADDED
@@ -0,0 +1,254 @@
1
+ // src/errors.ts
2
+ var DurableObjectError = class extends Error {
3
+ /** Error code for programmatic handling */
4
+ code;
5
+ constructor(code, message, options) {
6
+ super(message, options);
7
+ this.name = "DurableObjectError";
8
+ this.code = code;
9
+ }
10
+ };
11
+ var DurableObjectConfigError = class extends DurableObjectError {
12
+ /** The configuration field that is invalid */
13
+ field;
14
+ constructor(message, field) {
15
+ super("CONFIG_ERROR", message);
16
+ this.name = "DurableObjectConfigError";
17
+ this.field = field;
18
+ }
19
+ };
20
+ var DurableObjectNoHandlerError = class extends DurableObjectError {
21
+ constructor(objectName) {
22
+ super(
23
+ "NO_HANDLER",
24
+ `Durable object '${objectName}' must define at least one handler (methods, fetch, alarm, or webSocketMessage)`
25
+ );
26
+ this.name = "DurableObjectNoHandlerError";
27
+ }
28
+ };
29
+ var DurableObjectContextError = class extends DurableObjectError {
30
+ constructor() {
31
+ super(
32
+ "CONTEXT_ERROR",
33
+ "Durable object accessed outside of request handler. Durable objects can only be accessed during request handling."
34
+ );
35
+ this.name = "DurableObjectContextError";
36
+ }
37
+ };
38
+ var DurableObjectNotFoundError = class extends DurableObjectError {
39
+ /** The durable object name that was not found */
40
+ objectName;
41
+ /** Available durable object names */
42
+ availableObjects;
43
+ constructor(objectName, availableObjects) {
44
+ const available = availableObjects.length > 0 ? `Available durable objects: ${availableObjects.join(", ")}` : "No durable objects are configured";
45
+ super(
46
+ "DURABLE_OBJECT_NOT_FOUND",
47
+ `Durable object '${objectName}' not found in environment. ${available}`
48
+ );
49
+ this.name = "DurableObjectNotFoundError";
50
+ this.objectName = objectName;
51
+ this.availableObjects = availableObjects;
52
+ }
53
+ };
54
+ var DurableObjectStateError = class extends DurableObjectError {
55
+ /** The durable object name */
56
+ objectName;
57
+ constructor(objectName, message, options) {
58
+ super("STATE_ERROR", `State error in durable object '${objectName}': ${message}`, options);
59
+ this.name = "DurableObjectStateError";
60
+ this.objectName = objectName;
61
+ }
62
+ };
63
+ var DurableObjectSchemaValidationError = class extends DurableObjectError {
64
+ /** The validation errors from Zod */
65
+ validationErrors;
66
+ constructor(message, validationErrors) {
67
+ super("VALIDATION_ERROR", message);
68
+ this.name = "DurableObjectSchemaValidationError";
69
+ this.validationErrors = validationErrors;
70
+ }
71
+ };
72
+ var DurableObjectRPCError = class extends DurableObjectError {
73
+ /** The durable object name */
74
+ objectName;
75
+ /** The method that was called */
76
+ methodName;
77
+ constructor(objectName, methodName, message, options) {
78
+ super(
79
+ "RPC_ERROR",
80
+ `RPC error calling '${methodName}' on durable object '${objectName}': ${message}`,
81
+ options
82
+ );
83
+ this.name = "DurableObjectRPCError";
84
+ this.objectName = objectName;
85
+ this.methodName = methodName;
86
+ }
87
+ };
88
+ var DurableObjectMethodNotFoundError = class extends DurableObjectError {
89
+ /** The durable object name */
90
+ objectName;
91
+ /** The method that was not found */
92
+ methodName;
93
+ /** Available method names */
94
+ availableMethods;
95
+ constructor(objectName, methodName, availableMethods) {
96
+ const available = availableMethods.length > 0 ? `Available methods: ${availableMethods.join(", ")}` : "No RPC methods are defined";
97
+ super(
98
+ "METHOD_NOT_FOUND",
99
+ `Method '${methodName}' not found on durable object '${objectName}'. ${available}`
100
+ );
101
+ this.name = "DurableObjectMethodNotFoundError";
102
+ this.objectName = objectName;
103
+ this.methodName = methodName;
104
+ this.availableMethods = availableMethods;
105
+ }
106
+ };
107
+ var DurableObjectAlarmError = class extends DurableObjectError {
108
+ /** The durable object name */
109
+ objectName;
110
+ constructor(objectName, message, options) {
111
+ super("ALARM_ERROR", `Alarm error in durable object '${objectName}': ${message}`, options);
112
+ this.name = "DurableObjectAlarmError";
113
+ this.objectName = objectName;
114
+ }
115
+ };
116
+ var DurableObjectWebSocketError = class extends DurableObjectError {
117
+ /** The durable object name */
118
+ objectName;
119
+ constructor(objectName, message, options) {
120
+ super(
121
+ "WEBSOCKET_ERROR",
122
+ `WebSocket error in durable object '${objectName}': ${message}`,
123
+ options
124
+ );
125
+ this.name = "DurableObjectWebSocketError";
126
+ this.objectName = objectName;
127
+ }
128
+ };
129
+
130
+ // src/define-durable-object.ts
131
+ var NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
132
+ var RESERVED_METHODS = /* @__PURE__ */ new Set([
133
+ "fetch",
134
+ "alarm",
135
+ "webSocketMessage",
136
+ "webSocketClose",
137
+ "webSocketError",
138
+ "constructor",
139
+ "init"
140
+ ]);
141
+ function validateConfig(config) {
142
+ const hasHandler = config.fetch !== void 0 || config.alarm !== void 0 || config.webSocketMessage !== void 0 || config.methods !== void 0 && Object.keys(config.methods).length > 0;
143
+ if (!hasHandler) {
144
+ throw new DurableObjectNoHandlerError(config.name || "unknown");
145
+ }
146
+ if (config.name !== void 0) {
147
+ if (typeof config.name !== "string" || config.name.length === 0) {
148
+ throw new DurableObjectConfigError("name must be a non-empty string", "name");
149
+ }
150
+ if (!NAME_PATTERN.test(config.name)) {
151
+ throw new DurableObjectConfigError(
152
+ "name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens",
153
+ "name"
154
+ );
155
+ }
156
+ }
157
+ if (config.sqlite !== void 0 && typeof config.sqlite !== "boolean") {
158
+ throw new DurableObjectConfigError("sqlite must be a boolean", "sqlite");
159
+ }
160
+ if (config.methods !== void 0) {
161
+ if (typeof config.methods !== "object" || config.methods === null) {
162
+ throw new DurableObjectConfigError("methods must be an object", "methods");
163
+ }
164
+ for (const methodName of Object.keys(config.methods)) {
165
+ if (RESERVED_METHODS.has(methodName)) {
166
+ throw new DurableObjectConfigError(
167
+ `'${methodName}' is a reserved method name and cannot be used in methods config`,
168
+ "methods"
169
+ );
170
+ }
171
+ if (typeof config.methods[methodName] !== "function") {
172
+ throw new DurableObjectConfigError(
173
+ `methods.${methodName} must be a function`,
174
+ "methods"
175
+ );
176
+ }
177
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(methodName)) {
178
+ throw new DurableObjectConfigError(
179
+ `'${methodName}' is not a valid method name. Method names must be valid JavaScript identifiers`,
180
+ "methods"
181
+ );
182
+ }
183
+ }
184
+ }
185
+ if (config.init !== void 0 && typeof config.init !== "function") {
186
+ throw new DurableObjectConfigError("init must be a function", "init");
187
+ }
188
+ if (config.fetch !== void 0 && typeof config.fetch !== "function") {
189
+ throw new DurableObjectConfigError("fetch must be a function", "fetch");
190
+ }
191
+ if (config.alarm !== void 0 && typeof config.alarm !== "function") {
192
+ throw new DurableObjectConfigError("alarm must be a function", "alarm");
193
+ }
194
+ if (config.webSocketMessage !== void 0 && typeof config.webSocketMessage !== "function") {
195
+ throw new DurableObjectConfigError(
196
+ "webSocketMessage must be a function",
197
+ "webSocketMessage"
198
+ );
199
+ }
200
+ if (config.webSocketClose !== void 0 && typeof config.webSocketClose !== "function") {
201
+ throw new DurableObjectConfigError("webSocketClose must be a function", "webSocketClose");
202
+ }
203
+ if (config.webSocketError !== void 0 && typeof config.webSocketError !== "function") {
204
+ throw new DurableObjectConfigError("webSocketError must be a function", "webSocketError");
205
+ }
206
+ }
207
+ function defineDurableObject(config) {
208
+ validateConfig(config);
209
+ const definition = {
210
+ __brand: "cloudwerk-durable-object",
211
+ name: config.name,
212
+ sqlite: config.sqlite ?? false,
213
+ schema: config.schema,
214
+ config
215
+ };
216
+ return definition;
217
+ }
218
+ function isDurableObjectDefinition(value) {
219
+ return typeof value === "object" && value !== null && "__brand" in value && value.__brand === "cloudwerk-durable-object";
220
+ }
221
+ function getMethodNames(definition) {
222
+ const methods = definition.config.methods;
223
+ if (!methods) {
224
+ return [];
225
+ }
226
+ return Object.keys(methods);
227
+ }
228
+ function hasHandlers(definition) {
229
+ const { config } = definition;
230
+ return config.fetch !== void 0 || config.alarm !== void 0 || config.webSocketMessage !== void 0 || config.methods !== void 0 && Object.keys(config.methods).length > 0;
231
+ }
232
+ function hasWebSocketSupport(definition) {
233
+ const { config } = definition;
234
+ return config.webSocketMessage !== void 0 || config.webSocketClose !== void 0 || config.webSocketError !== void 0;
235
+ }
236
+ export {
237
+ DurableObjectAlarmError,
238
+ DurableObjectConfigError,
239
+ DurableObjectContextError,
240
+ DurableObjectError,
241
+ DurableObjectMethodNotFoundError,
242
+ DurableObjectNoHandlerError,
243
+ DurableObjectNotFoundError,
244
+ DurableObjectRPCError,
245
+ DurableObjectSchemaValidationError,
246
+ DurableObjectStateError,
247
+ DurableObjectWebSocketError,
248
+ defineDurableObject,
249
+ getMethodNames,
250
+ hasHandlers,
251
+ hasWebSocketSupport,
252
+ isDurableObjectDefinition
253
+ };
254
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/define-durable-object.ts"],"sourcesContent":["/**\n * @cloudwerk/durable-object - Error Classes\n *\n * Custom error classes for Durable Object operations.\n */\n\n// ============================================================================\n// Base Error\n// ============================================================================\n\n/**\n * Base error class for durable object-related errors.\n */\nexport class DurableObjectError extends Error {\n /** Error code for programmatic handling */\n readonly code: string\n\n constructor(code: string, message: string, options?: ErrorOptions) {\n super(message, options)\n this.name = 'DurableObjectError'\n this.code = code\n }\n}\n\n// ============================================================================\n// Configuration Errors\n// ============================================================================\n\n/**\n * Error thrown when durable object configuration is invalid.\n *\n * @example\n * ```typescript\n * // Thrown when no handlers are defined\n * export default defineDurableObject({\n * // Missing methods, fetch, alarm, or WebSocket handlers\n * })\n * ```\n */\nexport class DurableObjectConfigError extends DurableObjectError {\n /** The configuration field that is invalid */\n readonly field?: string\n\n constructor(message: string, field?: string) {\n super('CONFIG_ERROR', message)\n this.name = 'DurableObjectConfigError'\n this.field = field\n }\n}\n\n/**\n * Error thrown when no handler is defined for a durable object.\n */\nexport class DurableObjectNoHandlerError extends DurableObjectError {\n constructor(objectName: string) {\n super(\n 'NO_HANDLER',\n `Durable object '${objectName}' must define at least one handler (methods, fetch, alarm, or webSocketMessage)`\n )\n this.name = 'DurableObjectNoHandlerError'\n }\n}\n\n// ============================================================================\n// Runtime Errors\n// ============================================================================\n\n/**\n * Error thrown when accessing a durable object outside of request context.\n */\nexport class DurableObjectContextError extends DurableObjectError {\n constructor() {\n super(\n 'CONTEXT_ERROR',\n 'Durable object accessed outside of request handler. Durable objects can only be accessed during request handling.'\n )\n this.name = 'DurableObjectContextError'\n }\n}\n\n/**\n * Error thrown when a durable object binding is not found.\n *\n * @example\n * ```typescript\n * import { durableObjects } from '@cloudwerk/bindings'\n *\n * // Throws if COUNTER binding doesn't exist in environment\n * const counter = durableObjects.counter\n * ```\n */\nexport class DurableObjectNotFoundError extends DurableObjectError {\n /** The durable object name that was not found */\n readonly objectName: string\n\n /** Available durable object names */\n readonly availableObjects: string[]\n\n constructor(objectName: string, availableObjects: string[]) {\n const available =\n availableObjects.length > 0\n ? `Available durable objects: ${availableObjects.join(', ')}`\n : 'No durable objects are configured'\n\n super(\n 'DURABLE_OBJECT_NOT_FOUND',\n `Durable object '${objectName}' not found in environment. ${available}`\n )\n this.name = 'DurableObjectNotFoundError'\n this.objectName = objectName\n this.availableObjects = availableObjects\n }\n}\n\n// ============================================================================\n// State Errors\n// ============================================================================\n\n/**\n * Error thrown when state initialization fails.\n */\nexport class DurableObjectStateError extends DurableObjectError {\n /** The durable object name */\n readonly objectName: string\n\n constructor(objectName: string, message: string, options?: ErrorOptions) {\n super('STATE_ERROR', `State error in durable object '${objectName}': ${message}`, options)\n this.name = 'DurableObjectStateError'\n this.objectName = objectName\n }\n}\n\n/**\n * Error thrown when state validation fails.\n */\nexport class DurableObjectSchemaValidationError extends DurableObjectError {\n /** The validation errors from Zod */\n readonly validationErrors: unknown[]\n\n constructor(message: string, validationErrors: unknown[]) {\n super('VALIDATION_ERROR', message)\n this.name = 'DurableObjectSchemaValidationError'\n this.validationErrors = validationErrors\n }\n}\n\n// ============================================================================\n// RPC Errors\n// ============================================================================\n\n/**\n * Error thrown when an RPC method call fails.\n */\nexport class DurableObjectRPCError extends DurableObjectError {\n /** The durable object name */\n readonly objectName: string\n\n /** The method that was called */\n readonly methodName: string\n\n constructor(objectName: string, methodName: string, message: string, options?: ErrorOptions) {\n super(\n 'RPC_ERROR',\n `RPC error calling '${methodName}' on durable object '${objectName}': ${message}`,\n options\n )\n this.name = 'DurableObjectRPCError'\n this.objectName = objectName\n this.methodName = methodName\n }\n}\n\n/**\n * Error thrown when an RPC method is not found.\n */\nexport class DurableObjectMethodNotFoundError extends DurableObjectError {\n /** The durable object name */\n readonly objectName: string\n\n /** The method that was not found */\n readonly methodName: string\n\n /** Available method names */\n readonly availableMethods: string[]\n\n constructor(objectName: string, methodName: string, availableMethods: string[]) {\n const available =\n availableMethods.length > 0\n ? `Available methods: ${availableMethods.join(', ')}`\n : 'No RPC methods are defined'\n\n super(\n 'METHOD_NOT_FOUND',\n `Method '${methodName}' not found on durable object '${objectName}'. ${available}`\n )\n this.name = 'DurableObjectMethodNotFoundError'\n this.objectName = objectName\n this.methodName = methodName\n this.availableMethods = availableMethods\n }\n}\n\n// ============================================================================\n// Alarm Errors\n// ============================================================================\n\n/**\n * Error thrown when an alarm operation fails.\n */\nexport class DurableObjectAlarmError extends DurableObjectError {\n /** The durable object name */\n readonly objectName: string\n\n constructor(objectName: string, message: string, options?: ErrorOptions) {\n super('ALARM_ERROR', `Alarm error in durable object '${objectName}': ${message}`, options)\n this.name = 'DurableObjectAlarmError'\n this.objectName = objectName\n }\n}\n\n// ============================================================================\n// WebSocket Errors\n// ============================================================================\n\n/**\n * Error thrown when a WebSocket operation fails.\n */\nexport class DurableObjectWebSocketError extends DurableObjectError {\n /** The durable object name */\n readonly objectName: string\n\n constructor(objectName: string, message: string, options?: ErrorOptions) {\n super(\n 'WEBSOCKET_ERROR',\n `WebSocket error in durable object '${objectName}': ${message}`,\n options\n )\n this.name = 'DurableObjectWebSocketError'\n this.objectName = objectName\n }\n}\n","/**\n * @cloudwerk/durable-object - defineDurableObject()\n *\n * Factory function for creating Durable Object definitions with native RPC support.\n */\n\nimport type { DurableObjectConfig, DurableObjectDefinition } from './types.js'\nimport { DurableObjectConfigError, DurableObjectNoHandlerError } from './errors.js'\n\n// ============================================================================\n// Validation\n// ============================================================================\n\n/**\n * Valid name pattern for durable objects.\n * Must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.\n */\nconst NAME_PATTERN = /^[a-z][a-z0-9-]*$/\n\n/**\n * Reserved method names that cannot be used in the methods config.\n */\nconst RESERVED_METHODS = new Set([\n 'fetch',\n 'alarm',\n 'webSocketMessage',\n 'webSocketClose',\n 'webSocketError',\n 'constructor',\n 'init',\n])\n\n/**\n * Validate durable object configuration.\n *\n * @param config - Durable object configuration to validate\n * @throws DurableObjectConfigError if configuration is invalid\n * @throws DurableObjectNoHandlerError if no handler is defined\n */\nfunction validateConfig<TState, TEnv>(\n config: DurableObjectConfig<TState, TEnv>\n): void {\n // Must have at least one handler\n const hasHandler =\n config.fetch !== undefined ||\n config.alarm !== undefined ||\n config.webSocketMessage !== undefined ||\n (config.methods !== undefined && Object.keys(config.methods).length > 0)\n\n if (!hasHandler) {\n throw new DurableObjectNoHandlerError(config.name || 'unknown')\n }\n\n // Validate name if provided\n if (config.name !== undefined) {\n if (typeof config.name !== 'string' || config.name.length === 0) {\n throw new DurableObjectConfigError('name must be a non-empty string', 'name')\n }\n\n if (!NAME_PATTERN.test(config.name)) {\n throw new DurableObjectConfigError(\n 'name must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens',\n 'name'\n )\n }\n }\n\n // Validate sqlite flag\n if (config.sqlite !== undefined && typeof config.sqlite !== 'boolean') {\n throw new DurableObjectConfigError('sqlite must be a boolean', 'sqlite')\n }\n\n // Validate methods if provided\n if (config.methods !== undefined) {\n if (typeof config.methods !== 'object' || config.methods === null) {\n throw new DurableObjectConfigError('methods must be an object', 'methods')\n }\n\n for (const methodName of Object.keys(config.methods)) {\n // Check for reserved names\n if (RESERVED_METHODS.has(methodName)) {\n throw new DurableObjectConfigError(\n `'${methodName}' is a reserved method name and cannot be used in methods config`,\n 'methods'\n )\n }\n\n // Check that method is a function\n if (typeof config.methods[methodName] !== 'function') {\n throw new DurableObjectConfigError(\n `methods.${methodName} must be a function`,\n 'methods'\n )\n }\n\n // Validate method name format (must be valid JS identifier)\n if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(methodName)) {\n throw new DurableObjectConfigError(\n `'${methodName}' is not a valid method name. Method names must be valid JavaScript identifiers`,\n 'methods'\n )\n }\n }\n }\n\n // Validate handlers are functions\n if (config.init !== undefined && typeof config.init !== 'function') {\n throw new DurableObjectConfigError('init must be a function', 'init')\n }\n\n if (config.fetch !== undefined && typeof config.fetch !== 'function') {\n throw new DurableObjectConfigError('fetch must be a function', 'fetch')\n }\n\n if (config.alarm !== undefined && typeof config.alarm !== 'function') {\n throw new DurableObjectConfigError('alarm must be a function', 'alarm')\n }\n\n if (\n config.webSocketMessage !== undefined &&\n typeof config.webSocketMessage !== 'function'\n ) {\n throw new DurableObjectConfigError(\n 'webSocketMessage must be a function',\n 'webSocketMessage'\n )\n }\n\n if (\n config.webSocketClose !== undefined &&\n typeof config.webSocketClose !== 'function'\n ) {\n throw new DurableObjectConfigError('webSocketClose must be a function', 'webSocketClose')\n }\n\n if (\n config.webSocketError !== undefined &&\n typeof config.webSocketError !== 'function'\n ) {\n throw new DurableObjectConfigError('webSocketError must be a function', 'webSocketError')\n }\n}\n\n// ============================================================================\n// defineDurableObject()\n// ============================================================================\n\n/**\n * Define a Durable Object with native Cloudflare RPC support.\n *\n * This function creates a durable object definition that will be automatically\n * discovered and registered by Cloudwerk during build. The build process generates\n * a class extending Cloudflare's DurableObject with native RPC methods.\n *\n * @typeParam TState - The state type managed by this durable object\n * @typeParam TEnv - The environment bindings type\n * @param config - Durable object configuration\n * @returns Durable object definition\n *\n * @example\n * ```typescript\n * // app/objects/counter.ts\n * import { defineDurableObject } from '@cloudwerk/durable-object'\n *\n * interface CounterState {\n * value: number\n * }\n *\n * export default defineDurableObject<CounterState>({\n * sqlite: true,\n *\n * async init(ctx) {\n * ctx.sql.run(`CREATE TABLE IF NOT EXISTS logs (id INTEGER PRIMARY KEY, action TEXT)`)\n * return { value: 0 }\n * },\n *\n * methods: {\n * async increment(amount = 1) {\n * this.state.value += amount\n * this.ctx.sql.run(`INSERT INTO logs (action) VALUES ('increment')`)\n * return this.state.value\n * },\n *\n * async getValue() {\n * return this.state.value\n * },\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // Usage in route handler:\n * import { durableObjects } from '@cloudwerk/bindings'\n *\n * export async function POST(request: Request, { params }: Context) {\n * const id = durableObjects.Counter.idFromName(params.id)\n * const stub = durableObjects.Counter.get(id)\n *\n * // Native RPC - direct method calls!\n * const value = await stub.increment(5)\n *\n * return Response.json({ value })\n * }\n * ```\n *\n * @example\n * ```typescript\n * // With HTTP fetch handler\n * import { defineDurableObject } from '@cloudwerk/durable-object'\n *\n * export default defineDurableObject<{ count: number }>({\n * init: () => ({ count: 0 }),\n *\n * async fetch(request) {\n * const url = new URL(request.url)\n *\n * if (url.pathname === '/increment') {\n * this.state.count++\n * }\n *\n * return new Response(String(this.state.count))\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With WebSocket support\n * import { defineDurableObject } from '@cloudwerk/durable-object'\n *\n * interface ChatState {\n * messages: string[]\n * }\n *\n * export default defineDurableObject<ChatState>({\n * init: () => ({ messages: [] }),\n *\n * async fetch(request) {\n * const upgradeHeader = request.headers.get('Upgrade')\n * if (upgradeHeader !== 'websocket') {\n * return new Response('Expected WebSocket', { status: 426 })\n * }\n *\n * const pair = new WebSocketPair()\n * this.ctx.acceptWebSocket(pair[1])\n *\n * return new Response(null, { status: 101, webSocket: pair[0] })\n * },\n *\n * async webSocketMessage(ws, message) {\n * this.state.messages.push(String(message))\n *\n * // Broadcast to all connected clients\n * for (const client of this.ctx.getWebSockets()) {\n * client.send(String(message))\n * }\n * },\n * })\n * ```\n *\n * @example\n * ```typescript\n * // With alarm handler\n * import { defineDurableObject } from '@cloudwerk/durable-object'\n *\n * interface CleanupState {\n * lastCleanup: number\n * }\n *\n * export default defineDurableObject<CleanupState>({\n * async init(ctx) {\n * // Schedule first alarm for 1 hour from now\n * await ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)\n * return { lastCleanup: Date.now() }\n * },\n *\n * async alarm() {\n * // Perform periodic cleanup\n * this.state.lastCleanup = Date.now()\n *\n * // Reschedule for next hour\n * await this.ctx.storage.setAlarm(Date.now() + 60 * 60 * 1000)\n * },\n *\n * methods: {\n * getLastCleanup() {\n * return this.state.lastCleanup\n * },\n * },\n * })\n * ```\n */\nexport function defineDurableObject<TState = unknown, TEnv = unknown>(\n config: DurableObjectConfig<TState, TEnv>\n): DurableObjectDefinition<TState, TEnv> {\n // Validate configuration\n validateConfig(config)\n\n // Create the definition object\n const definition: DurableObjectDefinition<TState, TEnv> = {\n __brand: 'cloudwerk-durable-object',\n name: config.name,\n sqlite: config.sqlite ?? false,\n schema: config.schema,\n config,\n }\n\n return definition\n}\n\n/**\n * Check if a value is a durable object definition created by defineDurableObject().\n *\n * @param value - Value to check\n * @returns true if value is a DurableObjectDefinition\n */\nexport function isDurableObjectDefinition(\n value: unknown\n): value is DurableObjectDefinition {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__brand' in value &&\n (value as DurableObjectDefinition).__brand === 'cloudwerk-durable-object'\n )\n}\n\n/**\n * Extract method names from a durable object definition.\n *\n * @param definition - The durable object definition\n * @returns Array of method names\n */\nexport function getMethodNames(definition: DurableObjectDefinition): string[] {\n const methods = definition.config.methods\n if (!methods) {\n return []\n }\n return Object.keys(methods)\n}\n\n/**\n * Check if a durable object definition has any handlers.\n *\n * @param definition - The durable object definition\n * @returns true if at least one handler is defined\n */\nexport function hasHandlers(definition: DurableObjectDefinition): boolean {\n const { config } = definition\n return (\n config.fetch !== undefined ||\n config.alarm !== undefined ||\n config.webSocketMessage !== undefined ||\n (config.methods !== undefined && Object.keys(config.methods).length > 0)\n )\n}\n\n/**\n * Check if a durable object definition has WebSocket support.\n *\n * @param definition - The durable object definition\n * @returns true if any WebSocket handler is defined\n */\nexport function hasWebSocketSupport(definition: DurableObjectDefinition): boolean {\n const { config } = definition\n return (\n config.webSocketMessage !== undefined ||\n config.webSocketClose !== undefined ||\n config.webSocketError !== undefined\n )\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@cloudwerk/durable-object",
3
+ "version": "0.0.1",
4
+ "description": "Durable Objects support for Cloudwerk",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/squirrelsoft-dev/cloudwerk.git",
8
+ "directory": "packages/durable-object"
9
+ },
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "dependencies": {
21
+ "@cloudwerk/core": "0.12.0"
22
+ },
23
+ "devDependencies": {
24
+ "@cloudflare/workers-types": "^4.20241022.0",
25
+ "typescript": "^5.4.0",
26
+ "vitest": "^1.0.0",
27
+ "tsup": "^8.0.0"
28
+ },
29
+ "peerDependencies": {
30
+ "typescript": "^5.0.0",
31
+ "zod": "^3.0.0"
32
+ },
33
+ "peerDependenciesMeta": {
34
+ "zod": {
35
+ "optional": true
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "test": "vitest --run",
41
+ "test:watch": "vitest"
42
+ }
43
+ }