@pgflow/client 0.0.0-update-supabase-ba45e13a-20251119080026 → 0.0.0-worker-management-a4f969d1-20251208095931

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 CHANGED
@@ -1,10 +1,43 @@
1
1
  # @pgflow/client
2
2
 
3
- ## 0.0.0-update-supabase-ba45e13a-20251119080026
3
+ ## 0.0.0-worker-management-a4f969d1-20251208095931
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [0b84bb0]
8
+ - Updated dependencies [90276ce]
9
+ - @pgflow/core@0.0.0-worker-management-a4f969d1-20251208095931
10
+ - @pgflow/dsl@0.0.0-worker-management-a4f969d1-20251208095931
11
+
12
+ ## 0.9.1
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [992a86b]
17
+ - @pgflow/dsl@0.9.1
18
+ - @pgflow/core@0.9.1
19
+
20
+ ## 0.9.0
21
+
22
+ ### Patch Changes
23
+
24
+ - @pgflow/core@0.9.0
25
+ - @pgflow/dsl@0.9.0
26
+
27
+ ## 0.8.1
28
+
29
+ ### Patch Changes
30
+
31
+ - f1d3c32: Fix incorrect Supabase CLI version requirement from 2.34.3 to 2.50.3. CLI 2.50.3 is the first version to include pgmq 1.5.0+, which is required for pgflow 0.8.0+.
32
+ - Updated dependencies [f1d3c32]
33
+ - @pgflow/core@0.8.1
34
+ - @pgflow/dsl@0.8.1
35
+
36
+ ## 0.8.0
4
37
 
5
38
  ### Minor Changes
6
39
 
7
- - 3d8b13b: BREAKING CHANGE: pgflow 0.8.0 requires pgmq 1.5.0+, PostgreSQL 17, and Supabase CLI 2.34.3+
40
+ - 7380237: BREAKING CHANGE: pgflow 0.8.0 requires pgmq 1.5.0+, PostgreSQL 17, and Supabase CLI 2.50.3+
8
41
 
9
42
  This version modernizes infrastructure dependencies and will NOT work with pgmq 1.4.x or earlier. The migration includes a compatibility check that aborts with a clear error message if requirements are not met.
10
43
 
@@ -12,9 +45,9 @@
12
45
 
13
46
  - pgmq 1.5.0 or higher (previously supported 1.4.x)
14
47
  - PostgreSQL 17 (from 15)
15
- - Supabase CLI 2.34.3 or higher (includes pgmq 1.5.0+)
48
+ - Supabase CLI 2.50.3 or higher (includes pgmq 1.5.0+)
16
49
 
17
- **For Supabase users:** Upgrade your Supabase CLI to 2.34.3+ which includes pgmq 1.5.0 by default.
50
+ **For Supabase users:** Upgrade your Supabase CLI to 2.50.3+ which includes pgmq 1.5.0 by default.
18
51
 
19
52
  **For self-hosted users:** Upgrade pgmq to 1.5.0+ and PostgreSQL to 17 before upgrading pgflow.
20
53
 
@@ -22,9 +55,9 @@
22
55
 
23
56
  ### Patch Changes
24
57
 
25
- - Updated dependencies [3d8b13b]
26
- - @pgflow/core@0.0.0-update-supabase-ba45e13a-20251119080026
27
- - @pgflow/dsl@0.0.0-update-supabase-ba45e13a-20251119080026
58
+ - Updated dependencies [7380237]
59
+ - @pgflow/core@0.8.0
60
+ - @pgflow/dsl@0.8.0
28
61
 
29
62
  ## 0.7.3
30
63
 
package/README.md CHANGED
@@ -230,7 +230,7 @@ When using with `@pgflow/dsl`, you get full type safety:
230
230
  import { Flow } from '@pgflow/dsl';
231
231
 
232
232
  // Define your flow
233
- const AnalyzeWebsite = new Flow<{ url: string }>({ slug: 'analyze_website' })
233
+ const AnalyzeWebsite = new Flow<{ url: string }>({ slug: 'analyzeWebsite' })
234
234
  .step({ slug: 'scrape' }, async (input) => ({ content: 'html...' }))
235
235
  .step({ slug: 'analyze' }, async (input) => ({ sentiment: 0.8 }));
236
236
 
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgflow/client",
3
- "version": "0.7.3",
3
+ "version": "0.9.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -44,9 +44,8 @@
44
44
  "@pgflow/dsl": "workspace:*",
45
45
  "@types/uuid": "^10.0.0",
46
46
  "postgres": "^3.4.5",
47
- "supabase": "^2.34.3",
48
47
  "terser": "^5.43.0",
49
48
  "vite-plugin-dts": "~3.8.1",
50
49
  "vitest": "1.3.1"
51
50
  }
52
- }
51
+ }
@@ -0,0 +1,7 @@
1
+ export { PgflowClient } from './lib/PgflowClient.js';
2
+ export { FlowRunStatus, FlowStepStatus } from './lib/types.js';
3
+ export * from './lib/types.js';
4
+ import { PgflowClient } from './lib/PgflowClient.js';
5
+ import type { SupabaseClient } from '@supabase/supabase-js';
6
+ export declare function createClient(supabaseClient: SupabaseClient): PgflowClient<import("pkgs/dsl/dist/dsl.js").AnyFlow>;
7
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../src/browser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC/D,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG5D,wBAAgB,YAAY,CAAC,cAAc,EAAE,cAAc,wDAE1D"}
@@ -0,0 +1,10 @@
1
+ // Browser-specific entry point
2
+ export { PgflowClient } from './lib/PgflowClient.js';
3
+ export { FlowRunStatus, FlowStepStatus } from './lib/types.js';
4
+ export * from './lib/types.js';
5
+ // Import the PgflowClient constructor for the factory
6
+ import { PgflowClient } from './lib/PgflowClient.js';
7
+ // Factory function for creating PgflowClient instances
8
+ export function createClient(supabaseClient) {
9
+ return new PgflowClient(supabaseClient);
10
+ }
@@ -0,0 +1,6 @@
1
+ export * from './lib/types.js';
2
+ export { PgflowClient } from './lib/PgflowClient.js';
3
+ export { FlowRun } from './lib/FlowRun.js';
4
+ export { FlowStep } from './lib/FlowStep.js';
5
+ export type { Unsubscribe } from './lib/types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAGrD,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,YAAY,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,7 @@
1
+ // Export all types from types.ts
2
+ export * from './lib/types.js';
3
+ // Export main client class
4
+ export { PgflowClient } from './lib/PgflowClient.js';
5
+ // Export FlowRun and FlowStep classes (not just types)
6
+ export { FlowRun } from './lib/FlowRun.js';
7
+ export { FlowStep } from './lib/FlowStep.js';
@@ -0,0 +1,125 @@
1
+ import type { AnyFlow, ExtractFlowInput, ExtractFlowOutput, ExtractFlowSteps } from '@pgflow/dsl';
2
+ import { FlowRunStatus } from './types.js';
3
+ import type { FlowRunState, FlowRunEvents, Unsubscribe, FlowRunBase, FlowRunEvent, StepEvent } from './types.js';
4
+ import { FlowStep } from './FlowStep.js';
5
+ /**
6
+ * Represents a single execution of a flow
7
+ */
8
+ export declare class FlowRun<TFlow extends AnyFlow> implements FlowRunBase<FlowRunEvent<TFlow>> {
9
+ #private;
10
+ /**
11
+ * Creates a new FlowRun instance
12
+ *
13
+ * @param initialState - Initial state for the run
14
+ */
15
+ constructor(initialState: FlowRunState<TFlow>);
16
+ /**
17
+ * Get the run ID
18
+ */
19
+ get run_id(): string;
20
+ /**
21
+ * Get the flow slug
22
+ */
23
+ get flow_slug(): string;
24
+ /**
25
+ * Get the current status
26
+ */
27
+ get status(): FlowRunStatus;
28
+ /**
29
+ * Get the started_at timestamp
30
+ */
31
+ get started_at(): Date | null;
32
+ /**
33
+ * Get the completed_at timestamp
34
+ */
35
+ get completed_at(): Date | null;
36
+ /**
37
+ * Get the failed_at timestamp
38
+ */
39
+ get failed_at(): Date | null;
40
+ /**
41
+ * Get the flow input
42
+ */
43
+ get input(): ExtractFlowInput<TFlow>;
44
+ /**
45
+ * Get the flow output
46
+ */
47
+ get output(): ExtractFlowOutput<TFlow> | null;
48
+ /**
49
+ * Get the error object
50
+ */
51
+ get error(): Error | null;
52
+ /**
53
+ * Get the error message
54
+ */
55
+ get error_message(): string | null;
56
+ /**
57
+ * Get the number of remaining steps
58
+ */
59
+ get remaining_steps(): number;
60
+ /**
61
+ * Register an event handler for a run event
62
+ *
63
+ * @param event - Event type to listen for
64
+ * @param callback - Callback function to execute when event is emitted
65
+ * @returns Function to unsubscribe from the event
66
+ */
67
+ on<E extends keyof FlowRunEvents<TFlow>>(event: E, callback: FlowRunEvents<TFlow>[E]): Unsubscribe;
68
+ /**
69
+ * Get a FlowStep instance for a specific step
70
+ *
71
+ * @param stepSlug - Step slug to get
72
+ * @returns FlowStep instance for the specified step
73
+ */
74
+ step<TStepSlug extends keyof ExtractFlowSteps<TFlow> & string>(stepSlug: TStepSlug): FlowStep<TFlow, TStepSlug>;
75
+ /**
76
+ * Check if this run has a specific step
77
+ *
78
+ * @param stepSlug - Step slug to check
79
+ * @returns true if the step exists, false otherwise
80
+ */
81
+ hasStep(stepSlug: string): boolean;
82
+ /**
83
+ * Wait for the run to reach a specific status
84
+ *
85
+ * @param targetStatus - The status to wait for
86
+ * @param options - Optional timeout and abort signal
87
+ * @returns Promise that resolves with the run instance when the status is reached
88
+ */
89
+ waitForStatus(targetStatus: FlowRunStatus.Completed | FlowRunStatus.Failed, options?: {
90
+ timeoutMs?: number;
91
+ signal?: AbortSignal;
92
+ }): Promise<this>;
93
+ /**
94
+ * Apply state from database snapshot (no events emitted)
95
+ * Used when initializing state from start_flow_with_states() or get_run_with_states()
96
+ *
97
+ * @internal This method is only intended for use by PgflowClient.
98
+ * Applications should not call this directly.
99
+ */
100
+ applySnapshot(row: import('@pgflow/core').RunRow): void;
101
+ /**
102
+ * Updates the run state based on an event
103
+ *
104
+ * @internal This method is only intended for use by PgflowClient and tests.
105
+ * Applications should not call this directly - state updates should come from
106
+ * database events through the PgflowClient.
107
+ *
108
+ * TODO: After v1.0, make this method private and refactor tests to use PgflowClient
109
+ * with event emission instead of direct state manipulation.
110
+ */
111
+ updateState(event: FlowRunEvent<TFlow>): boolean;
112
+ /**
113
+ * Updates a step state based on an event
114
+ *
115
+ * @param stepSlug - Step slug to update
116
+ * @param event - Event data to update the step with
117
+ * @returns true if the state was updated, false otherwise
118
+ */
119
+ updateStepState<TStepSlug extends keyof ExtractFlowSteps<TFlow> & string>(stepSlug: TStepSlug, event: StepEvent<TFlow, TStepSlug>): boolean;
120
+ /**
121
+ * Clean up all resources held by this run
122
+ */
123
+ dispose(): void;
124
+ }
125
+ //# sourceMappingURL=FlowRun.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FlowRun.d.ts","sourceRoot":"","sources":["../../../src/lib/FlowRun.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAC3D,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,WAAW,EAEX,YAAY,EACZ,SAAS,EACV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;GAEG;AACH,qBAAa,OAAO,CAAC,KAAK,SAAS,OAAO,CACxC,YAAW,WAAW,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;;IAY3C;;;;OAIG;gBACS,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC;IAI7C;;OAEG;IACH,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED;;OAEG;IACH,IAAI,UAAU,IAAI,IAAI,GAAG,IAAI,CAE5B;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,IAAI,GAAG,IAAI,CAE9B;IAED;;OAEG;IACH,IAAI,SAAS,IAAI,IAAI,GAAG,IAAI,CAE3B;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,gBAAgB,CAAC,KAAK,CAAC,CAEnC;IAED;;OAEG;IACH,IAAI,MAAM,IAAI,iBAAiB,CAAC,KAAK,CAAC,GAAG,IAAI,CAE5C;IAED;;OAEG;IACH,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,CAExB;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,GAAG,IAAI,CAEjC;IAED;;OAEG;IACH,IAAI,eAAe,IAAI,MAAM,CAE5B;IAED;;;;;;OAMG;IACH,EAAE,CAAC,CAAC,SAAS,MAAM,aAAa,CAAC,KAAK,CAAC,EACrC,KAAK,EAAE,CAAC,EACR,QAAQ,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAChC,WAAW;IAad;;;;;OAKG;IACH,IAAI,CAAC,SAAS,SAAS,MAAM,gBAAgB,CAAC,KAAK,CAAC,GAAG,MAAM,EAC3D,QAAQ,EAAE,SAAS,GAClB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IA8B7B;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAKlC;;;;;;OAMG;IACH,aAAa,CACX,YAAY,EAAE,aAAa,CAAC,SAAS,GAAG,aAAa,CAAC,MAAM,EAC5D,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACrD,OAAO,CAAC,IAAI,CAAC;IA+DhB;;;;;;OAMG;IACH,aAAa,CAAC,GAAG,EAAE,OAAO,cAAc,EAAE,MAAM,GAAG,IAAI;IAavD;;;;;;;;;OASG;IACH,WAAW,CAAC,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,OAAO;IAmFhD;;;;;;OAMG;IACH,eAAe,CAAC,SAAS,SAAS,MAAM,gBAAgB,CAAC,KAAK,CAAC,GAAG,MAAM,EACtE,QAAQ,EAAE,SAAS,EACnB,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,GACjC,OAAO;IAyDV;;OAEG;IACH,OAAO,IAAI,IAAI;CAgBhB"}
@@ -0,0 +1,368 @@
1
+ import { createNanoEvents } from 'nanoevents';
2
+ import { FlowRunStatus, FlowStepStatus } from './types.js';
3
+ import { FlowStep } from './FlowStep.js';
4
+ /**
5
+ * Represents a single execution of a flow
6
+ */
7
+ export class FlowRun {
8
+ #state;
9
+ #events = createNanoEvents();
10
+ #steps = new Map();
11
+ #statusPrecedence = {
12
+ [FlowRunStatus.Started]: 0,
13
+ [FlowRunStatus.Completed]: 1,
14
+ [FlowRunStatus.Failed]: 2,
15
+ };
16
+ #disposed = false;
17
+ /**
18
+ * Creates a new FlowRun instance
19
+ *
20
+ * @param initialState - Initial state for the run
21
+ */
22
+ constructor(initialState) {
23
+ this.#state = initialState;
24
+ }
25
+ /**
26
+ * Get the run ID
27
+ */
28
+ get run_id() {
29
+ return this.#state.run_id;
30
+ }
31
+ /**
32
+ * Get the flow slug
33
+ */
34
+ get flow_slug() {
35
+ return this.#state.flow_slug;
36
+ }
37
+ /**
38
+ * Get the current status
39
+ */
40
+ get status() {
41
+ return this.#state.status;
42
+ }
43
+ /**
44
+ * Get the started_at timestamp
45
+ */
46
+ get started_at() {
47
+ return this.#state.started_at;
48
+ }
49
+ /**
50
+ * Get the completed_at timestamp
51
+ */
52
+ get completed_at() {
53
+ return this.#state.completed_at;
54
+ }
55
+ /**
56
+ * Get the failed_at timestamp
57
+ */
58
+ get failed_at() {
59
+ return this.#state.failed_at;
60
+ }
61
+ /**
62
+ * Get the flow input
63
+ */
64
+ get input() {
65
+ return this.#state.input;
66
+ }
67
+ /**
68
+ * Get the flow output
69
+ */
70
+ get output() {
71
+ return this.#state.output;
72
+ }
73
+ /**
74
+ * Get the error object
75
+ */
76
+ get error() {
77
+ return this.#state.error;
78
+ }
79
+ /**
80
+ * Get the error message
81
+ */
82
+ get error_message() {
83
+ return this.#state.error_message;
84
+ }
85
+ /**
86
+ * Get the number of remaining steps
87
+ */
88
+ get remaining_steps() {
89
+ return this.#state.remaining_steps;
90
+ }
91
+ /**
92
+ * Register an event handler for a run event
93
+ *
94
+ * @param event - Event type to listen for
95
+ * @param callback - Callback function to execute when event is emitted
96
+ * @returns Function to unsubscribe from the event
97
+ */
98
+ on(event, callback) {
99
+ this.#listenerCount++;
100
+ // Wrap the unsubscribe function to track listener count
101
+ const unsubscribe = this.#events.on(event, callback);
102
+ return () => {
103
+ unsubscribe();
104
+ this.#listenerCount--;
105
+ this.#checkAutoDispose();
106
+ };
107
+ }
108
+ /**
109
+ * Get a FlowStep instance for a specific step
110
+ *
111
+ * @param stepSlug - Step slug to get
112
+ * @returns FlowStep instance for the specified step
113
+ */
114
+ step(stepSlug) {
115
+ // Look up if we already have this step cached
116
+ const existingStep = this.#steps.get(stepSlug);
117
+ if (existingStep) {
118
+ // Safe to cast since we only store steps with matching slugs
119
+ return existingStep;
120
+ }
121
+ // Create a new step instance with default state
122
+ const step = new FlowStep({
123
+ run_id: this.run_id,
124
+ step_slug: stepSlug,
125
+ status: FlowStepStatus.Created,
126
+ output: null,
127
+ error: null,
128
+ error_message: null,
129
+ started_at: null,
130
+ completed_at: null,
131
+ failed_at: null,
132
+ });
133
+ // Cache the step
134
+ this.#steps.set(stepSlug, step);
135
+ return step;
136
+ }
137
+ /**
138
+ * Check if this run has a specific step
139
+ *
140
+ * @param stepSlug - Step slug to check
141
+ * @returns true if the step exists, false otherwise
142
+ */
143
+ hasStep(stepSlug) {
144
+ // Check if we have this step cached
145
+ return this.#steps.has(stepSlug);
146
+ }
147
+ /**
148
+ * Wait for the run to reach a specific status
149
+ *
150
+ * @param targetStatus - The status to wait for
151
+ * @param options - Optional timeout and abort signal
152
+ * @returns Promise that resolves with the run instance when the status is reached
153
+ */
154
+ waitForStatus(targetStatus, options) {
155
+ const timeoutMs = options?.timeoutMs ?? 5 * 60 * 1000; // Default 5 minutes
156
+ const { signal } = options || {};
157
+ // If we already have the target status, resolve immediately
158
+ if (this.status === targetStatus) {
159
+ return Promise.resolve(this);
160
+ }
161
+ // Otherwise, wait for the status to change
162
+ return new Promise((resolve, reject) => {
163
+ let timeoutId;
164
+ let cleanedUp = false;
165
+ // Set up timeout if provided
166
+ if (timeoutMs > 0) {
167
+ timeoutId = setTimeout(() => {
168
+ if (cleanedUp)
169
+ return; // Prevent firing if already cleaned up
170
+ cleanedUp = true;
171
+ unbind();
172
+ reject(new Error(`Timeout waiting for run ${this.run_id} to reach status '${targetStatus}'`));
173
+ }, timeoutMs);
174
+ }
175
+ // Set up abort signal if provided
176
+ let abortCleanup;
177
+ if (signal) {
178
+ const abortHandler = () => {
179
+ if (cleanedUp)
180
+ return; // Prevent double cleanup
181
+ cleanedUp = true;
182
+ if (timeoutId)
183
+ clearTimeout(timeoutId);
184
+ unbind();
185
+ reject(new Error(`Aborted waiting for run ${this.run_id} to reach status '${targetStatus}'`));
186
+ };
187
+ signal.addEventListener('abort', abortHandler);
188
+ abortCleanup = () => {
189
+ signal.removeEventListener('abort', abortHandler);
190
+ };
191
+ }
192
+ // Subscribe to all events
193
+ const unbind = this.on('*', (event) => {
194
+ if (event.status === targetStatus) {
195
+ if (cleanedUp)
196
+ return; // Prevent double cleanup
197
+ cleanedUp = true;
198
+ if (timeoutId)
199
+ clearTimeout(timeoutId);
200
+ if (abortCleanup)
201
+ abortCleanup();
202
+ unbind();
203
+ resolve(this);
204
+ }
205
+ });
206
+ });
207
+ }
208
+ /**
209
+ * Apply state from database snapshot (no events emitted)
210
+ * Used when initializing state from start_flow_with_states() or get_run_with_states()
211
+ *
212
+ * @internal This method is only intended for use by PgflowClient.
213
+ * Applications should not call this directly.
214
+ */
215
+ applySnapshot(row) {
216
+ // Direct state assignment from database row (no event conversion)
217
+ this.#state.status = row.status;
218
+ this.#state.input = row.input;
219
+ this.#state.output = row.output;
220
+ this.#state.started_at = row.started_at ? new Date(row.started_at) : null;
221
+ this.#state.completed_at = row.completed_at ? new Date(row.completed_at) : null;
222
+ this.#state.failed_at = row.failed_at ? new Date(row.failed_at) : null;
223
+ this.#state.remaining_steps = row.remaining_steps;
224
+ this.#state.error_message = null; // Database doesn't have error_message for runs
225
+ this.#state.error = null;
226
+ }
227
+ /**
228
+ * Updates the run state based on an event
229
+ *
230
+ * @internal This method is only intended for use by PgflowClient and tests.
231
+ * Applications should not call this directly - state updates should come from
232
+ * database events through the PgflowClient.
233
+ *
234
+ * TODO: After v1.0, make this method private and refactor tests to use PgflowClient
235
+ * with event emission instead of direct state manipulation.
236
+ */
237
+ updateState(event) {
238
+ // Validate the event is for this run
239
+ if (event.run_id !== this.#state.run_id) {
240
+ return false;
241
+ }
242
+ // Check if the event status has higher precedence than current status
243
+ if (!this.#shouldUpdateStatus(this.#state.status, event.status)) {
244
+ return false;
245
+ }
246
+ // Update state based on event type using narrowing type guards
247
+ switch (event.status) {
248
+ case FlowRunStatus.Started:
249
+ this.#state = {
250
+ ...this.#state,
251
+ status: FlowRunStatus.Started,
252
+ started_at: typeof event.started_at === 'string'
253
+ ? new Date(event.started_at)
254
+ : new Date(),
255
+ remaining_steps: 'remaining_steps' in event
256
+ ? Number(event.remaining_steps)
257
+ : this.#state.remaining_steps,
258
+ };
259
+ this.#events.emit('started', event);
260
+ break;
261
+ case FlowRunStatus.Completed:
262
+ this.#state = {
263
+ ...this.#state,
264
+ status: FlowRunStatus.Completed,
265
+ completed_at: typeof event.completed_at === 'string'
266
+ ? new Date(event.completed_at)
267
+ : new Date(),
268
+ output: event.output,
269
+ remaining_steps: 0,
270
+ };
271
+ this.#events.emit('completed', event);
272
+ // Check for auto-dispose
273
+ this.#checkAutoDispose();
274
+ break;
275
+ case FlowRunStatus.Failed:
276
+ this.#state = {
277
+ ...this.#state,
278
+ status: FlowRunStatus.Failed,
279
+ failed_at: typeof event.failed_at === 'string'
280
+ ? new Date(event.failed_at)
281
+ : new Date(),
282
+ error_message: typeof event.error_message === 'string'
283
+ ? event.error_message
284
+ : 'Unknown error',
285
+ error: new Error(typeof event.error_message === 'string'
286
+ ? event.error_message
287
+ : 'Unknown error'),
288
+ };
289
+ this.#events.emit('failed', event);
290
+ // Check for auto-dispose
291
+ this.#checkAutoDispose();
292
+ break;
293
+ default: {
294
+ // Exhaustiveness check - ensures all event statuses are handled
295
+ event;
296
+ return false;
297
+ }
298
+ }
299
+ // Also emit to the catch-all listener
300
+ this.#events.emit('*', event);
301
+ return true;
302
+ }
303
+ /**
304
+ * Updates a step state based on an event
305
+ *
306
+ * @param stepSlug - Step slug to update
307
+ * @param event - Event data to update the step with
308
+ * @returns true if the state was updated, false otherwise
309
+ */
310
+ updateStepState(stepSlug, event) {
311
+ const step = this.step(stepSlug);
312
+ return step.updateState(event);
313
+ }
314
+ // Track number of listeners
315
+ #listenerCount = 0;
316
+ /**
317
+ * Checks if auto-dispose should be triggered (when in terminal state with no listeners)
318
+ */
319
+ #checkAutoDispose() {
320
+ // Don't auto-dispose multiple times
321
+ if (this.#disposed) {
322
+ return;
323
+ }
324
+ // Only auto-dispose in terminal states
325
+ if (this.status !== FlowRunStatus.Completed &&
326
+ this.status !== FlowRunStatus.Failed) {
327
+ return;
328
+ }
329
+ // If there are no listeners, auto-dispose
330
+ if (this.#listenerCount === 0) {
331
+ this.dispose();
332
+ }
333
+ }
334
+ /**
335
+ * Determines if a status should be updated based on precedence
336
+ *
337
+ * @param currentStatus - Current status
338
+ * @param newStatus - New status
339
+ * @returns true if the status should be updated, false otherwise
340
+ */
341
+ #shouldUpdateStatus(currentStatus, newStatus) {
342
+ // Don't allow changes to terminal states
343
+ if (currentStatus === FlowRunStatus.Completed ||
344
+ currentStatus === FlowRunStatus.Failed) {
345
+ return false; // Terminal states should never change
346
+ }
347
+ const currentPrecedence = this.#statusPrecedence[currentStatus];
348
+ const newPrecedence = this.#statusPrecedence[newStatus];
349
+ // Only allow transitions to higher precedence non-terminal status
350
+ return newPrecedence > currentPrecedence;
351
+ }
352
+ /**
353
+ * Clean up all resources held by this run
354
+ */
355
+ dispose() {
356
+ if (this.#disposed) {
357
+ return;
358
+ }
359
+ // Clear the map to allow garbage collection of steps
360
+ this.#steps.clear();
361
+ // Create a new events object - this effectively clears all listeners
362
+ // without accessing the private internals of nanoevents
363
+ this.#events = createNanoEvents();
364
+ this.#listenerCount = 0;
365
+ // Mark as disposed
366
+ this.#disposed = true;
367
+ }
368
+ }