@dudousxd/nestjs-durable-store-mikro-orm 0.25.0 → 0.27.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 +350 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -15
- package/dist/index.d.ts +60 -15
- package/dist/index.js +349 -120
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,8 @@ __export(index_exports, {
|
|
|
29
29
|
MikroOrmStateStore: () => MikroOrmStateStore,
|
|
30
30
|
RunAttributeEntity: () => RunAttributeEntity,
|
|
31
31
|
RunAttributeRepository: () => RunAttributeRepository,
|
|
32
|
+
ScheduleEntity: () => ScheduleEntity,
|
|
33
|
+
ScheduleRepository: () => ScheduleRepository,
|
|
32
34
|
SignalWaiterEntity: () => SignalWaiterEntity,
|
|
33
35
|
SignalWaiterRepository: () => SignalWaiterRepository,
|
|
34
36
|
StepCheckpointEntity: () => StepCheckpointEntity,
|
|
@@ -76,6 +78,11 @@ var BufferedEventRepository = class extends import_core.EntityRepository {
|
|
|
76
78
|
__name(this, "BufferedEventRepository");
|
|
77
79
|
}
|
|
78
80
|
};
|
|
81
|
+
var ScheduleRepository = class extends import_core.EntityRepository {
|
|
82
|
+
static {
|
|
83
|
+
__name(this, "ScheduleRepository");
|
|
84
|
+
}
|
|
85
|
+
};
|
|
79
86
|
|
|
80
87
|
// src/entities.ts
|
|
81
88
|
function snakeCase(property) {
|
|
@@ -175,6 +182,24 @@ var BufferedEventEntity = class {
|
|
|
175
182
|
payload;
|
|
176
183
|
publishedAt;
|
|
177
184
|
};
|
|
185
|
+
var ScheduleEntity = class {
|
|
186
|
+
static {
|
|
187
|
+
__name(this, "ScheduleEntity");
|
|
188
|
+
}
|
|
189
|
+
[import_core2.EntityRepositoryType];
|
|
190
|
+
id;
|
|
191
|
+
namespace;
|
|
192
|
+
workflow;
|
|
193
|
+
paused;
|
|
194
|
+
/** Epoch ms as a `bigint` column — exact, so `updateSchedule`'s compare-and-set can't be defeated
|
|
195
|
+
* by a DATETIME's truncated milliseconds. Drivers may hand it back as a string. */
|
|
196
|
+
nextFireAt;
|
|
197
|
+
tags;
|
|
198
|
+
spec;
|
|
199
|
+
state;
|
|
200
|
+
createdAt;
|
|
201
|
+
updatedAt;
|
|
202
|
+
};
|
|
178
203
|
function namespaceFilterCond(args) {
|
|
179
204
|
if (args?.namespace === void 0) return {};
|
|
180
205
|
return {
|
|
@@ -574,13 +599,76 @@ function durableEntities(options = {}) {
|
|
|
574
599
|
}
|
|
575
600
|
}
|
|
576
601
|
});
|
|
602
|
+
const schedules = new import_core2.EntitySchema({
|
|
603
|
+
class: ScheduleEntity,
|
|
604
|
+
tableName: "durable_schedules",
|
|
605
|
+
repository: /* @__PURE__ */ __name(() => ScheduleRepository, "repository"),
|
|
606
|
+
// The poll's "due by T" scan: `paused = false AND next_fire_at <= T ORDER BY next_fire_at`.
|
|
607
|
+
indexes: [
|
|
608
|
+
{
|
|
609
|
+
name: "durable_schedules_due_idx",
|
|
610
|
+
properties: [
|
|
611
|
+
"paused",
|
|
612
|
+
"nextFireAt"
|
|
613
|
+
]
|
|
614
|
+
}
|
|
615
|
+
],
|
|
616
|
+
properties: {
|
|
617
|
+
id: {
|
|
618
|
+
type: "string",
|
|
619
|
+
primary: true,
|
|
620
|
+
fieldName: col("id")
|
|
621
|
+
},
|
|
622
|
+
namespace: {
|
|
623
|
+
type: "string",
|
|
624
|
+
default: "default",
|
|
625
|
+
fieldName: col("namespace")
|
|
626
|
+
},
|
|
627
|
+
workflow: {
|
|
628
|
+
type: "string",
|
|
629
|
+
fieldName: col("workflow")
|
|
630
|
+
},
|
|
631
|
+
paused: {
|
|
632
|
+
type: "boolean",
|
|
633
|
+
default: false,
|
|
634
|
+
fieldName: col("paused")
|
|
635
|
+
},
|
|
636
|
+
nextFireAt: {
|
|
637
|
+
type: "bigint",
|
|
638
|
+
nullable: true,
|
|
639
|
+
fieldName: col("nextFireAt")
|
|
640
|
+
},
|
|
641
|
+
tags: {
|
|
642
|
+
type: "json",
|
|
643
|
+
nullable: true,
|
|
644
|
+
fieldName: col("tags")
|
|
645
|
+
},
|
|
646
|
+
spec: {
|
|
647
|
+
type: "json",
|
|
648
|
+
fieldName: col("spec")
|
|
649
|
+
},
|
|
650
|
+
state: {
|
|
651
|
+
type: "json",
|
|
652
|
+
fieldName: col("state")
|
|
653
|
+
},
|
|
654
|
+
createdAt: {
|
|
655
|
+
type: "Date",
|
|
656
|
+
fieldName: col("createdAt")
|
|
657
|
+
},
|
|
658
|
+
updatedAt: {
|
|
659
|
+
type: "Date",
|
|
660
|
+
fieldName: col("updatedAt")
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
});
|
|
577
664
|
return [
|
|
578
665
|
workflowRuns,
|
|
579
666
|
stepCheckpoints,
|
|
580
667
|
runAttributes,
|
|
581
668
|
signalWaiters,
|
|
582
669
|
bufferedSignals,
|
|
583
|
-
bufferedEvents
|
|
670
|
+
bufferedEvents,
|
|
671
|
+
schedules
|
|
584
672
|
];
|
|
585
673
|
}
|
|
586
674
|
__name(durableEntities, "durableEntities");
|
|
@@ -598,7 +686,8 @@ var DURABLE_TABLE_NAMES = /* @__PURE__ */ new Set([
|
|
|
598
686
|
"durable_run_attributes",
|
|
599
687
|
"durable_signal_waiters",
|
|
600
688
|
"durable_buffered_signals",
|
|
601
|
-
"durable_buffered_events"
|
|
689
|
+
"durable_buffered_events",
|
|
690
|
+
"durable_schedules"
|
|
602
691
|
]);
|
|
603
692
|
function durableManagedTables() {
|
|
604
693
|
return [
|
|
@@ -895,71 +984,69 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
895
984
|
id: runId
|
|
896
985
|
});
|
|
897
986
|
}
|
|
987
|
+
async deleteRuns(runIds) {
|
|
988
|
+
if (runIds.length === 0) return;
|
|
989
|
+
const runId = {
|
|
990
|
+
$in: runIds
|
|
991
|
+
};
|
|
992
|
+
await this.fork().transactional(async (tem) => {
|
|
993
|
+
await tem.nativeDelete(StepCheckpointEntity, {
|
|
994
|
+
runId
|
|
995
|
+
});
|
|
996
|
+
await tem.nativeDelete(SignalWaiterEntity, {
|
|
997
|
+
runId
|
|
998
|
+
});
|
|
999
|
+
await tem.nativeDelete(RunAttributeEntity, {
|
|
1000
|
+
runId
|
|
1001
|
+
});
|
|
1002
|
+
await tem.nativeDelete(WorkflowRunEntity, {
|
|
1003
|
+
id: runId
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
898
1007
|
async pruneTerminalRuns(policy, nowMs, limit) {
|
|
899
|
-
|
|
1008
|
+
const statuses = policy.statuses.filter((s) => import_nestjs_durable_core.TERMINAL_RUN_STATUSES.includes(s));
|
|
1009
|
+
if (statuses.length === 0 || limit <= 0) return 0;
|
|
1010
|
+
if (policy.maxAge == null && policy.maxCount == null) return 0;
|
|
900
1011
|
const em = this.fork();
|
|
901
|
-
const
|
|
902
|
-
|
|
1012
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1013
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1014
|
+
const query = {
|
|
1015
|
+
...policy.scope ?? {},
|
|
1016
|
+
statuses
|
|
903
1017
|
};
|
|
1018
|
+
const scoped = /* @__PURE__ */ __name((extra) => this.runQueryBuilder(em, query, {
|
|
1019
|
+
...this.runWhere(query),
|
|
1020
|
+
...extra ?? {}
|
|
1021
|
+
}).select([
|
|
1022
|
+
`r.${idCol}`
|
|
1023
|
+
]), "scoped");
|
|
904
1024
|
const ids = /* @__PURE__ */ new Set();
|
|
905
1025
|
if (policy.maxAge != null) {
|
|
906
1026
|
const cutoff = new Date(nowMs - (0, import_nestjs_durable_core.parseDuration)(policy.maxAge));
|
|
907
|
-
const rows = await
|
|
908
|
-
status,
|
|
1027
|
+
const rows = await scoped({
|
|
909
1028
|
updatedAt: {
|
|
910
1029
|
$lt: cutoff
|
|
911
1030
|
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
orderBy: {
|
|
917
|
-
updatedAt: "asc"
|
|
918
|
-
},
|
|
919
|
-
limit
|
|
920
|
-
});
|
|
921
|
-
for (const r of rows) ids.add(r.id);
|
|
1031
|
+
}).orderBy({
|
|
1032
|
+
"r.updatedAt": "asc"
|
|
1033
|
+
}).limit(limit).execute();
|
|
1034
|
+
for (const r of rows) ids.add(String(r[idCol] ?? r.id));
|
|
922
1035
|
}
|
|
923
1036
|
if (policy.maxCount != null && ids.size < limit) {
|
|
924
|
-
const rows = await
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
"id"
|
|
929
|
-
],
|
|
930
|
-
orderBy: {
|
|
931
|
-
updatedAt: "desc",
|
|
932
|
-
id: "desc"
|
|
933
|
-
},
|
|
934
|
-
limit,
|
|
935
|
-
offset: policy.maxCount
|
|
936
|
-
});
|
|
1037
|
+
const rows = await scoped().orderBy({
|
|
1038
|
+
"r.updatedAt": "desc",
|
|
1039
|
+
"r.id": "desc"
|
|
1040
|
+
}).limit(limit).offset(policy.maxCount).execute();
|
|
937
1041
|
for (const r of rows) {
|
|
938
|
-
ids.add(r.id);
|
|
939
1042
|
if (ids.size >= limit) break;
|
|
1043
|
+
ids.add(String(r[idCol] ?? r.id));
|
|
940
1044
|
}
|
|
941
1045
|
}
|
|
942
|
-
if (ids.size === 0) return 0;
|
|
943
1046
|
const idList = [
|
|
944
1047
|
...ids
|
|
945
1048
|
].slice(0, limit);
|
|
946
|
-
|
|
947
|
-
$in: idList
|
|
948
|
-
};
|
|
949
|
-
await em.transactional(async (tem) => {
|
|
950
|
-
await tem.nativeDelete(StepCheckpointEntity, {
|
|
951
|
-
runId
|
|
952
|
-
});
|
|
953
|
-
await tem.nativeDelete(SignalWaiterEntity, {
|
|
954
|
-
runId
|
|
955
|
-
});
|
|
956
|
-
await tem.nativeDelete(RunAttributeEntity, {
|
|
957
|
-
runId
|
|
958
|
-
});
|
|
959
|
-
await tem.nativeDelete(WorkflowRunEntity, {
|
|
960
|
-
id: runId
|
|
961
|
-
});
|
|
962
|
-
});
|
|
1049
|
+
await this.deleteRuns(idList);
|
|
963
1050
|
return idList.length;
|
|
964
1051
|
}
|
|
965
1052
|
async getCheckpoint(runId, seq) {
|
|
@@ -1171,6 +1258,11 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1171
1258
|
const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(this.tagsColumn(em))}`);
|
|
1172
1259
|
return `${colExpr} LIKE '${pattern}'`;
|
|
1173
1260
|
}
|
|
1261
|
+
async countRuns(query) {
|
|
1262
|
+
const em = this.fork();
|
|
1263
|
+
const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([]).addSelect((0, import_core3.raw)("count(*) as count")).execute();
|
|
1264
|
+
return Number(rows[0]?.count ?? 0);
|
|
1265
|
+
}
|
|
1174
1266
|
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
1175
1267
|
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
1176
1268
|
* count them in the browser. */
|
|
@@ -1191,83 +1283,118 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1191
1283
|
count: Number(r.count)
|
|
1192
1284
|
})));
|
|
1193
1285
|
}
|
|
1194
|
-
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1286
|
+
/**
|
|
1287
|
+
* Distinct values of one filter axis over the same predicates {@link listRuns} pages, with counts —
|
|
1288
|
+
* what a console's value pickers list.
|
|
1289
|
+
*
|
|
1290
|
+
* All three axis families are answered by ONE grouped query against the filtered run set, so the
|
|
1291
|
+
* counts are exact over that whole set (not a sample), the ordering is stable, and a picker can
|
|
1292
|
+
* page and search server-side. That last part is what makes a picker usable on the axis that needs
|
|
1293
|
+
* it most: tag cardinality grows with the data, so the values an operator is looking for are
|
|
1294
|
+
* routinely outside any top slice, and a search that only filtered the fetched page could never
|
|
1295
|
+
* find them.
|
|
1296
|
+
*
|
|
1297
|
+
* The predicates come from {@link runQueryBuilder} as a subquery rather than being rewritten here,
|
|
1298
|
+
* so a picker and the list it filters can never disagree about which runs they are talking about.
|
|
1299
|
+
*/
|
|
1198
1300
|
async runValueFacets(axis, query, opts) {
|
|
1199
|
-
if (!(0, import_nestjs_durable_core.axisIsRunColumn)(axis)) {
|
|
1200
|
-
return this.scannedValueFacets(axis, query, opts);
|
|
1201
|
-
}
|
|
1202
1301
|
const em = this.fork();
|
|
1203
|
-
const
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1302
|
+
const quote = this.idQuote(em);
|
|
1303
|
+
const limit = Math.max(0, opts?.limit ?? import_nestjs_durable_core.RUN_VALUE_FACET_LIMIT);
|
|
1304
|
+
const offset = Math.max(0, opts?.offset ?? 0);
|
|
1305
|
+
if (limit === 0) return [];
|
|
1306
|
+
const runs = this.runSubquery(em, query, axis);
|
|
1307
|
+
const { source, valueExpr } = this.valueSource(em, axis, runs, quote);
|
|
1308
|
+
const conditions = [
|
|
1309
|
+
this.searchCondition(valueExpr, opts?.search)
|
|
1310
|
+
].filter((part) => part !== void 0);
|
|
1311
|
+
const where = conditions.length ? ` where ${conditions.join(" and ")}` : "";
|
|
1312
|
+
const engineLast = axis.field === "tag" ? `case when ${valueExpr} like '${import_nestjs_durable_core.ENGINE_MINTED_TAG_PREFIXES[0]}%' then 1 else 0 end, ` : "";
|
|
1313
|
+
const sql = `select ${valueExpr} as value, count(*) as count from ${source}${where} group by ${valueExpr} order by ${engineLast}count(*) desc, ${valueExpr} asc limit ${limit} offset ${offset}`;
|
|
1314
|
+
const rows = await em.getConnection().execute(sql);
|
|
1315
|
+
return rows.map((row) => ({
|
|
1316
|
+
// Blank and absent are the same thing to an operator on the origin axis — see facetOrigin.
|
|
1317
|
+
value: axis.field === "origin" ? (0, import_nestjs_durable_core.facetOrigin)(row.value) : this.text(row.value),
|
|
1212
1318
|
count: Number(row.count)
|
|
1213
|
-
}))
|
|
1319
|
+
}));
|
|
1320
|
+
}
|
|
1321
|
+
/** The filtered run set as a derived table, carrying only the columns the axis needs. One source
|
|
1322
|
+
* of truth with `listRuns`: the same builder, the same predicates. */
|
|
1323
|
+
runSubquery(em, query, axis) {
|
|
1324
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1325
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1326
|
+
const columns = axis.field === "tag" ? [
|
|
1327
|
+
`r.${idCol}`,
|
|
1328
|
+
`r.${this.tagsColumn(em)}`
|
|
1329
|
+
] : (0, import_nestjs_durable_core.axisIsRunColumn)(axis) ? [
|
|
1330
|
+
`r.${meta.properties[axis.field]?.fieldNames?.[0] ?? axis.field}`
|
|
1331
|
+
] : [
|
|
1332
|
+
`r.${idCol}`
|
|
1333
|
+
];
|
|
1334
|
+
return this.runQueryBuilder(em, query, this.runWhere(query)).select(columns).getFormattedQuery();
|
|
1335
|
+
}
|
|
1336
|
+
/** Where the values of an axis come from, and how to name one — a column of the filtered runs, an
|
|
1337
|
+
* element of their tags array, or a row of the attribute side table. */
|
|
1338
|
+
valueSource(em, axis, runs, quote) {
|
|
1339
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1340
|
+
if ((0, import_nestjs_durable_core.axisIsRunColumn)(axis)) {
|
|
1341
|
+
const col = meta.properties[axis.field]?.fieldNames?.[0] ?? axis.field;
|
|
1342
|
+
return {
|
|
1343
|
+
source: `(${runs}) as ${quote("r")}`,
|
|
1344
|
+
valueExpr: `${quote("r")}.${quote(col)}`
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
if (axis.field === "tag") {
|
|
1348
|
+
return {
|
|
1349
|
+
source: `(${runs}) as ${quote("r")} ${this.jsonArrayJoin(em, `${quote("r")}.${quote(this.tagsColumn(em))}`, quote)}`,
|
|
1350
|
+
valueExpr: `${quote("t")}.${quote("value")}`
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
const cols = this.attributeColumns(em);
|
|
1354
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1355
|
+
const a = quote("a");
|
|
1356
|
+
const join = `${quote(cols.table)} as ${a} join (${runs}) as ${quote("r")} on ${a}.${quote(cols.runId)} = ${quote("r")}.${quote(idCol)}`;
|
|
1357
|
+
if (axis.field === "attributeKey") {
|
|
1358
|
+
return {
|
|
1359
|
+
source: join,
|
|
1360
|
+
valueExpr: `${a}.${quote(cols.key)}`
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
const key = axis.key.replace(/'/g, "''");
|
|
1364
|
+
return {
|
|
1365
|
+
source: `${join} and ${a}.${quote(cols.key)} = '${key}'`,
|
|
1366
|
+
valueExpr: `coalesce(${a}.${quote(cols.strValue)}, cast(${a}.${quote(cols.numValue)} as char))`
|
|
1367
|
+
};
|
|
1214
1368
|
}
|
|
1215
1369
|
/**
|
|
1216
|
-
* The
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1220
|
-
* memory — pulls `input`, `output` and `error` for every run in the window, which on a control
|
|
1221
|
-
* plane with real payloads is tens of megabytes fetched to produce a list of a hundred short
|
|
1222
|
-
* strings, every time a picker opens. These project the one column each question needs instead:
|
|
1223
|
-
* the tags column, or the run ids that then drive one grouped read of the side table.
|
|
1370
|
+
* The dialect's way of turning a JSON array column into one row per element — the only portable
|
|
1371
|
+
* thing about it being that all three have one. Without it a tag picker cannot be grouped in the
|
|
1372
|
+
* database at all, and falls back to deduping a bounded page of runs in memory: counts that
|
|
1373
|
+
* describe a sample, and an order that cannot be paged.
|
|
1224
1374
|
*/
|
|
1225
|
-
|
|
1226
|
-
const
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
const idCol = runMeta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1231
|
-
if (axis.field === "tag") {
|
|
1232
|
-
const tagsCol = this.tagsColumn(em);
|
|
1233
|
-
const rows = await this.runQueryBuilder(em, query, where).select([
|
|
1234
|
-
`r.${tagsCol}`
|
|
1235
|
-
]).orderBy({
|
|
1236
|
-
createdAt: "desc"
|
|
1237
|
-
}).limit(scan).execute();
|
|
1238
|
-
const values = [];
|
|
1239
|
-
for (const row of rows) {
|
|
1240
|
-
const raw2 = row.tags ?? row[tagsCol];
|
|
1241
|
-
const tags = typeof raw2 === "string" ? JSON.parse(raw2) : raw2;
|
|
1242
|
-
if (!Array.isArray(tags)) continue;
|
|
1243
|
-
for (const tag of tags) values.push({
|
|
1244
|
-
value: String(tag),
|
|
1245
|
-
count: 1
|
|
1246
|
-
});
|
|
1247
|
-
}
|
|
1248
|
-
return (0, import_nestjs_durable_core.mergeRunValueFacetRows)(values, opts);
|
|
1375
|
+
jsonArrayJoin(em, colRef, quote) {
|
|
1376
|
+
const platform = String(em.getPlatform().constructor.name).toLowerCase();
|
|
1377
|
+
const t = quote("t");
|
|
1378
|
+
if (platform.includes("postgre")) {
|
|
1379
|
+
return `cross join lateral jsonb_array_elements_text(${colRef}::jsonb) as ${t}(${quote("value")})`;
|
|
1249
1380
|
}
|
|
1250
|
-
|
|
1251
|
-
`
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
return (0, import_nestjs_durable_core.mergeRunValueFacetRows)(attributes.map((row) => ({
|
|
1268
|
-
value: axis.field === "attributeKey" ? row.key : row.strValue ?? (row.numValue == null ? null : String(row.numValue)),
|
|
1269
|
-
count: 1
|
|
1270
|
-
})), opts);
|
|
1381
|
+
if (platform.includes("mysql") || platform.includes("mariadb")) {
|
|
1382
|
+
return `join json_table(cast(${colRef} as json), '$[*]' columns (${quote("value")} varchar(512) path '$')) as ${t}`;
|
|
1383
|
+
}
|
|
1384
|
+
return `join json_each(${colRef}) as ${t}`;
|
|
1385
|
+
}
|
|
1386
|
+
/** `lower(value) like '%needle%'` — the picker's search box, applied BEFORE the page is cut so a
|
|
1387
|
+
* match outside the top slice is still reachable. */
|
|
1388
|
+
searchCondition(valueExpr, search) {
|
|
1389
|
+
const needle = search?.trim().toLowerCase();
|
|
1390
|
+
if (!needle) return void 0;
|
|
1391
|
+
const escaped = needle.replace(/'/g, "''").replace(/[%_]/g, (c) => `\\${c}`);
|
|
1392
|
+
return `lower(${valueExpr}) like '%${escaped}%'`;
|
|
1393
|
+
}
|
|
1394
|
+
/** A grouped value as the string a picker shows. Drivers return a JSON element or a numeric
|
|
1395
|
+
* attribute as something other than a string; `null` stays null (the absent bucket). */
|
|
1396
|
+
text(value) {
|
|
1397
|
+
return value === null || value === void 0 ? null : String(value);
|
|
1271
1398
|
}
|
|
1272
1399
|
/** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
|
|
1273
1400
|
* alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
|
|
@@ -1327,6 +1454,76 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1327
1454
|
const ch = isMysql ? "`" : '"';
|
|
1328
1455
|
return (id) => `${ch}${id}${ch}`;
|
|
1329
1456
|
}
|
|
1457
|
+
// ---- persisted schedules ----------------------------------------------------------------------
|
|
1458
|
+
async saveSchedule(record) {
|
|
1459
|
+
const em = this.fork();
|
|
1460
|
+
await em.upsert(ScheduleEntity, toScheduleEntity(record));
|
|
1461
|
+
}
|
|
1462
|
+
async getSchedule(id) {
|
|
1463
|
+
const entity = await this.fork().findOne(ScheduleEntity, {
|
|
1464
|
+
id
|
|
1465
|
+
});
|
|
1466
|
+
return entity ? fromScheduleEntity(entity) : null;
|
|
1467
|
+
}
|
|
1468
|
+
/** One conditional `nativeUpdate`: the compare-and-set on `next_fire_at` (an exact bigint) is a
|
|
1469
|
+
* single statement. */
|
|
1470
|
+
async updateSchedule(id, patch, expectedNextFireAt) {
|
|
1471
|
+
const data = {};
|
|
1472
|
+
if (patch.namespace !== void 0) data.namespace = patch.namespace;
|
|
1473
|
+
if (patch.workflow !== void 0) data.workflow = patch.workflow;
|
|
1474
|
+
if (patch.paused !== void 0) data.paused = patch.paused;
|
|
1475
|
+
if ("nextFireAt" in patch) data.nextFireAt = patch.nextFireAt ?? null;
|
|
1476
|
+
if ("tags" in patch) data.tags = patch.tags ?? null;
|
|
1477
|
+
if (patch.spec !== void 0) data.spec = patch.spec;
|
|
1478
|
+
if (patch.state !== void 0) data.state = patch.state;
|
|
1479
|
+
if (patch.updatedAt !== void 0) data.updatedAt = patch.updatedAt;
|
|
1480
|
+
if (!Object.keys(data).length) return await this.getSchedule(id) !== null;
|
|
1481
|
+
const where = {
|
|
1482
|
+
id
|
|
1483
|
+
};
|
|
1484
|
+
if (expectedNextFireAt !== void 0) where.nextFireAt = expectedNextFireAt;
|
|
1485
|
+
const affected = await this.fork().nativeUpdate(ScheduleEntity, where, data);
|
|
1486
|
+
return affected > 0;
|
|
1487
|
+
}
|
|
1488
|
+
async deleteSchedule(id) {
|
|
1489
|
+
return await this.fork().nativeDelete(ScheduleEntity, {
|
|
1490
|
+
id
|
|
1491
|
+
}) > 0;
|
|
1492
|
+
}
|
|
1493
|
+
async listSchedules(query) {
|
|
1494
|
+
const em = this.fork();
|
|
1495
|
+
const where = {};
|
|
1496
|
+
if (query.namespace !== void 0) where.namespace = query.namespace;
|
|
1497
|
+
if (query.workflow !== void 0) where.workflow = query.workflow;
|
|
1498
|
+
if (query.paused !== void 0) where.paused = query.paused;
|
|
1499
|
+
if (query.dueBy !== void 0) {
|
|
1500
|
+
where.$and = [
|
|
1501
|
+
{
|
|
1502
|
+
paused: false
|
|
1503
|
+
},
|
|
1504
|
+
{
|
|
1505
|
+
nextFireAt: {
|
|
1506
|
+
$lte: query.dueBy
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
];
|
|
1510
|
+
}
|
|
1511
|
+
const quote = this.idQuote(em);
|
|
1512
|
+
const meta = em.getMetadata().get(ScheduleEntity);
|
|
1513
|
+
const tagsCol = meta.properties.tags?.fieldNames?.[0] ?? "tags";
|
|
1514
|
+
const qb = em.createQueryBuilder(ScheduleEntity, "r").where(where);
|
|
1515
|
+
if (query.tag !== void 0) {
|
|
1516
|
+
const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
|
|
1517
|
+
qb.andWhere(`${this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`)} LIKE '${pattern}'`);
|
|
1518
|
+
}
|
|
1519
|
+
qb.orderBy({
|
|
1520
|
+
"r.nextFireAt": "asc nulls last",
|
|
1521
|
+
"r.id": "asc"
|
|
1522
|
+
});
|
|
1523
|
+
if (query.limit != null) qb.limit(query.limit);
|
|
1524
|
+
if (query.offset != null) qb.offset(query.offset);
|
|
1525
|
+
return (await qb.getResultList()).map(fromScheduleEntity);
|
|
1526
|
+
}
|
|
1330
1527
|
async listCheckpoints(runId) {
|
|
1331
1528
|
const em = this.fork();
|
|
1332
1529
|
const rows = await em.find(StepCheckpointEntity, {
|
|
@@ -1565,6 +1762,38 @@ function fromCheckpointEntity(e) {
|
|
|
1565
1762
|
};
|
|
1566
1763
|
}
|
|
1567
1764
|
__name(fromCheckpointEntity, "fromCheckpointEntity");
|
|
1765
|
+
function toScheduleEntity(r) {
|
|
1766
|
+
const e = new ScheduleEntity();
|
|
1767
|
+
e.id = r.id;
|
|
1768
|
+
e.namespace = r.namespace;
|
|
1769
|
+
e.workflow = r.workflow;
|
|
1770
|
+
e.paused = r.paused;
|
|
1771
|
+
e.nextFireAt = r.nextFireAt;
|
|
1772
|
+
e.tags = r.tags ?? null;
|
|
1773
|
+
e.spec = r.spec;
|
|
1774
|
+
e.state = r.state;
|
|
1775
|
+
e.createdAt = r.createdAt;
|
|
1776
|
+
e.updatedAt = r.updatedAt;
|
|
1777
|
+
return e;
|
|
1778
|
+
}
|
|
1779
|
+
__name(toScheduleEntity, "toScheduleEntity");
|
|
1780
|
+
function fromScheduleEntity(e) {
|
|
1781
|
+
return {
|
|
1782
|
+
id: e.id,
|
|
1783
|
+
namespace: e.namespace,
|
|
1784
|
+
workflow: e.workflow,
|
|
1785
|
+
paused: Boolean(e.paused),
|
|
1786
|
+
nextFireAt: e.nextFireAt == null ? null : Number(e.nextFireAt),
|
|
1787
|
+
...e.tags?.length ? {
|
|
1788
|
+
tags: e.tags
|
|
1789
|
+
} : {},
|
|
1790
|
+
spec: e.spec ?? {},
|
|
1791
|
+
state: e.state ?? {},
|
|
1792
|
+
createdAt: e.createdAt,
|
|
1793
|
+
updatedAt: e.updatedAt
|
|
1794
|
+
};
|
|
1795
|
+
}
|
|
1796
|
+
__name(fromScheduleEntity, "fromScheduleEntity");
|
|
1568
1797
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1569
1798
|
0 && (module.exports = {
|
|
1570
1799
|
BufferedEventEntity,
|
|
@@ -1575,6 +1804,8 @@ __name(fromCheckpointEntity, "fromCheckpointEntity");
|
|
|
1575
1804
|
MikroOrmStateStore,
|
|
1576
1805
|
RunAttributeEntity,
|
|
1577
1806
|
RunAttributeRepository,
|
|
1807
|
+
ScheduleEntity,
|
|
1808
|
+
ScheduleRepository,
|
|
1578
1809
|
SignalWaiterEntity,
|
|
1579
1810
|
SignalWaiterRepository,
|
|
1580
1811
|
StepCheckpointEntity,
|