@uploadista/event-emitter-durable-object 0.0.8 → 0.0.9

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/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`@uploadista/core/errors`);c=s(c);let l=require(`@uploadista/core/types`);l=s(l);let u=require(`effect`);u=s(u);function d({durableObject:e}){function t(t){let n=e.idFromName(t);return e.get(n)}return{emit:(e,n)=>{let r=t(e);return u.Effect.tryPromise({try:async()=>{await r.emit(n)},catch:e=>c.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:e})})},subscribe:(e,n)=>u.Effect.tryPromise({try:async()=>{await t(e).subscribe(n)},catch:e=>c.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:e})}),unsubscribe:e=>u.Effect.tryPromise({try:async()=>{await t(e).unsubscribe()},catch:e=>c.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:e})})}}const f=e=>u.Layer.succeed(l.UploadEventEmitter,d(e));exports.uploadEventEmitterDurableObjectStore=f;
1
+ let e=require(`@uploadista/core/errors`),t=require(`@uploadista/core/types`),n=require(`effect`),r=require(`cloudflare:workers`);function i({durableObject:t}){function r(e){let n=t.idFromName(e);return t.get(n)}return{emit:(t,i)=>n.Effect.tryPromise({try:async()=>{console.log(`[DO EventEmitter] Emitting to eventKey: ${t}`,i.substring(0,200)),await r(t).emit(i),console.log(`[DO EventEmitter] Successfully emitted to eventKey: ${t}`)},catch:n=>(console.error(`[DO EventEmitter] Failed to emit to eventKey: ${t}`,n),e.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:n}))}),subscribe:(t,i)=>n.Effect.tryPromise({try:async()=>{await r(t).subscribe(i)},catch:t=>e.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:t})}),unsubscribe:t=>n.Effect.tryPromise({try:async()=>{await r(t).unsubscribe()},catch:t=>e.UploadistaError.fromCode(`UNKNOWN_ERROR`,{cause:t})})}}const a=e=>n.Layer.succeed(t.BaseEventEmitterService,i(e));var o=class extends r.DurableObject{async fetch(e){console.log(`[DO fetch] WebSocket connection request: ${e.url}`);let t=new WebSocketPair,[n,r]=Object.values(t);return this.ctx.acceptWebSocket(r),console.log(`[DO fetch] WebSocket accepted, total connections: ${this.ctx.getWebSockets().length}`),new Response(null,{status:101,webSocket:n})}async emit(e){let t=this.ctx.getWebSockets();console.log(`[DO emit] Broadcasting message to ${t.length} WebSocket(s):`,e.substring(0,200));for(let n of t)try{n.send(e)}catch(e){console.error(`Failed to send message to WebSocket:`,e)}}async subscribe(e){}async unsubscribe(){let e=this.ctx.getWebSockets();for(let t of e)t.close(1e3,`Unsubscribed`)}async webSocketMessage(e,t){console.log(`WebSocket message received: ${t}`)}async webSocketClose(e,t,n,r){if(e.readyState===WebSocket.OPEN){let n=t===1006||t<1e3||t>4999?1e3:t;e.close(n,`Durable Object closing WebSocket`)}}async webSocketError(e,t){console.error(`WebSocket error:`,t),e.readyState===WebSocket.OPEN&&e.close(1011,`WebSocket error occurred`)}};exports.UploadistaDurableObjectImpl=o,exports.durableObjectBaseEventEmitter=i,exports.durableObjectEventEmitter=a;
package/dist/index.d.cts CHANGED
@@ -1,20 +1,136 @@
1
- import { UploadEvent, UploadEventEmitter, WebSocketConnection } from "@uploadista/core/types";
1
+ import { BaseEventEmitter, WebSocketConnection } from "@uploadista/core/types";
2
2
  import { Layer } from "effect";
3
3
  import { DurableObjectNamespace, Rpc } from "@cloudflare/workers-types";
4
+ import { DurableObject } from "cloudflare:workers";
4
5
 
5
6
  //#region src/event-emitter-durable-object.d.ts
7
+
8
+ /**
9
+ * RPC interface for event emitter Durable Objects.
10
+ *
11
+ * Defines the methods that can be called on a Durable Object instance via RPC.
12
+ */
6
13
  type EventEmitterDurableObjectBranded<T> = Rpc.DurableObjectBranded & {
7
- emit: (event: T) => Promise<void>;
14
+ /**
15
+ * Emit a message to all connected WebSocket clients.
16
+ * @param message - The message to broadcast (typically a JSON string)
17
+ */
18
+ emit: (message: T) => Promise<void>;
19
+ /**
20
+ * Subscribe a WebSocket connection to this DO instance.
21
+ * Note: The actual WebSocket connection is established via fetch()
22
+ * @param connection - WebSocket connection details
23
+ */
8
24
  subscribe: (connection: WebSocketConnection) => Promise<void>;
25
+ /**
26
+ * Unsubscribe from events by closing all WebSocket connections.
27
+ */
9
28
  unsubscribe: () => Promise<void>;
10
29
  };
30
+ /**
31
+ * Durable Object namespace type for event emitters.
32
+ *
33
+ * @template T - Type of messages (typically string for JSON messages)
34
+ */
11
35
  type EventEmitterDurableObject<T> = DurableObjectNamespace<EventEmitterDurableObjectBranded<T>>;
12
36
  //#endregion
13
37
  //#region src/do-event-emitter.d.ts
14
- type UploadEventEmitterDurableObjectStoreConfig = {
15
- durableObject: EventEmitterDurableObject<UploadEvent>;
38
+ type DurableObjectEventEmitterConfig = {
39
+ durableObject: EventEmitterDurableObject<string>;
16
40
  };
17
- declare const uploadEventEmitterDurableObjectStore: (config: UploadEventEmitterDurableObjectStoreConfig) => Layer.Layer<UploadEventEmitter, never, never>;
41
+ /**
42
+ * Creates a BaseEventEmitter implementation using Cloudflare Durable Objects.
43
+ *
44
+ * This implementation:
45
+ * - Routes events to Durable Object instances by eventKey
46
+ * - Each eventKey gets its own DO instance
47
+ * - WebSocket connections are managed by the DO
48
+ * - No external broadcaster needed - DO is single source of truth
49
+ *
50
+ * @param config - Configuration with Durable Object namespace
51
+ * @returns BaseEventEmitter implementation
52
+ */
53
+ declare function durableObjectBaseEventEmitter({
54
+ durableObject
55
+ }: DurableObjectEventEmitterConfig): BaseEventEmitter;
56
+ /**
57
+ * Creates a Layer for BaseEventEmitterService using Durable Objects.
58
+ *
59
+ * Use this when creating an Uploadista server with Durable Objects:
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const server = await createUploadistaServer({
64
+ * eventEmitter: durableObjectEventEmitter({
65
+ * durableObject: env.UPLOADISTA_DO,
66
+ * }),
67
+ * // ... other config
68
+ * });
69
+ * ```
70
+ */
71
+ declare const durableObjectEventEmitter: (config: DurableObjectEventEmitterConfig) => Layer.Layer<unknown, never, never>;
72
+ //#endregion
73
+ //#region src/durable-object-impl.d.ts
74
+ /**
75
+ * Base Durable Object implementation for Uploadista event emission.
76
+ *
77
+ * This class provides:
78
+ * - Hibernatable WebSocket connections
79
+ * - Event broadcasting to all connected clients
80
+ * - Automatic connection management
81
+ * - RPC methods for emit/subscribe/unsubscribe
82
+ *
83
+ * Extend this class in your Worker to create event emitter Durable Objects:
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * export class UploadistaDurableObject extends UploadistaDurableObjectImpl {}
88
+ * ```
89
+ */
90
+ declare class UploadistaDurableObjectImpl extends DurableObject {
91
+ /**
92
+ * Handles WebSocket upgrade requests.
93
+ * Creates a hibernatable WebSocket connection.
94
+ */
95
+ fetch(request: Request): Promise<Response>;
96
+ /**
97
+ * RPC method: Emit a message to all connected WebSocket clients.
98
+ *
99
+ * @param message - The message to broadcast (typically a JSON string)
100
+ */
101
+ emit(message: string): Promise<void>;
102
+ /**
103
+ * RPC method: Subscribe a WebSocket connection.
104
+ *
105
+ * Note: This is called via RPC, the actual WebSocket connection
106
+ * is established via the fetch() handler.
107
+ */
108
+ subscribe(_connection: WebSocketConnection): Promise<void>;
109
+ /**
110
+ * RPC method: Unsubscribe from events.
111
+ *
112
+ * Closes all WebSocket connections for this DO instance.
113
+ */
114
+ unsubscribe(): Promise<void>;
115
+ /**
116
+ * Hibernation API: Handle incoming WebSocket messages.
117
+ *
118
+ * Called automatically when a message arrives on a hibernated WebSocket.
119
+ */
120
+ webSocketMessage(_ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
121
+ /**
122
+ * Hibernation API: Handle WebSocket close events.
123
+ *
124
+ * Called automatically when a WebSocket connection closes.
125
+ */
126
+ webSocketClose(ws: WebSocket, code: number, _reason: string, _wasClean: boolean): Promise<void>;
127
+ /**
128
+ * Hibernation API: Handle WebSocket errors.
129
+ *
130
+ * Called automatically when a WebSocket error occurs.
131
+ */
132
+ webSocketError(ws: WebSocket, error: unknown): Promise<void>;
133
+ }
18
134
  //#endregion
19
- export { type EventEmitterDurableObject, uploadEventEmitterDurableObjectStore };
135
+ export { type DurableObjectEventEmitterConfig, type EventEmitterDurableObject, UploadistaDurableObjectImpl, durableObjectBaseEventEmitter, durableObjectEventEmitter };
20
136
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/event-emitter-durable-object.ts","../src/do-event-emitter.ts"],"sourcesContent":[],"mappings":";;;;;KAGY,sCAAsC,GAAA,CAAI;gBACtC,MAAM;0BACI,wBAAwB;EAFtC,WAAA,EAAA,GAAA,GAGS,OAHT,CAAA,IAAA,CAAA;CAAsC;AAClC,KAMJ,yBANI,CAAA,CAAA,CAAA,GAM2B,sBAN3B,CAOd,gCAPc,CAOmB,CAPnB,CAAA,CAAA;;;KCQJ,0CAAA;iBACK,0BAA0B;ADV3C,CAAA;AACgB,cC8DH,oCD9DG,EAAA,CAAA,MAAA,EC+DN,0CD/DM,EAAA,GC+DoC,KAAA,CAAA,KD/DpC,CC+DoC,kBD/DpC,EAAA,KAAA,EAAA,KAAA,CAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/event-emitter-durable-object.ts","../src/do-event-emitter.ts","../src/durable-object-impl.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAAsD,KAA1C,gCAA0C,CAAA,CAAA,CAAA,GAAJ,GAAA,CAAI,oBAAA,GAAA;EAKpC;;;;EAYG,IAAA,EAAA,CAAA,OAAA,EAZH,CAYG,EAAA,GAZG,OAYH,CAAA,IAAA,CAAA;EAAO;AAQ5B;;;;EAAiE,SAAA,EAAA,CAAA,UAAA,EAbvC,mBAauC,EAAA,GAbf,OAae,CAAA,IAAA,CAAA;;;;ECxBrD,WAAA,EAAA,GAAA,GDgBS,OChBT,CAAA,IAAA,CAAA;AAgBZ,CAAA;;;;;AAkEA;KD1DY,+BAA+B,uBACzC,iCAAiC;;;KCzBvB,+BAAA;iBACK;;ADFjB;;;;;;;;AAyBA;;;;AAAiE,iBCRjD,6BAAA,CDQiD;EAAA;AAAA,CAAA,ECN9D,+BDM8D,CAAA,ECN5B,gBDM4B;;;;ACxBjE;AAgBA;;;;;AAkEA;;;;ACnEA;;AAKyC,cD8D5B,yBC9D4B,EAAA,CAAA,MAAA,ED+D/B,+BC/D+B,EAAA,GD+DA,KAAA,CAAA,KC/DA,CAAA,OAAA,EAAA,KAAA,EAAA,KAAA,CAAA;;;;;;;AFrBzC;;;;;;;;AAyBA;;;;AAAiE,cETpD,2BAAA,SAAoC,aAAA,CFSgB;;;;ACxBjE;EAgBgB,KAAA,CAAA,OAAA,ECIO,ODJP,CAAA,ECIiB,ODJY,CCIJ,QDJI,CAAA;EAC3C;;;;AAiEF;yBCtC+B;;;AA7B/B;;;;EA6B+B,SAAA,CAAA,WAAA,EAmBA,mBAnBA,CAAA,EAmBsB,OAnBtB,CAAA,IAAA,CAAA;EAmBA;;;;;EAuBuC,WAAA,CAAA,CAAA,EAZ/C,OAY+C,CAAA,IAAA,CAAA;EAW9D;;;;;EAlFsD,gBAAA,CAAA,GAAA,EAuEhC,SAvEgC,EAAA,OAAA,EAuEZ,WAvEY,GAAA,MAAA,CAAA,EAuEQ,OAvER,CAAA,IAAA,CAAA;;;;;;qBAkFtD,+DAGc;;;;;;qBAgBK,4BAAyB"}
@@ -0,0 +1,136 @@
1
+ import { BaseEventEmitter, WebSocketConnection } from "@uploadista/core/types";
2
+ import { Layer } from "effect";
3
+ import { DurableObject } from "cloudflare:workers";
4
+ import { DurableObjectNamespace, Rpc } from "@cloudflare/workers-types";
5
+
6
+ //#region src/event-emitter-durable-object.d.ts
7
+
8
+ /**
9
+ * RPC interface for event emitter Durable Objects.
10
+ *
11
+ * Defines the methods that can be called on a Durable Object instance via RPC.
12
+ */
13
+ type EventEmitterDurableObjectBranded<T> = Rpc.DurableObjectBranded & {
14
+ /**
15
+ * Emit a message to all connected WebSocket clients.
16
+ * @param message - The message to broadcast (typically a JSON string)
17
+ */
18
+ emit: (message: T) => Promise<void>;
19
+ /**
20
+ * Subscribe a WebSocket connection to this DO instance.
21
+ * Note: The actual WebSocket connection is established via fetch()
22
+ * @param connection - WebSocket connection details
23
+ */
24
+ subscribe: (connection: WebSocketConnection) => Promise<void>;
25
+ /**
26
+ * Unsubscribe from events by closing all WebSocket connections.
27
+ */
28
+ unsubscribe: () => Promise<void>;
29
+ };
30
+ /**
31
+ * Durable Object namespace type for event emitters.
32
+ *
33
+ * @template T - Type of messages (typically string for JSON messages)
34
+ */
35
+ type EventEmitterDurableObject<T> = DurableObjectNamespace<EventEmitterDurableObjectBranded<T>>;
36
+ //#endregion
37
+ //#region src/do-event-emitter.d.ts
38
+ type DurableObjectEventEmitterConfig = {
39
+ durableObject: EventEmitterDurableObject<string>;
40
+ };
41
+ /**
42
+ * Creates a BaseEventEmitter implementation using Cloudflare Durable Objects.
43
+ *
44
+ * This implementation:
45
+ * - Routes events to Durable Object instances by eventKey
46
+ * - Each eventKey gets its own DO instance
47
+ * - WebSocket connections are managed by the DO
48
+ * - No external broadcaster needed - DO is single source of truth
49
+ *
50
+ * @param config - Configuration with Durable Object namespace
51
+ * @returns BaseEventEmitter implementation
52
+ */
53
+ declare function durableObjectBaseEventEmitter({
54
+ durableObject
55
+ }: DurableObjectEventEmitterConfig): BaseEventEmitter;
56
+ /**
57
+ * Creates a Layer for BaseEventEmitterService using Durable Objects.
58
+ *
59
+ * Use this when creating an Uploadista server with Durable Objects:
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * const server = await createUploadistaServer({
64
+ * eventEmitter: durableObjectEventEmitter({
65
+ * durableObject: env.UPLOADISTA_DO,
66
+ * }),
67
+ * // ... other config
68
+ * });
69
+ * ```
70
+ */
71
+ declare const durableObjectEventEmitter: (config: DurableObjectEventEmitterConfig) => Layer.Layer<unknown, never, never>;
72
+ //#endregion
73
+ //#region src/durable-object-impl.d.ts
74
+ /**
75
+ * Base Durable Object implementation for Uploadista event emission.
76
+ *
77
+ * This class provides:
78
+ * - Hibernatable WebSocket connections
79
+ * - Event broadcasting to all connected clients
80
+ * - Automatic connection management
81
+ * - RPC methods for emit/subscribe/unsubscribe
82
+ *
83
+ * Extend this class in your Worker to create event emitter Durable Objects:
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * export class UploadistaDurableObject extends UploadistaDurableObjectImpl {}
88
+ * ```
89
+ */
90
+ declare class UploadistaDurableObjectImpl extends DurableObject {
91
+ /**
92
+ * Handles WebSocket upgrade requests.
93
+ * Creates a hibernatable WebSocket connection.
94
+ */
95
+ fetch(request: Request): Promise<Response>;
96
+ /**
97
+ * RPC method: Emit a message to all connected WebSocket clients.
98
+ *
99
+ * @param message - The message to broadcast (typically a JSON string)
100
+ */
101
+ emit(message: string): Promise<void>;
102
+ /**
103
+ * RPC method: Subscribe a WebSocket connection.
104
+ *
105
+ * Note: This is called via RPC, the actual WebSocket connection
106
+ * is established via the fetch() handler.
107
+ */
108
+ subscribe(_connection: WebSocketConnection): Promise<void>;
109
+ /**
110
+ * RPC method: Unsubscribe from events.
111
+ *
112
+ * Closes all WebSocket connections for this DO instance.
113
+ */
114
+ unsubscribe(): Promise<void>;
115
+ /**
116
+ * Hibernation API: Handle incoming WebSocket messages.
117
+ *
118
+ * Called automatically when a message arrives on a hibernated WebSocket.
119
+ */
120
+ webSocketMessage(_ws: WebSocket, message: ArrayBuffer | string): Promise<void>;
121
+ /**
122
+ * Hibernation API: Handle WebSocket close events.
123
+ *
124
+ * Called automatically when a WebSocket connection closes.
125
+ */
126
+ webSocketClose(ws: WebSocket, code: number, _reason: string, _wasClean: boolean): Promise<void>;
127
+ /**
128
+ * Hibernation API: Handle WebSocket errors.
129
+ *
130
+ * Called automatically when a WebSocket error occurs.
131
+ */
132
+ webSocketError(ws: WebSocket, error: unknown): Promise<void>;
133
+ }
134
+ //#endregion
135
+ export { type DurableObjectEventEmitterConfig, type EventEmitterDurableObject, UploadistaDurableObjectImpl, durableObjectBaseEventEmitter, durableObjectEventEmitter };
136
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/event-emitter-durable-object.ts","../src/do-event-emitter.ts","../src/durable-object-impl.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;AAQA;AAAsD,KAA1C,gCAA0C,CAAA,CAAA,CAAA,GAAJ,GAAA,CAAI,oBAAA,GAAA;EAKpC;;;;EAYG,IAAA,EAAA,CAAA,OAAA,EAZH,CAYG,EAAA,GAZG,OAYH,CAAA,IAAA,CAAA;EAAO;AAQ5B;;;;EAAiE,SAAA,EAAA,CAAA,UAAA,EAbvC,mBAauC,EAAA,GAbf,OAae,CAAA,IAAA,CAAA;;;;ECxBrD,WAAA,EAAA,GAAA,GDgBS,OChBT,CAAA,IAAA,CAAA;AAgBZ,CAAA;;;;;AAkEA;KD1DY,+BAA+B,uBACzC,iCAAiC;;;KCzBvB,+BAAA;iBACK;;ADFjB;;;;;;;;AAyBA;;;;AAAiE,iBCRjD,6BAAA,CDQiD;EAAA;AAAA,CAAA,ECN9D,+BDM8D,CAAA,ECN5B,gBDM4B;;;;ACxBjE;AAgBA;;;;;AAkEA;;;;ACnEA;;AAKyC,cD8D5B,yBC9D4B,EAAA,CAAA,MAAA,ED+D/B,+BC/D+B,EAAA,GD+DA,KAAA,CAAA,KC/DA,CAAA,OAAA,EAAA,KAAA,EAAA,KAAA,CAAA;;;;;;;AFrBzC;;;;;;;;AAyBA;;;;AAAiE,cETpD,2BAAA,SAAoC,aAAA,CFSgB;;;;ACxBjE;EAgBgB,KAAA,CAAA,OAAA,ECIO,ODJP,CAAA,ECIiB,ODJY,CCIJ,QDJI,CAAA;EAC3C;;;;AAiEF;yBCtC+B;;;AA7B/B;;;;EA6B+B,SAAA,CAAA,WAAA,EAmBA,mBAnBA,CAAA,EAmBsB,OAnBtB,CAAA,IAAA,CAAA;EAmBA;;;;;EAuBuC,WAAA,CAAA,CAAA,EAZ/C,OAY+C,CAAA,IAAA,CAAA;EAW9D;;;;;EAlFsD,gBAAA,CAAA,GAAA,EAuEhC,SAvEgC,EAAA,OAAA,EAuEZ,WAvEY,GAAA,MAAA,CAAA,EAuEQ,OAvER,CAAA,IAAA,CAAA;;;;;;qBAkFtD,+DAGc;;;;;;qBAgBK,4BAAyB"}
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import{UploadistaError as e}from"@uploadista/core/errors";import{BaseEventEmitterService as t}from"@uploadista/core/types";import{Effect as n,Layer as r}from"effect";import{DurableObject as i}from"cloudflare:workers";function a({durableObject:t}){function r(e){let n=t.idFromName(e);return t.get(n)}return{emit:(t,i)=>n.tryPromise({try:async()=>{console.log(`[DO EventEmitter] Emitting to eventKey: ${t}`,i.substring(0,200)),await r(t).emit(i),console.log(`[DO EventEmitter] Successfully emitted to eventKey: ${t}`)},catch:n=>(console.error(`[DO EventEmitter] Failed to emit to eventKey: ${t}`,n),e.fromCode(`UNKNOWN_ERROR`,{cause:n}))}),subscribe:(t,i)=>n.tryPromise({try:async()=>{await r(t).subscribe(i)},catch:t=>e.fromCode(`UNKNOWN_ERROR`,{cause:t})}),unsubscribe:t=>n.tryPromise({try:async()=>{await r(t).unsubscribe()},catch:t=>e.fromCode(`UNKNOWN_ERROR`,{cause:t})})}}const o=e=>r.succeed(t,a(e));var s=class extends i{async fetch(e){console.log(`[DO fetch] WebSocket connection request: ${e.url}`);let t=new WebSocketPair,[n,r]=Object.values(t);return this.ctx.acceptWebSocket(r),console.log(`[DO fetch] WebSocket accepted, total connections: ${this.ctx.getWebSockets().length}`),new Response(null,{status:101,webSocket:n})}async emit(e){let t=this.ctx.getWebSockets();console.log(`[DO emit] Broadcasting message to ${t.length} WebSocket(s):`,e.substring(0,200));for(let n of t)try{n.send(e)}catch(e){console.error(`Failed to send message to WebSocket:`,e)}}async subscribe(e){}async unsubscribe(){let e=this.ctx.getWebSockets();for(let t of e)t.close(1e3,`Unsubscribed`)}async webSocketMessage(e,t){console.log(`WebSocket message received: ${t}`)}async webSocketClose(e,t,n,r){if(e.readyState===WebSocket.OPEN){let n=t===1006||t<1e3||t>4999?1e3:t;e.close(n,`Durable Object closing WebSocket`)}}async webSocketError(e,t){console.error(`WebSocket error:`,t),e.readyState===WebSocket.OPEN&&e.close(1011,`WebSocket error occurred`)}};export{s as UploadistaDurableObjectImpl,a as durableObjectBaseEventEmitter,o as durableObjectEventEmitter};
2
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/do-event-emitter.ts","../src/durable-object-impl.ts"],"sourcesContent":["import { UploadistaError } from \"@uploadista/core/errors\";\nimport type { WebSocketConnection } from \"@uploadista/core/types\";\nimport {\n type BaseEventEmitter,\n BaseEventEmitterService,\n} from \"@uploadista/core/types\";\nimport { Effect, Layer } from \"effect\";\nimport type { EventEmitterDurableObject } from \"./event-emitter-durable-object\";\n\nexport type DurableObjectEventEmitterConfig = {\n durableObject: EventEmitterDurableObject<string>;\n};\n\n/**\n * Creates a BaseEventEmitter implementation using Cloudflare Durable Objects.\n *\n * This implementation:\n * - Routes events to Durable Object instances by eventKey\n * - Each eventKey gets its own DO instance\n * - WebSocket connections are managed by the DO\n * - No external broadcaster needed - DO is single source of truth\n *\n * @param config - Configuration with Durable Object namespace\n * @returns BaseEventEmitter implementation\n */\nexport function durableObjectBaseEventEmitter({\n durableObject,\n}: DurableObjectEventEmitterConfig): BaseEventEmitter {\n function getStub(eventKey: string) {\n const id = durableObject.idFromName(eventKey);\n return durableObject.get(id);\n }\n\n return {\n emit: (eventKey: string, message: string) => {\n return Effect.tryPromise({\n try: async () => {\n console.log(`[DO EventEmitter] Emitting to eventKey: ${eventKey}`, message.substring(0, 200));\n const stub = getStub(eventKey);\n // Call the emit RPC method on the Durable Object\n await (stub as any).emit(message);\n console.log(`[DO EventEmitter] Successfully emitted to eventKey: ${eventKey}`);\n },\n catch: (cause) => {\n console.error(`[DO EventEmitter] Failed to emit to eventKey: ${eventKey}`, cause);\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n subscribe: (eventKey: string, connection: WebSocketConnection) => {\n return Effect.tryPromise({\n try: async () => {\n const stub = getStub(eventKey);\n // Call the subscribe RPC method on the Durable Object\n await (stub as any).subscribe(connection);\n },\n catch: (cause) => {\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n unsubscribe: (eventKey: string) => {\n return Effect.tryPromise({\n try: async () => {\n const stub = getStub(eventKey);\n // Call the unsubscribe RPC method on the Durable Object\n await (stub as any).unsubscribe();\n },\n catch: (cause) => {\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n };\n}\n\n/**\n * Creates a Layer for BaseEventEmitterService using Durable Objects.\n *\n * Use this when creating an Uploadista server with Durable Objects:\n *\n * @example\n * ```typescript\n * const server = await createUploadistaServer({\n * eventEmitter: durableObjectEventEmitter({\n * durableObject: env.UPLOADISTA_DO,\n * }),\n * // ... other config\n * });\n * ```\n */\nexport const durableObjectEventEmitter = (\n config: DurableObjectEventEmitterConfig,\n) =>\n Layer.succeed(\n BaseEventEmitterService,\n durableObjectBaseEventEmitter(config),\n );\n","import { DurableObject } from \"cloudflare:workers\";\nimport type { WebSocketConnection } from \"@uploadista/core/types\";\n\n// WebSocketPair is available globally in Cloudflare Workers\ndeclare const WebSocketPair: {\n new (): { 0: WebSocket; 1: WebSocket };\n};\n\n/**\n * Base Durable Object implementation for Uploadista event emission.\n *\n * This class provides:\n * - Hibernatable WebSocket connections\n * - Event broadcasting to all connected clients\n * - Automatic connection management\n * - RPC methods for emit/subscribe/unsubscribe\n *\n * Extend this class in your Worker to create event emitter Durable Objects:\n *\n * @example\n * ```typescript\n * export class UploadistaDurableObject extends UploadistaDurableObjectImpl {}\n * ```\n */\nexport class UploadistaDurableObjectImpl extends DurableObject {\n /**\n * Handles WebSocket upgrade requests.\n * Creates a hibernatable WebSocket connection.\n */\n async fetch(request: Request): Promise<Response> {\n console.log(`[DO fetch] WebSocket connection request: ${request.url}`);\n\n // Creates two ends of a WebSocket connection\n const pair = new WebSocketPair();\n const [client, server] = Object.values(pair);\n\n // Accept WebSocket with hibernation support\n // This allows the DO to be evicted from memory during idle periods\n this.ctx.acceptWebSocket(server);\n\n console.log(`[DO fetch] WebSocket accepted, total connections: ${this.ctx.getWebSockets().length}`);\n\n return new Response(null, {\n status: 101,\n webSocket: client as unknown,\n } as ResponseInit);\n }\n\n /**\n * RPC method: Emit a message to all connected WebSocket clients.\n *\n * @param message - The message to broadcast (typically a JSON string)\n */\n async emit(message: string): Promise<void> {\n const websockets = this.ctx.getWebSockets();\n console.log(`[DO emit] Broadcasting message to ${websockets.length} WebSocket(s):`, message.substring(0, 200));\n\n for (const ws of websockets) {\n try {\n ws.send(message);\n } catch (error) {\n console.error(\"Failed to send message to WebSocket:\", error);\n }\n }\n }\n\n /**\n * RPC method: Subscribe a WebSocket connection.\n *\n * Note: This is called via RPC, the actual WebSocket connection\n * is established via the fetch() handler.\n */\n async subscribe(_connection: WebSocketConnection): Promise<void> {\n // The connection is already established via fetch()\n // This method exists for API compatibility\n return;\n }\n\n /**\n * RPC method: Unsubscribe from events.\n *\n * Closes all WebSocket connections for this DO instance.\n */\n async unsubscribe(): Promise<void> {\n const websockets = this.ctx.getWebSockets();\n for (const ws of websockets) {\n ws.close(1000, \"Unsubscribed\");\n }\n }\n\n /**\n * Hibernation API: Handle incoming WebSocket messages.\n *\n * Called automatically when a message arrives on a hibernated WebSocket.\n */\n async webSocketMessage(_ws: WebSocket, message: ArrayBuffer | string) {\n // Log message for debugging\n console.log(`WebSocket message received: ${message}`);\n }\n\n /**\n * Hibernation API: Handle WebSocket close events.\n *\n * Called automatically when a WebSocket connection closes.\n */\n async webSocketClose(\n ws: WebSocket,\n code: number,\n _reason: string,\n _wasClean: boolean,\n ) {\n // Clean up the connection\n if (ws.readyState === WebSocket.OPEN) {\n // Use a valid close code instead of potentially reserved ones\n const validCloseCode =\n code === 1006 || code < 1000 || code > 4999 ? 1000 : code;\n ws.close(validCloseCode, \"Durable Object closing WebSocket\");\n }\n }\n\n /**\n * Hibernation API: Handle WebSocket errors.\n *\n * Called automatically when a WebSocket error occurs.\n */\n async webSocketError(ws: WebSocket, error: unknown) {\n console.error(\"WebSocket error:\", error);\n if (ws.readyState === WebSocket.OPEN) {\n ws.close(1011, \"WebSocket error occurred\");\n }\n }\n}\n"],"mappings":"yNAyBA,SAAgB,EAA8B,CAC5C,iBACoD,CACpD,SAAS,EAAQ,EAAkB,CACjC,IAAM,EAAK,EAAc,WAAW,EAAS,CAC7C,OAAO,EAAc,IAAI,EAAG,CAG9B,MAAO,CACL,MAAO,EAAkB,IAChB,EAAO,WAAW,CACvB,IAAK,SAAY,CACf,QAAQ,IAAI,2CAA2C,IAAY,EAAQ,UAAU,EAAG,IAAI,CAAC,CAG7F,MAFa,EAAQ,EAAS,CAEV,KAAK,EAAQ,CACjC,QAAQ,IAAI,uDAAuD,IAAW,EAEhF,MAAQ,IACN,QAAQ,MAAM,iDAAiD,IAAY,EAAM,CAC1E,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,EAE9D,CAAC,CAEJ,WAAY,EAAkB,IACrB,EAAO,WAAW,CACvB,IAAK,SAAY,CAGf,MAFa,EAAQ,EAAS,CAEV,UAAU,EAAW,EAE3C,MAAQ,GACC,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,CAE9D,CAAC,CAEJ,YAAc,GACL,EAAO,WAAW,CACvB,IAAK,SAAY,CAGf,MAFa,EAAQ,EAAS,CAEV,aAAa,EAEnC,MAAQ,GACC,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,CAE9D,CAAC,CAEL,CAkBH,MAAa,EACX,GAEA,EAAM,QACJ,EACA,EAA8B,EAAO,CACtC,CCzEH,IAAa,EAAb,cAAiD,CAAc,CAK7D,MAAM,MAAM,EAAqC,CAC/C,QAAQ,IAAI,4CAA4C,EAAQ,MAAM,CAGtE,IAAM,EAAO,IAAI,cACX,CAAC,EAAQ,GAAU,OAAO,OAAO,EAAK,CAQ5C,OAJA,KAAK,IAAI,gBAAgB,EAAO,CAEhC,QAAQ,IAAI,qDAAqD,KAAK,IAAI,eAAe,CAAC,SAAS,CAE5F,IAAI,SAAS,KAAM,CACxB,OAAQ,IACR,UAAW,EACZ,CAAiB,CAQpB,MAAM,KAAK,EAAgC,CACzC,IAAM,EAAa,KAAK,IAAI,eAAe,CAC3C,QAAQ,IAAI,qCAAqC,EAAW,OAAO,gBAAiB,EAAQ,UAAU,EAAG,IAAI,CAAC,CAE9G,IAAK,IAAM,KAAM,EACf,GAAI,CACF,EAAG,KAAK,EAAQ,OACT,EAAO,CACd,QAAQ,MAAM,uCAAwC,EAAM,EAWlE,MAAM,UAAU,EAAiD,EAWjE,MAAM,aAA6B,CACjC,IAAM,EAAa,KAAK,IAAI,eAAe,CAC3C,IAAK,IAAM,KAAM,EACf,EAAG,MAAM,IAAM,eAAe,CASlC,MAAM,iBAAiB,EAAgB,EAA+B,CAEpE,QAAQ,IAAI,+BAA+B,IAAU,CAQvD,MAAM,eACJ,EACA,EACA,EACA,EACA,CAEA,GAAI,EAAG,aAAe,UAAU,KAAM,CAEpC,IAAM,EACJ,IAAS,MAAQ,EAAO,KAAQ,EAAO,KAAO,IAAO,EACvD,EAAG,MAAM,EAAgB,mCAAmC,EAShE,MAAM,eAAe,EAAe,EAAgB,CAClD,QAAQ,MAAM,mBAAoB,EAAM,CACpC,EAAG,aAAe,UAAU,MAC9B,EAAG,MAAM,KAAM,2BAA2B"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/event-emitter-durable-object",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.0.9",
5
5
  "description": "Durable Object event emitter for Uploadista",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -14,12 +14,12 @@
14
14
  },
15
15
  "dependencies": {
16
16
  "@cloudflare/workers-types": "4.20251014.0",
17
- "effect": "3.18.4",
18
- "@uploadista/core": "0.0.8"
17
+ "effect": "3.19.0",
18
+ "@uploadista/core": "0.0.9"
19
19
  },
20
20
  "devDependencies": {
21
- "tsdown": "0.15.10",
22
- "@uploadista/typescript-config": "0.0.8"
21
+ "tsdown": "0.16.0",
22
+ "@uploadista/typescript-config": "0.0.9"
23
23
  },
24
24
  "scripts": {
25
25
  "build": "tsdown",
@@ -1,60 +1,70 @@
1
1
  import { UploadistaError } from "@uploadista/core/errors";
2
+ import type { WebSocketConnection } from "@uploadista/core/types";
2
3
  import {
3
- type EventEmitter,
4
- type UploadEvent,
5
- UploadEventEmitter,
4
+ type BaseEventEmitter,
5
+ BaseEventEmitterService,
6
6
  } from "@uploadista/core/types";
7
7
  import { Effect, Layer } from "effect";
8
- import type {
9
- EventEmitterDurableObject,
10
- EventEmitterDurableObjectBranded,
11
- } from "./event-emitter-durable-object";
8
+ import type { EventEmitterDurableObject } from "./event-emitter-durable-object";
12
9
 
13
- export type UploadEventEmitterDurableObjectStoreConfig = {
14
- durableObject: EventEmitterDurableObject<UploadEvent>;
10
+ export type DurableObjectEventEmitterConfig = {
11
+ durableObject: EventEmitterDurableObject<string>;
15
12
  };
16
13
 
17
- export function makeEventEmitterDurableObjectStore<T>({
14
+ /**
15
+ * Creates a BaseEventEmitter implementation using Cloudflare Durable Objects.
16
+ *
17
+ * This implementation:
18
+ * - Routes events to Durable Object instances by eventKey
19
+ * - Each eventKey gets its own DO instance
20
+ * - WebSocket connections are managed by the DO
21
+ * - No external broadcaster needed - DO is single source of truth
22
+ *
23
+ * @param config - Configuration with Durable Object namespace
24
+ * @returns BaseEventEmitter implementation
25
+ */
26
+ export function durableObjectBaseEventEmitter({
18
27
  durableObject,
19
- }: UploadEventEmitterDurableObjectStoreConfig): EventEmitter<T> {
20
- function getStub(key: string) {
21
- const id = durableObject.idFromName(key);
22
- return durableObject.get(
23
- id,
24
- ) as unknown as EventEmitterDurableObjectBranded<T>;
28
+ }: DurableObjectEventEmitterConfig): BaseEventEmitter {
29
+ function getStub(eventKey: string) {
30
+ const id = durableObject.idFromName(eventKey);
31
+ return durableObject.get(id);
25
32
  }
26
33
 
27
34
  return {
28
- emit: (key: string, event: T) => {
29
- const stub = getStub(key);
35
+ emit: (eventKey: string, message: string) => {
30
36
  return Effect.tryPromise({
31
37
  try: async () => {
32
- await stub.emit(event);
33
- return;
38
+ console.log(`[DO EventEmitter] Emitting to eventKey: ${eventKey}`, message.substring(0, 200));
39
+ const stub = getStub(eventKey);
40
+ // Call the emit RPC method on the Durable Object
41
+ await (stub as any).emit(message);
42
+ console.log(`[DO EventEmitter] Successfully emitted to eventKey: ${eventKey}`);
34
43
  },
35
44
  catch: (cause) => {
45
+ console.error(`[DO EventEmitter] Failed to emit to eventKey: ${eventKey}`, cause);
36
46
  return UploadistaError.fromCode("UNKNOWN_ERROR", { cause });
37
47
  },
38
48
  });
39
49
  },
40
- subscribe: (key: string, connection) => {
50
+ subscribe: (eventKey: string, connection: WebSocketConnection) => {
41
51
  return Effect.tryPromise({
42
52
  try: async () => {
43
- const stub = getStub(key);
44
- await stub.subscribe(connection);
45
- return;
53
+ const stub = getStub(eventKey);
54
+ // Call the subscribe RPC method on the Durable Object
55
+ await (stub as any).subscribe(connection);
46
56
  },
47
57
  catch: (cause) => {
48
58
  return UploadistaError.fromCode("UNKNOWN_ERROR", { cause });
49
59
  },
50
60
  });
51
61
  },
52
- unsubscribe: (key: string) => {
62
+ unsubscribe: (eventKey: string) => {
53
63
  return Effect.tryPromise({
54
64
  try: async () => {
55
- const stub = getStub(key);
56
- await stub.unsubscribe();
57
- return;
65
+ const stub = getStub(eventKey);
66
+ // Call the unsubscribe RPC method on the Durable Object
67
+ await (stub as any).unsubscribe();
58
68
  },
59
69
  catch: (cause) => {
60
70
  return UploadistaError.fromCode("UNKNOWN_ERROR", { cause });
@@ -64,10 +74,25 @@ export function makeEventEmitterDurableObjectStore<T>({
64
74
  };
65
75
  }
66
76
 
67
- export const uploadEventEmitterDurableObjectStore = (
68
- config: UploadEventEmitterDurableObjectStoreConfig,
77
+ /**
78
+ * Creates a Layer for BaseEventEmitterService using Durable Objects.
79
+ *
80
+ * Use this when creating an Uploadista server with Durable Objects:
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const server = await createUploadistaServer({
85
+ * eventEmitter: durableObjectEventEmitter({
86
+ * durableObject: env.UPLOADISTA_DO,
87
+ * }),
88
+ * // ... other config
89
+ * });
90
+ * ```
91
+ */
92
+ export const durableObjectEventEmitter = (
93
+ config: DurableObjectEventEmitterConfig,
69
94
  ) =>
70
95
  Layer.succeed(
71
- UploadEventEmitter,
72
- makeEventEmitterDurableObjectStore<UploadEvent>(config),
96
+ BaseEventEmitterService,
97
+ durableObjectBaseEventEmitter(config),
73
98
  );
@@ -1,58 +1,132 @@
1
- // import { DurableObject } from "cloudflare:workers";
2
- // import { WebSocketPair } from "@cloudflare/workers-types";
3
- // import type { UploadEvent } from "@uploadista/core/types";
4
-
5
- // export class UploadEventDurableObject extends DurableObject {
6
- // async fetch(_request: Request): Promise<Response> {
7
- // // Creates two ends of a WebSocket connection.
8
- // const webSocketPair = new WebSocketPair();
9
- // const [client, server] = Object.values(webSocketPair);
10
-
11
- // // Calling `acceptWebSocket()` informs the runtime that this WebSocket is to begin terminating
12
- // // request within the Durable Object. It has the effect of "accepting" the connection,
13
- // // and allowing the WebSocket to send and receive messages.
14
- // // Unlike `ws.accept()`, `state.acceptWebSocket(ws)` informs the Workers Runtime that the WebSocket
15
- // // is "hibernatable", so the runtime does not need to pin this Durable Object to memory while
16
- // // the connection is open. During periods of inactivity, the Durable Object can be evicted
17
- // // from memory, but the WebSocket connection will remain open. If at some later point the
18
- // // WebSocket receives a message, the runtime will recreate the Durable Object
19
- // // (run the `constructor`) and deliver the message to the appropriate handler.
20
- // this.ctx.acceptWebSocket(server);
21
-
22
- // return new Response(null, {
23
- // status: 101,
24
- // webSocket: client,
25
- // });
26
- // }
27
-
28
- // emit(event: UploadEvent) {
29
- // for (const ws of this.ctx.getWebSockets()) {
30
- // ws.send(JSON.stringify(event satisfies UploadEvent));
31
- // }
32
- // }
33
-
34
- // async webSocketMessage(ws: WebSocket, message: ArrayBuffer | string) {
35
- // // Upon receiving a message from the client, the server replies with the same message,
36
- // // and the total number of connections with the "[Durable Object]: " prefix
37
- // ws.send(
38
- // `[Durable Object] message: ${message}, connections: ${this.ctx.getWebSockets().length}`,
39
- // );
40
- // }
41
-
42
- // async webSocketClose(
43
- // ws: WebSocket,
44
- // code: number,
45
- // _reason: string,
46
- // _wasClean: boolean,
47
- // ) {
48
- // // If the client closes the connection, the runtime will invoke the webSocketClose() handler.
49
- // // Don't try to close an already closed WebSocket or use reserved close codes
50
- // if (ws.readyState === WebSocket.OPEN) {
51
- // // Use a valid close code instead of the potentially reserved one from the client
52
- // // 1000 = Normal Closure, 1001 = Going Away are safe codes to use
53
- // const validCloseCode =
54
- // code === 1006 || code < 1000 || code > 4999 ? 1000 : code;
55
- // ws.close(validCloseCode, "Durable Object is closing WebSocket");
56
- // }
57
- // }
58
- // }
1
+ import { DurableObject } from "cloudflare:workers";
2
+ import type { WebSocketConnection } from "@uploadista/core/types";
3
+
4
+ // WebSocketPair is available globally in Cloudflare Workers
5
+ declare const WebSocketPair: {
6
+ new (): { 0: WebSocket; 1: WebSocket };
7
+ };
8
+
9
+ /**
10
+ * Base Durable Object implementation for Uploadista event emission.
11
+ *
12
+ * This class provides:
13
+ * - Hibernatable WebSocket connections
14
+ * - Event broadcasting to all connected clients
15
+ * - Automatic connection management
16
+ * - RPC methods for emit/subscribe/unsubscribe
17
+ *
18
+ * Extend this class in your Worker to create event emitter Durable Objects:
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * export class UploadistaDurableObject extends UploadistaDurableObjectImpl {}
23
+ * ```
24
+ */
25
+ export class UploadistaDurableObjectImpl extends DurableObject {
26
+ /**
27
+ * Handles WebSocket upgrade requests.
28
+ * Creates a hibernatable WebSocket connection.
29
+ */
30
+ async fetch(request: Request): Promise<Response> {
31
+ console.log(`[DO fetch] WebSocket connection request: ${request.url}`);
32
+
33
+ // Creates two ends of a WebSocket connection
34
+ const pair = new WebSocketPair();
35
+ const [client, server] = Object.values(pair);
36
+
37
+ // Accept WebSocket with hibernation support
38
+ // This allows the DO to be evicted from memory during idle periods
39
+ this.ctx.acceptWebSocket(server);
40
+
41
+ console.log(`[DO fetch] WebSocket accepted, total connections: ${this.ctx.getWebSockets().length}`);
42
+
43
+ return new Response(null, {
44
+ status: 101,
45
+ webSocket: client as unknown,
46
+ } as ResponseInit);
47
+ }
48
+
49
+ /**
50
+ * RPC method: Emit a message to all connected WebSocket clients.
51
+ *
52
+ * @param message - The message to broadcast (typically a JSON string)
53
+ */
54
+ async emit(message: string): Promise<void> {
55
+ const websockets = this.ctx.getWebSockets();
56
+ console.log(`[DO emit] Broadcasting message to ${websockets.length} WebSocket(s):`, message.substring(0, 200));
57
+
58
+ for (const ws of websockets) {
59
+ try {
60
+ ws.send(message);
61
+ } catch (error) {
62
+ console.error("Failed to send message to WebSocket:", error);
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * RPC method: Subscribe a WebSocket connection.
69
+ *
70
+ * Note: This is called via RPC, the actual WebSocket connection
71
+ * is established via the fetch() handler.
72
+ */
73
+ async subscribe(_connection: WebSocketConnection): Promise<void> {
74
+ // The connection is already established via fetch()
75
+ // This method exists for API compatibility
76
+ return;
77
+ }
78
+
79
+ /**
80
+ * RPC method: Unsubscribe from events.
81
+ *
82
+ * Closes all WebSocket connections for this DO instance.
83
+ */
84
+ async unsubscribe(): Promise<void> {
85
+ const websockets = this.ctx.getWebSockets();
86
+ for (const ws of websockets) {
87
+ ws.close(1000, "Unsubscribed");
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Hibernation API: Handle incoming WebSocket messages.
93
+ *
94
+ * Called automatically when a message arrives on a hibernated WebSocket.
95
+ */
96
+ async webSocketMessage(_ws: WebSocket, message: ArrayBuffer | string) {
97
+ // Log message for debugging
98
+ console.log(`WebSocket message received: ${message}`);
99
+ }
100
+
101
+ /**
102
+ * Hibernation API: Handle WebSocket close events.
103
+ *
104
+ * Called automatically when a WebSocket connection closes.
105
+ */
106
+ async webSocketClose(
107
+ ws: WebSocket,
108
+ code: number,
109
+ _reason: string,
110
+ _wasClean: boolean,
111
+ ) {
112
+ // Clean up the connection
113
+ if (ws.readyState === WebSocket.OPEN) {
114
+ // Use a valid close code instead of potentially reserved ones
115
+ const validCloseCode =
116
+ code === 1006 || code < 1000 || code > 4999 ? 1000 : code;
117
+ ws.close(validCloseCode, "Durable Object closing WebSocket");
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Hibernation API: Handle WebSocket errors.
123
+ *
124
+ * Called automatically when a WebSocket error occurs.
125
+ */
126
+ async webSocketError(ws: WebSocket, error: unknown) {
127
+ console.error("WebSocket error:", error);
128
+ if (ws.readyState === WebSocket.OPEN) {
129
+ ws.close(1011, "WebSocket error occurred");
130
+ }
131
+ }
132
+ }
@@ -1,13 +1,36 @@
1
1
  import type { DurableObjectNamespace, Rpc } from "@cloudflare/workers-types";
2
2
  import type { WebSocketConnection } from "@uploadista/core/types";
3
3
 
4
+ /**
5
+ * RPC interface for event emitter Durable Objects.
6
+ *
7
+ * Defines the methods that can be called on a Durable Object instance via RPC.
8
+ */
4
9
  export type EventEmitterDurableObjectBranded<T> = Rpc.DurableObjectBranded & {
5
- emit: (event: T) => Promise<void>;
10
+ /**
11
+ * Emit a message to all connected WebSocket clients.
12
+ * @param message - The message to broadcast (typically a JSON string)
13
+ */
14
+ emit: (message: T) => Promise<void>;
15
+
16
+ /**
17
+ * Subscribe a WebSocket connection to this DO instance.
18
+ * Note: The actual WebSocket connection is established via fetch()
19
+ * @param connection - WebSocket connection details
20
+ */
6
21
  subscribe: (connection: WebSocketConnection) => Promise<void>;
22
+
23
+ /**
24
+ * Unsubscribe from events by closing all WebSocket connections.
25
+ */
7
26
  unsubscribe: () => Promise<void>;
8
27
  };
9
28
 
10
- // Durable Object
29
+ /**
30
+ * Durable Object namespace type for event emitters.
31
+ *
32
+ * @template T - Type of messages (typically string for JSON messages)
33
+ */
11
34
  export type EventEmitterDurableObject<T> = DurableObjectNamespace<
12
35
  EventEmitterDurableObjectBranded<T>
13
36
  >;
package/src/index.ts CHANGED
@@ -1,3 +1,7 @@
1
- export { uploadEventEmitterDurableObjectStore } from "./do-event-emitter";
2
- // export { UploadEventDurableObject } from "./durable-object-impl";
1
+ export {
2
+ durableObjectEventEmitter,
3
+ durableObjectBaseEventEmitter,
4
+ type DurableObjectEventEmitterConfig,
5
+ } from "./do-event-emitter";
6
+ export { UploadistaDurableObjectImpl } from "./durable-object-impl";
3
7
  export type { EventEmitterDurableObject } from "./event-emitter-durable-object";
package/dist/index.d.ts DELETED
@@ -1,20 +0,0 @@
1
- import { UploadEvent, UploadEventEmitter, WebSocketConnection } from "@uploadista/core/types";
2
- import { Layer } from "effect";
3
- import { DurableObjectNamespace, Rpc } from "@cloudflare/workers-types";
4
-
5
- //#region src/event-emitter-durable-object.d.ts
6
- type EventEmitterDurableObjectBranded<T> = Rpc.DurableObjectBranded & {
7
- emit: (event: T) => Promise<void>;
8
- subscribe: (connection: WebSocketConnection) => Promise<void>;
9
- unsubscribe: () => Promise<void>;
10
- };
11
- type EventEmitterDurableObject<T> = DurableObjectNamespace<EventEmitterDurableObjectBranded<T>>;
12
- //#endregion
13
- //#region src/do-event-emitter.d.ts
14
- type UploadEventEmitterDurableObjectStoreConfig = {
15
- durableObject: EventEmitterDurableObject<UploadEvent>;
16
- };
17
- declare const uploadEventEmitterDurableObjectStore: (config: UploadEventEmitterDurableObjectStoreConfig) => Layer.Layer<UploadEventEmitter, never, never>;
18
- //#endregion
19
- export { type EventEmitterDurableObject, uploadEventEmitterDurableObjectStore };
20
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/event-emitter-durable-object.ts","../src/do-event-emitter.ts"],"sourcesContent":[],"mappings":";;;;;KAGY,sCAAsC,GAAA,CAAI;gBACtC,MAAM;0BACI,wBAAwB;EAFtC,WAAA,EAAA,GAAA,GAGS,OAHT,CAAA,IAAA,CAAA;CAAsC;AAClC,KAMJ,yBANI,CAAA,CAAA,CAAA,GAM2B,sBAN3B,CAOd,gCAPc,CAOmB,CAPnB,CAAA,CAAA;;;KCQJ,0CAAA;iBACK,0BAA0B;ADV3C,CAAA;AACgB,cC8DH,oCD9DG,EAAA,CAAA,MAAA,EC+DN,0CD/DM,EAAA,GC+DoC,KAAA,CAAA,KD/DpC,CC+DoC,kBD/DpC,EAAA,KAAA,EAAA,KAAA,CAAA"}
package/dist/index.js DELETED
@@ -1,2 +0,0 @@
1
- import{UploadistaError as e}from"@uploadista/core/errors";import{UploadEventEmitter as t}from"@uploadista/core/types";import{Effect as n,Layer as r}from"effect";function i({durableObject:t}){function r(e){let n=t.idFromName(e);return t.get(n)}return{emit:(t,i)=>{let a=r(t);return n.tryPromise({try:async()=>{await a.emit(i)},catch:t=>e.fromCode(`UNKNOWN_ERROR`,{cause:t})})},subscribe:(t,i)=>n.tryPromise({try:async()=>{await r(t).subscribe(i)},catch:t=>e.fromCode(`UNKNOWN_ERROR`,{cause:t})}),unsubscribe:t=>n.tryPromise({try:async()=>{await r(t).unsubscribe()},catch:t=>e.fromCode(`UNKNOWN_ERROR`,{cause:t})})}}const a=e=>r.succeed(t,i(e));export{a as uploadEventEmitterDurableObjectStore};
2
- //# sourceMappingURL=index.js.map
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/do-event-emitter.ts"],"sourcesContent":["import { UploadistaError } from \"@uploadista/core/errors\";\nimport {\n type EventEmitter,\n type UploadEvent,\n UploadEventEmitter,\n} from \"@uploadista/core/types\";\nimport { Effect, Layer } from \"effect\";\nimport type {\n EventEmitterDurableObject,\n EventEmitterDurableObjectBranded,\n} from \"./event-emitter-durable-object\";\n\nexport type UploadEventEmitterDurableObjectStoreConfig = {\n durableObject: EventEmitterDurableObject<UploadEvent>;\n};\n\nexport function makeEventEmitterDurableObjectStore<T>({\n durableObject,\n}: UploadEventEmitterDurableObjectStoreConfig): EventEmitter<T> {\n function getStub(key: string) {\n const id = durableObject.idFromName(key);\n return durableObject.get(\n id,\n ) as unknown as EventEmitterDurableObjectBranded<T>;\n }\n\n return {\n emit: (key: string, event: T) => {\n const stub = getStub(key);\n return Effect.tryPromise({\n try: async () => {\n await stub.emit(event);\n return;\n },\n catch: (cause) => {\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n subscribe: (key: string, connection) => {\n return Effect.tryPromise({\n try: async () => {\n const stub = getStub(key);\n await stub.subscribe(connection);\n return;\n },\n catch: (cause) => {\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n unsubscribe: (key: string) => {\n return Effect.tryPromise({\n try: async () => {\n const stub = getStub(key);\n await stub.unsubscribe();\n return;\n },\n catch: (cause) => {\n return UploadistaError.fromCode(\"UNKNOWN_ERROR\", { cause });\n },\n });\n },\n };\n}\n\nexport const uploadEventEmitterDurableObjectStore = (\n config: UploadEventEmitterDurableObjectStoreConfig,\n) =>\n Layer.succeed(\n UploadEventEmitter,\n makeEventEmitterDurableObjectStore<UploadEvent>(config),\n );\n"],"mappings":"iKAgBA,SAAgB,EAAsC,CACpD,iBAC8D,CAC9D,SAAS,EAAQ,EAAa,CAC5B,IAAM,EAAK,EAAc,WAAW,EAAI,CACxC,OAAO,EAAc,IACnB,EACD,CAGH,MAAO,CACL,MAAO,EAAa,IAAa,CAC/B,IAAM,EAAO,EAAQ,EAAI,CACzB,OAAO,EAAO,WAAW,CACvB,IAAK,SAAY,CACf,MAAM,EAAK,KAAK,EAAM,EAGxB,MAAQ,GACC,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,CAE9D,CAAC,EAEJ,WAAY,EAAa,IAChB,EAAO,WAAW,CACvB,IAAK,SAAY,CAEf,MADa,EAAQ,EAAI,CACd,UAAU,EAAW,EAGlC,MAAQ,GACC,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,CAE9D,CAAC,CAEJ,YAAc,GACL,EAAO,WAAW,CACvB,IAAK,SAAY,CAEf,MADa,EAAQ,EAAI,CACd,aAAa,EAG1B,MAAQ,GACC,EAAgB,SAAS,gBAAiB,CAAE,QAAO,CAAC,CAE9D,CAAC,CAEL,CAGH,MAAa,EACX,GAEA,EAAM,QACJ,EACA,EAAgD,EAAO,CACxD"}