@pgflow/client 0.0.0-update-supabase-ee31ce05-20251119075709 → 0.0.0-worker-compilation-b0fe6186-20251201124606
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/CHANGELOG.md +31 -7
- package/README.md +1 -1
- package/dist/package.json +2 -3
- package/dist/src/browser.d.ts +7 -0
- package/dist/src/browser.d.ts.map +1 -0
- package/dist/src/browser.js +10 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +7 -0
- package/dist/src/lib/FlowRun.d.ts +125 -0
- package/dist/src/lib/FlowRun.d.ts.map +1 -0
- package/dist/src/lib/FlowRun.js +368 -0
- package/dist/src/lib/FlowStep.d.ts +90 -0
- package/dist/src/lib/FlowStep.d.ts.map +1 -0
- package/dist/src/lib/FlowStep.js +244 -0
- package/dist/src/lib/PgflowClient.d.ts +75 -0
- package/dist/src/lib/PgflowClient.d.ts.map +1 -0
- package/dist/src/lib/PgflowClient.js +227 -0
- package/dist/src/lib/SupabaseBroadcastAdapter.d.ts +65 -0
- package/dist/src/lib/SupabaseBroadcastAdapter.d.ts.map +1 -0
- package/dist/src/lib/SupabaseBroadcastAdapter.js +332 -0
- package/dist/src/lib/eventAdapters.d.ts +20 -0
- package/dist/src/lib/eventAdapters.d.ts.map +1 -0
- package/dist/src/lib/eventAdapters.js +142 -0
- package/dist/src/lib/types.d.ts +307 -0
- package/dist/src/lib/types.d.ts.map +1 -0
- package/dist/src/lib/types.js +91 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -0
- package/package.json +4 -5
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import type { FlowRow, StepRow, RunRow, StepStateRow } from '@pgflow/core';
|
|
3
|
+
import type { IFlowRealtime, BroadcastRunEvent, BroadcastStepEvent, Unsubscribe } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Adapter to handle realtime communication with Supabase
|
|
6
|
+
*/
|
|
7
|
+
export declare class SupabaseBroadcastAdapter implements IFlowRealtime {
|
|
8
|
+
#private;
|
|
9
|
+
/**
|
|
10
|
+
* Creates a new instance of SupabaseBroadcastAdapter
|
|
11
|
+
*
|
|
12
|
+
* @param supabase - Supabase client instance
|
|
13
|
+
*/
|
|
14
|
+
constructor(supabase: SupabaseClient, opts?: {
|
|
15
|
+
reconnectDelayMs?: number;
|
|
16
|
+
stabilizationDelayMs?: number;
|
|
17
|
+
schedule?: typeof setTimeout;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Fetches flow definition metadata from the database
|
|
21
|
+
*
|
|
22
|
+
* @param flow_slug - Flow slug to fetch
|
|
23
|
+
*/
|
|
24
|
+
fetchFlowDefinition(flow_slug: string): Promise<{
|
|
25
|
+
flow: FlowRow;
|
|
26
|
+
steps: StepRow[];
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Registers a callback for run events
|
|
30
|
+
*
|
|
31
|
+
* @param callback - Function to call when run events are received
|
|
32
|
+
* @returns Function to unsubscribe from the event
|
|
33
|
+
*/
|
|
34
|
+
onRunEvent(callback: (event: BroadcastRunEvent) => void): Unsubscribe;
|
|
35
|
+
/**
|
|
36
|
+
* Registers a callback for step events
|
|
37
|
+
*
|
|
38
|
+
* @param callback - Function to call when step events are received
|
|
39
|
+
* @returns Function to unsubscribe from the event
|
|
40
|
+
*/
|
|
41
|
+
onStepEvent(callback: (event: BroadcastStepEvent) => void): Unsubscribe;
|
|
42
|
+
/**
|
|
43
|
+
* Subscribes to a flow run's events
|
|
44
|
+
*
|
|
45
|
+
* @param run_id - Run ID to subscribe to
|
|
46
|
+
* @returns Function to unsubscribe
|
|
47
|
+
*/
|
|
48
|
+
subscribeToRun(run_id: string): Promise<() => void>;
|
|
49
|
+
/**
|
|
50
|
+
* Unsubscribes from a run's events
|
|
51
|
+
*
|
|
52
|
+
* @param run_id - Run ID to unsubscribe from
|
|
53
|
+
*/
|
|
54
|
+
unsubscribe(run_id: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Fetches current state of a run and its steps
|
|
57
|
+
*
|
|
58
|
+
* @param run_id - Run ID to fetch
|
|
59
|
+
*/
|
|
60
|
+
getRunWithStates(run_id: string): Promise<{
|
|
61
|
+
run: RunRow;
|
|
62
|
+
steps: StepStateRow[];
|
|
63
|
+
}>;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=SupabaseBroadcastAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SupabaseBroadcastAdapter.d.ts","sourceRoot":"","sources":["../../../src/lib/SupabaseBroadcastAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3E,OAAO,KAAK,EACV,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EACZ,MAAM,YAAY,CAAC;AAQpB;;GAEG;AACH,qBAAa,wBAAyB,YAAW,aAAa;;IAS5D;;;;OAIG;gBAED,QAAQ,EAAE,cAAc,EACxB,IAAI,GAAE;QACJ,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,QAAQ,CAAC,EAAE,OAAO,UAAU,CAAC;KACzB;IAmLR;;;;OAIG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;QACpD,IAAI,EAAE,OAAO,CAAC;QACd,KAAK,EAAE,OAAO,EAAE,CAAC;KAClB,CAAC;IAiCF;;;;;OAKG;IACH,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAYrE;;;;;OAKG;IACH,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,WAAW;IAevE;;;;;OAKG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAgEzD;;;;OAIG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIjC;;;;OAIG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;QAC9C,GAAG,EAAE,MAAM,CAAC;QACZ,KAAK,EAAE,YAAY,EAAE,CAAC;KACvB,CAAC;CAiCH"}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { createNanoEvents } from 'nanoevents';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter to handle realtime communication with Supabase
|
|
4
|
+
*/
|
|
5
|
+
export class SupabaseBroadcastAdapter {
|
|
6
|
+
#supabase;
|
|
7
|
+
#channels = new Map();
|
|
8
|
+
#emitter = createNanoEvents();
|
|
9
|
+
#reconnectionDelay;
|
|
10
|
+
#stabilizationDelay;
|
|
11
|
+
#schedule;
|
|
12
|
+
/**
|
|
13
|
+
* Creates a new instance of SupabaseBroadcastAdapter
|
|
14
|
+
*
|
|
15
|
+
* @param supabase - Supabase client instance
|
|
16
|
+
*/
|
|
17
|
+
constructor(supabase, opts = {}) {
|
|
18
|
+
this.#supabase = supabase;
|
|
19
|
+
this.#reconnectionDelay = opts.reconnectDelayMs ?? 2000;
|
|
20
|
+
this.#stabilizationDelay = opts.stabilizationDelayMs ?? 300;
|
|
21
|
+
this.#schedule = opts.schedule ?? setTimeout;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Handle broadcast messages from Supabase
|
|
25
|
+
* @param payload - The message payload
|
|
26
|
+
*/
|
|
27
|
+
#handleBroadcastMessage(msg) {
|
|
28
|
+
const { event, payload } = msg;
|
|
29
|
+
// run_id is already inside the payload coming from the database trigger
|
|
30
|
+
// so just preserve it without overwriting
|
|
31
|
+
const eventData = payload;
|
|
32
|
+
// Auto-parse JSON strings in broadcast data (realtime sends JSONB as strings)
|
|
33
|
+
this.#parseJsonFields(eventData);
|
|
34
|
+
if (event.startsWith('run:')) {
|
|
35
|
+
// Handle run events
|
|
36
|
+
this.#emitter.emit('runEvent', eventData);
|
|
37
|
+
}
|
|
38
|
+
else if (event.startsWith('step:')) {
|
|
39
|
+
// Handle step events
|
|
40
|
+
this.#emitter.emit('stepEvent', eventData);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse JSON string fields in broadcast event data
|
|
45
|
+
* @param eventData - The event data object to parse
|
|
46
|
+
*/
|
|
47
|
+
#parseJsonFields(eventData) {
|
|
48
|
+
// Parse output field if it's a JSON string
|
|
49
|
+
if ('output' in eventData && typeof eventData.output === 'string') {
|
|
50
|
+
try {
|
|
51
|
+
eventData.output = JSON.parse(eventData.output);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Keep as string if not valid JSON
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Parse input field if it's a JSON string
|
|
58
|
+
if ('input' in eventData && typeof eventData.input === 'string') {
|
|
59
|
+
try {
|
|
60
|
+
eventData.input = JSON.parse(eventData.input);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// Keep as string if not valid JSON
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Handle channel errors and reconnection
|
|
69
|
+
* @param run_id - The run ID
|
|
70
|
+
* @param channelName - The channel name
|
|
71
|
+
* @param channel - The RealtimeChannel instance
|
|
72
|
+
* @param error - The error object
|
|
73
|
+
*/
|
|
74
|
+
async #handleChannelError(run_id, channelName, channel, error) {
|
|
75
|
+
console.error(`Channel ${channelName} error:`, error);
|
|
76
|
+
// Schedule reconnection attempt
|
|
77
|
+
this.#schedule(async () => {
|
|
78
|
+
if (this.#channels.has(run_id)) {
|
|
79
|
+
await this.#reconnectChannel(run_id, channelName);
|
|
80
|
+
}
|
|
81
|
+
}, this.#reconnectionDelay);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Creates and configures a channel for a run
|
|
85
|
+
* @param run_id - The run ID
|
|
86
|
+
* @param channelName - The channel name
|
|
87
|
+
* @returns The configured RealtimeChannel
|
|
88
|
+
*/
|
|
89
|
+
#createAndConfigureChannel(run_id, channelName) {
|
|
90
|
+
const channel = this.#supabase.channel(channelName);
|
|
91
|
+
// Listen to *all* broadcast messages; filter inside the handler.
|
|
92
|
+
// Using the 3-arg overload with event filter for proper Supabase v2 client compatibility.
|
|
93
|
+
channel.on('broadcast', { event: '*' }, this.#handleBroadcastMessage.bind(this));
|
|
94
|
+
// Note: Lifecycle event listeners (subscribed, closed, error) are handled
|
|
95
|
+
// by the calling code to avoid conflicts when multiple listeners try to
|
|
96
|
+
// handle the same events.
|
|
97
|
+
return channel;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Reconnect to a channel and refresh state
|
|
101
|
+
* @param run_id - The run ID
|
|
102
|
+
* @param channelName - The channel name
|
|
103
|
+
*/
|
|
104
|
+
async #reconnectChannel(run_id, channelName) {
|
|
105
|
+
console.log(`Attempting to reconnect to ${channelName}`);
|
|
106
|
+
try {
|
|
107
|
+
// Fetch current state to avoid missing events during disconnection
|
|
108
|
+
const currentState = await this.getRunWithStates(run_id);
|
|
109
|
+
// Update state based on current data
|
|
110
|
+
this.#refreshStateFromSnapshot(run_id, currentState);
|
|
111
|
+
// Create a new channel as the old one can't be reused
|
|
112
|
+
const newChannel = this.#createAndConfigureChannel(run_id, channelName);
|
|
113
|
+
// Set up lifecycle event handlers for reconnection
|
|
114
|
+
newChannel.on('system', { event: 'subscribed' }, () => {
|
|
115
|
+
console.log(`Reconnected and subscribed to channel ${channelName}`);
|
|
116
|
+
});
|
|
117
|
+
newChannel.on('system', { event: 'closed' }, () => {
|
|
118
|
+
console.log(`Reconnected channel ${channelName} closed`);
|
|
119
|
+
});
|
|
120
|
+
newChannel.on('system', { event: 'error' }, (payload) => this.#handleChannelError(run_id, channelName, newChannel, payload.error));
|
|
121
|
+
// Subscribe and update the channels map
|
|
122
|
+
newChannel.subscribe();
|
|
123
|
+
this.#channels.set(run_id, newChannel);
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
console.error(`Failed to reconnect to ${channelName}:`, e);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Refresh client state from a state snapshot
|
|
131
|
+
* @param run_id - The run ID
|
|
132
|
+
* @param state - The state snapshot
|
|
133
|
+
*/
|
|
134
|
+
#refreshStateFromSnapshot(run_id, state) {
|
|
135
|
+
if (!state || !state.run)
|
|
136
|
+
return;
|
|
137
|
+
// Create proper run event with correct event_type
|
|
138
|
+
const runEvent = {
|
|
139
|
+
event_type: `run:${state.run.status}`,
|
|
140
|
+
...state.run
|
|
141
|
+
};
|
|
142
|
+
// Emit run event
|
|
143
|
+
this.#emitter.emit('runEvent', runEvent);
|
|
144
|
+
// Emit events for each step state
|
|
145
|
+
if (state.steps && Array.isArray(state.steps)) {
|
|
146
|
+
for (const step of state.steps) {
|
|
147
|
+
// Create proper step event with correct event_type
|
|
148
|
+
const stepEvent = {
|
|
149
|
+
event_type: `step:${step.status}`,
|
|
150
|
+
...step
|
|
151
|
+
};
|
|
152
|
+
// Emit step event
|
|
153
|
+
this.#emitter.emit('stepEvent', stepEvent);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Fetches flow definition metadata from the database
|
|
159
|
+
*
|
|
160
|
+
* @param flow_slug - Flow slug to fetch
|
|
161
|
+
*/
|
|
162
|
+
async fetchFlowDefinition(flow_slug) {
|
|
163
|
+
// Fetch flow details and steps in parallel
|
|
164
|
+
const [flowResult, stepsResult] = await Promise.all([
|
|
165
|
+
this.#supabase
|
|
166
|
+
.schema('pgflow')
|
|
167
|
+
.from('flows')
|
|
168
|
+
.select('*')
|
|
169
|
+
.eq('flow_slug', flow_slug)
|
|
170
|
+
.single(),
|
|
171
|
+
this.#supabase
|
|
172
|
+
.schema('pgflow')
|
|
173
|
+
.from('steps')
|
|
174
|
+
.select('*')
|
|
175
|
+
.eq('flow_slug', flow_slug)
|
|
176
|
+
.order('step_index', { ascending: true })
|
|
177
|
+
]);
|
|
178
|
+
// Handle flow result
|
|
179
|
+
if (flowResult.error)
|
|
180
|
+
throw flowResult.error;
|
|
181
|
+
if (!flowResult.data)
|
|
182
|
+
throw new Error(`Flow "${flow_slug}" not found`);
|
|
183
|
+
// Handle steps result
|
|
184
|
+
if (stepsResult.error)
|
|
185
|
+
throw stepsResult.error;
|
|
186
|
+
// Ensure steps is always an array, even if it's null or undefined
|
|
187
|
+
const stepsArray = Array.isArray(stepsResult.data) ? stepsResult.data : [];
|
|
188
|
+
return {
|
|
189
|
+
flow: flowResult.data,
|
|
190
|
+
steps: stepsArray,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Registers a callback for run events
|
|
195
|
+
*
|
|
196
|
+
* @param callback - Function to call when run events are received
|
|
197
|
+
* @returns Function to unsubscribe from the event
|
|
198
|
+
*/
|
|
199
|
+
onRunEvent(callback) {
|
|
200
|
+
// Add a guard to prevent errors if called after emitter is deleted
|
|
201
|
+
const unsubscribe = this.#emitter.on('runEvent', callback);
|
|
202
|
+
return () => {
|
|
203
|
+
try {
|
|
204
|
+
unsubscribe();
|
|
205
|
+
}
|
|
206
|
+
catch (e) {
|
|
207
|
+
console.warn('Could not unsubscribe from run event - emitter may have been disposed', e);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Registers a callback for step events
|
|
213
|
+
*
|
|
214
|
+
* @param callback - Function to call when step events are received
|
|
215
|
+
* @returns Function to unsubscribe from the event
|
|
216
|
+
*/
|
|
217
|
+
onStepEvent(callback) {
|
|
218
|
+
// Add a guard to prevent errors if called after emitter is deleted
|
|
219
|
+
const unsubscribe = this.#emitter.on('stepEvent', callback);
|
|
220
|
+
return () => {
|
|
221
|
+
try {
|
|
222
|
+
unsubscribe();
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
console.warn('Could not unsubscribe from step event - emitter may have been disposed', e);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Store unsubscribe functions per run ID for reference equality
|
|
230
|
+
#unsubscribeFunctions = new Map();
|
|
231
|
+
/**
|
|
232
|
+
* Subscribes to a flow run's events
|
|
233
|
+
*
|
|
234
|
+
* @param run_id - Run ID to subscribe to
|
|
235
|
+
* @returns Function to unsubscribe
|
|
236
|
+
*/
|
|
237
|
+
async subscribeToRun(run_id) {
|
|
238
|
+
const channelName = `pgflow:run:${run_id}`;
|
|
239
|
+
// If already subscribed, return the existing unsubscribe function
|
|
240
|
+
if (this.#channels.has(run_id)) {
|
|
241
|
+
const existingUnsubscribe = this.#unsubscribeFunctions.get(run_id);
|
|
242
|
+
if (existingUnsubscribe) {
|
|
243
|
+
return existingUnsubscribe;
|
|
244
|
+
}
|
|
245
|
+
// If channel exists but no unsubscribe function, something went wrong
|
|
246
|
+
// Let's clean up and recreate
|
|
247
|
+
this.#unsubscribe(run_id);
|
|
248
|
+
}
|
|
249
|
+
const channel = this.#supabase.channel(channelName);
|
|
250
|
+
// Listen to *all* broadcast messages; filter inside the handler.
|
|
251
|
+
// Using the 3-arg overload with event filter for proper Supabase v2 client compatibility.
|
|
252
|
+
channel.on('broadcast', { event: '*' }, this.#handleBroadcastMessage.bind(this));
|
|
253
|
+
// Set up error handling
|
|
254
|
+
channel.on('system', { event: 'closed' }, () => {
|
|
255
|
+
console.log(`Channel ${channelName} closed`);
|
|
256
|
+
});
|
|
257
|
+
channel.on('system', { event: 'error' }, (payload) => {
|
|
258
|
+
console.log(`Channel ${channelName} error:`, payload);
|
|
259
|
+
this.#handleChannelError(run_id, channelName, channel, payload.error);
|
|
260
|
+
});
|
|
261
|
+
// Subscribe to channel and wait for confirmation (like the working realtime-send test)
|
|
262
|
+
console.log(`Subscribing to channel ${channelName}...`);
|
|
263
|
+
const subscriptionPromise = new Promise((resolve, reject) => {
|
|
264
|
+
const timeout = setTimeout(() => {
|
|
265
|
+
reject(new Error(`Subscription timeout for channel ${channelName}`));
|
|
266
|
+
}, 20000); // Increased from 5s to 20s for slower CI environments
|
|
267
|
+
channel.subscribe((status) => {
|
|
268
|
+
console.log(`Channel ${channelName} subscription status:`, status);
|
|
269
|
+
if (status === 'SUBSCRIBED') {
|
|
270
|
+
clearTimeout(timeout);
|
|
271
|
+
resolve();
|
|
272
|
+
}
|
|
273
|
+
// Don't reject on CHANNEL_ERROR - it's a transient state
|
|
274
|
+
// Only reject on timeout
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
// Wait for the 'SUBSCRIBED' acknowledgment to avoid race conditions
|
|
278
|
+
await subscriptionPromise;
|
|
279
|
+
// Stabilization delay - known Supabase Realtime limitation
|
|
280
|
+
// The SUBSCRIBED event is emitted before backend routing is fully established.
|
|
281
|
+
// This delay ensures the backend can receive messages sent immediately after subscription.
|
|
282
|
+
// See: https://github.com/supabase/supabase-js/issues/1599
|
|
283
|
+
await new Promise(resolve => this.#schedule(resolve, this.#stabilizationDelay));
|
|
284
|
+
this.#channels.set(run_id, channel);
|
|
285
|
+
const unsubscribe = () => this.unsubscribe(run_id);
|
|
286
|
+
this.#unsubscribeFunctions.set(run_id, unsubscribe);
|
|
287
|
+
return unsubscribe;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Unsubscribes from a run's events
|
|
291
|
+
*
|
|
292
|
+
* @param run_id - Run ID to unsubscribe from
|
|
293
|
+
*/
|
|
294
|
+
unsubscribe(run_id) {
|
|
295
|
+
this.#unsubscribe(run_id);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Fetches current state of a run and its steps
|
|
299
|
+
*
|
|
300
|
+
* @param run_id - Run ID to fetch
|
|
301
|
+
*/
|
|
302
|
+
async getRunWithStates(run_id) {
|
|
303
|
+
// Call the RPC function which returns a JSONB object
|
|
304
|
+
const { data, error } = await this.#supabase
|
|
305
|
+
.schema('pgflow')
|
|
306
|
+
.rpc('get_run_with_states', { run_id });
|
|
307
|
+
if (error)
|
|
308
|
+
throw error;
|
|
309
|
+
if (!data)
|
|
310
|
+
throw new Error(`No data returned for run ${run_id}`);
|
|
311
|
+
return data;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Unsubscribes from a run's events
|
|
315
|
+
*
|
|
316
|
+
* @param run_id - Run ID to unsubscribe from
|
|
317
|
+
*/
|
|
318
|
+
#unsubscribe(run_id) {
|
|
319
|
+
const channel = this.#channels.get(run_id);
|
|
320
|
+
if (channel) {
|
|
321
|
+
// Close the channel
|
|
322
|
+
channel.unsubscribe();
|
|
323
|
+
this.#channels.delete(run_id);
|
|
324
|
+
// Also clean up the unsubscribe function reference
|
|
325
|
+
this.#unsubscribeFunctions.delete(run_id);
|
|
326
|
+
// We don't need to explicitly remove event listeners from the emitter
|
|
327
|
+
// as they will be garbage collected when no longer referenced.
|
|
328
|
+
// The event listeners are bound to specific callbacks provided by the client,
|
|
329
|
+
// which will retain references if they're still in use.
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AnyFlow, ExtractFlowSteps } from '@pgflow/dsl';
|
|
2
|
+
import type { RunRow, StepStateRow } from '@pgflow/core';
|
|
3
|
+
import type { BroadcastRunEvent, BroadcastStepEvent, FlowRunEvent, StepEvent } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Convert a broadcast run event to a typed run event
|
|
6
|
+
*/
|
|
7
|
+
export declare function toTypedRunEvent<TFlow extends AnyFlow>(evt: BroadcastRunEvent): FlowRunEvent<TFlow>;
|
|
8
|
+
/**
|
|
9
|
+
* Convert a broadcast step event to a typed step event
|
|
10
|
+
*/
|
|
11
|
+
export declare function toTypedStepEvent<TFlow extends AnyFlow, TStepSlug extends keyof ExtractFlowSteps<TFlow> & string>(evt: BroadcastStepEvent): StepEvent<TFlow, TStepSlug>;
|
|
12
|
+
/**
|
|
13
|
+
* Convert a database run row to a typed run event
|
|
14
|
+
*/
|
|
15
|
+
export declare function runRowToTypedEvent<TFlow extends AnyFlow>(row: RunRow): FlowRunEvent<TFlow>;
|
|
16
|
+
/**
|
|
17
|
+
* Convert a database step state row to a typed step event
|
|
18
|
+
*/
|
|
19
|
+
export declare function stepStateRowToTypedEvent<TFlow extends AnyFlow, TStepSlug extends keyof ExtractFlowSteps<TFlow> & string>(row: StepStateRow): StepEvent<TFlow, TStepSlug>;
|
|
20
|
+
//# sourceMappingURL=eventAdapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventAdapters.d.ts","sourceRoot":"","sources":["../../../src/lib/eventAdapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAGP,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAKzD,OAAO,KAAK,EACV,iBAAiB,EACjB,kBAAkB,EAClB,YAAY,EACZ,SAAS,EACV,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAS,OAAO,EACnD,GAAG,EAAE,iBAAiB,GACrB,YAAY,CAAC,KAAK,CAAC,CA+BrB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,SAAS,OAAO,EACrB,SAAS,SAAS,MAAM,gBAAgB,CAAC,KAAK,CAAC,GAAG,MAAM,EACxD,GAAG,EAAE,kBAAkB,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CA6BtD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,SAAS,OAAO,EACtD,GAAG,EAAE,MAAM,GACV,YAAY,CAAC,KAAK,CAAC,CAiCrB;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,SAAS,OAAO,EACrB,SAAS,SAAS,MAAM,gBAAgB,CAAC,KAAK,CAAC,GAAG,MAAM,EACxD,GAAG,EAAE,YAAY,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAgChD"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { FlowStepStatus, FlowRunStatus, } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert a broadcast run event to a typed run event
|
|
4
|
+
*/
|
|
5
|
+
export function toTypedRunEvent(evt) {
|
|
6
|
+
switch (evt.status) {
|
|
7
|
+
case FlowRunStatus.Started:
|
|
8
|
+
return {
|
|
9
|
+
event_type: 'run:started',
|
|
10
|
+
run_id: evt.run_id,
|
|
11
|
+
flow_slug: evt.flow_slug,
|
|
12
|
+
status: FlowRunStatus.Started,
|
|
13
|
+
started_at: evt.started_at,
|
|
14
|
+
remaining_steps: evt.remaining_steps,
|
|
15
|
+
input: evt.input,
|
|
16
|
+
};
|
|
17
|
+
case FlowRunStatus.Completed:
|
|
18
|
+
return {
|
|
19
|
+
event_type: 'run:completed',
|
|
20
|
+
run_id: evt.run_id,
|
|
21
|
+
flow_slug: evt.flow_slug,
|
|
22
|
+
status: FlowRunStatus.Completed,
|
|
23
|
+
completed_at: evt.completed_at,
|
|
24
|
+
output: evt.output,
|
|
25
|
+
};
|
|
26
|
+
case FlowRunStatus.Failed:
|
|
27
|
+
return {
|
|
28
|
+
event_type: 'run:failed',
|
|
29
|
+
run_id: evt.run_id,
|
|
30
|
+
flow_slug: evt.flow_slug,
|
|
31
|
+
status: FlowRunStatus.Failed,
|
|
32
|
+
failed_at: evt.failed_at,
|
|
33
|
+
error_message: evt.error_message,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convert a broadcast step event to a typed step event
|
|
39
|
+
*/
|
|
40
|
+
export function toTypedStepEvent(evt) {
|
|
41
|
+
switch (evt.status) {
|
|
42
|
+
case FlowStepStatus.Started:
|
|
43
|
+
return {
|
|
44
|
+
event_type: 'step:started',
|
|
45
|
+
run_id: evt.run_id,
|
|
46
|
+
step_slug: evt.step_slug,
|
|
47
|
+
status: FlowStepStatus.Started,
|
|
48
|
+
started_at: evt.started_at,
|
|
49
|
+
};
|
|
50
|
+
case FlowStepStatus.Completed:
|
|
51
|
+
return {
|
|
52
|
+
event_type: 'step:completed',
|
|
53
|
+
run_id: evt.run_id,
|
|
54
|
+
step_slug: evt.step_slug,
|
|
55
|
+
status: FlowStepStatus.Completed,
|
|
56
|
+
completed_at: evt.completed_at,
|
|
57
|
+
output: evt.output,
|
|
58
|
+
};
|
|
59
|
+
case FlowStepStatus.Failed:
|
|
60
|
+
return {
|
|
61
|
+
event_type: 'step:failed',
|
|
62
|
+
run_id: evt.run_id,
|
|
63
|
+
step_slug: evt.step_slug,
|
|
64
|
+
status: FlowStepStatus.Failed,
|
|
65
|
+
failed_at: evt.failed_at,
|
|
66
|
+
error_message: evt.error_message,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Convert a database run row to a typed run event
|
|
72
|
+
*/
|
|
73
|
+
export function runRowToTypedEvent(row) {
|
|
74
|
+
switch (row.status) {
|
|
75
|
+
case 'started':
|
|
76
|
+
return {
|
|
77
|
+
event_type: 'run:started',
|
|
78
|
+
run_id: row.run_id,
|
|
79
|
+
flow_slug: row.flow_slug,
|
|
80
|
+
status: FlowRunStatus.Started,
|
|
81
|
+
started_at: row.started_at,
|
|
82
|
+
remaining_steps: row.remaining_steps,
|
|
83
|
+
input: row.input,
|
|
84
|
+
};
|
|
85
|
+
case 'completed':
|
|
86
|
+
return {
|
|
87
|
+
event_type: 'run:completed',
|
|
88
|
+
run_id: row.run_id,
|
|
89
|
+
flow_slug: row.flow_slug,
|
|
90
|
+
status: FlowRunStatus.Completed,
|
|
91
|
+
completed_at: row.completed_at,
|
|
92
|
+
output: row.output,
|
|
93
|
+
};
|
|
94
|
+
case 'failed':
|
|
95
|
+
return {
|
|
96
|
+
event_type: 'run:failed',
|
|
97
|
+
run_id: row.run_id,
|
|
98
|
+
flow_slug: row.flow_slug,
|
|
99
|
+
status: FlowRunStatus.Failed,
|
|
100
|
+
failed_at: row.failed_at,
|
|
101
|
+
error_message: 'Flow failed', // Database doesn't have error_message for runs
|
|
102
|
+
};
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`Unknown run status: ${row.status}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Convert a database step state row to a typed step event
|
|
109
|
+
*/
|
|
110
|
+
export function stepStateRowToTypedEvent(row) {
|
|
111
|
+
switch (row.status) {
|
|
112
|
+
case 'created':
|
|
113
|
+
case 'started':
|
|
114
|
+
return {
|
|
115
|
+
event_type: 'step:started',
|
|
116
|
+
run_id: row.run_id,
|
|
117
|
+
step_slug: row.step_slug,
|
|
118
|
+
status: FlowStepStatus.Started,
|
|
119
|
+
started_at: row.started_at,
|
|
120
|
+
};
|
|
121
|
+
case 'completed':
|
|
122
|
+
return {
|
|
123
|
+
event_type: 'step:completed',
|
|
124
|
+
run_id: row.run_id,
|
|
125
|
+
step_slug: row.step_slug,
|
|
126
|
+
status: FlowStepStatus.Completed,
|
|
127
|
+
completed_at: row.completed_at,
|
|
128
|
+
output: {}, // Database doesn't have output in step_states
|
|
129
|
+
};
|
|
130
|
+
case 'failed':
|
|
131
|
+
return {
|
|
132
|
+
event_type: 'step:failed',
|
|
133
|
+
run_id: row.run_id,
|
|
134
|
+
step_slug: row.step_slug,
|
|
135
|
+
status: FlowStepStatus.Failed,
|
|
136
|
+
failed_at: row.failed_at,
|
|
137
|
+
error_message: row.error_message || 'Step failed',
|
|
138
|
+
};
|
|
139
|
+
default:
|
|
140
|
+
throw new Error(`Unknown step status: ${row.status}`);
|
|
141
|
+
}
|
|
142
|
+
}
|