@dudousxd/nestjs-durable-store-mikro-orm 0.25.0 → 0.26.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 +105 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -13
- package/dist/index.d.ts +31 -13
- package/dist/index.js +106 -71
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1191,83 +1191,118 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1191
1191
|
count: Number(r.count)
|
|
1192
1192
|
})));
|
|
1193
1193
|
}
|
|
1194
|
-
/**
|
|
1195
|
-
*
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1194
|
+
/**
|
|
1195
|
+
* Distinct values of one filter axis over the same predicates {@link listRuns} pages, with counts —
|
|
1196
|
+
* what a console's value pickers list.
|
|
1197
|
+
*
|
|
1198
|
+
* All three axis families are answered by ONE grouped query against the filtered run set, so the
|
|
1199
|
+
* counts are exact over that whole set (not a sample), the ordering is stable, and a picker can
|
|
1200
|
+
* page and search server-side. That last part is what makes a picker usable on the axis that needs
|
|
1201
|
+
* it most: tag cardinality grows with the data, so the values an operator is looking for are
|
|
1202
|
+
* routinely outside any top slice, and a search that only filtered the fetched page could never
|
|
1203
|
+
* find them.
|
|
1204
|
+
*
|
|
1205
|
+
* The predicates come from {@link runQueryBuilder} as a subquery rather than being rewritten here,
|
|
1206
|
+
* so a picker and the list it filters can never disagree about which runs they are talking about.
|
|
1207
|
+
*/
|
|
1198
1208
|
async runValueFacets(axis, query, opts) {
|
|
1199
|
-
if (!(0, import_nestjs_durable_core.axisIsRunColumn)(axis)) {
|
|
1200
|
-
return this.scannedValueFacets(axis, query, opts);
|
|
1201
|
-
}
|
|
1202
1209
|
const em = this.fork();
|
|
1203
|
-
const
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1210
|
+
const quote = this.idQuote(em);
|
|
1211
|
+
const limit = Math.max(0, opts?.limit ?? import_nestjs_durable_core.RUN_VALUE_FACET_LIMIT);
|
|
1212
|
+
const offset = Math.max(0, opts?.offset ?? 0);
|
|
1213
|
+
if (limit === 0) return [];
|
|
1214
|
+
const runs = this.runSubquery(em, query, axis);
|
|
1215
|
+
const { source, valueExpr } = this.valueSource(em, axis, runs, quote);
|
|
1216
|
+
const conditions = [
|
|
1217
|
+
this.searchCondition(valueExpr, opts?.search)
|
|
1218
|
+
].filter((part) => part !== void 0);
|
|
1219
|
+
const where = conditions.length ? ` where ${conditions.join(" and ")}` : "";
|
|
1220
|
+
const engineLast = axis.field === "tag" ? `case when ${valueExpr} like '${import_nestjs_durable_core.ENGINE_MINTED_TAG_PREFIXES[0]}%' then 1 else 0 end, ` : "";
|
|
1221
|
+
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}`;
|
|
1222
|
+
const rows = await em.getConnection().execute(sql);
|
|
1223
|
+
return rows.map((row) => ({
|
|
1224
|
+
// Blank and absent are the same thing to an operator on the origin axis — see facetOrigin.
|
|
1225
|
+
value: axis.field === "origin" ? (0, import_nestjs_durable_core.facetOrigin)(row.value) : this.text(row.value),
|
|
1212
1226
|
count: Number(row.count)
|
|
1213
|
-
}))
|
|
1227
|
+
}));
|
|
1228
|
+
}
|
|
1229
|
+
/** The filtered run set as a derived table, carrying only the columns the axis needs. One source
|
|
1230
|
+
* of truth with `listRuns`: the same builder, the same predicates. */
|
|
1231
|
+
runSubquery(em, query, axis) {
|
|
1232
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1233
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1234
|
+
const columns = axis.field === "tag" ? [
|
|
1235
|
+
`r.${idCol}`,
|
|
1236
|
+
`r.${this.tagsColumn(em)}`
|
|
1237
|
+
] : (0, import_nestjs_durable_core.axisIsRunColumn)(axis) ? [
|
|
1238
|
+
`r.${meta.properties[axis.field]?.fieldNames?.[0] ?? axis.field}`
|
|
1239
|
+
] : [
|
|
1240
|
+
`r.${idCol}`
|
|
1241
|
+
];
|
|
1242
|
+
return this.runQueryBuilder(em, query, this.runWhere(query)).select(columns).getFormattedQuery();
|
|
1243
|
+
}
|
|
1244
|
+
/** Where the values of an axis come from, and how to name one — a column of the filtered runs, an
|
|
1245
|
+
* element of their tags array, or a row of the attribute side table. */
|
|
1246
|
+
valueSource(em, axis, runs, quote) {
|
|
1247
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1248
|
+
if ((0, import_nestjs_durable_core.axisIsRunColumn)(axis)) {
|
|
1249
|
+
const col = meta.properties[axis.field]?.fieldNames?.[0] ?? axis.field;
|
|
1250
|
+
return {
|
|
1251
|
+
source: `(${runs}) as ${quote("r")}`,
|
|
1252
|
+
valueExpr: `${quote("r")}.${quote(col)}`
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
if (axis.field === "tag") {
|
|
1256
|
+
return {
|
|
1257
|
+
source: `(${runs}) as ${quote("r")} ${this.jsonArrayJoin(em, `${quote("r")}.${quote(this.tagsColumn(em))}`, quote)}`,
|
|
1258
|
+
valueExpr: `${quote("t")}.${quote("value")}`
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
const cols = this.attributeColumns(em);
|
|
1262
|
+
const idCol = meta.properties.id?.fieldNames?.[0] ?? "id";
|
|
1263
|
+
const a = quote("a");
|
|
1264
|
+
const join = `${quote(cols.table)} as ${a} join (${runs}) as ${quote("r")} on ${a}.${quote(cols.runId)} = ${quote("r")}.${quote(idCol)}`;
|
|
1265
|
+
if (axis.field === "attributeKey") {
|
|
1266
|
+
return {
|
|
1267
|
+
source: join,
|
|
1268
|
+
valueExpr: `${a}.${quote(cols.key)}`
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
const key = axis.key.replace(/'/g, "''");
|
|
1272
|
+
return {
|
|
1273
|
+
source: `${join} and ${a}.${quote(cols.key)} = '${key}'`,
|
|
1274
|
+
valueExpr: `coalesce(${a}.${quote(cols.strValue)}, cast(${a}.${quote(cols.numValue)} as char))`
|
|
1275
|
+
};
|
|
1214
1276
|
}
|
|
1215
1277
|
/**
|
|
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.
|
|
1278
|
+
* The dialect's way of turning a JSON array column into one row per element — the only portable
|
|
1279
|
+
* thing about it being that all three have one. Without it a tag picker cannot be grouped in the
|
|
1280
|
+
* database at all, and falls back to deduping a bounded page of runs in memory: counts that
|
|
1281
|
+
* describe a sample, and an order that cannot be paged.
|
|
1224
1282
|
*/
|
|
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);
|
|
1283
|
+
jsonArrayJoin(em, colRef, quote) {
|
|
1284
|
+
const platform = String(em.getPlatform().constructor.name).toLowerCase();
|
|
1285
|
+
const t = quote("t");
|
|
1286
|
+
if (platform.includes("postgre")) {
|
|
1287
|
+
return `cross join lateral jsonb_array_elements_text(${colRef}::jsonb) as ${t}(${quote("value")})`;
|
|
1249
1288
|
}
|
|
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);
|
|
1289
|
+
if (platform.includes("mysql") || platform.includes("mariadb")) {
|
|
1290
|
+
return `join json_table(cast(${colRef} as json), '$[*]' columns (${quote("value")} varchar(512) path '$')) as ${t}`;
|
|
1291
|
+
}
|
|
1292
|
+
return `join json_each(${colRef}) as ${t}`;
|
|
1293
|
+
}
|
|
1294
|
+
/** `lower(value) like '%needle%'` — the picker's search box, applied BEFORE the page is cut so a
|
|
1295
|
+
* match outside the top slice is still reachable. */
|
|
1296
|
+
searchCondition(valueExpr, search) {
|
|
1297
|
+
const needle = search?.trim().toLowerCase();
|
|
1298
|
+
if (!needle) return void 0;
|
|
1299
|
+
const escaped = needle.replace(/'/g, "''").replace(/[%_]/g, (c) => `\\${c}`);
|
|
1300
|
+
return `lower(${valueExpr}) like '%${escaped}%'`;
|
|
1301
|
+
}
|
|
1302
|
+
/** A grouped value as the string a picker shows. Drivers return a JSON element or a numeric
|
|
1303
|
+
* attribute as something other than a string; `null` stays null (the absent bucket). */
|
|
1304
|
+
text(value) {
|
|
1305
|
+
return value === null || value === void 0 ? null : String(value);
|
|
1271
1306
|
}
|
|
1272
1307
|
/** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
|
|
1273
1308
|
* alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
|