@pgflow/client 0.0.0-array-map-steps-cd94242a-20251008042921 → 0.0.0-compatible-flow-86a8ccf0-20260319203539

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +171 -3
  2. package/README.md +6 -2
  3. package/dist/client/src/browser.d.ts +7 -0
  4. package/dist/client/src/browser.d.ts.map +1 -0
  5. package/dist/client/src/index.d.ts +6 -0
  6. package/dist/client/src/index.d.ts.map +1 -0
  7. package/dist/client/src/lib/FlowRun.d.ts +125 -0
  8. package/dist/client/src/lib/FlowRun.d.ts.map +1 -0
  9. package/dist/client/src/lib/FlowStep.d.ts +98 -0
  10. package/dist/client/src/lib/FlowStep.d.ts.map +1 -0
  11. package/dist/client/src/lib/PgflowClient.d.ts +76 -0
  12. package/dist/client/src/lib/PgflowClient.d.ts.map +1 -0
  13. package/dist/client/src/lib/SupabaseBroadcastAdapter.d.ts +66 -0
  14. package/dist/client/src/lib/SupabaseBroadcastAdapter.d.ts.map +1 -0
  15. package/dist/client/src/lib/eventAdapters.d.ts +21 -0
  16. package/dist/client/src/lib/eventAdapters.d.ts.map +1 -0
  17. package/dist/client/src/lib/types.d.ts +337 -0
  18. package/dist/client/src/lib/types.d.ts.map +1 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.js +455 -454
  21. package/dist/index.js.map +1 -1
  22. package/dist/package.json +8 -3
  23. package/dist/pgflow-client.browser.js +1 -1
  24. package/dist/pgflow-client.browser.js.map +1 -1
  25. package/dist/src/browser.d.ts +3 -3
  26. package/dist/src/browser.d.ts.map +1 -1
  27. package/dist/src/browser.js +10 -0
  28. package/dist/src/index.js +7 -0
  29. package/dist/src/lib/FlowRun.d.ts +11 -3
  30. package/dist/src/lib/FlowRun.d.ts.map +1 -1
  31. package/dist/src/lib/FlowRun.js +372 -0
  32. package/dist/src/lib/FlowStep.d.ts +20 -4
  33. package/dist/src/lib/FlowStep.d.ts.map +1 -1
  34. package/dist/src/lib/FlowStep.js +284 -0
  35. package/dist/src/lib/PgflowClient.d.ts +12 -10
  36. package/dist/src/lib/PgflowClient.d.ts.map +1 -1
  37. package/dist/src/lib/PgflowClient.js +227 -0
  38. package/dist/src/lib/SupabaseBroadcastAdapter.d.ts +4 -4
  39. package/dist/src/lib/SupabaseBroadcastAdapter.d.ts.map +1 -1
  40. package/dist/src/lib/SupabaseBroadcastAdapter.js +332 -0
  41. package/dist/src/lib/eventAdapters.d.ts +3 -4
  42. package/dist/src/lib/eventAdapters.d.ts.map +1 -1
  43. package/dist/src/lib/eventAdapters.js +160 -0
  44. package/dist/src/lib/types.d.ts +34 -6
  45. package/dist/src/lib/types.d.ts.map +1 -1
  46. package/dist/src/lib/types.js +102 -0
  47. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  48. package/package.json +10 -5
@@ -0,0 +1,284 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ import { FlowStepStatus } from './types.js';
3
+ /**
4
+ * Represents a single step in a flow run
5
+ */
6
+ export class FlowStep {
7
+ #state;
8
+ #events = createNanoEvents();
9
+ #statusPrecedence = {
10
+ [FlowStepStatus.Created]: 0,
11
+ [FlowStepStatus.Started]: 1,
12
+ [FlowStepStatus.Completed]: 2,
13
+ [FlowStepStatus.Failed]: 3,
14
+ [FlowStepStatus.Skipped]: 4,
15
+ };
16
+ /**
17
+ * Creates a new FlowStep instance
18
+ *
19
+ * @param initialState - Initial state for the step
20
+ */
21
+ constructor(initialState) {
22
+ this.#state = initialState;
23
+ }
24
+ /**
25
+ * Get the run ID this step belongs to
26
+ */
27
+ get run_id() {
28
+ return this.#state.run_id;
29
+ }
30
+ /**
31
+ * Get the step slug
32
+ */
33
+ get step_slug() {
34
+ return this.#state.step_slug;
35
+ }
36
+ /**
37
+ * Get the current status
38
+ */
39
+ get status() {
40
+ return this.#state.status;
41
+ }
42
+ /**
43
+ * Get the started_at timestamp
44
+ */
45
+ get started_at() {
46
+ return this.#state.started_at;
47
+ }
48
+ /**
49
+ * Get the completed_at timestamp
50
+ */
51
+ get completed_at() {
52
+ return this.#state.completed_at;
53
+ }
54
+ /**
55
+ * Get the failed_at timestamp
56
+ */
57
+ get failed_at() {
58
+ return this.#state.failed_at;
59
+ }
60
+ /**
61
+ * Get the skipped_at timestamp
62
+ */
63
+ get skipped_at() {
64
+ return this.#state.skipped_at;
65
+ }
66
+ /**
67
+ * Get the skip reason
68
+ */
69
+ get skip_reason() {
70
+ return this.#state.skip_reason;
71
+ }
72
+ /**
73
+ * Get the step output
74
+ */
75
+ get output() {
76
+ return this.#state.output;
77
+ }
78
+ /**
79
+ * Get the error object
80
+ */
81
+ get error() {
82
+ return this.#state.error;
83
+ }
84
+ /**
85
+ * Get the error message
86
+ */
87
+ get error_message() {
88
+ return this.#state.error_message;
89
+ }
90
+ /**
91
+ * Register an event handler for a step event
92
+ *
93
+ * @param event - Event type to listen for
94
+ * @param callback - Callback function to execute when event is emitted
95
+ * @returns Function to unsubscribe from the event
96
+ */
97
+ on(event, callback) {
98
+ return this.#events.on(event, callback);
99
+ }
100
+ /**
101
+ * Wait for the step to reach a specific status
102
+ *
103
+ * @param targetStatus - The status to wait for
104
+ * @param options - Optional timeout and abort signal
105
+ * @returns Promise that resolves with the step instance when the status is reached
106
+ */
107
+ waitForStatus(targetStatus, options) {
108
+ const timeoutMs = options?.timeoutMs ?? 5 * 60 * 1000; // Default 5 minutes
109
+ const { signal } = options || {};
110
+ // If we already have the target status, resolve immediately
111
+ if (this.status === targetStatus) {
112
+ return Promise.resolve(this);
113
+ }
114
+ // Otherwise, wait for the status to change
115
+ return new Promise((resolve, reject) => {
116
+ let timeoutId;
117
+ let cleanedUp = false;
118
+ // Set up timeout if provided
119
+ if (timeoutMs > 0) {
120
+ timeoutId = setTimeout(() => {
121
+ if (cleanedUp)
122
+ return; // Prevent firing if already cleaned up
123
+ cleanedUp = true;
124
+ unbind();
125
+ reject(new Error(`Timeout waiting for step ${this.step_slug} to reach status '${targetStatus}'`));
126
+ }, timeoutMs);
127
+ }
128
+ // Set up abort signal if provided
129
+ let abortCleanup;
130
+ if (signal) {
131
+ const abortHandler = () => {
132
+ if (cleanedUp)
133
+ return; // Prevent double cleanup
134
+ cleanedUp = true;
135
+ if (timeoutId)
136
+ clearTimeout(timeoutId);
137
+ unbind();
138
+ reject(new Error(`Aborted waiting for step ${this.step_slug} to reach status '${targetStatus}'`));
139
+ };
140
+ signal.addEventListener('abort', abortHandler);
141
+ abortCleanup = () => {
142
+ signal.removeEventListener('abort', abortHandler);
143
+ };
144
+ }
145
+ // Subscribe to all events
146
+ const unbind = this.on('*', (event) => {
147
+ if (event.status === targetStatus) {
148
+ if (cleanedUp)
149
+ return; // Prevent double cleanup
150
+ cleanedUp = true;
151
+ if (timeoutId)
152
+ clearTimeout(timeoutId);
153
+ if (abortCleanup)
154
+ abortCleanup();
155
+ unbind();
156
+ resolve(this);
157
+ }
158
+ });
159
+ });
160
+ }
161
+ /**
162
+ * Apply state from database snapshot (no events emitted)
163
+ * Used when initializing state from start_flow_with_states() or get_run_with_states()
164
+ *
165
+ * @internal This method is only intended for use by PgflowClient.
166
+ * Applications should not call this directly.
167
+ */
168
+ applySnapshot(row) {
169
+ // Direct state assignment from database row (no event conversion)
170
+ this.#state.status = row.status;
171
+ this.#state.started_at = row.started_at ? new Date(row.started_at) : null;
172
+ this.#state.completed_at = row.completed_at
173
+ ? new Date(row.completed_at)
174
+ : null;
175
+ this.#state.failed_at = row.failed_at ? new Date(row.failed_at) : null;
176
+ this.#state.error_message = row.error_message;
177
+ this.#state.error = row.error_message ? new Error(row.error_message) : null;
178
+ this.#state.skipped_at = row.skipped_at ? new Date(row.skipped_at) : null;
179
+ this.#state.skip_reason = row.skip_reason;
180
+ // Note: output is not stored in step_states table, remains null
181
+ }
182
+ /**
183
+ * Updates the step state based on an event
184
+ *
185
+ * @internal This method is only intended for use by FlowRun and tests.
186
+ * Applications should not call this directly - state updates should come from
187
+ * database events through the PgflowClient.
188
+ *
189
+ * TODO: After v1.0, make this method private and refactor tests to use PgflowClient
190
+ * with event emission instead of direct state manipulation.
191
+ */
192
+ updateState(event) {
193
+ // Validate event is for this step
194
+ if (event.step_slug !== this.#state.step_slug) {
195
+ return false;
196
+ }
197
+ // Validate event is for this run
198
+ if (event.run_id !== this.#state.run_id) {
199
+ return false;
200
+ }
201
+ // Check if the event status has higher precedence than current status
202
+ if (!this.#shouldUpdateStatus(this.#state.status, event.status)) {
203
+ return false;
204
+ }
205
+ // Update state based on event type using narrowing type guards
206
+ switch (event.status) {
207
+ case FlowStepStatus.Started:
208
+ this.#state = {
209
+ ...this.#state,
210
+ status: FlowStepStatus.Started,
211
+ started_at: typeof event.started_at === 'string'
212
+ ? new Date(event.started_at)
213
+ : new Date(),
214
+ };
215
+ this.#events.emit('started', event);
216
+ break;
217
+ case FlowStepStatus.Completed:
218
+ this.#state = {
219
+ ...this.#state,
220
+ status: FlowStepStatus.Completed,
221
+ completed_at: typeof event.completed_at === 'string'
222
+ ? new Date(event.completed_at)
223
+ : new Date(),
224
+ output: event.output,
225
+ };
226
+ this.#events.emit('completed', event);
227
+ break;
228
+ case FlowStepStatus.Failed:
229
+ this.#state = {
230
+ ...this.#state,
231
+ status: FlowStepStatus.Failed,
232
+ failed_at: typeof event.failed_at === 'string'
233
+ ? new Date(event.failed_at)
234
+ : new Date(),
235
+ error_message: typeof event.error_message === 'string'
236
+ ? event.error_message
237
+ : 'Unknown error',
238
+ error: new Error(typeof event.error_message === 'string'
239
+ ? event.error_message
240
+ : 'Unknown error'),
241
+ };
242
+ this.#events.emit('failed', event);
243
+ break;
244
+ case FlowStepStatus.Skipped:
245
+ this.#state = {
246
+ ...this.#state,
247
+ status: FlowStepStatus.Skipped,
248
+ skipped_at: typeof event.skipped_at === 'string'
249
+ ? new Date(event.skipped_at)
250
+ : new Date(),
251
+ skip_reason: event.skip_reason,
252
+ };
253
+ this.#events.emit('skipped', event);
254
+ break;
255
+ default: {
256
+ // Exhaustiveness check - ensures all event statuses are handled
257
+ event;
258
+ return false;
259
+ }
260
+ }
261
+ // Also emit to the catch-all listener
262
+ this.#events.emit('*', event);
263
+ return true;
264
+ }
265
+ /**
266
+ * Determines if a status should be updated based on precedence
267
+ *
268
+ * @param currentStatus - Current status
269
+ * @param newStatus - New status
270
+ * @returns true if the status should be updated, false otherwise
271
+ */
272
+ #shouldUpdateStatus(currentStatus, newStatus) {
273
+ // Don't allow changes to terminal states
274
+ if (currentStatus === FlowStepStatus.Completed ||
275
+ currentStatus === FlowStepStatus.Failed ||
276
+ currentStatus === FlowStepStatus.Skipped) {
277
+ return false; // Terminal states should never change
278
+ }
279
+ const currentPrecedence = this.#statusPrecedence[currentStatus];
280
+ const newPrecedence = this.#statusPrecedence[newStatus];
281
+ // Only allow transitions to higher precedence non-terminal status
282
+ return newPrecedence > currentPrecedence;
283
+ }
284
+ }
@@ -1,9 +1,7 @@
1
+ import type { SupabaseClient } from '@supabase/supabase-js';
2
+ import type { AnyFlow, ExtractFlowInput } from '@pgflow/dsl';
3
+ import type { IFlowClient, BroadcastRunEvent, BroadcastStepEvent, Unsubscribe } from './types.js';
1
4
  import { FlowRun } from './FlowRun.js';
2
- import { IFlowClient, BroadcastRunEvent, BroadcastStepEvent, Unsubscribe } from './types.js';
3
- import { RunRow } from '../../../core/src/index.ts';
4
- import { AnyFlow, ExtractFlowInput } from '../../../dsl/src/index.ts';
5
- import { SupabaseClient } from '@supabase/supabase-js';
6
-
7
5
  /**
8
6
  * Client for interacting with pgflow
9
7
  */
@@ -13,8 +11,12 @@ export declare class PgflowClient<TFlow extends AnyFlow = AnyFlow> implements IF
13
11
  * Creates a new PgflowClient instance
14
12
  *
15
13
  * @param supabaseClient - Supabase client instance
14
+ * @param opts - Optional configuration
16
15
  */
17
- constructor(supabaseClient: SupabaseClient);
16
+ constructor(supabaseClient: SupabaseClient, opts?: {
17
+ realtimeStabilizationDelayMs?: number;
18
+ schedule?: typeof setTimeout;
19
+ });
18
20
  /**
19
21
  * Start a flow with optional run_id
20
22
  *
@@ -38,8 +40,8 @@ export declare class PgflowClient<TFlow extends AnyFlow = AnyFlow> implements IF
38
40
  * Fetch flow definition metadata
39
41
  */
40
42
  fetchFlowDefinition(flow_slug: string): Promise<{
41
- flow: import('../../../core/src/index.ts').FlowRow;
42
- steps: import('../../../core/src/index.ts').StepRow[];
43
+ flow: import("pkgs/core/dist/types.js").FlowRow;
44
+ steps: import("pkgs/core/dist/types.js").StepRow[];
43
45
  }>;
44
46
  /**
45
47
  * Register a callback for run events
@@ -59,8 +61,8 @@ export declare class PgflowClient<TFlow extends AnyFlow = AnyFlow> implements IF
59
61
  * Fetch current state of a run and its steps
60
62
  */
61
63
  getRunWithStates(run_id: string): Promise<{
62
- run: RunRow;
63
- steps: import('../../../core/src/index.ts').StepStateRow[];
64
+ run: import("pkgs/core/dist/types.js").RunRow;
65
+ steps: import("pkgs/core/dist/types.js").StepStateRow[];
64
66
  }>;
65
67
  /**
66
68
  * Get a flow run by ID
@@ -1 +1 @@
1
- {"version":3,"file":"PgflowClient.d.ts","sourceRoot":"","sources":["../../../src/lib/PgflowClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC;;GAEG;AACH,qBAAa,YAAY,CAAC,KAAK,SAAS,OAAO,GAAG,OAAO,CAAE,YAAW,WAAW,CAAC,KAAK,CAAC;;IAOtF;;;;OAIG;gBACS,cAAc,EAAE,cAAc;IAwB1C;;;;;;;OAOG;IACG,SAAS,CAAC,aAAa,SAAS,KAAK,EACzC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,gBAAgB,CAAC,aAAa,CAAC,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAwDlC;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAc5B;;OAEG;IACH,UAAU,IAAI,IAAI;IAQlB;;OAEG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM;;;;IAI3C;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAIrE;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,WAAW;IAIvE;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAIzD;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM;;;;IAIrC;;;;;OAKG;IACG,MAAM,CAAC,aAAa,SAAS,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;CA6E1G"}
1
+ {"version":3,"file":"PgflowClient.d.ts","sourceRoot":"","sources":["../../../src/lib/PgflowClient.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EACjB,kBAAkB,EAClB,WAAW,EAEZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC;;GAEG;AACH,qBAAa,YAAY,CAAC,KAAK,SAAS,OAAO,GAAG,OAAO,CAAE,YAAW,WAAW,CAAC,KAAK,CAAC;;IAOtF;;;;;OAKG;gBAED,cAAc,EAAE,cAAc,EAC9B,IAAI,GAAE;QACJ,4BAA4B,CAAC,EAAE,MAAM,CAAC;QACtC,QAAQ,CAAC,EAAE,OAAO,UAAU,CAAC;KACzB;IA4BR;;;;;;;OAOG;IACG,SAAS,CAAC,aAAa,SAAS,KAAK,EACzC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,gBAAgB,CAAC,aAAa,CAAC,EACtC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAwDlC;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAc5B;;OAEG;IACH,UAAU,IAAI,IAAI;IAQlB;;OAEG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM;;;;IAI3C;;;OAGG;IACH,UAAU,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAAG,WAAW;IAIrE;;;OAGG;IACH,WAAW,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,WAAW;IAIvE;;OAEG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC;IAIzD;;OAEG;IACG,gBAAgB,CAAC,MAAM,EAAE,MAAM;;;;IAIrC;;;;;OAKG;IACG,MAAM,CAAC,aAAa,SAAS,KAAK,GAAG,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;CA6E1G"}
@@ -0,0 +1,227 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+ import { FlowRunStatus } from './types.js';
3
+ import { SupabaseBroadcastAdapter } from './SupabaseBroadcastAdapter.js';
4
+ import { FlowRun } from './FlowRun.js';
5
+ import { toTypedRunEvent, toTypedStepEvent } from './eventAdapters.js';
6
+ /**
7
+ * Client for interacting with pgflow
8
+ */
9
+ export class PgflowClient {
10
+ #supabase;
11
+ #realtimeAdapter;
12
+ // Use the widest event type - keeps the compiler happy but
13
+ // still provides the structural API we need (updateState/step/...)
14
+ #runs = new Map();
15
+ /**
16
+ * Creates a new PgflowClient instance
17
+ *
18
+ * @param supabaseClient - Supabase client instance
19
+ * @param opts - Optional configuration
20
+ */
21
+ constructor(supabaseClient, opts = {}) {
22
+ this.#supabase = supabaseClient;
23
+ this.#realtimeAdapter = new SupabaseBroadcastAdapter(supabaseClient, {
24
+ stabilizationDelayMs: opts.realtimeStabilizationDelayMs,
25
+ schedule: opts.schedule,
26
+ });
27
+ // Set up global event listeners - properly typed
28
+ this.#realtimeAdapter.onRunEvent((event) => {
29
+ const run = this.#runs.get(event.run_id);
30
+ if (run) {
31
+ // Convert broadcast event to typed event before updating state
32
+ run.updateState(toTypedRunEvent(event));
33
+ }
34
+ });
35
+ this.#realtimeAdapter.onStepEvent((event) => {
36
+ const run = this.#runs.get(event.run_id);
37
+ if (run) {
38
+ // Always materialize the step before updating to avoid event loss
39
+ // This ensures we cache all steps even if they were never explicitly requested
40
+ const stepSlug = event.step_slug;
41
+ run.step(stepSlug).updateState(toTypedStepEvent(event));
42
+ }
43
+ });
44
+ }
45
+ /**
46
+ * Start a flow with optional run_id
47
+ *
48
+ * @param flow_slug - Flow slug to start
49
+ * @param input - Input data for the flow
50
+ * @param run_id - Optional run ID (will be generated if not provided)
51
+ * @returns Promise that resolves with the FlowRun instance
52
+ */
53
+ async startFlow(flow_slug, input, run_id) {
54
+ // Generate a run_id if not provided
55
+ const id = run_id || uuidv4();
56
+ // Create initial state for the flow run
57
+ const initialState = {
58
+ run_id: id,
59
+ flow_slug,
60
+ status: FlowRunStatus.Started,
61
+ input: input,
62
+ output: null,
63
+ error: null,
64
+ error_message: null,
65
+ started_at: null,
66
+ completed_at: null,
67
+ failed_at: null,
68
+ remaining_steps: -1, // Use -1 to indicate unknown until first snapshot arrives
69
+ };
70
+ // Create the flow run instance
71
+ const run = new FlowRun(initialState);
72
+ // Store the run
73
+ this.#runs.set(id, run);
74
+ // Set up subscription for run and step events (wait for subscription confirmation)
75
+ await this.#realtimeAdapter.subscribeToRun(id);
76
+ // Start the flow with the predetermined run_id (only after subscription is ready)
77
+ const { data, error } = await this.#supabase.schema('pgflow').rpc('start_flow_with_states', {
78
+ flow_slug: flow_slug,
79
+ input: input,
80
+ run_id: id
81
+ });
82
+ if (error) {
83
+ // Clean up subscription and run instance
84
+ this.dispose(id);
85
+ throw error;
86
+ }
87
+ // Apply the run state snapshot (no events)
88
+ if (data.run) {
89
+ run.applySnapshot(data.run);
90
+ }
91
+ // Apply step state snapshots (no events)
92
+ if (data.steps && Array.isArray(data.steps)) {
93
+ for (const stepState of data.steps) {
94
+ run.step(stepState.step_slug).applySnapshot(stepState);
95
+ }
96
+ }
97
+ return run;
98
+ }
99
+ /**
100
+ * Dispose a specific flow run
101
+ *
102
+ * @param runId - Run ID to dispose
103
+ */
104
+ dispose(runId) {
105
+ const run = this.#runs.get(runId);
106
+ if (run) {
107
+ // First unsubscribe from the realtime adapter
108
+ this.#realtimeAdapter.unsubscribe(runId);
109
+ // Then dispose the run
110
+ run.dispose();
111
+ // Finally remove from the runs map
112
+ this.#runs.delete(runId);
113
+ }
114
+ }
115
+ /**
116
+ * Dispose all flow runs
117
+ */
118
+ disposeAll() {
119
+ for (const runId of this.#runs.keys()) {
120
+ this.dispose(runId);
121
+ }
122
+ }
123
+ // Delegate IFlowRealtime methods to the adapter
124
+ /**
125
+ * Fetch flow definition metadata
126
+ */
127
+ async fetchFlowDefinition(flow_slug) {
128
+ return this.#realtimeAdapter.fetchFlowDefinition(flow_slug);
129
+ }
130
+ /**
131
+ * Register a callback for run events
132
+ * @returns Function to unsubscribe from the event
133
+ */
134
+ onRunEvent(callback) {
135
+ return this.#realtimeAdapter.onRunEvent(callback);
136
+ }
137
+ /**
138
+ * Register a callback for step events
139
+ * @returns Function to unsubscribe from the event
140
+ */
141
+ onStepEvent(callback) {
142
+ return this.#realtimeAdapter.onStepEvent(callback);
143
+ }
144
+ /**
145
+ * Subscribe to a flow run's events
146
+ */
147
+ async subscribeToRun(run_id) {
148
+ return await this.#realtimeAdapter.subscribeToRun(run_id);
149
+ }
150
+ /**
151
+ * Fetch current state of a run and its steps
152
+ */
153
+ async getRunWithStates(run_id) {
154
+ return this.#realtimeAdapter.getRunWithStates(run_id);
155
+ }
156
+ /**
157
+ * Get a flow run by ID
158
+ *
159
+ * @param run_id - ID of the run to get
160
+ * @returns Promise that resolves with the FlowRun instance or null if not found
161
+ */
162
+ async getRun(run_id) {
163
+ // Check if we already have this run cached
164
+ const existingRun = this.#runs.get(run_id);
165
+ if (existingRun) {
166
+ return existingRun;
167
+ }
168
+ try {
169
+ // Fetch the run state from the database
170
+ const { run, steps } = await this.getRunWithStates(run_id);
171
+ if (!run) {
172
+ return null;
173
+ }
174
+ // Validate required fields
175
+ if (!run.run_id || !run.flow_slug || !run.status) {
176
+ throw new Error('Invalid run data: missing required fields');
177
+ }
178
+ // Validate status is a valid FlowRunStatus
179
+ const validStatuses = Object.values(FlowRunStatus);
180
+ if (!validStatuses.includes(run.status)) {
181
+ throw new Error(`Invalid run data: invalid status '${run.status}'`);
182
+ }
183
+ // Create flow run with minimal initial state
184
+ const initialState = {
185
+ run_id: run.run_id,
186
+ flow_slug: run.flow_slug,
187
+ status: run.status,
188
+ input: run.input,
189
+ output: null,
190
+ error: null,
191
+ error_message: null,
192
+ started_at: null,
193
+ completed_at: null,
194
+ failed_at: null,
195
+ remaining_steps: 0,
196
+ };
197
+ // Create the flow run instance
198
+ const flowRun = new FlowRun(initialState);
199
+ // Apply the complete state from database snapshot
200
+ flowRun.applySnapshot(run);
201
+ // Store the run
202
+ this.#runs.set(run_id, flowRun);
203
+ // Set up subscription for run and step events
204
+ await this.#realtimeAdapter.subscribeToRun(run_id);
205
+ // Initialize steps from snapshot
206
+ if (steps && Array.isArray(steps)) {
207
+ for (const stepState of steps) {
208
+ // Validate step has required fields
209
+ if (!stepState.step_slug || !stepState.status) {
210
+ throw new Error('Invalid step data: missing required fields');
211
+ }
212
+ // Apply snapshot state directly (no events)
213
+ flowRun.step(stepState.step_slug).applySnapshot(stepState);
214
+ }
215
+ }
216
+ return flowRun;
217
+ }
218
+ catch (error) {
219
+ console.error('Error getting run:', error);
220
+ // Re-throw if it's a validation error
221
+ if (error instanceof Error && (error.message.includes('Invalid run data') || error.message.includes('Invalid step data'))) {
222
+ throw error;
223
+ }
224
+ return null;
225
+ }
226
+ }
227
+ }
@@ -1,7 +1,6 @@
1
- import { IFlowRealtime, BroadcastRunEvent, BroadcastStepEvent, Unsubscribe } from './types.js';
2
- import { FlowRow, StepRow, RunRow, StepStateRow } from '../../../core/src/index.ts';
3
- import { SupabaseClient } from '@supabase/supabase-js';
4
-
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';
5
4
  /**
6
5
  * Adapter to handle realtime communication with Supabase
7
6
  */
@@ -14,6 +13,7 @@ export declare class SupabaseBroadcastAdapter implements IFlowRealtime {
14
13
  */
15
14
  constructor(supabase: SupabaseClient, opts?: {
16
15
  reconnectDelayMs?: number;
16
+ stabilizationDelayMs?: number;
17
17
  schedule?: typeof setTimeout;
18
18
  });
19
19
  /**
@@ -1 +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;;IAQ5D;;;;OAIG;gBAED,QAAQ,EAAE,cAAc,EACxB,IAAI,GAAE;QAAE,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,OAAO,UAAU,CAAA;KAAO;IAkLxE;;;;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;IA0DzD;;;;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"}
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"}