@dudousxd/nestjs-durable-store-drizzle 0.16.0 → 0.18.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/dist/index.cjs +154 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +396 -2
- package/dist/index.d.ts +396 -2
- package/dist/index.js +152 -3
- package/dist/index.js.map +1 -1
- package/dist/pg/index.cjs +924 -0
- package/dist/pg/index.cjs.map +1 -0
- package/dist/pg/index.d.cts +2380 -0
- package/dist/pg/index.d.ts +2380 -0
- package/dist/pg/index.js +885 -0
- package/dist/pg/index.js.map +1 -0
- package/package.json +42 -8
package/dist/pg/index.js
ADDED
|
@@ -0,0 +1,885 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/pg/ddl.ts
|
|
5
|
+
import { sql } from "drizzle-orm";
|
|
6
|
+
var DURABLE_PG_DDL = [
|
|
7
|
+
`CREATE TABLE IF NOT EXISTS "durable_workflow_runs" (
|
|
8
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
9
|
+
"workflow" text NOT NULL,
|
|
10
|
+
"workflow_version" text NOT NULL,
|
|
11
|
+
"status" text NOT NULL,
|
|
12
|
+
"input" jsonb,
|
|
13
|
+
"output" jsonb,
|
|
14
|
+
"error" jsonb,
|
|
15
|
+
"wake_at" timestamp with time zone,
|
|
16
|
+
"locked_by" text,
|
|
17
|
+
"locked_until" timestamp with time zone,
|
|
18
|
+
"awaiting_decision_task_id" text,
|
|
19
|
+
"recovery_attempts" integer,
|
|
20
|
+
"tags" jsonb,
|
|
21
|
+
"search_attributes" jsonb,
|
|
22
|
+
"priority" integer,
|
|
23
|
+
"namespace" text DEFAULT 'default' NOT NULL,
|
|
24
|
+
"origin" text,
|
|
25
|
+
"created_at" timestamp with time zone NOT NULL,
|
|
26
|
+
"updated_at" timestamp with time zone NOT NULL
|
|
27
|
+
)`,
|
|
28
|
+
`CREATE TABLE IF NOT EXISTS "durable_step_checkpoints" (
|
|
29
|
+
"run_id" text NOT NULL,
|
|
30
|
+
"seq" integer NOT NULL,
|
|
31
|
+
"name" text NOT NULL,
|
|
32
|
+
"kind" text NOT NULL,
|
|
33
|
+
"step_id" text NOT NULL,
|
|
34
|
+
"status" text NOT NULL,
|
|
35
|
+
"input" jsonb,
|
|
36
|
+
"output" jsonb,
|
|
37
|
+
"error" jsonb,
|
|
38
|
+
"events" jsonb,
|
|
39
|
+
"attempts" integer NOT NULL,
|
|
40
|
+
"worker_group" text,
|
|
41
|
+
"parallel_group" text,
|
|
42
|
+
"wake_at" timestamp with time zone,
|
|
43
|
+
"enqueued_at" timestamp with time zone,
|
|
44
|
+
"started_at" timestamp with time zone NOT NULL,
|
|
45
|
+
"finished_at" timestamp with time zone NOT NULL,
|
|
46
|
+
CONSTRAINT "durable_step_checkpoints_run_id_seq_pk" PRIMARY KEY("run_id","seq")
|
|
47
|
+
)`,
|
|
48
|
+
`CREATE TABLE IF NOT EXISTS "durable_run_attributes" (
|
|
49
|
+
"run_id" text NOT NULL,
|
|
50
|
+
"key" text NOT NULL,
|
|
51
|
+
"str_value" text,
|
|
52
|
+
"num_value" double precision,
|
|
53
|
+
CONSTRAINT "durable_run_attributes_run_id_key_pk" PRIMARY KEY("run_id","key")
|
|
54
|
+
)`,
|
|
55
|
+
`CREATE TABLE IF NOT EXISTS "durable_signal_waiters" (
|
|
56
|
+
"token" text PRIMARY KEY NOT NULL,
|
|
57
|
+
"run_id" text NOT NULL,
|
|
58
|
+
"seq" integer NOT NULL,
|
|
59
|
+
"parallel_group" text
|
|
60
|
+
)`,
|
|
61
|
+
`CREATE TABLE IF NOT EXISTS "durable_buffered_signals" (
|
|
62
|
+
"id" bigserial PRIMARY KEY NOT NULL,
|
|
63
|
+
"token" text NOT NULL,
|
|
64
|
+
"payload" jsonb
|
|
65
|
+
)`,
|
|
66
|
+
`CREATE TABLE IF NOT EXISTS "durable_buffered_events" (
|
|
67
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
68
|
+
"name" text NOT NULL,
|
|
69
|
+
"payload" jsonb,
|
|
70
|
+
"published_at" timestamp with time zone NOT NULL
|
|
71
|
+
)`,
|
|
72
|
+
`CREATE TABLE IF NOT EXISTS "durable_schedules" (
|
|
73
|
+
"id" text PRIMARY KEY NOT NULL,
|
|
74
|
+
"namespace" text DEFAULT 'default' NOT NULL,
|
|
75
|
+
"workflow" text NOT NULL,
|
|
76
|
+
"paused" boolean DEFAULT false NOT NULL,
|
|
77
|
+
"next_fire_at" timestamp with time zone,
|
|
78
|
+
"tags" jsonb,
|
|
79
|
+
"spec" jsonb NOT NULL,
|
|
80
|
+
"state" jsonb NOT NULL,
|
|
81
|
+
"created_at" timestamp with time zone NOT NULL,
|
|
82
|
+
"updated_at" timestamp with time zone NOT NULL
|
|
83
|
+
)`,
|
|
84
|
+
`CREATE INDEX IF NOT EXISTS "durable_runs_status_idx" ON "durable_workflow_runs" USING btree ("status","wake_at")`,
|
|
85
|
+
`CREATE INDEX IF NOT EXISTS "durable_runs_workflow_status_idx" ON "durable_workflow_runs" USING btree ("workflow","status")`,
|
|
86
|
+
`CREATE INDEX IF NOT EXISTS "durable_workflow_runs_namespace_status_idx" ON "durable_workflow_runs" USING btree ("namespace","status","created_at")`,
|
|
87
|
+
`CREATE INDEX IF NOT EXISTS "durable_runs_tags_gin_idx" ON "durable_workflow_runs" USING gin ("tags" jsonb_path_ops)`,
|
|
88
|
+
`CREATE INDEX IF NOT EXISTS "durable_runs_status_updated_idx" ON "durable_workflow_runs" USING btree ("status","updated_at")`,
|
|
89
|
+
`CREATE INDEX IF NOT EXISTS "durable_run_attributes_num_idx" ON "durable_run_attributes" USING btree ("key","num_value")`,
|
|
90
|
+
`CREATE INDEX IF NOT EXISTS "durable_run_attributes_str_idx" ON "durable_run_attributes" USING btree ("key","str_value")`,
|
|
91
|
+
`CREATE INDEX IF NOT EXISTS "durable_signal_waiters_run_id_idx" ON "durable_signal_waiters" USING btree ("run_id")`,
|
|
92
|
+
`CREATE INDEX IF NOT EXISTS "durable_buffered_signals_token_idx" ON "durable_buffered_signals" USING btree ("token","id")`,
|
|
93
|
+
`CREATE INDEX IF NOT EXISTS "durable_buffered_events_name_published_at_idx" ON "durable_buffered_events" USING btree ("name","published_at")`,
|
|
94
|
+
`CREATE INDEX IF NOT EXISTS "durable_schedules_due_idx" ON "durable_schedules" USING btree ("paused","next_fire_at")`
|
|
95
|
+
];
|
|
96
|
+
var SCHEMA_LOCK_KEY = "durable_schema";
|
|
97
|
+
async function ensureDrizzlePgDurableSchema(db) {
|
|
98
|
+
await db.transaction(async (tx) => {
|
|
99
|
+
await tx.execute(sql`SELECT pg_advisory_xact_lock(hashtext(${SCHEMA_LOCK_KEY}))`);
|
|
100
|
+
for (const statement of DURABLE_PG_DDL) await tx.execute(sql.raw(statement));
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
__name(ensureDrizzlePgDurableSchema, "ensureDrizzlePgDurableSchema");
|
|
104
|
+
|
|
105
|
+
// src/pg/drizzle-pg-state-store.ts
|
|
106
|
+
import { RUN_VALUE_FACET_SCAN, TERMINAL_RUN_STATUSES, attributePredicateOperands, axisIsRunColumn, mergeRunFacetRows, mergeRunValueFacetRows, normalizeAttributeRows, parseDuration, runValueFacetsFromRuns } from "@dudousxd/nestjs-durable-core";
|
|
107
|
+
import { and, asc, desc, eq, exists, inArray, isNotNull, isNull, like, lt, lte, or, sql as sql2 } from "drizzle-orm";
|
|
108
|
+
|
|
109
|
+
// src/pg/schema.ts
|
|
110
|
+
import { getTableName } from "drizzle-orm";
|
|
111
|
+
import { bigserial, boolean, doublePrecision, index, integer, jsonb, pgTable, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
|
|
112
|
+
var DEFAULT_NAMESPACE = "default";
|
|
113
|
+
var tstz = /* @__PURE__ */ __name((name) => timestamp(name, {
|
|
114
|
+
withTimezone: true,
|
|
115
|
+
mode: "date"
|
|
116
|
+
}), "tstz");
|
|
117
|
+
var durableWorkflowRuns = pgTable("durable_workflow_runs", {
|
|
118
|
+
id: text("id").primaryKey(),
|
|
119
|
+
workflow: text("workflow").notNull(),
|
|
120
|
+
workflowVersion: text("workflow_version").notNull(),
|
|
121
|
+
status: text("status").notNull(),
|
|
122
|
+
input: jsonb("input"),
|
|
123
|
+
output: jsonb("output"),
|
|
124
|
+
error: jsonb("error"),
|
|
125
|
+
wakeAt: tstz("wake_at"),
|
|
126
|
+
lockedBy: text("locked_by"),
|
|
127
|
+
lockedUntil: tstz("locked_until"),
|
|
128
|
+
// REMOTE turn the engine suspended on awaiting a decision; matched by completeRemoteDecision so
|
|
129
|
+
// only the currently-awaited turn's decision is applied. Nullable: cleared when a decision lands.
|
|
130
|
+
awaitingDecisionTaskId: text("awaiting_decision_task_id"),
|
|
131
|
+
recoveryAttempts: integer("recovery_attempts"),
|
|
132
|
+
tags: jsonb("tags").$type(),
|
|
133
|
+
searchAttributes: jsonb("search_attributes").$type(),
|
|
134
|
+
priority: integer("priority"),
|
|
135
|
+
// The worker-pool partition (tenant) — THE isolation boundary every poll predicate filters on.
|
|
136
|
+
// NOT NULL DEFAULT 'default' so a row can never fall outside every worker's namespace.
|
|
137
|
+
namespace: text("namespace").notNull().default(DEFAULT_NAMESPACE),
|
|
138
|
+
// Which package declared the workflow. Nullable, no default: NULL = unknown, never guessed.
|
|
139
|
+
origin: text("origin"),
|
|
140
|
+
createdAt: tstz("created_at").notNull(),
|
|
141
|
+
updatedAt: tstz("updated_at").notNull()
|
|
142
|
+
}, (t) => [
|
|
143
|
+
// Timer poller (`listDueTimers`) + recovery (`listIncompleteRuns`). Same name as the TypeORM DDL.
|
|
144
|
+
index("durable_runs_status_idx").on(t.status, t.wakeAt),
|
|
145
|
+
// Dashboard `listRuns({ workflow, status })` and the singleton gate's per-workflow scans.
|
|
146
|
+
index("durable_runs_workflow_status_idx").on(t.workflow, t.status),
|
|
147
|
+
// Every namespaced poll tick: `namespace = $1 AND status = $2 ORDER BY created_at` (FIFO pick-up).
|
|
148
|
+
// Name matches the MikroORM adapter + the SQLite drizzle schema so a store swap keeps the index.
|
|
149
|
+
index("durable_workflow_runs_namespace_status_idx").on(t.namespace, t.status, t.createdAt),
|
|
150
|
+
// `listRuns({ tag })` compiles to `tags @> '["tag"]'`, which a `jsonb_path_ops` GIN index serves —
|
|
151
|
+
// this is what keeps the singleton gate (`tag: singleton:<key>`, e.g. a per-tenant concurrency
|
|
152
|
+
// key) an index probe instead of a scan of every run the workflow ever had.
|
|
153
|
+
index("durable_runs_tags_gin_idx").using("gin", t.tags.op("jsonb_path_ops")),
|
|
154
|
+
// Retention (`pruneTerminalRuns`) orders a status set by `updated_at`.
|
|
155
|
+
index("durable_runs_status_updated_idx").on(t.status, t.updatedAt)
|
|
156
|
+
]);
|
|
157
|
+
var durableStepCheckpoints = pgTable(
|
|
158
|
+
"durable_step_checkpoints",
|
|
159
|
+
{
|
|
160
|
+
runId: text("run_id").notNull(),
|
|
161
|
+
seq: integer("seq").notNull(),
|
|
162
|
+
name: text("name").notNull(),
|
|
163
|
+
kind: text("kind").notNull(),
|
|
164
|
+
stepId: text("step_id").notNull(),
|
|
165
|
+
status: text("status").notNull(),
|
|
166
|
+
input: jsonb("input"),
|
|
167
|
+
output: jsonb("output"),
|
|
168
|
+
error: jsonb("error"),
|
|
169
|
+
events: jsonb("events"),
|
|
170
|
+
attempts: integer("attempts").notNull(),
|
|
171
|
+
workerGroup: text("worker_group"),
|
|
172
|
+
parallelGroup: text("parallel_group"),
|
|
173
|
+
wakeAt: tstz("wake_at"),
|
|
174
|
+
enqueuedAt: tstz("enqueued_at"),
|
|
175
|
+
startedAt: tstz("started_at").notNull(),
|
|
176
|
+
finishedAt: tstz("finished_at").notNull()
|
|
177
|
+
},
|
|
178
|
+
// (run_id, seq) is the primary key, so listCheckpoints / getEvent already hit an index.
|
|
179
|
+
(t) => [
|
|
180
|
+
primaryKey({
|
|
181
|
+
columns: [
|
|
182
|
+
t.runId,
|
|
183
|
+
t.seq
|
|
184
|
+
]
|
|
185
|
+
})
|
|
186
|
+
]
|
|
187
|
+
);
|
|
188
|
+
var durableRunAttributes = pgTable("durable_run_attributes", {
|
|
189
|
+
runId: text("run_id").notNull(),
|
|
190
|
+
key: text("key").notNull(),
|
|
191
|
+
strValue: text("str_value"),
|
|
192
|
+
numValue: doublePrecision("num_value")
|
|
193
|
+
}, (t) => [
|
|
194
|
+
primaryKey({
|
|
195
|
+
columns: [
|
|
196
|
+
t.runId,
|
|
197
|
+
t.key
|
|
198
|
+
]
|
|
199
|
+
}),
|
|
200
|
+
index("durable_run_attributes_num_idx").on(t.key, t.numValue),
|
|
201
|
+
index("durable_run_attributes_str_idx").on(t.key, t.strValue)
|
|
202
|
+
]);
|
|
203
|
+
var durableSignalWaiters = pgTable(
|
|
204
|
+
"durable_signal_waiters",
|
|
205
|
+
{
|
|
206
|
+
token: text("token").primaryKey(),
|
|
207
|
+
runId: text("run_id").notNull(),
|
|
208
|
+
seq: integer("seq").notNull(),
|
|
209
|
+
parallelGroup: text("parallel_group")
|
|
210
|
+
},
|
|
211
|
+
// deleteRun / pruneTerminalRuns delete a run's waiters by run_id.
|
|
212
|
+
(t) => [
|
|
213
|
+
index("durable_signal_waiters_run_id_idx").on(t.runId)
|
|
214
|
+
]
|
|
215
|
+
);
|
|
216
|
+
var durableBufferedSignals = pgTable(
|
|
217
|
+
"durable_buffered_signals",
|
|
218
|
+
{
|
|
219
|
+
id: bigserial("id", {
|
|
220
|
+
mode: "number"
|
|
221
|
+
}).primaryKey(),
|
|
222
|
+
token: text("token").notNull(),
|
|
223
|
+
payload: jsonb("payload")
|
|
224
|
+
},
|
|
225
|
+
// Taken FIFO per token (smallest id first).
|
|
226
|
+
(t) => [
|
|
227
|
+
index("durable_buffered_signals_token_idx").on(t.token, t.id)
|
|
228
|
+
]
|
|
229
|
+
);
|
|
230
|
+
var durableBufferedEvents = pgTable("durable_buffered_events", {
|
|
231
|
+
id: text("id").primaryKey(),
|
|
232
|
+
name: text("name").notNull(),
|
|
233
|
+
payload: jsonb("payload"),
|
|
234
|
+
publishedAt: tstz("published_at").notNull()
|
|
235
|
+
}, (t) => [
|
|
236
|
+
index("durable_buffered_events_name_published_at_idx").on(t.name, t.publishedAt)
|
|
237
|
+
]);
|
|
238
|
+
var durableSchedules = pgTable(
|
|
239
|
+
"durable_schedules",
|
|
240
|
+
{
|
|
241
|
+
id: text("id").primaryKey(),
|
|
242
|
+
namespace: text("namespace").notNull().default(DEFAULT_NAMESPACE),
|
|
243
|
+
workflow: text("workflow").notNull(),
|
|
244
|
+
paused: boolean("paused").notNull().default(false),
|
|
245
|
+
nextFireAt: tstz("next_fire_at"),
|
|
246
|
+
tags: jsonb("tags").$type(),
|
|
247
|
+
spec: jsonb("spec").$type().notNull(),
|
|
248
|
+
state: jsonb("state").$type().notNull(),
|
|
249
|
+
createdAt: tstz("created_at").notNull(),
|
|
250
|
+
updatedAt: tstz("updated_at").notNull()
|
|
251
|
+
},
|
|
252
|
+
// The poll's "due by T" scan: `paused = false AND next_fire_at <= T ORDER BY next_fire_at`.
|
|
253
|
+
(t) => [
|
|
254
|
+
index("durable_schedules_due_idx").on(t.paused, t.nextFireAt)
|
|
255
|
+
]
|
|
256
|
+
);
|
|
257
|
+
var durablePgSchema = {
|
|
258
|
+
durableWorkflowRuns,
|
|
259
|
+
durableStepCheckpoints,
|
|
260
|
+
durableRunAttributes,
|
|
261
|
+
durableSignalWaiters,
|
|
262
|
+
durableBufferedSignals,
|
|
263
|
+
durableBufferedEvents,
|
|
264
|
+
durableSchedules
|
|
265
|
+
};
|
|
266
|
+
function durablePgManagedTables() {
|
|
267
|
+
return Object.values(durablePgSchema).map((table) => getTableName(table));
|
|
268
|
+
}
|
|
269
|
+
__name(durablePgManagedTables, "durablePgManagedTables");
|
|
270
|
+
|
|
271
|
+
// src/pg/drizzle-pg-state-store.ts
|
|
272
|
+
function anyOf(conditions) {
|
|
273
|
+
const present = conditions.filter((c) => c !== void 0);
|
|
274
|
+
return present.length ? or(...present) : sql2`1 = 0`;
|
|
275
|
+
}
|
|
276
|
+
__name(anyOf, "anyOf");
|
|
277
|
+
function namespaceFilter(namespace) {
|
|
278
|
+
return namespace === void 0 ? void 0 : eq(durableWorkflowRuns.namespace, namespace);
|
|
279
|
+
}
|
|
280
|
+
__name(namespaceFilter, "namespaceFilter");
|
|
281
|
+
function hasTag(tag) {
|
|
282
|
+
return sql2`${durableWorkflowRuns.tags} @> ${JSON.stringify([
|
|
283
|
+
tag
|
|
284
|
+
])}::jsonb`;
|
|
285
|
+
}
|
|
286
|
+
__name(hasTag, "hasTag");
|
|
287
|
+
var date = /* @__PURE__ */ __name((ms2) => ms2 == null ? null : new Date(ms2), "date");
|
|
288
|
+
var ms = /* @__PURE__ */ __name((d) => d == null ? void 0 : d.getTime(), "ms");
|
|
289
|
+
var DrizzlePgStateStore = class _DrizzlePgStateStore {
|
|
290
|
+
static {
|
|
291
|
+
__name(this, "DrizzlePgStateStore");
|
|
292
|
+
}
|
|
293
|
+
db;
|
|
294
|
+
options;
|
|
295
|
+
scopeNamespace;
|
|
296
|
+
constructor(db, options = {}) {
|
|
297
|
+
this.db = db;
|
|
298
|
+
this.options = options;
|
|
299
|
+
this.scopeNamespace = options.scope?.namespace;
|
|
300
|
+
}
|
|
301
|
+
/** A store confined to `scope.namespace`, sharing this store's db (no new connection). Used by the
|
|
302
|
+
* NestJS module's `scopeReads`; `{ namespace: undefined }` is the unscoped operator view. */
|
|
303
|
+
withScope(scope) {
|
|
304
|
+
return new _DrizzlePgStateStore(this.db, {
|
|
305
|
+
...this.options,
|
|
306
|
+
scope
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
/** Idempotent CREATE TABLE / INDEX IF NOT EXISTS under an advisory lock — called on boot when the
|
|
310
|
+
* module's `autoSchema` is on. Turn `autoSchema` off if drizzle-kit owns these tables. */
|
|
311
|
+
async ensureSchema() {
|
|
312
|
+
await ensureDrizzlePgDurableSchema(this.db);
|
|
313
|
+
}
|
|
314
|
+
/** The read-scope predicate on the runs table (undefined when the store is unscoped). */
|
|
315
|
+
scope() {
|
|
316
|
+
return namespaceFilter(this.scopeNamespace);
|
|
317
|
+
}
|
|
318
|
+
async createRun(run) {
|
|
319
|
+
await this.db.transaction(async (tx) => {
|
|
320
|
+
await tx.insert(durableWorkflowRuns).values(toRunRow(run));
|
|
321
|
+
await reindexAttributes(tx, run.id, run.searchAttributes);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async updateRun(runId, patch) {
|
|
325
|
+
const row = toRunPatch(patch);
|
|
326
|
+
const reindex = "searchAttributes" in patch;
|
|
327
|
+
if (!Object.keys(row).length && !reindex) return;
|
|
328
|
+
await this.db.transaction(async (tx) => {
|
|
329
|
+
if (Object.keys(row).length) await tx.update(durableWorkflowRuns).set(row).where(eq(durableWorkflowRuns.id, runId));
|
|
330
|
+
if (reindex) await reindexAttributes(tx, runId, patch.searchAttributes);
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
async getRun(runId) {
|
|
334
|
+
const rows = await this.db.select().from(durableWorkflowRuns).where(and(eq(durableWorkflowRuns.id, runId), this.scope())).limit(1);
|
|
335
|
+
return rows[0] ? fromRunRow(rows[0]) : null;
|
|
336
|
+
}
|
|
337
|
+
async deleteRun(runId) {
|
|
338
|
+
await this.db.transaction(async (tx) => {
|
|
339
|
+
await deleteRunsCascade(tx, [
|
|
340
|
+
runId
|
|
341
|
+
]);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
async deleteRuns(runIds) {
|
|
345
|
+
if (runIds.length === 0) return;
|
|
346
|
+
await this.db.transaction(async (tx) => {
|
|
347
|
+
await deleteRunsCascade(tx, runIds);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
async pruneTerminalRuns(policy, nowMs, limit) {
|
|
351
|
+
const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
|
|
352
|
+
if (statuses.length === 0 || limit <= 0) return 0;
|
|
353
|
+
if (policy.maxAge == null && policy.maxCount == null) return 0;
|
|
354
|
+
const scoped = this.runFilters({
|
|
355
|
+
...policy.scope ?? {},
|
|
356
|
+
statuses
|
|
357
|
+
});
|
|
358
|
+
return this.db.transaction(async (tx) => {
|
|
359
|
+
const ids = /* @__PURE__ */ new Set();
|
|
360
|
+
if (policy.maxAge != null) {
|
|
361
|
+
const cutoff = new Date(nowMs - parseDuration(policy.maxAge));
|
|
362
|
+
const rows = await tx.select({
|
|
363
|
+
id: durableWorkflowRuns.id
|
|
364
|
+
}).from(durableWorkflowRuns).where(and(...scoped, lt(durableWorkflowRuns.updatedAt, cutoff))).orderBy(asc(durableWorkflowRuns.updatedAt)).limit(limit).for("update", {
|
|
365
|
+
skipLocked: true
|
|
366
|
+
});
|
|
367
|
+
for (const r of rows) ids.add(r.id);
|
|
368
|
+
}
|
|
369
|
+
if (policy.maxCount != null && ids.size < limit) {
|
|
370
|
+
const rows = await tx.select({
|
|
371
|
+
id: durableWorkflowRuns.id
|
|
372
|
+
}).from(durableWorkflowRuns).where(and(...scoped)).orderBy(desc(durableWorkflowRuns.updatedAt), desc(durableWorkflowRuns.id)).limit(limit).offset(policy.maxCount).for("update", {
|
|
373
|
+
skipLocked: true
|
|
374
|
+
});
|
|
375
|
+
for (const r of rows) {
|
|
376
|
+
if (ids.size >= limit) break;
|
|
377
|
+
ids.add(r.id);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (ids.size === 0) return 0;
|
|
381
|
+
const idList = [
|
|
382
|
+
...ids
|
|
383
|
+
].slice(0, limit);
|
|
384
|
+
await deleteRunsCascade(tx, idList);
|
|
385
|
+
return idList.length;
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async getCheckpoint(runId, seq) {
|
|
389
|
+
const rows = await this.db.select().from(durableStepCheckpoints).where(and(eq(durableStepCheckpoints.runId, runId), eq(durableStepCheckpoints.seq, seq))).limit(1);
|
|
390
|
+
return rows[0] ? fromCheckpointRow(rows[0]) : null;
|
|
391
|
+
}
|
|
392
|
+
async saveCheckpoint(checkpoint) {
|
|
393
|
+
await upsertCheckpoint(this.db, checkpoint);
|
|
394
|
+
}
|
|
395
|
+
async transaction(work) {
|
|
396
|
+
return this.db.transaction(async (tx) => work({
|
|
397
|
+
raw: tx,
|
|
398
|
+
saveCheckpoint: /* @__PURE__ */ __name((cp) => upsertCheckpoint(tx, cp), "saveCheckpoint")
|
|
399
|
+
}));
|
|
400
|
+
}
|
|
401
|
+
/** Crash recovery. Namespace-scoped: a worker re-drives only its own tenant's runs. */
|
|
402
|
+
async listIncompleteRuns(namespace) {
|
|
403
|
+
const rows = await this.db.select().from(durableWorkflowRuns).where(and(inArray(durableWorkflowRuns.status, [
|
|
404
|
+
"running",
|
|
405
|
+
"cancelling"
|
|
406
|
+
]), namespaceFilter(namespace), this.scope()));
|
|
407
|
+
return rows.map(fromRunRow);
|
|
408
|
+
}
|
|
409
|
+
/** FIFO pick-up, served by `durable_workflow_runs_namespace_status_idx`. The engine then leases
|
|
410
|
+
* each run with {@link tryLockRun}; the listing itself takes no locks (it runs outside a tx). */
|
|
411
|
+
async listPendingRuns(limit, namespace) {
|
|
412
|
+
const rows = await this.db.select().from(durableWorkflowRuns).where(and(eq(durableWorkflowRuns.status, "pending"), namespaceFilter(namespace), this.scope())).orderBy(asc(durableWorkflowRuns.createdAt)).limit(limit);
|
|
413
|
+
return rows.map(fromRunRow);
|
|
414
|
+
}
|
|
415
|
+
async listDueTimers(nowMs, namespace) {
|
|
416
|
+
const rows = await this.db.select().from(durableWorkflowRuns).where(and(eq(durableWorkflowRuns.status, "suspended"), isNotNull(durableWorkflowRuns.wakeAt), lte(durableWorkflowRuns.wakeAt, new Date(nowMs)), namespaceFilter(namespace), this.scope()));
|
|
417
|
+
return rows.map(fromRunRow);
|
|
418
|
+
}
|
|
419
|
+
async tryLockRun(runId, owner, leaseUntilMs, nowMs) {
|
|
420
|
+
const rows = await this.db.update(durableWorkflowRuns).set({
|
|
421
|
+
lockedBy: owner,
|
|
422
|
+
lockedUntil: new Date(leaseUntilMs)
|
|
423
|
+
}).where(and(eq(durableWorkflowRuns.id, runId), or(isNull(durableWorkflowRuns.lockedUntil), lte(durableWorkflowRuns.lockedUntil, new Date(nowMs))))).returning({
|
|
424
|
+
id: durableWorkflowRuns.id
|
|
425
|
+
});
|
|
426
|
+
return rows.length === 1;
|
|
427
|
+
}
|
|
428
|
+
async releaseRunLock(runId) {
|
|
429
|
+
await this.db.update(durableWorkflowRuns).set({
|
|
430
|
+
lockedBy: null,
|
|
431
|
+
lockedUntil: null
|
|
432
|
+
}).where(eq(durableWorkflowRuns.id, runId));
|
|
433
|
+
}
|
|
434
|
+
async renewRunLock(runId, owner, leaseUntilMs) {
|
|
435
|
+
const rows = await this.db.update(durableWorkflowRuns).set({
|
|
436
|
+
lockedUntil: new Date(leaseUntilMs)
|
|
437
|
+
}).where(and(eq(durableWorkflowRuns.id, runId), eq(durableWorkflowRuns.lockedBy, owner))).returning({
|
|
438
|
+
id: durableWorkflowRuns.id
|
|
439
|
+
});
|
|
440
|
+
return rows.length === 1;
|
|
441
|
+
}
|
|
442
|
+
/** The predicates every run query shares — {@link listRuns} pages them, the facets group them. */
|
|
443
|
+
runFilters(query) {
|
|
444
|
+
return [
|
|
445
|
+
this.scope(),
|
|
446
|
+
query.workflow ? eq(durableWorkflowRuns.workflow, query.workflow) : void 0,
|
|
447
|
+
query.workflows ? query.workflows.length ? inArray(durableWorkflowRuns.workflow, query.workflows) : sql2`1 = 0` : void 0,
|
|
448
|
+
// The tenant boundary. Also the engine's execution-timeout sweep path
|
|
449
|
+
// (`listRuns({ workflow, status, namespace })`) — unscoped, it would cancel another tenant's runs.
|
|
450
|
+
namespaceFilter(query.namespace),
|
|
451
|
+
query.namespaces ? query.namespaces.length ? inArray(durableWorkflowRuns.namespace, query.namespaces) : sql2`1 = 0` : void 0,
|
|
452
|
+
// `null` selects the unattributed bucket; a value never matches a NULL origin.
|
|
453
|
+
query.origin === null ? isNull(durableWorkflowRuns.origin) : query.origin !== void 0 ? eq(durableWorkflowRuns.origin, query.origin) : void 0,
|
|
454
|
+
query.origins ? anyOf(query.origins.map((o) => o === null ? isNull(durableWorkflowRuns.origin) : eq(durableWorkflowRuns.origin, o))) : void 0,
|
|
455
|
+
query.status ? eq(durableWorkflowRuns.status, query.status) : void 0,
|
|
456
|
+
query.statuses ? query.statuses.length ? inArray(durableWorkflowRuns.status, query.statuses) : sql2`1 = 0` : void 0,
|
|
457
|
+
query.tag ? hasTag(query.tag) : void 0,
|
|
458
|
+
query.tags ? anyOf(query.tags.map(hasTag)) : void 0,
|
|
459
|
+
// One EXISTS per attribute predicate against the normalized side table (ANDed).
|
|
460
|
+
...query.attributes?.map((f) => this.attributeExists(f)) ?? []
|
|
461
|
+
].filter((f) => f !== void 0);
|
|
462
|
+
}
|
|
463
|
+
async listRuns(query) {
|
|
464
|
+
const filters = this.runFilters(query);
|
|
465
|
+
const base = this.db.select().from(durableWorkflowRuns).where(filters.length ? and(...filters) : void 0).orderBy(desc(durableWorkflowRuns.createdAt), desc(durableWorkflowRuns.id)).$dynamic();
|
|
466
|
+
if (query.limit != null) base.limit(query.limit);
|
|
467
|
+
if (query.offset != null) base.offset(query.offset);
|
|
468
|
+
const rows = await base;
|
|
469
|
+
return rows.map(fromRunRow);
|
|
470
|
+
}
|
|
471
|
+
async countRuns(query) {
|
|
472
|
+
const filters = this.runFilters(query);
|
|
473
|
+
const rows = await this.db.select({
|
|
474
|
+
count: sql2`count(*)`.mapWith(Number)
|
|
475
|
+
}).from(durableWorkflowRuns).where(filters.length ? and(...filters) : void 0);
|
|
476
|
+
return rows[0]?.count ?? 0;
|
|
477
|
+
}
|
|
478
|
+
async runFacets(query) {
|
|
479
|
+
const filters = this.runFilters(query);
|
|
480
|
+
const rows = await this.db.select({
|
|
481
|
+
status: durableWorkflowRuns.status,
|
|
482
|
+
origin: durableWorkflowRuns.origin,
|
|
483
|
+
count: sql2`count(*)`.mapWith(Number)
|
|
484
|
+
}).from(durableWorkflowRuns).where(filters.length ? and(...filters) : void 0).groupBy(durableWorkflowRuns.status, durableWorkflowRuns.origin);
|
|
485
|
+
return mergeRunFacetRows(rows.map((r) => ({
|
|
486
|
+
status: r.status,
|
|
487
|
+
origin: r.origin,
|
|
488
|
+
count: r.count
|
|
489
|
+
})));
|
|
490
|
+
}
|
|
491
|
+
/** Run-table axes are an exact `GROUP BY`; the tag / attribute axes are counted over a bounded
|
|
492
|
+
* page of runs (see `RunValueFacetOptions.scan`), like the TypeORM and SQLite Drizzle adapters. */
|
|
493
|
+
async runValueFacets(axis, query, opts) {
|
|
494
|
+
if (!axisIsRunColumn(axis)) {
|
|
495
|
+
const runs = await this.listRuns({
|
|
496
|
+
...query,
|
|
497
|
+
limit: opts?.scan ?? RUN_VALUE_FACET_SCAN
|
|
498
|
+
});
|
|
499
|
+
return runValueFacetsFromRuns(runs, axis, opts);
|
|
500
|
+
}
|
|
501
|
+
const column = durableWorkflowRuns[axis.field];
|
|
502
|
+
const filters = this.runFilters(query);
|
|
503
|
+
const rows = await this.db.select({
|
|
504
|
+
value: column,
|
|
505
|
+
count: sql2`count(*)`.mapWith(Number)
|
|
506
|
+
}).from(durableWorkflowRuns).where(filters.length ? and(...filters) : void 0).groupBy(column);
|
|
507
|
+
return mergeRunValueFacetRows(rows.map((r) => ({
|
|
508
|
+
value: r.value ?? null,
|
|
509
|
+
count: r.count
|
|
510
|
+
})), opts);
|
|
511
|
+
}
|
|
512
|
+
/** One attribute predicate as an EXISTS on the side table, correlated to the outer run. `ne` also
|
|
513
|
+
* excludes runs lacking the key (EXISTS requires the row). Numbers compare `num_value`, strings
|
|
514
|
+
* and booleans `str_value`; an `in` set ORs its members inside the one EXISTS. */
|
|
515
|
+
attributeExists(f) {
|
|
516
|
+
const comparisons = attributePredicateOperands(f).map(({ column, comparator, operand }) => sql2`${column === "numValue" ? durableRunAttributes.numValue : durableRunAttributes.strValue} ${sql2.raw(comparator)} ${operand}`);
|
|
517
|
+
return exists(this.db.select({
|
|
518
|
+
one: sql2`1`
|
|
519
|
+
}).from(durableRunAttributes).where(and(eq(durableRunAttributes.runId, durableWorkflowRuns.id), eq(durableRunAttributes.key, f.key), anyOf(comparisons))));
|
|
520
|
+
}
|
|
521
|
+
// ---- persisted schedules ----------------------------------------------------------------------
|
|
522
|
+
async saveSchedule(record) {
|
|
523
|
+
const row = toScheduleRow(record);
|
|
524
|
+
await this.db.insert(durableSchedules).values(row).onConflictDoUpdate({
|
|
525
|
+
target: durableSchedules.id,
|
|
526
|
+
set: row
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
async getSchedule(id) {
|
|
530
|
+
const rows = await this.db.select().from(durableSchedules).where(eq(durableSchedules.id, id)).limit(1);
|
|
531
|
+
return rows[0] ? fromScheduleRow(rows[0]) : null;
|
|
532
|
+
}
|
|
533
|
+
/** A single conditional `UPDATE … RETURNING`: the compare-and-set on `next_fire_at` is atomic. */
|
|
534
|
+
async updateSchedule(id, patch, expectedNextFireAt) {
|
|
535
|
+
const set = toSchedulePatch(patch);
|
|
536
|
+
if (!Object.keys(set).length) return await this.getSchedule(id) !== null;
|
|
537
|
+
const rows = await this.db.update(durableSchedules).set(set).where(and(eq(durableSchedules.id, id), expectedNextFireAt === void 0 ? void 0 : expectedNextFireAt === null ? isNull(durableSchedules.nextFireAt) : eq(durableSchedules.nextFireAt, new Date(expectedNextFireAt)))).returning({
|
|
538
|
+
id: durableSchedules.id
|
|
539
|
+
});
|
|
540
|
+
return rows.length > 0;
|
|
541
|
+
}
|
|
542
|
+
async deleteSchedule(id) {
|
|
543
|
+
const rows = await this.db.delete(durableSchedules).where(eq(durableSchedules.id, id)).returning({
|
|
544
|
+
id: durableSchedules.id
|
|
545
|
+
});
|
|
546
|
+
return rows.length > 0;
|
|
547
|
+
}
|
|
548
|
+
async listSchedules(query) {
|
|
549
|
+
const base = this.db.select().from(durableSchedules).where(and(query.namespace !== void 0 ? eq(durableSchedules.namespace, query.namespace) : void 0, query.workflow !== void 0 ? eq(durableSchedules.workflow, query.workflow) : void 0, query.paused !== void 0 ? eq(durableSchedules.paused, query.paused) : void 0, query.tag !== void 0 ? sql2`${durableSchedules.tags} @> ${JSON.stringify([
|
|
550
|
+
query.tag
|
|
551
|
+
])}::jsonb` : void 0, query.dueBy !== void 0 ? and(eq(durableSchedules.paused, false), lte(durableSchedules.nextFireAt, new Date(query.dueBy))) : void 0)).orderBy(sql2`${durableSchedules.nextFireAt} ASC NULLS LAST`, asc(durableSchedules.id)).$dynamic();
|
|
552
|
+
if (query.limit != null) base.limit(query.limit);
|
|
553
|
+
if (query.offset != null) base.offset(query.offset);
|
|
554
|
+
return (await base).map(fromScheduleRow);
|
|
555
|
+
}
|
|
556
|
+
async listCheckpoints(runId) {
|
|
557
|
+
const rows = await this.db.select().from(durableStepCheckpoints).where(eq(durableStepCheckpoints.runId, runId)).orderBy(asc(durableStepCheckpoints.seq));
|
|
558
|
+
return rows.map(fromCheckpointRow);
|
|
559
|
+
}
|
|
560
|
+
async getLatestCheckpointByName(runId, name) {
|
|
561
|
+
const rows = await this.db.select().from(durableStepCheckpoints).where(and(eq(durableStepCheckpoints.runId, runId), eq(durableStepCheckpoints.name, name))).orderBy(desc(durableStepCheckpoints.seq)).limit(1);
|
|
562
|
+
return rows[0] ? fromCheckpointRow(rows[0]) : void 0;
|
|
563
|
+
}
|
|
564
|
+
async listCheckpointsByNamePrefix(runId, prefixes) {
|
|
565
|
+
if (prefixes.length === 0) return [];
|
|
566
|
+
const rows = await this.db.select().from(durableStepCheckpoints).where(and(eq(durableStepCheckpoints.runId, runId), or(...prefixes.map((p) => like(durableStepCheckpoints.name, `${escapeLike(p)}%`))))).orderBy(asc(durableStepCheckpoints.seq));
|
|
567
|
+
return rows.map(fromCheckpointRow);
|
|
568
|
+
}
|
|
569
|
+
async putSignalWaiter(waiter) {
|
|
570
|
+
const set = {
|
|
571
|
+
runId: waiter.runId,
|
|
572
|
+
seq: waiter.seq,
|
|
573
|
+
parallelGroup: waiter.parallelGroup ?? null
|
|
574
|
+
};
|
|
575
|
+
await this.db.insert(durableSignalWaiters).values({
|
|
576
|
+
token: waiter.token,
|
|
577
|
+
...set
|
|
578
|
+
}).onConflictDoUpdate({
|
|
579
|
+
target: durableSignalWaiters.token,
|
|
580
|
+
set
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
async takeSignalWaiter(token) {
|
|
584
|
+
const rows = await this.db.delete(durableSignalWaiters).where(eq(durableSignalWaiters.token, token)).returning();
|
|
585
|
+
const row = rows[0];
|
|
586
|
+
return row ? fromWaiterRow(row) : null;
|
|
587
|
+
}
|
|
588
|
+
async listSignalWaiters(prefix) {
|
|
589
|
+
const rows = await this.db.select().from(durableSignalWaiters).where(like(durableSignalWaiters.token, `${escapeLike(prefix)}%`));
|
|
590
|
+
return rows.map(fromWaiterRow);
|
|
591
|
+
}
|
|
592
|
+
async removeSignalWaiter(waiter) {
|
|
593
|
+
await this.db.delete(durableSignalWaiters).where(and(eq(durableSignalWaiters.token, waiter.token), eq(durableSignalWaiters.runId, waiter.runId), eq(durableSignalWaiters.seq, waiter.seq)));
|
|
594
|
+
}
|
|
595
|
+
async bufferSignal(token, payload) {
|
|
596
|
+
await this.db.insert(durableBufferedSignals).values({
|
|
597
|
+
token,
|
|
598
|
+
payload: payload ?? null
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
async takeBufferedSignal(token) {
|
|
602
|
+
const oldest = this.db.select({
|
|
603
|
+
id: durableBufferedSignals.id
|
|
604
|
+
}).from(durableBufferedSignals).where(eq(durableBufferedSignals.token, token)).orderBy(asc(durableBufferedSignals.id)).limit(1).for("update", {
|
|
605
|
+
skipLocked: true
|
|
606
|
+
});
|
|
607
|
+
const rows = await this.db.delete(durableBufferedSignals).where(inArray(durableBufferedSignals.id, oldest)).returning({
|
|
608
|
+
payload: durableBufferedSignals.payload
|
|
609
|
+
});
|
|
610
|
+
const row = rows[0];
|
|
611
|
+
return row ? {
|
|
612
|
+
payload: row.payload ?? void 0
|
|
613
|
+
} : null;
|
|
614
|
+
}
|
|
615
|
+
async bufferEvent(input) {
|
|
616
|
+
await this.db.insert(durableBufferedEvents).values({
|
|
617
|
+
id: input.id,
|
|
618
|
+
name: input.name,
|
|
619
|
+
payload: input.payload ?? null,
|
|
620
|
+
publishedAt: new Date(input.publishedAt)
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
async listBufferedEvents(name, limit) {
|
|
624
|
+
const rows = await this.db.select().from(durableBufferedEvents).where(eq(durableBufferedEvents.name, name)).orderBy(asc(durableBufferedEvents.publishedAt), asc(durableBufferedEvents.id)).limit(limit);
|
|
625
|
+
return rows.map((r) => ({
|
|
626
|
+
id: r.id,
|
|
627
|
+
payload: r.payload ?? void 0,
|
|
628
|
+
publishedAt: r.publishedAt.getTime()
|
|
629
|
+
}));
|
|
630
|
+
}
|
|
631
|
+
async removeBufferedEvent(id) {
|
|
632
|
+
const rows = await this.db.delete(durableBufferedEvents).where(eq(durableBufferedEvents.id, id)).returning({
|
|
633
|
+
id: durableBufferedEvents.id
|
|
634
|
+
});
|
|
635
|
+
return rows.length === 1;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
638
|
+
function escapeLike(value) {
|
|
639
|
+
return value.replace(/[\\%_]/g, (c) => `\\${c}`);
|
|
640
|
+
}
|
|
641
|
+
__name(escapeLike, "escapeLike");
|
|
642
|
+
async function upsertCheckpoint(db, cp) {
|
|
643
|
+
const row = toCheckpointRow(cp);
|
|
644
|
+
await db.insert(durableStepCheckpoints).values(row).onConflictDoUpdate({
|
|
645
|
+
target: [
|
|
646
|
+
durableStepCheckpoints.runId,
|
|
647
|
+
durableStepCheckpoints.seq
|
|
648
|
+
],
|
|
649
|
+
set: row
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
__name(upsertCheckpoint, "upsertCheckpoint");
|
|
653
|
+
async function reindexAttributes(db, runId, attributes) {
|
|
654
|
+
await db.delete(durableRunAttributes).where(eq(durableRunAttributes.runId, runId));
|
|
655
|
+
const rows = normalizeAttributeRows(runId, attributes);
|
|
656
|
+
if (rows.length) await db.insert(durableRunAttributes).values(rows);
|
|
657
|
+
}
|
|
658
|
+
__name(reindexAttributes, "reindexAttributes");
|
|
659
|
+
async function deleteRunsCascade(db, runIds) {
|
|
660
|
+
await db.delete(durableStepCheckpoints).where(inArray(durableStepCheckpoints.runId, runIds));
|
|
661
|
+
await db.delete(durableSignalWaiters).where(inArray(durableSignalWaiters.runId, runIds));
|
|
662
|
+
await db.delete(durableRunAttributes).where(inArray(durableRunAttributes.runId, runIds));
|
|
663
|
+
await db.delete(durableWorkflowRuns).where(inArray(durableWorkflowRuns.id, runIds));
|
|
664
|
+
}
|
|
665
|
+
__name(deleteRunsCascade, "deleteRunsCascade");
|
|
666
|
+
function fromWaiterRow(row) {
|
|
667
|
+
return {
|
|
668
|
+
token: row.token,
|
|
669
|
+
runId: row.runId,
|
|
670
|
+
seq: row.seq,
|
|
671
|
+
parallelGroup: row.parallelGroup ?? void 0
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
__name(fromWaiterRow, "fromWaiterRow");
|
|
675
|
+
function toRunRow(run) {
|
|
676
|
+
return {
|
|
677
|
+
id: run.id,
|
|
678
|
+
workflow: run.workflow,
|
|
679
|
+
workflowVersion: run.workflowVersion,
|
|
680
|
+
status: run.status,
|
|
681
|
+
input: run.input ?? null,
|
|
682
|
+
output: run.output ?? null,
|
|
683
|
+
error: run.error ?? null,
|
|
684
|
+
wakeAt: date(run.wakeAt),
|
|
685
|
+
lockedBy: run.lockedBy ?? null,
|
|
686
|
+
lockedUntil: date(run.lockedUntil),
|
|
687
|
+
awaitingDecisionTaskId: run.awaitingDecisionTaskId ?? null,
|
|
688
|
+
recoveryAttempts: run.recoveryAttempts ?? null,
|
|
689
|
+
tags: run.tags ?? null,
|
|
690
|
+
searchAttributes: run.searchAttributes ?? null,
|
|
691
|
+
priority: run.priority ?? null,
|
|
692
|
+
namespace: run.namespace ?? DEFAULT_NAMESPACE,
|
|
693
|
+
origin: run.origin ?? null,
|
|
694
|
+
createdAt: run.createdAt,
|
|
695
|
+
updatedAt: run.updatedAt
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
__name(toRunRow, "toRunRow");
|
|
699
|
+
function toRunPatch(patch) {
|
|
700
|
+
const row = {};
|
|
701
|
+
if (patch.workflow !== void 0) row.workflow = patch.workflow;
|
|
702
|
+
if (patch.workflowVersion !== void 0) row.workflowVersion = patch.workflowVersion;
|
|
703
|
+
if (patch.status !== void 0) row.status = patch.status;
|
|
704
|
+
if ("input" in patch) row.input = patch.input ?? null;
|
|
705
|
+
if ("output" in patch) row.output = patch.output ?? null;
|
|
706
|
+
if ("error" in patch) row.error = patch.error ?? null;
|
|
707
|
+
if ("wakeAt" in patch) row.wakeAt = date(patch.wakeAt);
|
|
708
|
+
if ("lockedBy" in patch) row.lockedBy = patch.lockedBy ?? null;
|
|
709
|
+
if ("lockedUntil" in patch) row.lockedUntil = date(patch.lockedUntil);
|
|
710
|
+
if ("awaitingDecisionTaskId" in patch) row.awaitingDecisionTaskId = patch.awaitingDecisionTaskId ?? null;
|
|
711
|
+
if ("recoveryAttempts" in patch) row.recoveryAttempts = patch.recoveryAttempts ?? null;
|
|
712
|
+
if ("tags" in patch) row.tags = patch.tags ?? null;
|
|
713
|
+
if ("searchAttributes" in patch) row.searchAttributes = patch.searchAttributes ?? null;
|
|
714
|
+
if ("priority" in patch) row.priority = patch.priority ?? null;
|
|
715
|
+
if ("namespace" in patch) row.namespace = patch.namespace ?? DEFAULT_NAMESPACE;
|
|
716
|
+
if ("origin" in patch) row.origin = patch.origin ?? null;
|
|
717
|
+
if (patch.createdAt != null) row.createdAt = patch.createdAt;
|
|
718
|
+
if (patch.updatedAt != null) row.updatedAt = patch.updatedAt;
|
|
719
|
+
return row;
|
|
720
|
+
}
|
|
721
|
+
__name(toRunPatch, "toRunPatch");
|
|
722
|
+
function fromRunRow(row) {
|
|
723
|
+
return {
|
|
724
|
+
id: row.id,
|
|
725
|
+
workflow: row.workflow,
|
|
726
|
+
workflowVersion: row.workflowVersion,
|
|
727
|
+
status: row.status,
|
|
728
|
+
input: row.input ?? void 0,
|
|
729
|
+
output: row.output ?? void 0,
|
|
730
|
+
error: row.error ?? void 0,
|
|
731
|
+
wakeAt: ms(row.wakeAt),
|
|
732
|
+
lockedBy: row.lockedBy ?? void 0,
|
|
733
|
+
lockedUntil: ms(row.lockedUntil),
|
|
734
|
+
awaitingDecisionTaskId: row.awaitingDecisionTaskId ?? void 0,
|
|
735
|
+
recoveryAttempts: row.recoveryAttempts ?? void 0,
|
|
736
|
+
tags: row.tags ?? void 0,
|
|
737
|
+
searchAttributes: row.searchAttributes ?? void 0,
|
|
738
|
+
priority: row.priority ?? void 0,
|
|
739
|
+
namespace: row.namespace,
|
|
740
|
+
origin: row.origin ?? void 0,
|
|
741
|
+
createdAt: row.createdAt,
|
|
742
|
+
updatedAt: row.updatedAt
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
__name(fromRunRow, "fromRunRow");
|
|
746
|
+
function toCheckpointRow(cp) {
|
|
747
|
+
return {
|
|
748
|
+
runId: cp.runId,
|
|
749
|
+
seq: cp.seq,
|
|
750
|
+
name: cp.name,
|
|
751
|
+
kind: cp.kind,
|
|
752
|
+
stepId: cp.stepId,
|
|
753
|
+
status: cp.status,
|
|
754
|
+
input: cp.input ?? null,
|
|
755
|
+
output: cp.output ?? null,
|
|
756
|
+
error: cp.error ?? null,
|
|
757
|
+
events: cp.events ?? null,
|
|
758
|
+
attempts: cp.attempts,
|
|
759
|
+
workerGroup: cp.workerGroup ?? null,
|
|
760
|
+
parallelGroup: cp.parallelGroup ?? null,
|
|
761
|
+
wakeAt: date(cp.wakeAt),
|
|
762
|
+
enqueuedAt: cp.enqueuedAt ?? cp.startedAt,
|
|
763
|
+
startedAt: cp.startedAt,
|
|
764
|
+
finishedAt: cp.finishedAt
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
__name(toCheckpointRow, "toCheckpointRow");
|
|
768
|
+
function fromCheckpointRow(row) {
|
|
769
|
+
return {
|
|
770
|
+
runId: row.runId,
|
|
771
|
+
seq: row.seq,
|
|
772
|
+
name: row.name,
|
|
773
|
+
kind: row.kind,
|
|
774
|
+
stepId: row.stepId,
|
|
775
|
+
status: row.status,
|
|
776
|
+
input: row.input ?? void 0,
|
|
777
|
+
output: row.output ?? void 0,
|
|
778
|
+
error: row.error ?? void 0,
|
|
779
|
+
events: row.events ?? void 0,
|
|
780
|
+
attempts: row.attempts,
|
|
781
|
+
workerGroup: row.workerGroup ?? void 0,
|
|
782
|
+
parallelGroup: row.parallelGroup ?? void 0,
|
|
783
|
+
wakeAt: ms(row.wakeAt),
|
|
784
|
+
// Older rows may lack enqueued_at; treat the start as enqueue time (queue-wait reads zero).
|
|
785
|
+
enqueuedAt: row.enqueuedAt ?? row.startedAt,
|
|
786
|
+
startedAt: row.startedAt,
|
|
787
|
+
finishedAt: row.finishedAt
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
__name(fromCheckpointRow, "fromCheckpointRow");
|
|
791
|
+
function toScheduleRow(r) {
|
|
792
|
+
return {
|
|
793
|
+
id: r.id,
|
|
794
|
+
namespace: r.namespace,
|
|
795
|
+
workflow: r.workflow,
|
|
796
|
+
paused: r.paused,
|
|
797
|
+
nextFireAt: r.nextFireAt == null ? null : new Date(r.nextFireAt),
|
|
798
|
+
tags: r.tags ?? null,
|
|
799
|
+
spec: r.spec,
|
|
800
|
+
state: r.state,
|
|
801
|
+
createdAt: r.createdAt,
|
|
802
|
+
updatedAt: r.updatedAt
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
__name(toScheduleRow, "toScheduleRow");
|
|
806
|
+
function toSchedulePatch(patch) {
|
|
807
|
+
const set = {};
|
|
808
|
+
if (patch.namespace !== void 0) set.namespace = patch.namespace;
|
|
809
|
+
if (patch.workflow !== void 0) set.workflow = patch.workflow;
|
|
810
|
+
if (patch.paused !== void 0) set.paused = patch.paused;
|
|
811
|
+
if ("nextFireAt" in patch) set.nextFireAt = patch.nextFireAt == null ? null : new Date(patch.nextFireAt);
|
|
812
|
+
if ("tags" in patch) set.tags = patch.tags ?? null;
|
|
813
|
+
if (patch.spec !== void 0) set.spec = patch.spec;
|
|
814
|
+
if (patch.state !== void 0) set.state = patch.state;
|
|
815
|
+
if (patch.updatedAt !== void 0) set.updatedAt = patch.updatedAt;
|
|
816
|
+
return set;
|
|
817
|
+
}
|
|
818
|
+
__name(toSchedulePatch, "toSchedulePatch");
|
|
819
|
+
function fromScheduleRow(row) {
|
|
820
|
+
return {
|
|
821
|
+
id: row.id,
|
|
822
|
+
namespace: row.namespace,
|
|
823
|
+
workflow: row.workflow,
|
|
824
|
+
paused: row.paused,
|
|
825
|
+
nextFireAt: row.nextFireAt ? row.nextFireAt.getTime() : null,
|
|
826
|
+
...row.tags ? {
|
|
827
|
+
tags: row.tags
|
|
828
|
+
} : {},
|
|
829
|
+
spec: row.spec ?? {},
|
|
830
|
+
state: row.state ?? {},
|
|
831
|
+
createdAt: row.createdAt,
|
|
832
|
+
updatedAt: row.updatedAt
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
__name(fromScheduleRow, "fromScheduleRow");
|
|
836
|
+
|
|
837
|
+
// src/pg/executor.ts
|
|
838
|
+
import { sql as sql3 } from "drizzle-orm";
|
|
839
|
+
function toDrizzleSql(text2, params = []) {
|
|
840
|
+
const chunks = [];
|
|
841
|
+
let last = 0;
|
|
842
|
+
for (const match of text2.matchAll(/\$(\d+)/g)) {
|
|
843
|
+
const index2 = Number(match[1]) - 1;
|
|
844
|
+
if (index2 < 0 || index2 >= params.length) throw new Error(`placeholder ${match[0]} has no bound parameter`);
|
|
845
|
+
chunks.push(sql3.raw(text2.slice(last, match.index)), sql3`${params[index2]}`);
|
|
846
|
+
last = (match.index ?? 0) + match[0].length;
|
|
847
|
+
}
|
|
848
|
+
chunks.push(sql3.raw(text2.slice(last)));
|
|
849
|
+
return sql3.join(chunks);
|
|
850
|
+
}
|
|
851
|
+
__name(toDrizzleSql, "toDrizzleSql");
|
|
852
|
+
function rowsOf(result) {
|
|
853
|
+
if (Array.isArray(result)) return result;
|
|
854
|
+
return result?.rows ?? [];
|
|
855
|
+
}
|
|
856
|
+
__name(rowsOf, "rowsOf");
|
|
857
|
+
function drizzlePgExecutor(db) {
|
|
858
|
+
return {
|
|
859
|
+
dialect: "postgres",
|
|
860
|
+
escapeId: /* @__PURE__ */ __name((id) => `"${id.replace(/"/g, '""')}"`, "escapeId"),
|
|
861
|
+
query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await db.execute(toDrizzleSql(text2, params))), "query"),
|
|
862
|
+
transaction: /* @__PURE__ */ __name((fn) => db.transaction((tx) => fn({
|
|
863
|
+
query: /* @__PURE__ */ __name(async (text2, params) => rowsOf(await tx.execute(toDrizzleSql(text2, params))), "query")
|
|
864
|
+
})), "transaction")
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
__name(drizzlePgExecutor, "drizzlePgExecutor");
|
|
868
|
+
export {
|
|
869
|
+
DEFAULT_NAMESPACE,
|
|
870
|
+
DURABLE_PG_DDL,
|
|
871
|
+
DrizzlePgStateStore,
|
|
872
|
+
drizzlePgExecutor,
|
|
873
|
+
durableBufferedEvents,
|
|
874
|
+
durableBufferedSignals,
|
|
875
|
+
durablePgManagedTables,
|
|
876
|
+
durablePgSchema,
|
|
877
|
+
durableRunAttributes,
|
|
878
|
+
durableSchedules,
|
|
879
|
+
durableSignalWaiters,
|
|
880
|
+
durableStepCheckpoints,
|
|
881
|
+
durableWorkflowRuns,
|
|
882
|
+
ensureDrizzlePgDurableSchema,
|
|
883
|
+
toDrizzleSql
|
|
884
|
+
};
|
|
885
|
+
//# sourceMappingURL=index.js.map
|