@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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { StateStore, WorkflowRun, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
1
+ import { StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, RunValueAxis, RunValueFacetOptions, RunValueFacetRow, ScheduleRecord, ScheduleQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
2
2
  import * as drizzle_orm_sqlite_core from 'drizzle-orm/sqlite-core';
3
3
  import { BaseSQLiteDatabase } from 'drizzle-orm/sqlite-core';
4
4
 
@@ -18,6 +18,8 @@ declare class DrizzleStateStore implements StateStore {
18
18
  private reindexAttributes;
19
19
  getRun(runId: string): Promise<WorkflowRun | null>;
20
20
  deleteRun(runId: string): Promise<void>;
21
+ deleteRuns(runIds: string[]): Promise<void>;
22
+ pruneTerminalRuns(policy: RetentionPolicy, nowMs: number, limit: number): Promise<number>;
21
23
  getCheckpoint(runId: string, seq: number): Promise<StepCheckpoint | null>;
22
24
  saveCheckpoint(checkpoint: StepCheckpoint): Promise<void>;
23
25
  transaction<T>(work: (tx: {
@@ -39,6 +41,7 @@ declare class DrizzleStateStore implements StateStore {
39
41
  * them, so a facet count and the page it labels are always taken over the same set. */
40
42
  private runFilters;
41
43
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
44
+ countRuns(query: Omit<RunQuery, 'limit' | 'offset'>): Promise<number>;
42
45
  /** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
43
46
  * console can show whole-set counts next to a bounded page instead of downloading every run to
44
47
  * count them in the browser. */
@@ -53,6 +56,11 @@ declare class DrizzleStateStore implements StateStore {
53
56
  * contract): EXISTS already requires the key row to be present, so EXISTS(... <> ...) is exactly
54
57
  * ne-with-present. Numeric operands compare `num_value`, everything else `str_value`. */
55
58
  private attributeExists;
59
+ saveSchedule(record: ScheduleRecord): Promise<void>;
60
+ getSchedule(id: string): Promise<ScheduleRecord | null>;
61
+ updateSchedule(id: string, patch: Partial<Omit<ScheduleRecord, 'id' | 'createdAt'>>, expectedNextFireAt?: number | null): Promise<boolean>;
62
+ deleteSchedule(id: string): Promise<boolean>;
63
+ listSchedules(query: ScheduleQuery): Promise<ScheduleRecord[]>;
56
64
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
57
65
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
58
66
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
@@ -1042,6 +1050,203 @@ declare const bufferedEvents: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
1042
1050
  };
1043
1051
  dialect: "sqlite";
1044
1052
  }>;
1053
+ /**
1054
+ * Persisted schedules (`engine.schedules`). `spec`/`state` are JSON documents the engine owns; the
1055
+ * store only filters on the columns. `next_fire_at` is epoch ms, NULL when it never fires again.
1056
+ *
1057
+ * MIGRATION (you own this adapter's migrations): `drizzle-kit generate` picks the table up from
1058
+ * `durableSchema`; by hand it is the `durable_schedules` CREATE TABLE + index in the package docs.
1059
+ * Only needed if you use persisted schedules.
1060
+ */
1061
+ declare const durableSchedules: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
1062
+ name: "durable_schedules";
1063
+ schema: undefined;
1064
+ columns: {
1065
+ id: drizzle_orm_sqlite_core.SQLiteColumn<{
1066
+ name: "id";
1067
+ tableName: "durable_schedules";
1068
+ dataType: "string";
1069
+ columnType: "SQLiteText";
1070
+ data: string;
1071
+ driverParam: string;
1072
+ notNull: true;
1073
+ hasDefault: false;
1074
+ isPrimaryKey: true;
1075
+ isAutoincrement: false;
1076
+ hasRuntimeDefault: false;
1077
+ enumValues: [string, ...string[]];
1078
+ baseColumn: never;
1079
+ identity: undefined;
1080
+ generated: undefined;
1081
+ }, {}, {
1082
+ length: number | undefined;
1083
+ }>;
1084
+ namespace: drizzle_orm_sqlite_core.SQLiteColumn<{
1085
+ name: "namespace";
1086
+ tableName: "durable_schedules";
1087
+ dataType: "string";
1088
+ columnType: "SQLiteText";
1089
+ data: string;
1090
+ driverParam: string;
1091
+ notNull: true;
1092
+ hasDefault: true;
1093
+ isPrimaryKey: false;
1094
+ isAutoincrement: false;
1095
+ hasRuntimeDefault: false;
1096
+ enumValues: [string, ...string[]];
1097
+ baseColumn: never;
1098
+ identity: undefined;
1099
+ generated: undefined;
1100
+ }, {}, {
1101
+ length: number | undefined;
1102
+ }>;
1103
+ workflow: drizzle_orm_sqlite_core.SQLiteColumn<{
1104
+ name: "workflow";
1105
+ tableName: "durable_schedules";
1106
+ dataType: "string";
1107
+ columnType: "SQLiteText";
1108
+ data: string;
1109
+ driverParam: string;
1110
+ notNull: true;
1111
+ hasDefault: false;
1112
+ isPrimaryKey: false;
1113
+ isAutoincrement: false;
1114
+ hasRuntimeDefault: false;
1115
+ enumValues: [string, ...string[]];
1116
+ baseColumn: never;
1117
+ identity: undefined;
1118
+ generated: undefined;
1119
+ }, {}, {
1120
+ length: number | undefined;
1121
+ }>;
1122
+ paused: drizzle_orm_sqlite_core.SQLiteColumn<{
1123
+ name: "paused";
1124
+ tableName: "durable_schedules";
1125
+ dataType: "boolean";
1126
+ columnType: "SQLiteBoolean";
1127
+ data: boolean;
1128
+ driverParam: number;
1129
+ notNull: true;
1130
+ hasDefault: true;
1131
+ isPrimaryKey: false;
1132
+ isAutoincrement: false;
1133
+ hasRuntimeDefault: false;
1134
+ enumValues: undefined;
1135
+ baseColumn: never;
1136
+ identity: undefined;
1137
+ generated: undefined;
1138
+ }, {}, {}>;
1139
+ nextFireAt: drizzle_orm_sqlite_core.SQLiteColumn<{
1140
+ name: "next_fire_at";
1141
+ tableName: "durable_schedules";
1142
+ dataType: "number";
1143
+ columnType: "SQLiteInteger";
1144
+ data: number;
1145
+ driverParam: number;
1146
+ notNull: false;
1147
+ hasDefault: false;
1148
+ isPrimaryKey: false;
1149
+ isAutoincrement: false;
1150
+ hasRuntimeDefault: false;
1151
+ enumValues: undefined;
1152
+ baseColumn: never;
1153
+ identity: undefined;
1154
+ generated: undefined;
1155
+ }, {}, {}>;
1156
+ tags: drizzle_orm_sqlite_core.SQLiteColumn<{
1157
+ name: "tags";
1158
+ tableName: "durable_schedules";
1159
+ dataType: "json";
1160
+ columnType: "SQLiteTextJson";
1161
+ data: string[];
1162
+ driverParam: string;
1163
+ notNull: false;
1164
+ hasDefault: false;
1165
+ isPrimaryKey: false;
1166
+ isAutoincrement: false;
1167
+ hasRuntimeDefault: false;
1168
+ enumValues: undefined;
1169
+ baseColumn: never;
1170
+ identity: undefined;
1171
+ generated: undefined;
1172
+ }, {}, {
1173
+ $type: string[];
1174
+ }>;
1175
+ spec: drizzle_orm_sqlite_core.SQLiteColumn<{
1176
+ name: "spec";
1177
+ tableName: "durable_schedules";
1178
+ dataType: "json";
1179
+ columnType: "SQLiteTextJson";
1180
+ data: Record<string, unknown>;
1181
+ driverParam: string;
1182
+ notNull: true;
1183
+ hasDefault: false;
1184
+ isPrimaryKey: false;
1185
+ isAutoincrement: false;
1186
+ hasRuntimeDefault: false;
1187
+ enumValues: undefined;
1188
+ baseColumn: never;
1189
+ identity: undefined;
1190
+ generated: undefined;
1191
+ }, {}, {
1192
+ $type: Record<string, unknown>;
1193
+ }>;
1194
+ state: drizzle_orm_sqlite_core.SQLiteColumn<{
1195
+ name: "state";
1196
+ tableName: "durable_schedules";
1197
+ dataType: "json";
1198
+ columnType: "SQLiteTextJson";
1199
+ data: Record<string, unknown>;
1200
+ driverParam: string;
1201
+ notNull: true;
1202
+ hasDefault: false;
1203
+ isPrimaryKey: false;
1204
+ isAutoincrement: false;
1205
+ hasRuntimeDefault: false;
1206
+ enumValues: undefined;
1207
+ baseColumn: never;
1208
+ identity: undefined;
1209
+ generated: undefined;
1210
+ }, {}, {
1211
+ $type: Record<string, unknown>;
1212
+ }>;
1213
+ createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{
1214
+ name: "created_at";
1215
+ tableName: "durable_schedules";
1216
+ dataType: "number";
1217
+ columnType: "SQLiteInteger";
1218
+ data: number;
1219
+ driverParam: number;
1220
+ notNull: true;
1221
+ hasDefault: false;
1222
+ isPrimaryKey: false;
1223
+ isAutoincrement: false;
1224
+ hasRuntimeDefault: false;
1225
+ enumValues: undefined;
1226
+ baseColumn: never;
1227
+ identity: undefined;
1228
+ generated: undefined;
1229
+ }, {}, {}>;
1230
+ updatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
1231
+ name: "updated_at";
1232
+ tableName: "durable_schedules";
1233
+ dataType: "number";
1234
+ columnType: "SQLiteInteger";
1235
+ data: number;
1236
+ driverParam: number;
1237
+ notNull: true;
1238
+ hasDefault: false;
1239
+ isPrimaryKey: false;
1240
+ isAutoincrement: false;
1241
+ hasRuntimeDefault: false;
1242
+ enumValues: undefined;
1243
+ baseColumn: never;
1244
+ identity: undefined;
1245
+ generated: undefined;
1246
+ }, {}, {}>;
1247
+ };
1248
+ dialect: "sqlite";
1249
+ }>;
1045
1250
  declare const durableSchema: {
1046
1251
  workflowRuns: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
1047
1252
  name: "durable_workflow_runs";
@@ -2004,6 +2209,195 @@ declare const durableSchema: {
2004
2209
  };
2005
2210
  dialect: "sqlite";
2006
2211
  }>;
2212
+ durableSchedules: drizzle_orm_sqlite_core.SQLiteTableWithColumns<{
2213
+ name: "durable_schedules";
2214
+ schema: undefined;
2215
+ columns: {
2216
+ id: drizzle_orm_sqlite_core.SQLiteColumn<{
2217
+ name: "id";
2218
+ tableName: "durable_schedules";
2219
+ dataType: "string";
2220
+ columnType: "SQLiteText";
2221
+ data: string;
2222
+ driverParam: string;
2223
+ notNull: true;
2224
+ hasDefault: false;
2225
+ isPrimaryKey: true;
2226
+ isAutoincrement: false;
2227
+ hasRuntimeDefault: false;
2228
+ enumValues: [string, ...string[]];
2229
+ baseColumn: never;
2230
+ identity: undefined;
2231
+ generated: undefined;
2232
+ }, {}, {
2233
+ length: number | undefined;
2234
+ }>;
2235
+ namespace: drizzle_orm_sqlite_core.SQLiteColumn<{
2236
+ name: "namespace";
2237
+ tableName: "durable_schedules";
2238
+ dataType: "string";
2239
+ columnType: "SQLiteText";
2240
+ data: string;
2241
+ driverParam: string;
2242
+ notNull: true;
2243
+ hasDefault: true;
2244
+ isPrimaryKey: false;
2245
+ isAutoincrement: false;
2246
+ hasRuntimeDefault: false;
2247
+ enumValues: [string, ...string[]];
2248
+ baseColumn: never;
2249
+ identity: undefined;
2250
+ generated: undefined;
2251
+ }, {}, {
2252
+ length: number | undefined;
2253
+ }>;
2254
+ workflow: drizzle_orm_sqlite_core.SQLiteColumn<{
2255
+ name: "workflow";
2256
+ tableName: "durable_schedules";
2257
+ dataType: "string";
2258
+ columnType: "SQLiteText";
2259
+ data: string;
2260
+ driverParam: string;
2261
+ notNull: true;
2262
+ hasDefault: false;
2263
+ isPrimaryKey: false;
2264
+ isAutoincrement: false;
2265
+ hasRuntimeDefault: false;
2266
+ enumValues: [string, ...string[]];
2267
+ baseColumn: never;
2268
+ identity: undefined;
2269
+ generated: undefined;
2270
+ }, {}, {
2271
+ length: number | undefined;
2272
+ }>;
2273
+ paused: drizzle_orm_sqlite_core.SQLiteColumn<{
2274
+ name: "paused";
2275
+ tableName: "durable_schedules";
2276
+ dataType: "boolean";
2277
+ columnType: "SQLiteBoolean";
2278
+ data: boolean;
2279
+ driverParam: number;
2280
+ notNull: true;
2281
+ hasDefault: true;
2282
+ isPrimaryKey: false;
2283
+ isAutoincrement: false;
2284
+ hasRuntimeDefault: false;
2285
+ enumValues: undefined;
2286
+ baseColumn: never;
2287
+ identity: undefined;
2288
+ generated: undefined;
2289
+ }, {}, {}>;
2290
+ nextFireAt: drizzle_orm_sqlite_core.SQLiteColumn<{
2291
+ name: "next_fire_at";
2292
+ tableName: "durable_schedules";
2293
+ dataType: "number";
2294
+ columnType: "SQLiteInteger";
2295
+ data: number;
2296
+ driverParam: number;
2297
+ notNull: false;
2298
+ hasDefault: false;
2299
+ isPrimaryKey: false;
2300
+ isAutoincrement: false;
2301
+ hasRuntimeDefault: false;
2302
+ enumValues: undefined;
2303
+ baseColumn: never;
2304
+ identity: undefined;
2305
+ generated: undefined;
2306
+ }, {}, {}>;
2307
+ tags: drizzle_orm_sqlite_core.SQLiteColumn<{
2308
+ name: "tags";
2309
+ tableName: "durable_schedules";
2310
+ dataType: "json";
2311
+ columnType: "SQLiteTextJson";
2312
+ data: string[];
2313
+ driverParam: string;
2314
+ notNull: false;
2315
+ hasDefault: false;
2316
+ isPrimaryKey: false;
2317
+ isAutoincrement: false;
2318
+ hasRuntimeDefault: false;
2319
+ enumValues: undefined;
2320
+ baseColumn: never;
2321
+ identity: undefined;
2322
+ generated: undefined;
2323
+ }, {}, {
2324
+ $type: string[];
2325
+ }>;
2326
+ spec: drizzle_orm_sqlite_core.SQLiteColumn<{
2327
+ name: "spec";
2328
+ tableName: "durable_schedules";
2329
+ dataType: "json";
2330
+ columnType: "SQLiteTextJson";
2331
+ data: Record<string, unknown>;
2332
+ driverParam: string;
2333
+ notNull: true;
2334
+ hasDefault: false;
2335
+ isPrimaryKey: false;
2336
+ isAutoincrement: false;
2337
+ hasRuntimeDefault: false;
2338
+ enumValues: undefined;
2339
+ baseColumn: never;
2340
+ identity: undefined;
2341
+ generated: undefined;
2342
+ }, {}, {
2343
+ $type: Record<string, unknown>;
2344
+ }>;
2345
+ state: drizzle_orm_sqlite_core.SQLiteColumn<{
2346
+ name: "state";
2347
+ tableName: "durable_schedules";
2348
+ dataType: "json";
2349
+ columnType: "SQLiteTextJson";
2350
+ data: Record<string, unknown>;
2351
+ driverParam: string;
2352
+ notNull: true;
2353
+ hasDefault: false;
2354
+ isPrimaryKey: false;
2355
+ isAutoincrement: false;
2356
+ hasRuntimeDefault: false;
2357
+ enumValues: undefined;
2358
+ baseColumn: never;
2359
+ identity: undefined;
2360
+ generated: undefined;
2361
+ }, {}, {
2362
+ $type: Record<string, unknown>;
2363
+ }>;
2364
+ createdAt: drizzle_orm_sqlite_core.SQLiteColumn<{
2365
+ name: "created_at";
2366
+ tableName: "durable_schedules";
2367
+ dataType: "number";
2368
+ columnType: "SQLiteInteger";
2369
+ data: number;
2370
+ driverParam: number;
2371
+ notNull: true;
2372
+ hasDefault: false;
2373
+ isPrimaryKey: false;
2374
+ isAutoincrement: false;
2375
+ hasRuntimeDefault: false;
2376
+ enumValues: undefined;
2377
+ baseColumn: never;
2378
+ identity: undefined;
2379
+ generated: undefined;
2380
+ }, {}, {}>;
2381
+ updatedAt: drizzle_orm_sqlite_core.SQLiteColumn<{
2382
+ name: "updated_at";
2383
+ tableName: "durable_schedules";
2384
+ dataType: "number";
2385
+ columnType: "SQLiteInteger";
2386
+ data: number;
2387
+ driverParam: number;
2388
+ notNull: true;
2389
+ hasDefault: false;
2390
+ isPrimaryKey: false;
2391
+ isAutoincrement: false;
2392
+ hasRuntimeDefault: false;
2393
+ enumValues: undefined;
2394
+ baseColumn: never;
2395
+ identity: undefined;
2396
+ generated: undefined;
2397
+ }, {}, {}>;
2398
+ };
2399
+ dialect: "sqlite";
2400
+ }>;
2007
2401
  };
2008
2402
  /**
2009
2403
  * Tables this store creates and manages (you run the migrations — see the package README — but
@@ -2021,4 +2415,4 @@ declare const durableSchema: {
2021
2415
  */
2022
2416
  declare function durableManagedTables(): string[];
2023
2417
 
2024
- export { DEFAULT_NAMESPACE, DrizzleStateStore, bufferedEvents, bufferedSignals, durableManagedTables, durableSchema, runAttributes, signalWaiters, stepCheckpoints, workflowRuns };
2418
+ export { DEFAULT_NAMESPACE, DrizzleStateStore, bufferedEvents, bufferedSignals, durableManagedTables, durableSchedules, durableSchema, runAttributes, signalWaiters, stepCheckpoints, workflowRuns };
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // src/drizzle-state-store.ts
5
- import { RUN_VALUE_FACET_SCAN, attributePredicateOperands, axisIsRunColumn, mergeRunFacetRows, mergeRunValueFacetRows, normalizeAttributeRows, runValueFacetsFromRuns } from "@dudousxd/nestjs-durable-core";
6
- import { and, asc, desc, eq, exists, inArray, isNotNull, isNull, like, lte, or, sql } from "drizzle-orm";
5
+ import { RUN_VALUE_FACET_SCAN, TERMINAL_RUN_STATUSES, attributePredicateOperands, axisIsRunColumn, mergeRunFacetRows, mergeRunValueFacetRows, normalizeAttributeRows, parseDuration, runValueFacetsFromRuns } from "@dudousxd/nestjs-durable-core";
6
+ import { and, asc, desc, eq, exists, inArray, isNotNull, isNull, like, lt, lte, or, sql } from "drizzle-orm";
7
7
 
8
8
  // src/schema.ts
9
9
  import { getTableName } from "drizzle-orm";
@@ -160,13 +160,41 @@ var bufferedEvents = sqliteTable("durable_buffered_events", {
160
160
  }),
161
161
  publishedAt: integer("published_at").notNull()
162
162
  });
163
+ var durableSchedules = sqliteTable(
164
+ "durable_schedules",
165
+ {
166
+ id: text("id").primaryKey(),
167
+ namespace: text("namespace").notNull().default(DEFAULT_NAMESPACE),
168
+ workflow: text("workflow").notNull(),
169
+ paused: integer("paused", {
170
+ mode: "boolean"
171
+ }).notNull().default(false),
172
+ nextFireAt: integer("next_fire_at"),
173
+ tags: text("tags", {
174
+ mode: "json"
175
+ }).$type(),
176
+ spec: text("spec", {
177
+ mode: "json"
178
+ }).$type().notNull(),
179
+ state: text("state", {
180
+ mode: "json"
181
+ }).$type().notNull(),
182
+ createdAt: integer("created_at").notNull(),
183
+ updatedAt: integer("updated_at").notNull()
184
+ },
185
+ // The poll's "due by T" scan: `paused = 0 AND next_fire_at <= T ORDER BY next_fire_at`.
186
+ (t) => [
187
+ index("durable_schedules_due_idx").on(t.paused, t.nextFireAt)
188
+ ]
189
+ );
163
190
  var durableSchema = {
164
191
  workflowRuns,
165
192
  stepCheckpoints,
166
193
  runAttributes,
167
194
  signalWaiters,
168
195
  bufferedSignals,
169
- bufferedEvents
196
+ bufferedEvents,
197
+ durableSchedules
170
198
  };
171
199
  function durableManagedTables() {
172
200
  return Object.values(durableSchema).map((table) => getTableName(table));
@@ -219,6 +247,44 @@ var DrizzleStateStore = class {
219
247
  await this.db.delete(runAttributes).where(eq(runAttributes.runId, runId));
220
248
  await this.db.delete(workflowRuns).where(eq(workflowRuns.id, runId));
221
249
  }
250
+ async deleteRuns(runIds) {
251
+ if (runIds.length === 0) return;
252
+ await this.db.delete(stepCheckpoints).where(inArray(stepCheckpoints.runId, runIds));
253
+ await this.db.delete(signalWaiters).where(inArray(signalWaiters.runId, runIds));
254
+ await this.db.delete(runAttributes).where(inArray(runAttributes.runId, runIds));
255
+ await this.db.delete(workflowRuns).where(inArray(workflowRuns.id, runIds));
256
+ }
257
+ async pruneTerminalRuns(policy, nowMs, limit) {
258
+ const statuses = policy.statuses.filter((s) => TERMINAL_RUN_STATUSES.includes(s));
259
+ if (statuses.length === 0 || limit <= 0) return 0;
260
+ if (policy.maxAge == null && policy.maxCount == null) return 0;
261
+ const scoped = this.runFilters({
262
+ ...policy.scope ?? {},
263
+ statuses
264
+ });
265
+ const ids = /* @__PURE__ */ new Set();
266
+ if (policy.maxAge != null) {
267
+ const cutoff = nowMs - parseDuration(policy.maxAge);
268
+ const rows = await this.db.select({
269
+ id: workflowRuns.id
270
+ }).from(workflowRuns).where(and(...scoped, lt(workflowRuns.updatedAt, cutoff))).orderBy(asc(workflowRuns.updatedAt)).limit(limit);
271
+ for (const r of rows) ids.add(r.id);
272
+ }
273
+ if (policy.maxCount != null && ids.size < limit) {
274
+ const rows = await this.db.select({
275
+ id: workflowRuns.id
276
+ }).from(workflowRuns).where(and(...scoped)).orderBy(desc(workflowRuns.updatedAt), desc(workflowRuns.id)).limit(limit).offset(policy.maxCount);
277
+ for (const r of rows) {
278
+ if (ids.size >= limit) break;
279
+ ids.add(r.id);
280
+ }
281
+ }
282
+ const doomed = [
283
+ ...ids
284
+ ].slice(0, limit);
285
+ await this.deleteRuns(doomed);
286
+ return doomed.length;
287
+ }
222
288
  async getCheckpoint(runId, seq) {
223
289
  const rows = await this.db.select().from(stepCheckpoints).where(and(eq(stepCheckpoints.runId, runId), eq(stepCheckpoints.seq, seq))).limit(1);
224
290
  return rows[0] ? fromCheckpointRow(rows[0]) : null;
@@ -330,6 +396,13 @@ var DrizzleStateStore = class {
330
396
  const rows = await this.db.select().from(workflowRuns).where(filters.length ? and(...filters) : void 0).orderBy(desc(workflowRuns.createdAt)).limit(query.limit ?? -1).offset(query.offset ?? 0);
331
397
  return rows.map(fromRunRow);
332
398
  }
399
+ async countRuns(query) {
400
+ const filters = this.runFilters(query);
401
+ const rows = await this.db.select({
402
+ count: sql`count(*)`
403
+ }).from(workflowRuns).where(filters.length ? and(...filters) : void 0);
404
+ return Number(rows[0]?.count ?? 0);
405
+ }
333
406
  /** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
334
407
  * console can show whole-set counts next to a bounded page instead of downloading every run to
335
408
  * count them in the browser. */
@@ -379,6 +452,36 @@ var DrizzleStateStore = class {
379
452
  one: sql`1`
380
453
  }).from(runAttributes).where(and(eq(runAttributes.runId, workflowRuns.id), eq(runAttributes.key, f.key), anyOf(comparisons))));
381
454
  }
455
+ // ---- persisted schedules ----------------------------------------------------------------------
456
+ async saveSchedule(record) {
457
+ const row = toScheduleRow(record);
458
+ await this.db.insert(durableSchedules).values(row).onConflictDoUpdate({
459
+ target: durableSchedules.id,
460
+ set: row
461
+ });
462
+ }
463
+ async getSchedule(id) {
464
+ const rows = await this.db.select().from(durableSchedules).where(eq(durableSchedules.id, id)).limit(1);
465
+ return rows[0] ? fromScheduleRow(rows[0]) : null;
466
+ }
467
+ async updateSchedule(id, patch, expectedNextFireAt) {
468
+ const set = toSchedulePatch(patch);
469
+ if (!Object.keys(set).length) return await this.getSchedule(id) !== null;
470
+ 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, expectedNextFireAt))).returning({
471
+ id: durableSchedules.id
472
+ });
473
+ return rows.length > 0;
474
+ }
475
+ async deleteSchedule(id) {
476
+ const rows = await this.db.delete(durableSchedules).where(eq(durableSchedules.id, id)).returning({
477
+ id: durableSchedules.id
478
+ });
479
+ return rows.length > 0;
480
+ }
481
+ async listSchedules(query) {
482
+ const rows = await 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 ? like(durableSchedules.tags, `%"${query.tag}"%`) : void 0, query.dueBy !== void 0 ? and(eq(durableSchedules.paused, false), lte(durableSchedules.nextFireAt, query.dueBy)) : void 0)).orderBy(sql`${durableSchedules.nextFireAt} IS NULL`, asc(durableSchedules.nextFireAt), asc(durableSchedules.id)).limit(query.limit ?? -1).offset(query.offset ?? 0);
483
+ return rows.map(fromScheduleRow);
484
+ }
382
485
  async listCheckpoints(runId) {
383
486
  const rows = await this.db.select().from(stepCheckpoints).where(eq(stepCheckpoints.runId, runId)).orderBy(asc(stepCheckpoints.seq));
384
487
  return rows.map(fromCheckpointRow);
@@ -591,12 +694,58 @@ function fromCheckpointRow(row) {
591
694
  };
592
695
  }
593
696
  __name(fromCheckpointRow, "fromCheckpointRow");
697
+ function toScheduleRow(r) {
698
+ return {
699
+ id: r.id,
700
+ namespace: r.namespace,
701
+ workflow: r.workflow,
702
+ paused: r.paused,
703
+ nextFireAt: r.nextFireAt,
704
+ tags: r.tags ?? null,
705
+ spec: r.spec,
706
+ state: r.state,
707
+ createdAt: r.createdAt.getTime(),
708
+ updatedAt: r.updatedAt.getTime()
709
+ };
710
+ }
711
+ __name(toScheduleRow, "toScheduleRow");
712
+ function toSchedulePatch(patch) {
713
+ const set = {};
714
+ if ("namespace" in patch && patch.namespace !== void 0) set.namespace = patch.namespace;
715
+ if ("workflow" in patch && patch.workflow !== void 0) set.workflow = patch.workflow;
716
+ if ("paused" in patch && patch.paused !== void 0) set.paused = patch.paused;
717
+ if ("nextFireAt" in patch) set.nextFireAt = patch.nextFireAt ?? null;
718
+ if ("tags" in patch) set.tags = patch.tags ?? null;
719
+ if ("spec" in patch && patch.spec !== void 0) set.spec = patch.spec;
720
+ if ("state" in patch && patch.state !== void 0) set.state = patch.state;
721
+ if ("updatedAt" in patch && patch.updatedAt !== void 0) set.updatedAt = patch.updatedAt.getTime();
722
+ return set;
723
+ }
724
+ __name(toSchedulePatch, "toSchedulePatch");
725
+ function fromScheduleRow(row) {
726
+ return {
727
+ id: row.id,
728
+ namespace: row.namespace,
729
+ workflow: row.workflow,
730
+ paused: row.paused,
731
+ nextFireAt: row.nextFireAt ?? null,
732
+ ...row.tags ? {
733
+ tags: row.tags
734
+ } : {},
735
+ spec: row.spec ?? {},
736
+ state: row.state ?? {},
737
+ createdAt: new Date(row.createdAt),
738
+ updatedAt: new Date(row.updatedAt)
739
+ };
740
+ }
741
+ __name(fromScheduleRow, "fromScheduleRow");
594
742
  export {
595
743
  DEFAULT_NAMESPACE,
596
744
  DrizzleStateStore,
597
745
  bufferedEvents,
598
746
  bufferedSignals,
599
747
  durableManagedTables,
748
+ durableSchedules,
600
749
  durableSchema,
601
750
  runAttributes,
602
751
  signalWaiters,