@dudousxd/nestjs-durable-store-drizzle 0.17.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.
@@ -1,6 +1,6 @@
1
1
  import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
2
2
  import { PgDatabase } from 'drizzle-orm/pg-core';
3
- import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, StoreTransaction, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
3
+ import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, StoreTransaction, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
4
4
  import { SQL } from 'drizzle-orm';
5
5
 
6
6
  /**
@@ -80,6 +80,7 @@ declare class DrizzlePgStateStore implements StateStore {
80
80
  updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
81
81
  getRun(runId: string): Promise<WorkflowRun | null>;
82
82
  deleteRun(runId: string): Promise<void>;
83
+ deleteRuns(runIds: string[]): Promise<void>;
83
84
  pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
84
85
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
85
86
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
@@ -96,6 +97,7 @@ declare class DrizzlePgStateStore implements StateStore {
96
97
  /** The predicates every run query shares — {@link listRuns} pages them, the facets group them. */
97
98
  private runFilters;
98
99
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
100
+ countRuns(query: Omit<RunQuery, 'limit' | 'offset'>): Promise<number>;
99
101
  runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
100
102
  /** Run-table axes are an exact `GROUP BY`; the tag / attribute axes are counted over a bounded
101
103
  * page of runs (see `RunValueFacetOptions.scan`), like the TypeORM and SQLite Drizzle adapters. */
@@ -104,6 +106,12 @@ declare class DrizzlePgStateStore implements StateStore {
104
106
  * excludes runs lacking the key (EXISTS requires the row). Numbers compare `num_value`, strings
105
107
  * and booleans `str_value`; an `in` set ORs its members inside the one EXISTS. */
106
108
  private attributeExists;
109
+ saveSchedule(record: ScheduleRecord): Promise<void>;
110
+ getSchedule(id: string): Promise<ScheduleRecord | null>;
111
+ /** A single conditional `UPDATE … RETURNING`: the compare-and-set on `next_fire_at` is atomic. */
112
+ updateSchedule(id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null): Promise<boolean>;
113
+ deleteSchedule(id: string): Promise<boolean>;
114
+ listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
107
115
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
108
116
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
109
117
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
@@ -1076,6 +1084,189 @@ declare const durableBufferedEvents: drizzle_orm_pg_core.PgTableWithColumns<{
1076
1084
  };
1077
1085
  dialect: "pg";
1078
1086
  }>;
1087
+ declare const durableSchedules: drizzle_orm_pg_core.PgTableWithColumns<{
1088
+ name: "durable_schedules";
1089
+ schema: undefined;
1090
+ columns: {
1091
+ id: drizzle_orm_pg_core.PgColumn<{
1092
+ name: "id";
1093
+ tableName: "durable_schedules";
1094
+ dataType: "string";
1095
+ columnType: "PgText";
1096
+ data: string;
1097
+ driverParam: string;
1098
+ notNull: true;
1099
+ hasDefault: false;
1100
+ isPrimaryKey: true;
1101
+ isAutoincrement: false;
1102
+ hasRuntimeDefault: false;
1103
+ enumValues: [string, ...string[]];
1104
+ baseColumn: never;
1105
+ identity: undefined;
1106
+ generated: undefined;
1107
+ }, {}, {}>;
1108
+ namespace: drizzle_orm_pg_core.PgColumn<{
1109
+ name: "namespace";
1110
+ tableName: "durable_schedules";
1111
+ dataType: "string";
1112
+ columnType: "PgText";
1113
+ data: string;
1114
+ driverParam: string;
1115
+ notNull: true;
1116
+ hasDefault: true;
1117
+ isPrimaryKey: false;
1118
+ isAutoincrement: false;
1119
+ hasRuntimeDefault: false;
1120
+ enumValues: [string, ...string[]];
1121
+ baseColumn: never;
1122
+ identity: undefined;
1123
+ generated: undefined;
1124
+ }, {}, {}>;
1125
+ workflow: drizzle_orm_pg_core.PgColumn<{
1126
+ name: "workflow";
1127
+ tableName: "durable_schedules";
1128
+ dataType: "string";
1129
+ columnType: "PgText";
1130
+ data: string;
1131
+ driverParam: string;
1132
+ notNull: true;
1133
+ hasDefault: false;
1134
+ isPrimaryKey: false;
1135
+ isAutoincrement: false;
1136
+ hasRuntimeDefault: false;
1137
+ enumValues: [string, ...string[]];
1138
+ baseColumn: never;
1139
+ identity: undefined;
1140
+ generated: undefined;
1141
+ }, {}, {}>;
1142
+ paused: drizzle_orm_pg_core.PgColumn<{
1143
+ name: "paused";
1144
+ tableName: "durable_schedules";
1145
+ dataType: "boolean";
1146
+ columnType: "PgBoolean";
1147
+ data: boolean;
1148
+ driverParam: boolean;
1149
+ notNull: true;
1150
+ hasDefault: true;
1151
+ isPrimaryKey: false;
1152
+ isAutoincrement: false;
1153
+ hasRuntimeDefault: false;
1154
+ enumValues: undefined;
1155
+ baseColumn: never;
1156
+ identity: undefined;
1157
+ generated: undefined;
1158
+ }, {}, {}>;
1159
+ nextFireAt: drizzle_orm_pg_core.PgColumn<{
1160
+ name: string;
1161
+ tableName: "durable_schedules";
1162
+ dataType: "date";
1163
+ columnType: "PgTimestamp";
1164
+ data: Date;
1165
+ driverParam: string;
1166
+ notNull: false;
1167
+ hasDefault: false;
1168
+ isPrimaryKey: false;
1169
+ isAutoincrement: false;
1170
+ hasRuntimeDefault: false;
1171
+ enumValues: undefined;
1172
+ baseColumn: never;
1173
+ identity: undefined;
1174
+ generated: undefined;
1175
+ }, {}, {}>;
1176
+ tags: drizzle_orm_pg_core.PgColumn<{
1177
+ name: "tags";
1178
+ tableName: "durable_schedules";
1179
+ dataType: "json";
1180
+ columnType: "PgJsonb";
1181
+ data: string[];
1182
+ driverParam: unknown;
1183
+ notNull: false;
1184
+ hasDefault: false;
1185
+ isPrimaryKey: false;
1186
+ isAutoincrement: false;
1187
+ hasRuntimeDefault: false;
1188
+ enumValues: undefined;
1189
+ baseColumn: never;
1190
+ identity: undefined;
1191
+ generated: undefined;
1192
+ }, {}, {
1193
+ $type: string[];
1194
+ }>;
1195
+ spec: drizzle_orm_pg_core.PgColumn<{
1196
+ name: "spec";
1197
+ tableName: "durable_schedules";
1198
+ dataType: "json";
1199
+ columnType: "PgJsonb";
1200
+ data: Record<string, unknown>;
1201
+ driverParam: unknown;
1202
+ notNull: true;
1203
+ hasDefault: false;
1204
+ isPrimaryKey: false;
1205
+ isAutoincrement: false;
1206
+ hasRuntimeDefault: false;
1207
+ enumValues: undefined;
1208
+ baseColumn: never;
1209
+ identity: undefined;
1210
+ generated: undefined;
1211
+ }, {}, {
1212
+ $type: Record<string, unknown>;
1213
+ }>;
1214
+ state: drizzle_orm_pg_core.PgColumn<{
1215
+ name: "state";
1216
+ tableName: "durable_schedules";
1217
+ dataType: "json";
1218
+ columnType: "PgJsonb";
1219
+ data: Record<string, unknown>;
1220
+ driverParam: unknown;
1221
+ notNull: true;
1222
+ hasDefault: false;
1223
+ isPrimaryKey: false;
1224
+ isAutoincrement: false;
1225
+ hasRuntimeDefault: false;
1226
+ enumValues: undefined;
1227
+ baseColumn: never;
1228
+ identity: undefined;
1229
+ generated: undefined;
1230
+ }, {}, {
1231
+ $type: Record<string, unknown>;
1232
+ }>;
1233
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1234
+ name: string;
1235
+ tableName: "durable_schedules";
1236
+ dataType: "date";
1237
+ columnType: "PgTimestamp";
1238
+ data: Date;
1239
+ driverParam: string;
1240
+ notNull: true;
1241
+ hasDefault: false;
1242
+ isPrimaryKey: false;
1243
+ isAutoincrement: false;
1244
+ hasRuntimeDefault: false;
1245
+ enumValues: undefined;
1246
+ baseColumn: never;
1247
+ identity: undefined;
1248
+ generated: undefined;
1249
+ }, {}, {}>;
1250
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
1251
+ name: string;
1252
+ tableName: "durable_schedules";
1253
+ dataType: "date";
1254
+ columnType: "PgTimestamp";
1255
+ data: Date;
1256
+ driverParam: string;
1257
+ notNull: true;
1258
+ hasDefault: false;
1259
+ isPrimaryKey: false;
1260
+ isAutoincrement: false;
1261
+ hasRuntimeDefault: false;
1262
+ enumValues: undefined;
1263
+ baseColumn: never;
1264
+ identity: undefined;
1265
+ generated: undefined;
1266
+ }, {}, {}>;
1267
+ };
1268
+ dialect: "pg";
1269
+ }>;
1079
1270
  /**
1080
1271
  * Every durable table, keyed by the name to export it under. Spread into your drizzle-kit schema file
1081
1272
  * (`export const { durableWorkflowRuns, ... } = durablePgSchema;` or `export * from` the `/pg` entry)
@@ -1995,6 +2186,189 @@ declare const durablePgSchema: {
1995
2186
  };
1996
2187
  dialect: "pg";
1997
2188
  }>;
2189
+ durableSchedules: drizzle_orm_pg_core.PgTableWithColumns<{
2190
+ name: "durable_schedules";
2191
+ schema: undefined;
2192
+ columns: {
2193
+ id: drizzle_orm_pg_core.PgColumn<{
2194
+ name: "id";
2195
+ tableName: "durable_schedules";
2196
+ dataType: "string";
2197
+ columnType: "PgText";
2198
+ data: string;
2199
+ driverParam: string;
2200
+ notNull: true;
2201
+ hasDefault: false;
2202
+ isPrimaryKey: true;
2203
+ isAutoincrement: false;
2204
+ hasRuntimeDefault: false;
2205
+ enumValues: [string, ...string[]];
2206
+ baseColumn: never;
2207
+ identity: undefined;
2208
+ generated: undefined;
2209
+ }, {}, {}>;
2210
+ namespace: drizzle_orm_pg_core.PgColumn<{
2211
+ name: "namespace";
2212
+ tableName: "durable_schedules";
2213
+ dataType: "string";
2214
+ columnType: "PgText";
2215
+ data: string;
2216
+ driverParam: string;
2217
+ notNull: true;
2218
+ hasDefault: true;
2219
+ isPrimaryKey: false;
2220
+ isAutoincrement: false;
2221
+ hasRuntimeDefault: false;
2222
+ enumValues: [string, ...string[]];
2223
+ baseColumn: never;
2224
+ identity: undefined;
2225
+ generated: undefined;
2226
+ }, {}, {}>;
2227
+ workflow: drizzle_orm_pg_core.PgColumn<{
2228
+ name: "workflow";
2229
+ tableName: "durable_schedules";
2230
+ dataType: "string";
2231
+ columnType: "PgText";
2232
+ data: string;
2233
+ driverParam: string;
2234
+ notNull: true;
2235
+ hasDefault: false;
2236
+ isPrimaryKey: false;
2237
+ isAutoincrement: false;
2238
+ hasRuntimeDefault: false;
2239
+ enumValues: [string, ...string[]];
2240
+ baseColumn: never;
2241
+ identity: undefined;
2242
+ generated: undefined;
2243
+ }, {}, {}>;
2244
+ paused: drizzle_orm_pg_core.PgColumn<{
2245
+ name: "paused";
2246
+ tableName: "durable_schedules";
2247
+ dataType: "boolean";
2248
+ columnType: "PgBoolean";
2249
+ data: boolean;
2250
+ driverParam: boolean;
2251
+ notNull: true;
2252
+ hasDefault: true;
2253
+ isPrimaryKey: false;
2254
+ isAutoincrement: false;
2255
+ hasRuntimeDefault: false;
2256
+ enumValues: undefined;
2257
+ baseColumn: never;
2258
+ identity: undefined;
2259
+ generated: undefined;
2260
+ }, {}, {}>;
2261
+ nextFireAt: drizzle_orm_pg_core.PgColumn<{
2262
+ name: string;
2263
+ tableName: "durable_schedules";
2264
+ dataType: "date";
2265
+ columnType: "PgTimestamp";
2266
+ data: Date;
2267
+ driverParam: string;
2268
+ notNull: false;
2269
+ hasDefault: false;
2270
+ isPrimaryKey: false;
2271
+ isAutoincrement: false;
2272
+ hasRuntimeDefault: false;
2273
+ enumValues: undefined;
2274
+ baseColumn: never;
2275
+ identity: undefined;
2276
+ generated: undefined;
2277
+ }, {}, {}>;
2278
+ tags: drizzle_orm_pg_core.PgColumn<{
2279
+ name: "tags";
2280
+ tableName: "durable_schedules";
2281
+ dataType: "json";
2282
+ columnType: "PgJsonb";
2283
+ data: string[];
2284
+ driverParam: unknown;
2285
+ notNull: false;
2286
+ hasDefault: false;
2287
+ isPrimaryKey: false;
2288
+ isAutoincrement: false;
2289
+ hasRuntimeDefault: false;
2290
+ enumValues: undefined;
2291
+ baseColumn: never;
2292
+ identity: undefined;
2293
+ generated: undefined;
2294
+ }, {}, {
2295
+ $type: string[];
2296
+ }>;
2297
+ spec: drizzle_orm_pg_core.PgColumn<{
2298
+ name: "spec";
2299
+ tableName: "durable_schedules";
2300
+ dataType: "json";
2301
+ columnType: "PgJsonb";
2302
+ data: Record<string, unknown>;
2303
+ driverParam: unknown;
2304
+ notNull: true;
2305
+ hasDefault: false;
2306
+ isPrimaryKey: false;
2307
+ isAutoincrement: false;
2308
+ hasRuntimeDefault: false;
2309
+ enumValues: undefined;
2310
+ baseColumn: never;
2311
+ identity: undefined;
2312
+ generated: undefined;
2313
+ }, {}, {
2314
+ $type: Record<string, unknown>;
2315
+ }>;
2316
+ state: drizzle_orm_pg_core.PgColumn<{
2317
+ name: "state";
2318
+ tableName: "durable_schedules";
2319
+ dataType: "json";
2320
+ columnType: "PgJsonb";
2321
+ data: Record<string, unknown>;
2322
+ driverParam: unknown;
2323
+ notNull: true;
2324
+ hasDefault: false;
2325
+ isPrimaryKey: false;
2326
+ isAutoincrement: false;
2327
+ hasRuntimeDefault: false;
2328
+ enumValues: undefined;
2329
+ baseColumn: never;
2330
+ identity: undefined;
2331
+ generated: undefined;
2332
+ }, {}, {
2333
+ $type: Record<string, unknown>;
2334
+ }>;
2335
+ createdAt: drizzle_orm_pg_core.PgColumn<{
2336
+ name: string;
2337
+ tableName: "durable_schedules";
2338
+ dataType: "date";
2339
+ columnType: "PgTimestamp";
2340
+ data: Date;
2341
+ driverParam: string;
2342
+ notNull: true;
2343
+ hasDefault: false;
2344
+ isPrimaryKey: false;
2345
+ isAutoincrement: false;
2346
+ hasRuntimeDefault: false;
2347
+ enumValues: undefined;
2348
+ baseColumn: never;
2349
+ identity: undefined;
2350
+ generated: undefined;
2351
+ }, {}, {}>;
2352
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
2353
+ name: string;
2354
+ tableName: "durable_schedules";
2355
+ dataType: "date";
2356
+ columnType: "PgTimestamp";
2357
+ data: Date;
2358
+ driverParam: string;
2359
+ notNull: true;
2360
+ hasDefault: false;
2361
+ isPrimaryKey: false;
2362
+ isAutoincrement: false;
2363
+ hasRuntimeDefault: false;
2364
+ enumValues: undefined;
2365
+ baseColumn: never;
2366
+ identity: undefined;
2367
+ generated: undefined;
2368
+ }, {}, {}>;
2369
+ };
2370
+ dialect: "pg";
2371
+ }>;
1998
2372
  };
1999
2373
  /**
2000
2374
  * The durable table names, derived from {@link durablePgSchema} (one list, never drifts). Feed to a
@@ -2003,4 +2377,4 @@ declare const durablePgSchema: {
2003
2377
  */
2004
2378
  declare function durablePgManagedTables(): string[];
2005
2379
 
2006
- export { DEFAULT_NAMESPACE, DURABLE_PG_DDL, type DrizzlePgDatabase, type DrizzlePgSqlExecutor, DrizzlePgStateStore, type DrizzlePgStateStoreOptions, drizzlePgExecutor, durableBufferedEvents, durableBufferedSignals, durablePgManagedTables, durablePgSchema, durableRunAttributes, durableSignalWaiters, durableStepCheckpoints, durableWorkflowRuns, ensureDrizzlePgDurableSchema, toDrizzleSql };
2380
+ export { DEFAULT_NAMESPACE, DURABLE_PG_DDL, type DrizzlePgDatabase, type DrizzlePgSqlExecutor, DrizzlePgStateStore, type DrizzlePgStateStoreOptions, drizzlePgExecutor, durableBufferedEvents, durableBufferedSignals, durablePgManagedTables, durablePgSchema, durableRunAttributes, durableSchedules, durableSignalWaiters, durableStepCheckpoints, durableWorkflowRuns, ensureDrizzlePgDurableSchema, toDrizzleSql };
package/dist/pg/index.js CHANGED
@@ -68,6 +68,18 @@ var DURABLE_PG_DDL = [
68
68
  "name" text NOT NULL,
69
69
  "payload" jsonb,
70
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
71
83
  )`,
72
84
  `CREATE INDEX IF NOT EXISTS "durable_runs_status_idx" ON "durable_workflow_runs" USING btree ("status","wake_at")`,
73
85
  `CREATE INDEX IF NOT EXISTS "durable_runs_workflow_status_idx" ON "durable_workflow_runs" USING btree ("workflow","status")`,
@@ -78,7 +90,8 @@ var DURABLE_PG_DDL = [
78
90
  `CREATE INDEX IF NOT EXISTS "durable_run_attributes_str_idx" ON "durable_run_attributes" USING btree ("key","str_value")`,
79
91
  `CREATE INDEX IF NOT EXISTS "durable_signal_waiters_run_id_idx" ON "durable_signal_waiters" USING btree ("run_id")`,
80
92
  `CREATE INDEX IF NOT EXISTS "durable_buffered_signals_token_idx" ON "durable_buffered_signals" USING btree ("token","id")`,
81
- `CREATE INDEX IF NOT EXISTS "durable_buffered_events_name_published_at_idx" ON "durable_buffered_events" USING btree ("name","published_at")`
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")`
82
95
  ];
83
96
  var SCHEMA_LOCK_KEY = "durable_schema";
84
97
  async function ensureDrizzlePgDurableSchema(db) {
@@ -95,7 +108,7 @@ import { and, asc, desc, eq, exists, inArray, isNotNull, isNull, like, lt, lte,
95
108
 
96
109
  // src/pg/schema.ts
97
110
  import { getTableName } from "drizzle-orm";
98
- import { bigserial, doublePrecision, index, integer, jsonb, pgTable, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
111
+ import { bigserial, boolean, doublePrecision, index, integer, jsonb, pgTable, primaryKey, text, timestamp } from "drizzle-orm/pg-core";
99
112
  var DEFAULT_NAMESPACE = "default";
100
113
  var tstz = /* @__PURE__ */ __name((name) => timestamp(name, {
101
114
  withTimezone: true,
@@ -222,13 +235,33 @@ var durableBufferedEvents = pgTable("durable_buffered_events", {
222
235
  }, (t) => [
223
236
  index("durable_buffered_events_name_published_at_idx").on(t.name, t.publishedAt)
224
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
+ );
225
257
  var durablePgSchema = {
226
258
  durableWorkflowRuns,
227
259
  durableStepCheckpoints,
228
260
  durableRunAttributes,
229
261
  durableSignalWaiters,
230
262
  durableBufferedSignals,
231
- durableBufferedEvents
263
+ durableBufferedEvents,
264
+ durableSchedules
232
265
  };
233
266
  function durablePgManagedTables() {
234
267
  return Object.values(durablePgSchema).map((table) => getTableName(table));
@@ -308,17 +341,27 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
308
341
  ]);
309
342
  });
310
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
+ }
311
350
  async pruneTerminalRuns(policy, nowMs, limit) {
312
351
  const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
313
352
  if (statuses.length === 0 || limit <= 0) return 0;
314
353
  if (policy.maxAge == null && policy.maxCount == null) return 0;
354
+ const scoped = this.runFilters({
355
+ ...policy.scope ?? {},
356
+ statuses
357
+ });
315
358
  return this.db.transaction(async (tx) => {
316
359
  const ids = /* @__PURE__ */ new Set();
317
360
  if (policy.maxAge != null) {
318
361
  const cutoff = new Date(nowMs - parseDuration(policy.maxAge));
319
362
  const rows = await tx.select({
320
363
  id: durableWorkflowRuns.id
321
- }).from(durableWorkflowRuns).where(and(inArray(durableWorkflowRuns.status, statuses), lt(durableWorkflowRuns.updatedAt, cutoff))).orderBy(asc(durableWorkflowRuns.updatedAt)).limit(limit).for("update", {
364
+ }).from(durableWorkflowRuns).where(and(...scoped, lt(durableWorkflowRuns.updatedAt, cutoff))).orderBy(asc(durableWorkflowRuns.updatedAt)).limit(limit).for("update", {
322
365
  skipLocked: true
323
366
  });
324
367
  for (const r of rows) ids.add(r.id);
@@ -326,7 +369,7 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
326
369
  if (policy.maxCount != null && ids.size < limit) {
327
370
  const rows = await tx.select({
328
371
  id: durableWorkflowRuns.id
329
- }).from(durableWorkflowRuns).where(inArray(durableWorkflowRuns.status, statuses)).orderBy(desc(durableWorkflowRuns.updatedAt), desc(durableWorkflowRuns.id)).limit(limit).offset(policy.maxCount).for("update", {
372
+ }).from(durableWorkflowRuns).where(and(...scoped)).orderBy(desc(durableWorkflowRuns.updatedAt), desc(durableWorkflowRuns.id)).limit(limit).offset(policy.maxCount).for("update", {
330
373
  skipLocked: true
331
374
  });
332
375
  for (const r of rows) {
@@ -425,6 +468,13 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
425
468
  const rows = await base;
426
469
  return rows.map(fromRunRow);
427
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
+ }
428
478
  async runFacets(query) {
429
479
  const filters = this.runFilters(query);
430
480
  const rows = await this.db.select({
@@ -468,6 +518,41 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
468
518
  one: sql2`1`
469
519
  }).from(durableRunAttributes).where(and(eq(durableRunAttributes.runId, durableWorkflowRuns.id), eq(durableRunAttributes.key, f.key), anyOf(comparisons))));
470
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
+ }
471
556
  async listCheckpoints(runId) {
472
557
  const rows = await this.db.select().from(durableStepCheckpoints).where(eq(durableStepCheckpoints.runId, runId)).orderBy(asc(durableStepCheckpoints.seq));
473
558
  return rows.map(fromCheckpointRow);
@@ -703,6 +788,51 @@ function fromCheckpointRow(row) {
703
788
  };
704
789
  }
705
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");
706
836
 
707
837
  // src/pg/executor.ts
708
838
  import { sql as sql3 } from "drizzle-orm";
@@ -745,6 +875,7 @@ export {
745
875
  durablePgManagedTables,
746
876
  durablePgSchema,
747
877
  durableRunAttributes,
878
+ durableSchedules,
748
879
  durableSignalWaiters,
749
880
  durableStepCheckpoints,
750
881
  durableWorkflowRuns,