@dudousxd/nestjs-durable-store-drizzle 0.17.0 → 0.18.1

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
  /**
@@ -76,10 +76,12 @@ declare class DrizzlePgStateStore implements StateStore {
76
76
  ensureSchema(): Promise<void>;
77
77
  /** The read-scope predicate on the runs table (undefined when the store is unscoped). */
78
78
  private scope;
79
+ tryAdmitSingleton(runId: string, tag: string, workflow: string, limit: number, retryWakeAt?: number): Promise<boolean>;
79
80
  createRun(run: WorkflowRun): Promise<void>;
80
- updateRun(runId: string, patch: Partial<WorkflowRun>): Promise<void>;
81
+ updateRun(runId: string, inputPatch: Partial<WorkflowRun>): Promise<void>;
81
82
  getRun(runId: string): Promise<WorkflowRun | null>;
82
83
  deleteRun(runId: string): Promise<void>;
84
+ deleteRuns(runIds: string[]): Promise<void>;
83
85
  pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
84
86
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
85
87
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
@@ -96,6 +98,7 @@ declare class DrizzlePgStateStore implements StateStore {
96
98
  /** The predicates every run query shares — {@link listRuns} pages them, the facets group them. */
97
99
  private runFilters;
98
100
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
101
+ countRuns(query: Omit<RunQuery, 'limit' | 'offset'>): Promise<number>;
99
102
  runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
100
103
  /** Run-table axes are an exact `GROUP BY`; the tag / attribute axes are counted over a bounded
101
104
  * page of runs (see `RunValueFacetOptions.scan`), like the TypeORM and SQLite Drizzle adapters. */
@@ -104,6 +107,12 @@ declare class DrizzlePgStateStore implements StateStore {
104
107
  * excludes runs lacking the key (EXISTS requires the row). Numbers compare `num_value`, strings
105
108
  * and booleans `str_value`; an `in` set ORs its members inside the one EXISTS. */
106
109
  private attributeExists;
110
+ saveSchedule(record: ScheduleRecord): Promise<void>;
111
+ getSchedule(id: string): Promise<ScheduleRecord | null>;
112
+ /** A single conditional `UPDATE … RETURNING`: the compare-and-set on `next_fire_at` is atomic. */
113
+ updateSchedule(id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null): Promise<boolean>;
114
+ deleteSchedule(id: string): Promise<boolean>;
115
+ listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
107
116
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
108
117
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
109
118
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
@@ -1076,6 +1085,189 @@ declare const durableBufferedEvents: drizzle_orm_pg_core.PgTableWithColumns<{
1076
1085
  };
1077
1086
  dialect: "pg";
1078
1087
  }>;
1088
+ declare const durableSchedules: drizzle_orm_pg_core.PgTableWithColumns<{
1089
+ name: "durable_schedules";
1090
+ schema: undefined;
1091
+ columns: {
1092
+ id: drizzle_orm_pg_core.PgColumn<{
1093
+ name: "id";
1094
+ tableName: "durable_schedules";
1095
+ dataType: "string";
1096
+ columnType: "PgText";
1097
+ data: string;
1098
+ driverParam: string;
1099
+ notNull: true;
1100
+ hasDefault: false;
1101
+ isPrimaryKey: true;
1102
+ isAutoincrement: false;
1103
+ hasRuntimeDefault: false;
1104
+ enumValues: [string, ...string[]];
1105
+ baseColumn: never;
1106
+ identity: undefined;
1107
+ generated: undefined;
1108
+ }, {}, {}>;
1109
+ namespace: drizzle_orm_pg_core.PgColumn<{
1110
+ name: "namespace";
1111
+ tableName: "durable_schedules";
1112
+ dataType: "string";
1113
+ columnType: "PgText";
1114
+ data: string;
1115
+ driverParam: string;
1116
+ notNull: true;
1117
+ hasDefault: true;
1118
+ isPrimaryKey: false;
1119
+ isAutoincrement: false;
1120
+ hasRuntimeDefault: false;
1121
+ enumValues: [string, ...string[]];
1122
+ baseColumn: never;
1123
+ identity: undefined;
1124
+ generated: undefined;
1125
+ }, {}, {}>;
1126
+ workflow: drizzle_orm_pg_core.PgColumn<{
1127
+ name: "workflow";
1128
+ tableName: "durable_schedules";
1129
+ dataType: "string";
1130
+ columnType: "PgText";
1131
+ data: string;
1132
+ driverParam: string;
1133
+ notNull: true;
1134
+ hasDefault: false;
1135
+ isPrimaryKey: false;
1136
+ isAutoincrement: false;
1137
+ hasRuntimeDefault: false;
1138
+ enumValues: [string, ...string[]];
1139
+ baseColumn: never;
1140
+ identity: undefined;
1141
+ generated: undefined;
1142
+ }, {}, {}>;
1143
+ paused: drizzle_orm_pg_core.PgColumn<{
1144
+ name: "paused";
1145
+ tableName: "durable_schedules";
1146
+ dataType: "boolean";
1147
+ columnType: "PgBoolean";
1148
+ data: boolean;
1149
+ driverParam: boolean;
1150
+ notNull: true;
1151
+ hasDefault: true;
1152
+ isPrimaryKey: false;
1153
+ isAutoincrement: false;
1154
+ hasRuntimeDefault: false;
1155
+ enumValues: undefined;
1156
+ baseColumn: never;
1157
+ identity: undefined;
1158
+ generated: undefined;
1159
+ }, {}, {}>;
1160
+ nextFireAt: drizzle_orm_pg_core.PgColumn<{
1161
+ name: string;
1162
+ tableName: "durable_schedules";
1163
+ dataType: "date";
1164
+ columnType: "PgTimestamp";
1165
+ data: Date;
1166
+ driverParam: string;
1167
+ notNull: false;
1168
+ hasDefault: false;
1169
+ isPrimaryKey: false;
1170
+ isAutoincrement: false;
1171
+ hasRuntimeDefault: false;
1172
+ enumValues: undefined;
1173
+ baseColumn: never;
1174
+ identity: undefined;
1175
+ generated: undefined;
1176
+ }, {}, {}>;
1177
+ tags: drizzle_orm_pg_core.PgColumn<{
1178
+ name: "tags";
1179
+ tableName: "durable_schedules";
1180
+ dataType: "json";
1181
+ columnType: "PgJsonb";
1182
+ data: string[];
1183
+ driverParam: unknown;
1184
+ notNull: false;
1185
+ hasDefault: false;
1186
+ isPrimaryKey: false;
1187
+ isAutoincrement: false;
1188
+ hasRuntimeDefault: false;
1189
+ enumValues: undefined;
1190
+ baseColumn: never;
1191
+ identity: undefined;
1192
+ generated: undefined;
1193
+ }, {}, {
1194
+ $type: string[];
1195
+ }>;
1196
+ spec: drizzle_orm_pg_core.PgColumn<{
1197
+ name: "spec";
1198
+ tableName: "durable_schedules";
1199
+ dataType: "json";
1200
+ columnType: "PgJsonb";
1201
+ data: Record<string, unknown>;
1202
+ driverParam: unknown;
1203
+ notNull: true;
1204
+ hasDefault: false;
1205
+ isPrimaryKey: false;
1206
+ isAutoincrement: false;
1207
+ hasRuntimeDefault: false;
1208
+ enumValues: undefined;
1209
+ baseColumn: never;
1210
+ identity: undefined;
1211
+ generated: undefined;
1212
+ }, {}, {
1213
+ $type: Record<string, unknown>;
1214
+ }>;
1215
+ state: drizzle_orm_pg_core.PgColumn<{
1216
+ name: "state";
1217
+ tableName: "durable_schedules";
1218
+ dataType: "json";
1219
+ columnType: "PgJsonb";
1220
+ data: Record<string, unknown>;
1221
+ driverParam: unknown;
1222
+ notNull: true;
1223
+ hasDefault: false;
1224
+ isPrimaryKey: false;
1225
+ isAutoincrement: false;
1226
+ hasRuntimeDefault: false;
1227
+ enumValues: undefined;
1228
+ baseColumn: never;
1229
+ identity: undefined;
1230
+ generated: undefined;
1231
+ }, {}, {
1232
+ $type: Record<string, unknown>;
1233
+ }>;
1234
+ createdAt: drizzle_orm_pg_core.PgColumn<{
1235
+ name: string;
1236
+ tableName: "durable_schedules";
1237
+ dataType: "date";
1238
+ columnType: "PgTimestamp";
1239
+ data: Date;
1240
+ driverParam: string;
1241
+ notNull: true;
1242
+ hasDefault: false;
1243
+ isPrimaryKey: false;
1244
+ isAutoincrement: false;
1245
+ hasRuntimeDefault: false;
1246
+ enumValues: undefined;
1247
+ baseColumn: never;
1248
+ identity: undefined;
1249
+ generated: undefined;
1250
+ }, {}, {}>;
1251
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
1252
+ name: string;
1253
+ tableName: "durable_schedules";
1254
+ dataType: "date";
1255
+ columnType: "PgTimestamp";
1256
+ data: Date;
1257
+ driverParam: string;
1258
+ notNull: true;
1259
+ hasDefault: false;
1260
+ isPrimaryKey: false;
1261
+ isAutoincrement: false;
1262
+ hasRuntimeDefault: false;
1263
+ enumValues: undefined;
1264
+ baseColumn: never;
1265
+ identity: undefined;
1266
+ generated: undefined;
1267
+ }, {}, {}>;
1268
+ };
1269
+ dialect: "pg";
1270
+ }>;
1079
1271
  /**
1080
1272
  * Every durable table, keyed by the name to export it under. Spread into your drizzle-kit schema file
1081
1273
  * (`export const { durableWorkflowRuns, ... } = durablePgSchema;` or `export * from` the `/pg` entry)
@@ -1995,6 +2187,189 @@ declare const durablePgSchema: {
1995
2187
  };
1996
2188
  dialect: "pg";
1997
2189
  }>;
2190
+ durableSchedules: drizzle_orm_pg_core.PgTableWithColumns<{
2191
+ name: "durable_schedules";
2192
+ schema: undefined;
2193
+ columns: {
2194
+ id: drizzle_orm_pg_core.PgColumn<{
2195
+ name: "id";
2196
+ tableName: "durable_schedules";
2197
+ dataType: "string";
2198
+ columnType: "PgText";
2199
+ data: string;
2200
+ driverParam: string;
2201
+ notNull: true;
2202
+ hasDefault: false;
2203
+ isPrimaryKey: true;
2204
+ isAutoincrement: false;
2205
+ hasRuntimeDefault: false;
2206
+ enumValues: [string, ...string[]];
2207
+ baseColumn: never;
2208
+ identity: undefined;
2209
+ generated: undefined;
2210
+ }, {}, {}>;
2211
+ namespace: drizzle_orm_pg_core.PgColumn<{
2212
+ name: "namespace";
2213
+ tableName: "durable_schedules";
2214
+ dataType: "string";
2215
+ columnType: "PgText";
2216
+ data: string;
2217
+ driverParam: string;
2218
+ notNull: true;
2219
+ hasDefault: true;
2220
+ isPrimaryKey: false;
2221
+ isAutoincrement: false;
2222
+ hasRuntimeDefault: false;
2223
+ enumValues: [string, ...string[]];
2224
+ baseColumn: never;
2225
+ identity: undefined;
2226
+ generated: undefined;
2227
+ }, {}, {}>;
2228
+ workflow: drizzle_orm_pg_core.PgColumn<{
2229
+ name: "workflow";
2230
+ tableName: "durable_schedules";
2231
+ dataType: "string";
2232
+ columnType: "PgText";
2233
+ data: string;
2234
+ driverParam: string;
2235
+ notNull: true;
2236
+ hasDefault: false;
2237
+ isPrimaryKey: false;
2238
+ isAutoincrement: false;
2239
+ hasRuntimeDefault: false;
2240
+ enumValues: [string, ...string[]];
2241
+ baseColumn: never;
2242
+ identity: undefined;
2243
+ generated: undefined;
2244
+ }, {}, {}>;
2245
+ paused: drizzle_orm_pg_core.PgColumn<{
2246
+ name: "paused";
2247
+ tableName: "durable_schedules";
2248
+ dataType: "boolean";
2249
+ columnType: "PgBoolean";
2250
+ data: boolean;
2251
+ driverParam: boolean;
2252
+ notNull: true;
2253
+ hasDefault: true;
2254
+ isPrimaryKey: false;
2255
+ isAutoincrement: false;
2256
+ hasRuntimeDefault: false;
2257
+ enumValues: undefined;
2258
+ baseColumn: never;
2259
+ identity: undefined;
2260
+ generated: undefined;
2261
+ }, {}, {}>;
2262
+ nextFireAt: drizzle_orm_pg_core.PgColumn<{
2263
+ name: string;
2264
+ tableName: "durable_schedules";
2265
+ dataType: "date";
2266
+ columnType: "PgTimestamp";
2267
+ data: Date;
2268
+ driverParam: string;
2269
+ notNull: false;
2270
+ hasDefault: false;
2271
+ isPrimaryKey: false;
2272
+ isAutoincrement: false;
2273
+ hasRuntimeDefault: false;
2274
+ enumValues: undefined;
2275
+ baseColumn: never;
2276
+ identity: undefined;
2277
+ generated: undefined;
2278
+ }, {}, {}>;
2279
+ tags: drizzle_orm_pg_core.PgColumn<{
2280
+ name: "tags";
2281
+ tableName: "durable_schedules";
2282
+ dataType: "json";
2283
+ columnType: "PgJsonb";
2284
+ data: string[];
2285
+ driverParam: unknown;
2286
+ notNull: false;
2287
+ hasDefault: false;
2288
+ isPrimaryKey: false;
2289
+ isAutoincrement: false;
2290
+ hasRuntimeDefault: false;
2291
+ enumValues: undefined;
2292
+ baseColumn: never;
2293
+ identity: undefined;
2294
+ generated: undefined;
2295
+ }, {}, {
2296
+ $type: string[];
2297
+ }>;
2298
+ spec: drizzle_orm_pg_core.PgColumn<{
2299
+ name: "spec";
2300
+ tableName: "durable_schedules";
2301
+ dataType: "json";
2302
+ columnType: "PgJsonb";
2303
+ data: Record<string, unknown>;
2304
+ driverParam: unknown;
2305
+ notNull: true;
2306
+ hasDefault: false;
2307
+ isPrimaryKey: false;
2308
+ isAutoincrement: false;
2309
+ hasRuntimeDefault: false;
2310
+ enumValues: undefined;
2311
+ baseColumn: never;
2312
+ identity: undefined;
2313
+ generated: undefined;
2314
+ }, {}, {
2315
+ $type: Record<string, unknown>;
2316
+ }>;
2317
+ state: drizzle_orm_pg_core.PgColumn<{
2318
+ name: "state";
2319
+ tableName: "durable_schedules";
2320
+ dataType: "json";
2321
+ columnType: "PgJsonb";
2322
+ data: Record<string, unknown>;
2323
+ driverParam: unknown;
2324
+ notNull: true;
2325
+ hasDefault: false;
2326
+ isPrimaryKey: false;
2327
+ isAutoincrement: false;
2328
+ hasRuntimeDefault: false;
2329
+ enumValues: undefined;
2330
+ baseColumn: never;
2331
+ identity: undefined;
2332
+ generated: undefined;
2333
+ }, {}, {
2334
+ $type: Record<string, unknown>;
2335
+ }>;
2336
+ createdAt: drizzle_orm_pg_core.PgColumn<{
2337
+ name: string;
2338
+ tableName: "durable_schedules";
2339
+ dataType: "date";
2340
+ columnType: "PgTimestamp";
2341
+ data: Date;
2342
+ driverParam: string;
2343
+ notNull: true;
2344
+ hasDefault: false;
2345
+ isPrimaryKey: false;
2346
+ isAutoincrement: false;
2347
+ hasRuntimeDefault: false;
2348
+ enumValues: undefined;
2349
+ baseColumn: never;
2350
+ identity: undefined;
2351
+ generated: undefined;
2352
+ }, {}, {}>;
2353
+ updatedAt: drizzle_orm_pg_core.PgColumn<{
2354
+ name: string;
2355
+ tableName: "durable_schedules";
2356
+ dataType: "date";
2357
+ columnType: "PgTimestamp";
2358
+ data: Date;
2359
+ driverParam: string;
2360
+ notNull: true;
2361
+ hasDefault: false;
2362
+ isPrimaryKey: false;
2363
+ isAutoincrement: false;
2364
+ hasRuntimeDefault: false;
2365
+ enumValues: undefined;
2366
+ baseColumn: never;
2367
+ identity: undefined;
2368
+ generated: undefined;
2369
+ }, {}, {}>;
2370
+ };
2371
+ dialect: "pg";
2372
+ }>;
1998
2373
  };
1999
2374
  /**
2000
2375
  * The durable table names, derived from {@link durablePgSchema} (one list, never drifts). Feed to a
@@ -2003,4 +2378,4 @@ declare const durablePgSchema: {
2003
2378
  */
2004
2379
  declare function durablePgManagedTables(): string[];
2005
2380
 
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 };
2381
+ 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) {
@@ -90,12 +103,12 @@ async function ensureDrizzlePgDurableSchema(db) {
90
103
  __name(ensureDrizzlePgDurableSchema, "ensureDrizzlePgDurableSchema");
91
104
 
92
105
  // src/pg/drizzle-pg-state-store.ts
93
- import { RUN_VALUE_FACET_SCAN, TERMINAL_RUN_STATUSES, attributePredicateOperands, axisIsRunColumn, mergeRunFacetRows, mergeRunValueFacetRows, normalizeAttributeRows, parseDuration, runValueFacetsFromRuns } from "@dudousxd/nestjs-durable-core";
106
+ import { RUN_VALUE_FACET_SCAN, SINGLETON_ACTIVE_STATUSES, TERMINAL_RUN_STATUSES, attributePredicateOperands, axisIsRunColumn, clearTerminalSingletonAdmission, mergeRunFacetRows, mergeRunValueFacetRows, normalizeAttributeRows, parseDuration, runValueFacetsFromRuns, singletonAdmissionDenialPatch, singletonAdmissionTags } from "@dudousxd/nestjs-durable-core";
94
107
  import { and, asc, desc, eq, exists, inArray, isNotNull, isNull, like, lt, lte, or, sql as sql2 } from "drizzle-orm";
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));
@@ -282,13 +315,34 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
282
315
  scope() {
283
316
  return namespaceFilter(this.scopeNamespace);
284
317
  }
318
+ async tryAdmitSingleton(runId, tag, workflow, limit, retryWakeAt) {
319
+ return this.db.transaction(async (tx) => {
320
+ const where = and(eq(durableWorkflowRuns.workflow, workflow), inArray(durableWorkflowRuns.status, SINGLETON_ACTIVE_STATUSES), this.scope());
321
+ await tx.update(durableWorkflowRuns).set({
322
+ workflow
323
+ }).where(where);
324
+ const rows = await tx.select().from(durableWorkflowRuns).where(where);
325
+ const runs = rows.map(fromRunRow);
326
+ const tags = singletonAdmissionTags(runs, runId, tag, workflow, limit);
327
+ if (!tags) {
328
+ const patch = singletonAdmissionDenialPatch(runs, runId, tag, workflow, retryWakeAt);
329
+ if (patch) await tx.update(durableWorkflowRuns).set(toRunPatch(patch)).where(eq(durableWorkflowRuns.id, runId));
330
+ return false;
331
+ }
332
+ await tx.update(durableWorkflowRuns).set({
333
+ tags
334
+ }).where(eq(durableWorkflowRuns.id, runId));
335
+ return true;
336
+ });
337
+ }
285
338
  async createRun(run) {
286
339
  await this.db.transaction(async (tx) => {
287
340
  await tx.insert(durableWorkflowRuns).values(toRunRow(run));
288
341
  await reindexAttributes(tx, run.id, run.searchAttributes);
289
342
  });
290
343
  }
291
- async updateRun(runId, patch) {
344
+ async updateRun(runId, inputPatch) {
345
+ const patch = inputPatch.status && TERMINAL_RUN_STATUSES.includes(inputPatch.status) ? await clearTerminalSingletonAdmission(runId, inputPatch, (id) => this.withScope({}).getRun(id)) : inputPatch;
292
346
  const row = toRunPatch(patch);
293
347
  const reindex = "searchAttributes" in patch;
294
348
  if (!Object.keys(row).length && !reindex) return;
@@ -308,17 +362,27 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
308
362
  ]);
309
363
  });
310
364
  }
365
+ async deleteRuns(runIds) {
366
+ if (runIds.length === 0) return;
367
+ await this.db.transaction(async (tx) => {
368
+ await deleteRunsCascade(tx, runIds);
369
+ });
370
+ }
311
371
  async pruneTerminalRuns(policy, nowMs, limit) {
312
372
  const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
313
373
  if (statuses.length === 0 || limit <= 0) return 0;
314
374
  if (policy.maxAge == null && policy.maxCount == null) return 0;
375
+ const scoped = this.runFilters({
376
+ ...policy.scope ?? {},
377
+ statuses
378
+ });
315
379
  return this.db.transaction(async (tx) => {
316
380
  const ids = /* @__PURE__ */ new Set();
317
381
  if (policy.maxAge != null) {
318
382
  const cutoff = new Date(nowMs - parseDuration(policy.maxAge));
319
383
  const rows = await tx.select({
320
384
  id: durableWorkflowRuns.id
321
- }).from(durableWorkflowRuns).where(and(inArray(durableWorkflowRuns.status, statuses), lt(durableWorkflowRuns.updatedAt, cutoff))).orderBy(asc(durableWorkflowRuns.updatedAt)).limit(limit).for("update", {
385
+ }).from(durableWorkflowRuns).where(and(...scoped, lt(durableWorkflowRuns.updatedAt, cutoff))).orderBy(asc(durableWorkflowRuns.updatedAt)).limit(limit).for("update", {
322
386
  skipLocked: true
323
387
  });
324
388
  for (const r of rows) ids.add(r.id);
@@ -326,7 +390,7 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
326
390
  if (policy.maxCount != null && ids.size < limit) {
327
391
  const rows = await tx.select({
328
392
  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", {
393
+ }).from(durableWorkflowRuns).where(and(...scoped)).orderBy(desc(durableWorkflowRuns.updatedAt), desc(durableWorkflowRuns.id)).limit(limit).offset(policy.maxCount).for("update", {
330
394
  skipLocked: true
331
395
  });
332
396
  for (const r of rows) {
@@ -425,6 +489,13 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
425
489
  const rows = await base;
426
490
  return rows.map(fromRunRow);
427
491
  }
492
+ async countRuns(query) {
493
+ const filters = this.runFilters(query);
494
+ const rows = await this.db.select({
495
+ count: sql2`count(*)`.mapWith(Number)
496
+ }).from(durableWorkflowRuns).where(filters.length ? and(...filters) : void 0);
497
+ return rows[0]?.count ?? 0;
498
+ }
428
499
  async runFacets(query) {
429
500
  const filters = this.runFilters(query);
430
501
  const rows = await this.db.select({
@@ -468,6 +539,41 @@ var DrizzlePgStateStore = class _DrizzlePgStateStore {
468
539
  one: sql2`1`
469
540
  }).from(durableRunAttributes).where(and(eq(durableRunAttributes.runId, durableWorkflowRuns.id), eq(durableRunAttributes.key, f.key), anyOf(comparisons))));
470
541
  }
542
+ // ---- persisted schedules ----------------------------------------------------------------------
543
+ async saveSchedule(record) {
544
+ const row = toScheduleRow(record);
545
+ await this.db.insert(durableSchedules).values(row).onConflictDoUpdate({
546
+ target: durableSchedules.id,
547
+ set: row
548
+ });
549
+ }
550
+ async getSchedule(id) {
551
+ const rows = await this.db.select().from(durableSchedules).where(eq(durableSchedules.id, id)).limit(1);
552
+ return rows[0] ? fromScheduleRow(rows[0]) : null;
553
+ }
554
+ /** A single conditional `UPDATE … RETURNING`: the compare-and-set on `next_fire_at` is atomic. */
555
+ async updateSchedule(id, patch, expectedNextFireAt) {
556
+ const set = toSchedulePatch(patch);
557
+ if (!Object.keys(set).length) return await this.getSchedule(id) !== null;
558
+ 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({
559
+ id: durableSchedules.id
560
+ });
561
+ return rows.length > 0;
562
+ }
563
+ async deleteSchedule(id) {
564
+ const rows = await this.db.delete(durableSchedules).where(eq(durableSchedules.id, id)).returning({
565
+ id: durableSchedules.id
566
+ });
567
+ return rows.length > 0;
568
+ }
569
+ async listSchedules(query) {
570
+ 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([
571
+ query.tag
572
+ ])}::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();
573
+ if (query.limit != null) base.limit(query.limit);
574
+ if (query.offset != null) base.offset(query.offset);
575
+ return (await base).map(fromScheduleRow);
576
+ }
471
577
  async listCheckpoints(runId) {
472
578
  const rows = await this.db.select().from(durableStepCheckpoints).where(eq(durableStepCheckpoints.runId, runId)).orderBy(asc(durableStepCheckpoints.seq));
473
579
  return rows.map(fromCheckpointRow);
@@ -703,6 +809,51 @@ function fromCheckpointRow(row) {
703
809
  };
704
810
  }
705
811
  __name(fromCheckpointRow, "fromCheckpointRow");
812
+ function toScheduleRow(r) {
813
+ return {
814
+ id: r.id,
815
+ namespace: r.namespace,
816
+ workflow: r.workflow,
817
+ paused: r.paused,
818
+ nextFireAt: r.nextFireAt == null ? null : new Date(r.nextFireAt),
819
+ tags: r.tags ?? null,
820
+ spec: r.spec,
821
+ state: r.state,
822
+ createdAt: r.createdAt,
823
+ updatedAt: r.updatedAt
824
+ };
825
+ }
826
+ __name(toScheduleRow, "toScheduleRow");
827
+ function toSchedulePatch(patch) {
828
+ const set = {};
829
+ if (patch.namespace !== void 0) set.namespace = patch.namespace;
830
+ if (patch.workflow !== void 0) set.workflow = patch.workflow;
831
+ if (patch.paused !== void 0) set.paused = patch.paused;
832
+ if ("nextFireAt" in patch) set.nextFireAt = patch.nextFireAt == null ? null : new Date(patch.nextFireAt);
833
+ if ("tags" in patch) set.tags = patch.tags ?? null;
834
+ if (patch.spec !== void 0) set.spec = patch.spec;
835
+ if (patch.state !== void 0) set.state = patch.state;
836
+ if (patch.updatedAt !== void 0) set.updatedAt = patch.updatedAt;
837
+ return set;
838
+ }
839
+ __name(toSchedulePatch, "toSchedulePatch");
840
+ function fromScheduleRow(row) {
841
+ return {
842
+ id: row.id,
843
+ namespace: row.namespace,
844
+ workflow: row.workflow,
845
+ paused: row.paused,
846
+ nextFireAt: row.nextFireAt ? row.nextFireAt.getTime() : null,
847
+ ...row.tags ? {
848
+ tags: row.tags
849
+ } : {},
850
+ spec: row.spec ?? {},
851
+ state: row.state ?? {},
852
+ createdAt: row.createdAt,
853
+ updatedAt: row.updatedAt
854
+ };
855
+ }
856
+ __name(fromScheduleRow, "fromScheduleRow");
706
857
 
707
858
  // src/pg/executor.ts
708
859
  import { sql as sql3 } from "drizzle-orm";
@@ -745,6 +896,7 @@ export {
745
896
  durablePgManagedTables,
746
897
  durablePgSchema,
747
898
  durableRunAttributes,
899
+ durableSchedules,
748
900
  durableSignalWaiters,
749
901
  durableStepCheckpoints,
750
902
  durableWorkflowRuns,