melony 0.3.1 → 0.3.3

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.
@@ -52,7 +52,7 @@ var MelonyClient = class {
52
52
  });
53
53
  try {
54
54
  const headers = await this.getRequestHeaders();
55
- const response = await fetch(this.url, {
55
+ const runResponse = await fetch(`${this.url}/runs`, {
56
56
  method: "POST",
57
57
  headers,
58
58
  body: JSON.stringify({
@@ -61,6 +61,42 @@ var MelonyClient = class {
61
61
  }),
62
62
  signal: this.abortController.signal
63
63
  });
64
+ if (!runResponse.ok)
65
+ throw new Error(`HTTP error! status: ${runResponse.status}`);
66
+ const { runId, threadId } = await runResponse.json();
67
+ yield* this.stream({ runId, threadId });
68
+ } catch (err) {
69
+ if (err instanceof Error && err.name === "AbortError") {
70
+ this.setState({ streaming: false });
71
+ return;
72
+ }
73
+ const error = err instanceof Error ? err : new Error(String(err));
74
+ this.setState({ error, streaming: false });
75
+ throw error;
76
+ }
77
+ }
78
+ /**
79
+ * Stream events from a specific run or thread.
80
+ */
81
+ async *stream(filter) {
82
+ if (this.abortController) this.abortController.abort();
83
+ this.abortController = new AbortController();
84
+ this.setState({
85
+ streaming: true,
86
+ error: null
87
+ });
88
+ try {
89
+ const headers = await this.getRequestHeaders();
90
+ const streamUrl = new URL(`${this.url}/stream`);
91
+ if (filter.runId) streamUrl.searchParams.set("runId", filter.runId);
92
+ if (filter.threadId) streamUrl.searchParams.set("threadId", filter.threadId);
93
+ const response = await fetch(streamUrl.toString(), {
94
+ headers: {
95
+ ...headers,
96
+ "Accept": "text/event-stream"
97
+ },
98
+ signal: this.abortController.signal
99
+ });
64
100
  if (!response.ok)
65
101
  throw new Error(`HTTP error! status: ${response.status}`);
66
102
  if (!response.body) throw new Error("No response body");
@@ -80,6 +116,13 @@ var MelonyClient = class {
80
116
  const handled = this.handleIncomingEvent(incomingEvent);
81
117
  if (!handled) continue;
82
118
  yield incomingEvent;
119
+ if (incomingEvent.type === "run:status:updated") {
120
+ const data = incomingEvent.data;
121
+ if (data?.status === "completed" || data?.status === "failed") {
122
+ this.setState({ streaming: false });
123
+ return;
124
+ }
125
+ }
83
126
  } catch (e) {
84
127
  console.error("Failed to parse event", e);
85
128
  }
@@ -96,6 +139,50 @@ var MelonyClient = class {
96
139
  throw error;
97
140
  }
98
141
  }
142
+ /**
143
+ * List all unique threads.
144
+ */
145
+ async listThreads() {
146
+ const headers = await this.getRequestHeaders();
147
+ const response = await fetch(`${this.url}/threads`, { headers });
148
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
149
+ const data = await response.json();
150
+ return data.threads;
151
+ }
152
+ /**
153
+ * List all runs, optionally filtered by thread ID.
154
+ */
155
+ async listRuns(filter) {
156
+ const headers = await this.getRequestHeaders();
157
+ const runsUrl = new URL(`${this.url}/runs`);
158
+ if (filter?.threadId) runsUrl.searchParams.set("threadId", filter.threadId);
159
+ const response = await fetch(runsUrl.toString(), { headers });
160
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
161
+ const data = await response.json();
162
+ return data.runs;
163
+ }
164
+ /**
165
+ * Get historical events for a run or thread.
166
+ */
167
+ async listEvents(filter) {
168
+ const headers = await this.getRequestHeaders();
169
+ const eventsUrl = new URL(`${this.url}/events`);
170
+ if (filter.runId) eventsUrl.searchParams.set("runId", filter.runId);
171
+ if (filter.threadId) eventsUrl.searchParams.set("threadId", filter.threadId);
172
+ const response = await fetch(eventsUrl.toString(), { headers });
173
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
174
+ const data = await response.json();
175
+ return data.events;
176
+ }
177
+ /**
178
+ * Check server health.
179
+ */
180
+ async getHealth() {
181
+ const headers = await this.getRequestHeaders();
182
+ const response = await fetch(`${this.url}/health`, { headers });
183
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
184
+ return await response.json();
185
+ }
99
186
  handleIncomingEvent(event) {
100
187
  const events = [...this.state.events];
101
188
  const index = event.id ? events.findIndex((e) => e.id === event.id) : -1;
@@ -125,5 +212,5 @@ var MelonyClient = class {
125
212
  };
126
213
 
127
214
  export { MelonyClient };
128
- //# sourceMappingURL=chunk-7YBEEXMK.js.map
129
- //# sourceMappingURL=chunk-7YBEEXMK.js.map
215
+ //# sourceMappingURL=chunk-EY5SDCRC.js.map
216
+ //# sourceMappingURL=chunk-EY5SDCRC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":"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","file":"chunk-EY5SDCRC.js","sourcesContent":["import { Event } from \"./types\";\nimport { generateId } from \"./utils/generate-id\";\n\nexport type { Event };\nexport { generateId };\n\nexport interface ClientState<TEvent extends Event = Event> {\n events: TEvent[];\n streaming: boolean;\n error: Error | null;\n context: Record<string, any>;\n}\n\nexport interface MelonyClientOptions<TEvent extends Event = Event> {\n url: string;\n initialEvents?: TEvent[];\n initialContext?: Record<string, any>;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n}\n\nexport class MelonyClient<TEvent extends Event = Event> {\n private state: ClientState<TEvent>;\n public readonly url: string;\n private headers?: MelonyClientOptions<TEvent>[\"headers\"];\n private lastServerState: any = null;\n private abortController: AbortController | null = null;\n private stateListeners: Set<(state: ClientState<TEvent>) => void> = new Set();\n\n constructor(options: MelonyClientOptions<TEvent>) {\n this.url = options.url;\n this.headers = options.headers;\n this.state = {\n events: options.initialEvents ?? [],\n streaming: false,\n error: null,\n context: options.initialContext ?? {},\n };\n }\n\n subscribe(listener: (state: ClientState<TEvent>) => void) {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n getState() {\n return { ...this.state };\n }\n\n private async getRequestHeaders() {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.headers) {\n const extraHeaders =\n typeof this.headers === \"function\"\n ? await this.headers()\n : this.headers;\n Object.assign(headers, extraHeaders);\n }\n return headers;\n }\n\n private setState(updates: Partial<ClientState<TEvent>>) {\n this.state = { ...this.state, ...updates };\n this.stateListeners.forEach((l) => l(this.getState()));\n }\n\n async *send(\n event: TEvent,\n additionalBody?: Record<string, any>\n ): AsyncGenerator<TEvent> {\n if (this.abortController) this.abortController.abort();\n this.abortController = new AbortController();\n\n const optimisticEvent: TEvent = {\n id: generateId(),\n ...event\n } as TEvent;\n\n this.setState({\n streaming: true,\n error: null,\n events: [...this.state.events, optimisticEvent],\n });\n\n try {\n const headers = await this.getRequestHeaders();\n \n // 1. Create a Run\n const runResponse = await fetch(`${this.url}/runs`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: optimisticEvent,\n ...additionalBody,\n }),\n signal: this.abortController.signal,\n });\n\n if (!runResponse.ok)\n throw new Error(`HTTP error! status: ${runResponse.status}`);\n \n const { runId, threadId } = await runResponse.json();\n yield* this.stream({ runId, threadId });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n this.setState({ streaming: false });\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n this.setState({ error, streaming: false });\n throw error;\n }\n }\n\n /**\n * Stream events from a specific run or thread.\n */\n async *stream(filter: { runId?: string; threadId?: string }): AsyncGenerator<TEvent> {\n if (this.abortController) this.abortController.abort();\n this.abortController = new AbortController();\n\n this.setState({\n streaming: true,\n error: null,\n });\n\n try {\n const headers = await this.getRequestHeaders();\n const streamUrl = new URL(`${this.url}/stream`);\n if (filter.runId) streamUrl.searchParams.set(\"runId\", filter.runId);\n if (filter.threadId) streamUrl.searchParams.set(\"threadId\", filter.threadId);\n\n const response = await fetch(streamUrl.toString(), {\n headers: {\n ...headers,\n \"Accept\": \"text/event-stream\",\n },\n signal: this.abortController.signal,\n });\n\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n if (!response.body) throw new Error(\"No response body\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n try {\n const incomingEvent: TEvent = JSON.parse(line.slice(6));\n const handled = this.handleIncomingEvent(incomingEvent);\n if (!handled) continue;\n yield incomingEvent;\n\n // If we receive a run:status:updated event with completed/failed, we can stop\n if (incomingEvent.type === \"run:status:updated\") {\n const data = incomingEvent.data as { status?: string };\n if (data?.status === \"completed\" || data?.status === \"failed\") {\n this.setState({ streaming: false });\n return;\n }\n }\n } catch (e) {\n console.error(\"Failed to parse event\", e);\n }\n }\n }\n this.setState({ streaming: false });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n this.setState({ streaming: false });\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n this.setState({ error, streaming: false });\n throw error;\n }\n }\n\n /**\n * List all unique threads.\n */\n async listThreads(): Promise<{ id: string }[]> {\n const headers = await this.getRequestHeaders();\n const response = await fetch(`${this.url}/threads`, { headers });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data = await response.json();\n return data.threads;\n }\n\n /**\n * List all runs, optionally filtered by thread ID.\n */\n async listRuns(filter?: { threadId?: string }): Promise<any[]> {\n const headers = await this.getRequestHeaders();\n const runsUrl = new URL(`${this.url}/runs`);\n if (filter?.threadId) runsUrl.searchParams.set(\"threadId\", filter.threadId);\n \n const response = await fetch(runsUrl.toString(), { headers });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data = await response.json();\n return data.runs;\n }\n\n /**\n * Get historical events for a run or thread.\n */\n async listEvents(filter: { runId?: string; threadId?: string }): Promise<TEvent[]> {\n const headers = await this.getRequestHeaders();\n const eventsUrl = new URL(`${this.url}/events`);\n if (filter.runId) eventsUrl.searchParams.set(\"runId\", filter.runId);\n if (filter.threadId) eventsUrl.searchParams.set(\"threadId\", filter.threadId);\n\n const response = await fetch(eventsUrl.toString(), { headers });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n const data = await response.json();\n return data.events;\n }\n\n /**\n * Check server health.\n */\n async getHealth(): Promise<any> {\n const headers = await this.getRequestHeaders();\n const response = await fetch(`${this.url}/health`, { headers });\n if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n return await response.json();\n }\n\n private handleIncomingEvent(event: TEvent) {\n const events = [...this.state.events];\n\n // Replace optimistic event if IDs match, otherwise push\n const index = event.id ? events.findIndex((e) => e.id === event.id) : -1;\n if (index !== -1) {\n events[index] = event;\n } else {\n events.push(event);\n }\n\n this.setState({ events });\n return true;\n }\n\n reset(events: TEvent[] = []) {\n this.stop();\n this.setState({\n events,\n error: null,\n streaming: false,\n });\n }\n\n stop() {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n this.setState({ streaming: false });\n }\n }\n}\n"]}
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { MelonyClient } from './chunk-7YBEEXMK.js';
2
+ import { MelonyClient } from './chunk-EY5SDCRC.js';
3
3
  import './chunk-WAI5H335.js';
4
4
  import 'dotenv/config';
5
5
  import * as readline from 'readline/promises';
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { E as Event } from './generate-id-nIiN6qMt.js';
2
- export { g as generateId } from './generate-id-nIiN6qMt.js';
1
+ import { E as Event } from './generate-id-wXNQEJ8t.js';
2
+ export { g as generateId } from './generate-id-wXNQEJ8t.js';
3
3
 
4
4
  interface ClientState<TEvent extends Event = Event> {
5
5
  events: TEvent[];
@@ -31,6 +31,36 @@ declare class MelonyClient<TEvent extends Event = Event> {
31
31
  private getRequestHeaders;
32
32
  private setState;
33
33
  send(event: TEvent, additionalBody?: Record<string, any>): AsyncGenerator<TEvent>;
34
+ /**
35
+ * Stream events from a specific run or thread.
36
+ */
37
+ stream(filter: {
38
+ runId?: string;
39
+ threadId?: string;
40
+ }): AsyncGenerator<TEvent>;
41
+ /**
42
+ * List all unique threads.
43
+ */
44
+ listThreads(): Promise<{
45
+ id: string;
46
+ }[]>;
47
+ /**
48
+ * List all runs, optionally filtered by thread ID.
49
+ */
50
+ listRuns(filter?: {
51
+ threadId?: string;
52
+ }): Promise<any[]>;
53
+ /**
54
+ * Get historical events for a run or thread.
55
+ */
56
+ listEvents(filter: {
57
+ runId?: string;
58
+ threadId?: string;
59
+ }): Promise<TEvent[]>;
60
+ /**
61
+ * Check server health.
62
+ */
63
+ getHealth(): Promise<any>;
34
64
  private handleIncomingEvent;
35
65
  reset(events?: TEvent[]): void;
36
66
  stop(): void;
package/dist/client.js CHANGED
@@ -1,4 +1,4 @@
1
- export { MelonyClient } from './chunk-7YBEEXMK.js';
1
+ export { MelonyClient } from './chunk-EY5SDCRC.js';
2
2
  export { generateId } from './chunk-WAI5H335.js';
3
3
  //# sourceMappingURL=client.js.map
4
4
  //# sourceMappingURL=client.js.map
@@ -27,13 +27,17 @@ declare class Runtime<TState = any, TEvent extends Event = Event> {
27
27
  * The core Event structure.
28
28
  * Fully unopinionated - just type, data, and optional metadata.
29
29
  */
30
- type Event<TData = any> = {
30
+ type Event<TData = any, TMeta = Record<string, any>> = {
31
31
  /** Unique identifier for the event */
32
32
  id?: string;
33
33
  /** The type of the event */
34
34
  type: string;
35
35
  /** The data associated with the event */
36
- data: TData;
36
+ data?: TData;
37
+ /** Optional metadata associated with the event */
38
+ meta?: TMeta;
39
+ /** Timestamp of when the event occurred */
40
+ timestamp?: number;
37
41
  };
38
42
  /**
39
43
  * Built-in error event emitted by the runtime.
@@ -44,6 +48,16 @@ interface ErrorEvent extends Event<{
44
48
  }> {
45
49
  type: "error";
46
50
  }
51
+ type RunStatus = "pending" | "running" | "completed" | "failed" | "suspended";
52
+ interface Run<TState = any, TEvent extends Event = Event> {
53
+ id: string;
54
+ threadId?: string;
55
+ status: RunStatus;
56
+ events: TEvent[];
57
+ state: TState;
58
+ startTime: number;
59
+ endTime?: number;
60
+ }
47
61
  interface RuntimeContext<TState = any, TEvent extends Event = Event> {
48
62
  state: TState;
49
63
  runId: string;
@@ -71,4 +85,4 @@ interface Config<TState = any, TEvent extends Event = Event> {
71
85
 
72
86
  declare const generateId: () => string;
73
87
 
74
- export { type Config as C, type Event as E, type Interceptor as I, Runtime as R, type RuntimeContext as a, type ErrorEvent as b, type EventHandler as c, generateId as g };
88
+ export { type Config as C, type Event as E, type Interceptor as I, Runtime as R, type RuntimeContext as a, type ErrorEvent as b, type RunStatus as c, type Run as d, type EventHandler as e, generateId as g };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { R as Runtime, E as Event, C as Config, a as RuntimeContext, I as Interceptor } from './generate-id-nIiN6qMt.js';
2
- export { b as ErrorEvent, c as EventHandler, g as generateId } from './generate-id-nIiN6qMt.js';
1
+ import { R as Runtime, E as Event, C as Config, a as RuntimeContext, I as Interceptor } from './generate-id-wXNQEJ8t.js';
2
+ export { b as ErrorEvent, e as EventHandler, d as Run, c as RunStatus, g as generateId } from './generate-id-wXNQEJ8t.js';
3
3
 
4
4
  declare const MelonyRuntime: typeof Runtime;
5
5
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "melony",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/client.ts"],"names":[],"mappings":";;;AAsBO,IAAM,eAAN,MAAiD;AAAA,EAQtD,YAAY,OAAA,EAAsC;AAJlD,IAAA,IAAA,CAAQ,eAAA,GAAuB,IAAA;AAC/B,IAAA,IAAA,CAAQ,eAAA,GAA0C,IAAA;AAClD,IAAA,IAAA,CAAQ,cAAA,uBAAgE,GAAA,EAAI;AAG1E,IAAA,IAAA,CAAK,MAAM,OAAA,CAAQ,GAAA;AACnB,IAAA,IAAA,CAAK,UAAU,OAAA,CAAQ,OAAA;AACvB,IAAA,IAAA,CAAK,KAAA,GAAQ;AAAA,MACX,MAAA,EAAQ,OAAA,CAAQ,aAAA,IAAiB,EAAC;AAAA,MAClC,SAAA,EAAW,KAAA;AAAA,MACX,KAAA,EAAO,IAAA;AAAA,MACP,OAAA,EAAS,OAAA,CAAQ,cAAA,IAAkB;AAAC,KACtC;AAAA,EACF;AAAA,EAEA,UAAU,QAAA,EAAgD;AACxD,IAAA,IAAA,CAAK,cAAA,CAAe,IAAI,QAAQ,CAAA;AAChC,IAAA,OAAO,MAAM;AACX,MAAA,IAAA,CAAK,cAAA,CAAe,OAAO,QAAQ,CAAA;AAAA,IACrC,CAAA;AAAA,EACF;AAAA,EAEA,QAAA,GAAW;AACT,IAAA,OAAO,EAAE,GAAG,IAAA,CAAK,KAAA,EAAM;AAAA,EACzB;AAAA,EAEA,MAAc,iBAAA,GAAoB;AAChC,IAAA,MAAM,OAAA,GAAkC;AAAA,MACtC,cAAA,EAAgB;AAAA,KAClB;AAEA,IAAA,IAAI,KAAK,OAAA,EAAS;AAChB,MAAA,MAAM,YAAA,GACJ,OAAO,IAAA,CAAK,OAAA,KAAY,aACpB,MAAM,IAAA,CAAK,OAAA,EAAQ,GACnB,IAAA,CAAK,OAAA;AACX,MAAA,MAAA,CAAO,MAAA,CAAO,SAAS,YAAY,CAAA;AAAA,IACrC;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEQ,SAAS,OAAA,EAAuC;AACtD,IAAA,IAAA,CAAK,QAAQ,EAAE,GAAG,IAAA,CAAK,KAAA,EAAO,GAAG,OAAA,EAAQ;AACzC,IAAA,IAAA,CAAK,cAAA,CAAe,QAAQ,CAAC,CAAA,KAAM,EAAE,IAAA,CAAK,QAAA,EAAU,CAAC,CAAA;AAAA,EACvD;AAAA,EAEA,OAAO,IAAA,CACL,KAAA,EACA,cAAA,EACwB;AACxB,IAAA,IAAI,IAAA,CAAK,eAAA,EAAiB,IAAA,CAAK,eAAA,CAAgB,KAAA,EAAM;AACrD,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAE3C,IAAA,MAAM,eAAA,GAA0B;AAAA,MAC9B,IAAI,UAAA,EAAW;AAAA,MACf,GAAG;AAAA,KACL;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS;AAAA,MACZ,SAAA,EAAW,IAAA;AAAA,MACX,KAAA,EAAO,IAAA;AAAA,MACP,QAAQ,CAAC,GAAG,IAAA,CAAK,KAAA,CAAM,QAAQ,eAAe;AAAA,KAC/C,CAAA;AAED,IAAA,IAAI;AACF,MAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,iBAAA,EAAkB;AAC7C,MAAA,MAAM,QAAA,GAAW,MAAM,KAAA,CAAM,IAAA,CAAK,GAAA,EAAK;AAAA,QACrC,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA;AAAA,QACA,IAAA,EAAM,KAAK,SAAA,CAAU;AAAA,UACnB,KAAA,EAAO,eAAA;AAAA,UACP,GAAG;AAAA,SACJ,CAAA;AAAA,QACD,MAAA,EAAQ,KAAK,eAAA,CAAgB;AAAA,OAC9B,CAAA;AAED,MAAA,IAAI,CAAC,QAAA,CAAS,EAAA;AACZ,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAC1D,MAAA,IAAI,CAAC,QAAA,CAAS,IAAA,EAAM,MAAM,IAAI,MAAM,kBAAkB,CAAA;AAEtD,MAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,MAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,MAAA,IAAI,MAAA,GAAS,EAAA;AAEb,MAAA,OAAO,IAAA,EAAM;AACX,QAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,QAAA,IAAI,IAAA,EAAM;AAEV,QAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,MAAM,CAAA;AACjC,QAAA,MAAA,GAAS,KAAA,CAAM,KAAI,IAAK,EAAA;AAExB,QAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,UAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,QAAQ,CAAA,EAAG;AAChC,UAAA,IAAI;AACF,YAAA,MAAM,gBAAwB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,CAAC,CAAC,CAAA;AACtD,YAAA,MAAM,OAAA,GAAU,IAAA,CAAK,mBAAA,CAAoB,aAAa,CAAA;AACtD,YAAA,IAAI,CAAC,OAAA,EAAS;AACd,YAAA,MAAM,aAAA;AAAA,UACR,SAAS,CAAA,EAAG;AACV,YAAA,OAAA,CAAQ,KAAA,CAAM,yBAAyB,CAAC,CAAA;AAAA,UAC1C;AAAA,QACF;AAAA,MACF;AACA,MAAA,IAAA,CAAK,QAAA,CAAS,EAAE,SAAA,EAAW,KAAA,EAAO,CAAA;AAAA,IACpC,SAAS,GAAA,EAAK;AACZ,MAAA,IAAI,GAAA,YAAe,KAAA,IAAS,GAAA,CAAI,IAAA,KAAS,YAAA,EAAc;AACrD,QAAA,IAAA,CAAK,QAAA,CAAS,EAAE,SAAA,EAAW,KAAA,EAAO,CAAA;AAClC,QAAA;AAAA,MACF;AACA,MAAA,MAAM,KAAA,GAAQ,eAAe,KAAA,GAAQ,GAAA,GAAM,IAAI,KAAA,CAAM,MAAA,CAAO,GAAG,CAAC,CAAA;AAChE,MAAA,IAAA,CAAK,QAAA,CAAS,EAAE,KAAA,EAAO,SAAA,EAAW,OAAO,CAAA;AACzC,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA,EAEQ,oBAAoB,KAAA,EAAe;AACzC,IAAA,MAAM,MAAA,GAAS,CAAC,GAAG,IAAA,CAAK,MAAM,MAAM,CAAA;AAGpC,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,EAAA,GAAK,MAAA,CAAO,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,KAAA,CAAM,EAAE,CAAA,GAAI,EAAA;AACtE,IAAA,IAAI,UAAU,EAAA,EAAI;AAChB,MAAA,MAAA,CAAO,KAAK,CAAA,GAAI,KAAA;AAAA,IAClB,CAAA,MAAO;AACL,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB;AAEA,IAAA,IAAA,CAAK,QAAA,CAAS,EAAE,MAAA,EAAQ,CAAA;AACxB,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,KAAA,CAAM,MAAA,GAAmB,EAAC,EAAG;AAC3B,IAAA,IAAA,CAAK,IAAA,EAAK;AACV,IAAA,IAAA,CAAK,QAAA,CAAS;AAAA,MACZ,MAAA;AAAA,MACA,KAAA,EAAO,IAAA;AAAA,MACP,SAAA,EAAW;AAAA,KACZ,CAAA;AAAA,EACH;AAAA,EAEA,IAAA,GAAO;AACL,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,IAAA,CAAK,gBAAgB,KAAA,EAAM;AAC3B,MAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AACvB,MAAA,IAAA,CAAK,QAAA,CAAS,EAAE,SAAA,EAAW,KAAA,EAAO,CAAA;AAAA,IACpC;AAAA,EACF;AACF","file":"chunk-7YBEEXMK.js","sourcesContent":["import { Event } from \"./types\";\nimport { generateId } from \"./utils/generate-id\";\n\nexport type { Event };\nexport { generateId };\n\nexport interface ClientState<TEvent extends Event = Event> {\n events: TEvent[];\n streaming: boolean;\n error: Error | null;\n context: Record<string, any>;\n}\n\nexport interface MelonyClientOptions<TEvent extends Event = Event> {\n url: string;\n initialEvents?: TEvent[];\n initialContext?: Record<string, any>;\n headers?:\n | Record<string, string>\n | (() => Record<string, string> | Promise<Record<string, string>>);\n}\n\nexport class MelonyClient<TEvent extends Event = Event> {\n private state: ClientState<TEvent>;\n public readonly url: string;\n private headers?: MelonyClientOptions<TEvent>[\"headers\"];\n private lastServerState: any = null;\n private abortController: AbortController | null = null;\n private stateListeners: Set<(state: ClientState<TEvent>) => void> = new Set();\n\n constructor(options: MelonyClientOptions<TEvent>) {\n this.url = options.url;\n this.headers = options.headers;\n this.state = {\n events: options.initialEvents ?? [],\n streaming: false,\n error: null,\n context: options.initialContext ?? {},\n };\n }\n\n subscribe(listener: (state: ClientState<TEvent>) => void) {\n this.stateListeners.add(listener);\n return () => {\n this.stateListeners.delete(listener);\n };\n }\n\n getState() {\n return { ...this.state };\n }\n\n private async getRequestHeaders() {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n };\n\n if (this.headers) {\n const extraHeaders =\n typeof this.headers === \"function\"\n ? await this.headers()\n : this.headers;\n Object.assign(headers, extraHeaders);\n }\n return headers;\n }\n\n private setState(updates: Partial<ClientState<TEvent>>) {\n this.state = { ...this.state, ...updates };\n this.stateListeners.forEach((l) => l(this.getState()));\n }\n\n async *send(\n event: TEvent,\n additionalBody?: Record<string, any>\n ): AsyncGenerator<TEvent> {\n if (this.abortController) this.abortController.abort();\n this.abortController = new AbortController();\n\n const optimisticEvent: TEvent = {\n id: generateId(),\n ...event\n } as TEvent;\n\n this.setState({\n streaming: true,\n error: null,\n events: [...this.state.events, optimisticEvent],\n });\n\n try {\n const headers = await this.getRequestHeaders();\n const response = await fetch(this.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify({\n event: optimisticEvent,\n ...additionalBody,\n }),\n signal: this.abortController.signal,\n });\n\n if (!response.ok)\n throw new Error(`HTTP error! status: ${response.status}`);\n if (!response.body) throw new Error(\"No response body\");\n\n const reader = response.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\\n\");\n buffer = lines.pop() || \"\";\n\n for (const line of lines) {\n if (!line.startsWith(\"data: \")) continue;\n try {\n const incomingEvent: TEvent = JSON.parse(line.slice(6));\n const handled = this.handleIncomingEvent(incomingEvent);\n if (!handled) continue;\n yield incomingEvent;\n } catch (e) {\n console.error(\"Failed to parse event\", e);\n }\n }\n }\n this.setState({ streaming: false });\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n this.setState({ streaming: false });\n return;\n }\n const error = err instanceof Error ? err : new Error(String(err));\n this.setState({ error, streaming: false });\n throw error;\n }\n }\n\n private handleIncomingEvent(event: TEvent) {\n const events = [...this.state.events];\n\n // Replace optimistic event if IDs match, otherwise push\n const index = event.id ? events.findIndex((e) => e.id === event.id) : -1;\n if (index !== -1) {\n events[index] = event;\n } else {\n events.push(event);\n }\n\n this.setState({ events });\n return true;\n }\n\n reset(events: TEvent[] = []) {\n this.stop();\n this.setState({\n events,\n error: null,\n streaming: false,\n });\n }\n\n stop() {\n if (this.abortController) {\n this.abortController.abort();\n this.abortController = null;\n this.setState({ streaming: false });\n }\n }\n}\n"]}