@drej/postgres 0.1.2 → 0.2.0

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,5 +1,22 @@
1
1
  # @drej/postgres
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2fd33e0: feat: run management API
8
+
9
+ Add `RunStatus` enum, `RunDetails` type, and `ListRunsOptions` for filtering. Replace `listRuns()` with `listRunDetails()`, `listAllRunDetails()`, `getRunDetails()`, and `deleteRun()` on both `IStorageAdapter` and `DrejClient`. Add `WorkflowRun.status` property that tracks execution state as events are consumed.
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies [22b8a32]
14
+ - Updated dependencies [799b6dd]
15
+ - Updated dependencies [8d9d8bb]
16
+ - Updated dependencies [2fd33e0]
17
+ - Updated dependencies [0d94c2a]
18
+ - @drej/core@0.3.0
19
+
3
20
  ## 0.1.2
4
21
 
5
22
  ### Patch Changes
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@drej/postgres",
3
- "version": "0.1.2",
4
- "publishConfig": { "access": "public" },
3
+ "version": "0.2.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
5
7
  "main": "./src/index.ts",
6
8
  "dependencies": {
7
- "postgres": "^3.4.5",
9
+ "postgres": "3.4.9",
8
10
  "@drej/core": "workspace:*"
9
11
  },
10
12
  "devDependencies": {
11
- "bun-types": "latest",
12
- "typescript": "latest"
13
+ "bun-types": "1.3.14",
14
+ "typescript": "6.0.3"
13
15
  }
14
16
  }
package/src/adapter.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import postgres from "postgres";
2
- import type { IStorageAdapter, LedgerEntry, LedgerEvent } from "@drej/core";
2
+ import type { IStorageAdapter, LedgerEntry, LedgerEvent, RunDetails, ListRunsOptions } from "@drej/core";
3
+ import { RunStatus } from "@drej/core";
3
4
  import { MIGRATION_SQL } from "./migrations";
4
5
 
5
6
  type Row = {
@@ -13,6 +14,42 @@ type Row = {
13
14
  ts: string;
14
15
  };
15
16
 
17
+ type AggRow = {
18
+ wf_name: string;
19
+ run_id: string;
20
+ started_at: string | null;
21
+ completed_at: string | null;
22
+ terminal_event: string | null;
23
+ error_msg: string | null;
24
+ step_count: string;
25
+ };
26
+
27
+ function terminalToStatus(event: string | null): RunStatus {
28
+ if (event === "workflow_complete") return RunStatus.Completed;
29
+ if (event === "workflow_failed") return RunStatus.Failed;
30
+ return RunStatus.Running;
31
+ }
32
+
33
+ function aggRowToDetails(row: AggRow): RunDetails {
34
+ return {
35
+ workflowName: row.wf_name,
36
+ runId: row.run_id,
37
+ status: terminalToStatus(row.terminal_event),
38
+ startedAt: Number(row.started_at),
39
+ completedAt: row.completed_at != null ? Number(row.completed_at) : undefined,
40
+ stepCount: Number(row.step_count),
41
+ error: row.error_msg ?? undefined,
42
+ };
43
+ }
44
+
45
+ function applyOpts(details: RunDetails[], opts?: ListRunsOptions): RunDetails[] {
46
+ let result = details;
47
+ if (opts?.before != null) result = result.filter((d) => d.startedAt < opts.before!);
48
+ if (opts?.status != null) result = result.filter((d) => d.status === opts.status);
49
+ if (opts?.limit != null) result = result.slice(0, opts.limit);
50
+ return result;
51
+ }
52
+
16
53
  function rowToEntry(row: Row): LedgerEntry {
17
54
  return {
18
55
  runId: row.run_id,
@@ -78,10 +115,42 @@ export class PostgresAdapter implements IStorageAdapter {
78
115
  return rows.length ? rowToEntry(rows[0]) : null;
79
116
  }
80
117
 
81
- async listRuns(workflowName: string): Promise<string[]> {
82
- const rows = await this.sql<{ run_id: string }[]>`
83
- SELECT DISTINCT run_id FROM drej_events WHERE wf_name = ${workflowName}
84
- `;
85
- return rows.map((r) => r.run_id);
118
+ private async _aggQuery(whereClause: string, params: string[]): Promise<AggRow[]> {
119
+ return this.sql.unsafe<AggRow[]>(
120
+ `WITH agg AS (
121
+ SELECT
122
+ wf_name,
123
+ run_id,
124
+ MIN(CASE WHEN event = 'run_started' THEN ts END) AS started_at,
125
+ MAX(CASE WHEN event IN ('workflow_complete', 'workflow_failed') THEN ts END) AS completed_at,
126
+ MAX(CASE WHEN event IN ('workflow_complete', 'workflow_failed') THEN event END) AS terminal_event,
127
+ MAX(CASE WHEN event = 'workflow_failed' THEN error END) AS error_msg,
128
+ COUNT(CASE WHEN event = 'step_complete' THEN 1 END)::int AS step_count
129
+ FROM drej_events
130
+ ${whereClause}
131
+ GROUP BY wf_name, run_id
132
+ )
133
+ SELECT * FROM agg WHERE started_at IS NOT NULL ORDER BY started_at DESC`,
134
+ params,
135
+ );
136
+ }
137
+
138
+ async listRunDetails(workflowName: string, opts?: ListRunsOptions): Promise<RunDetails[]> {
139
+ const rows = await this._aggQuery("WHERE wf_name = $1", [workflowName]);
140
+ return applyOpts(rows.map(aggRowToDetails), opts);
141
+ }
142
+
143
+ async listAllRunDetails(opts?: ListRunsOptions): Promise<RunDetails[]> {
144
+ const rows = await this._aggQuery("", []);
145
+ return applyOpts(rows.map(aggRowToDetails), opts);
146
+ }
147
+
148
+ async getRunDetails(workflowName: string, runId: string): Promise<RunDetails | null> {
149
+ const rows = await this._aggQuery("WHERE wf_name = $1 AND run_id = $2", [workflowName, runId]);
150
+ return rows.length ? aggRowToDetails(rows[0]) : null;
151
+ }
152
+
153
+ async deleteRun(workflowName: string, runId: string): Promise<void> {
154
+ await this.sql`DELETE FROM drej_events WHERE wf_name = ${workflowName} AND run_id = ${runId}`;
86
155
  }
87
156
  }