@dudousxd/nestjs-durable-store-mikro-orm 0.20.0 → 0.22.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 +80 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +81 -16
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _mikro_orm_core from '@mikro-orm/core';
|
|
2
2
|
import { EntitySchema, MikroORM } from '@mikro-orm/core';
|
|
3
|
-
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* How property names map to physical column names. The durable tables are adapter-agnostic: a run
|
|
@@ -32,6 +32,7 @@ declare class WorkflowRunEntity {
|
|
|
32
32
|
searchAttributes?: Record<string, string | number | boolean> | null;
|
|
33
33
|
priority?: number | null;
|
|
34
34
|
namespace: string;
|
|
35
|
+
origin?: string | null;
|
|
35
36
|
createdAt: Date;
|
|
36
37
|
updatedAt: Date;
|
|
37
38
|
}
|
|
@@ -152,7 +153,19 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
152
153
|
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
153
154
|
releaseRunLock(runId: string): Promise<void>;
|
|
154
155
|
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
156
|
+
/** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
|
|
157
|
+
* Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
|
|
158
|
+
* applied by the QueryBuilder path in both callers. */
|
|
159
|
+
private runWhere;
|
|
155
160
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
161
|
+
/** A QueryBuilder over `durable_workflow_runs` (root alias `r`) carrying `where` plus the two
|
|
162
|
+
* predicates only raw SQL can express — the `tag` JSON LIKE and one EXISTS per attribute filter.
|
|
163
|
+
* Shared so a facet count and the page it labels are taken over the SAME set. */
|
|
164
|
+
private runQueryBuilder;
|
|
165
|
+
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
166
|
+
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
167
|
+
* count them in the browser. */
|
|
168
|
+
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
156
169
|
/** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
|
|
157
170
|
* alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
|
|
158
171
|
* EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _mikro_orm_core from '@mikro-orm/core';
|
|
2
2
|
import { EntitySchema, MikroORM } from '@mikro-orm/core';
|
|
3
|
-
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
3
|
+
import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* How property names map to physical column names. The durable tables are adapter-agnostic: a run
|
|
@@ -32,6 +32,7 @@ declare class WorkflowRunEntity {
|
|
|
32
32
|
searchAttributes?: Record<string, string | number | boolean> | null;
|
|
33
33
|
priority?: number | null;
|
|
34
34
|
namespace: string;
|
|
35
|
+
origin?: string | null;
|
|
35
36
|
createdAt: Date;
|
|
36
37
|
updatedAt: Date;
|
|
37
38
|
}
|
|
@@ -152,7 +153,19 @@ declare class MikroOrmStateStore implements StateStore {
|
|
|
152
153
|
tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
|
|
153
154
|
releaseRunLock(runId: string): Promise<void>;
|
|
154
155
|
renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
|
|
156
|
+
/** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
|
|
157
|
+
* Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
|
|
158
|
+
* applied by the QueryBuilder path in both callers. */
|
|
159
|
+
private runWhere;
|
|
155
160
|
listRuns(query: RunQuery): Promise<WorkflowRun[]>;
|
|
161
|
+
/** A QueryBuilder over `durable_workflow_runs` (root alias `r`) carrying `where` plus the two
|
|
162
|
+
* predicates only raw SQL can express — the `tag` JSON LIKE and one EXISTS per attribute filter.
|
|
163
|
+
* Shared so a facet count and the page it labels are taken over the SAME set. */
|
|
164
|
+
private runQueryBuilder;
|
|
165
|
+
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
166
|
+
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
167
|
+
* count them in the browser. */
|
|
168
|
+
runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
|
|
156
169
|
/** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
|
|
157
170
|
* alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
|
|
158
171
|
* EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var WorkflowRunEntity = class {
|
|
|
33
33
|
searchAttributes;
|
|
34
34
|
priority;
|
|
35
35
|
namespace;
|
|
36
|
+
origin;
|
|
36
37
|
createdAt;
|
|
37
38
|
updatedAt;
|
|
38
39
|
};
|
|
@@ -230,6 +231,31 @@ function durableEntities(options = {}) {
|
|
|
230
231
|
default: "default",
|
|
231
232
|
fieldName: col("namespace")
|
|
232
233
|
},
|
|
234
|
+
// Which library/module registered the workflow this run belongs to (e.g.
|
|
235
|
+
// `@dudousxd/nestjs-catalog-pipeline`), derived at registration time — never caller input.
|
|
236
|
+
//
|
|
237
|
+
// NULLABLE WITH NO DEFAULT, deliberately unlike `namespace` above. `namespace` can honestly
|
|
238
|
+
// default a pre-existing row to `'default'` (that IS the namespace an unscoped engine ran it
|
|
239
|
+
// in); an origin cannot be reconstructed after the fact, so a row written before this column
|
|
240
|
+
// existed must read back as `undefined` = "unknown". Stamping it `'app'`/`'unknown'` would be
|
|
241
|
+
// indistinguishable from a real registration in the dashboard's facet — a wrong origin is worse
|
|
242
|
+
// than an absent one. Two MikroORM specifics reinforce it: a `default` here would NOT be
|
|
243
|
+
// back-filled onto existing rows anyway (the DDL default only applies to new inserts, and a
|
|
244
|
+
// TypeScript field initialiser never runs for rows the ORM did not create), so a default would
|
|
245
|
+
// buy nothing but a lie for future rows.
|
|
246
|
+
//
|
|
247
|
+
// No index, unlike `namespace`. That one is on the HOT path — every poll tick filters
|
|
248
|
+
// `listPendingRuns`/`listDueTimers`/`listIncompleteRuns` by it, hence
|
|
249
|
+
// `durable_workflow_runs_namespace_status_idx`. Nothing filters by `origin` except the
|
|
250
|
+
// dashboard's `listRuns`, where it is ANDed with the `status`/`workflow` predicates that
|
|
251
|
+
// `durable_workflow_runs_status_wake_at_idx` / `..._workflow_status_idx` already bound. If a
|
|
252
|
+
// deployment ever makes origin-filtered listings hot, add the composite index in your own
|
|
253
|
+
// migration rather than here.
|
|
254
|
+
origin: {
|
|
255
|
+
type: "string",
|
|
256
|
+
nullable: true,
|
|
257
|
+
fieldName: col("origin")
|
|
258
|
+
},
|
|
233
259
|
createdAt: {
|
|
234
260
|
type: "Date",
|
|
235
261
|
fieldName: col("createdAt")
|
|
@@ -472,7 +498,8 @@ __name(durableEntities, "durableEntities");
|
|
|
472
498
|
var ENTITIES = durableEntities();
|
|
473
499
|
|
|
474
500
|
// src/mikro-orm-state-store.ts
|
|
475
|
-
import { attributeColumnFor, attributeOperand, normalizeAttributeRows, parseDuration, sqlComparator } from "@dudousxd/nestjs-durable-core";
|
|
501
|
+
import { attributeColumnFor, attributeOperand, mergeRunFacetRows, normalizeAttributeRows, parseDuration, sqlComparator } from "@dudousxd/nestjs-durable-core";
|
|
502
|
+
import { raw } from "@mikro-orm/core";
|
|
476
503
|
|
|
477
504
|
// src/schema.ts
|
|
478
505
|
import { createHash } from "node:crypto";
|
|
@@ -950,11 +977,14 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
950
977
|
});
|
|
951
978
|
return affected === 1;
|
|
952
979
|
}
|
|
953
|
-
|
|
954
|
-
|
|
980
|
+
/** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
|
|
981
|
+
* Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
|
|
982
|
+
* applied by the QueryBuilder path in both callers. */
|
|
983
|
+
runWhere(query) {
|
|
955
984
|
const where = {};
|
|
956
985
|
if (query.workflow) where.workflow = query.workflow;
|
|
957
986
|
if (query.namespace !== void 0) where.namespace = query.namespace;
|
|
987
|
+
if (query.origin !== void 0) where.origin = query.origin;
|
|
958
988
|
if (query.status && query.statuses) {
|
|
959
989
|
where.$and = [
|
|
960
990
|
{
|
|
@@ -973,23 +1003,16 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
973
1003
|
$in: query.statuses
|
|
974
1004
|
};
|
|
975
1005
|
}
|
|
1006
|
+
return where;
|
|
1007
|
+
}
|
|
1008
|
+
async listRuns(query) {
|
|
1009
|
+
const em = this.fork();
|
|
1010
|
+
const where = this.runWhere(query);
|
|
976
1011
|
const orderBy = {
|
|
977
1012
|
createdAt: "desc"
|
|
978
1013
|
};
|
|
979
1014
|
if (query.tag || query.attributes?.length) {
|
|
980
|
-
|
|
981
|
-
const quote = this.idQuote(em);
|
|
982
|
-
const cols = this.attributeColumns(em);
|
|
983
|
-
const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where).orderBy(orderBy);
|
|
984
|
-
if (query.tag) {
|
|
985
|
-
const tagsCol = this.tagsColumn(em);
|
|
986
|
-
const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
|
|
987
|
-
const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`);
|
|
988
|
-
qb.andWhere(`${colExpr} LIKE '${pattern}'`);
|
|
989
|
-
}
|
|
990
|
-
for (const f of query.attributes ?? []) {
|
|
991
|
-
qb.andWhere(this.attributeExistsSql(f, quote, "r", cols));
|
|
992
|
-
}
|
|
1015
|
+
const qb = this.runQueryBuilder(em, query, where).orderBy(orderBy);
|
|
993
1016
|
if (query.limit != null) qb.limit(query.limit);
|
|
994
1017
|
if (query.offset != null) qb.offset(query.offset);
|
|
995
1018
|
const rows2 = await qb.getResultList();
|
|
@@ -1006,6 +1029,45 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
|
|
|
1006
1029
|
});
|
|
1007
1030
|
return rows.map(fromRunEntity);
|
|
1008
1031
|
}
|
|
1032
|
+
/** A QueryBuilder over `durable_workflow_runs` (root alias `r`) carrying `where` plus the two
|
|
1033
|
+
* predicates only raw SQL can express — the `tag` JSON LIKE and one EXISTS per attribute filter.
|
|
1034
|
+
* Shared so a facet count and the page it labels are taken over the SAME set. */
|
|
1035
|
+
runQueryBuilder(em, query, where) {
|
|
1036
|
+
if (this.scopeNamespace !== void 0) where.namespace = this.scopeNamespace;
|
|
1037
|
+
const quote = this.idQuote(em);
|
|
1038
|
+
const cols = this.attributeColumns(em);
|
|
1039
|
+
const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where);
|
|
1040
|
+
if (query.tag) {
|
|
1041
|
+
const tagsCol = this.tagsColumn(em);
|
|
1042
|
+
const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
|
|
1043
|
+
const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`);
|
|
1044
|
+
qb.andWhere(`${colExpr} LIKE '${pattern}'`);
|
|
1045
|
+
}
|
|
1046
|
+
for (const f of query.attributes ?? []) {
|
|
1047
|
+
qb.andWhere(this.attributeExistsSql(f, quote, "r", cols));
|
|
1048
|
+
}
|
|
1049
|
+
return qb;
|
|
1050
|
+
}
|
|
1051
|
+
/** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
|
|
1052
|
+
* console can show whole-set counts next to a bounded page instead of downloading every run to
|
|
1053
|
+
* count them in the browser. */
|
|
1054
|
+
async runFacets(query) {
|
|
1055
|
+
const em = this.fork();
|
|
1056
|
+
const meta = em.getMetadata().get(WorkflowRunEntity);
|
|
1057
|
+
const statusCol = meta.properties.status?.fieldNames?.[0] ?? "status";
|
|
1058
|
+
const originCol = meta.properties.origin?.fieldNames?.[0] ?? "origin";
|
|
1059
|
+
const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([
|
|
1060
|
+
`r.${statusCol}`,
|
|
1061
|
+
`r.${originCol}`
|
|
1062
|
+
]).addSelect(raw("count(*) as count")).groupBy([
|
|
1063
|
+
`r.${statusCol}`,
|
|
1064
|
+
`r.${originCol}`
|
|
1065
|
+
]).execute();
|
|
1066
|
+
return mergeRunFacetRows(rows.map((r) => ({
|
|
1067
|
+
...r,
|
|
1068
|
+
count: Number(r.count)
|
|
1069
|
+
})));
|
|
1070
|
+
}
|
|
1009
1071
|
/** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
|
|
1010
1072
|
* alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
|
|
1011
1073
|
* EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
|
|
@@ -1222,6 +1284,7 @@ function toRunEntity(run) {
|
|
|
1222
1284
|
e.searchAttributes = run.searchAttributes ?? null;
|
|
1223
1285
|
e.priority = run.priority ?? null;
|
|
1224
1286
|
e.namespace = run.namespace ?? "default";
|
|
1287
|
+
e.origin = run.origin ?? null;
|
|
1225
1288
|
e.createdAt = run.createdAt;
|
|
1226
1289
|
e.updatedAt = run.updatedAt;
|
|
1227
1290
|
return e;
|
|
@@ -1245,6 +1308,8 @@ function fromRunEntity(e) {
|
|
|
1245
1308
|
searchAttributes: e.searchAttributes ?? void 0,
|
|
1246
1309
|
priority: e.priority ?? void 0,
|
|
1247
1310
|
namespace: e.namespace,
|
|
1311
|
+
// NULL (a row written before the column existed) surfaces as `undefined` = unknown origin.
|
|
1312
|
+
origin: e.origin ?? void 0,
|
|
1248
1313
|
createdAt: e.createdAt,
|
|
1249
1314
|
updatedAt: e.updatedAt
|
|
1250
1315
|
};
|