@dudousxd/nestjs-durable-store-mikro-orm 0.22.0 → 0.24.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 CHANGED
@@ -22,13 +22,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
24
  BufferedEventEntity: () => BufferedEventEntity,
25
+ BufferedEventRepository: () => BufferedEventRepository,
25
26
  BufferedSignalEntity: () => BufferedSignalEntity,
27
+ BufferedSignalRepository: () => BufferedSignalRepository,
26
28
  ENTITIES: () => ENTITIES,
27
29
  MikroOrmStateStore: () => MikroOrmStateStore,
28
30
  RunAttributeEntity: () => RunAttributeEntity,
31
+ RunAttributeRepository: () => RunAttributeRepository,
29
32
  SignalWaiterEntity: () => SignalWaiterEntity,
33
+ SignalWaiterRepository: () => SignalWaiterRepository,
30
34
  StepCheckpointEntity: () => StepCheckpointEntity,
35
+ StepCheckpointRepository: () => StepCheckpointRepository,
31
36
  WorkflowRunEntity: () => WorkflowRunEntity,
37
+ WorkflowRunRepository: () => WorkflowRunRepository,
32
38
  durableEntities: () => durableEntities,
33
39
  durableManagedTables: () => durableManagedTables,
34
40
  ensureMikroOrmDurableSchema: () => ensureMikroOrmDurableSchema
@@ -36,7 +42,42 @@ __export(index_exports, {
36
42
  module.exports = __toCommonJS(index_exports);
37
43
 
38
44
  // src/entities.ts
45
+ var import_core2 = require("@mikro-orm/core");
46
+
47
+ // src/repositories.ts
39
48
  var import_core = require("@mikro-orm/core");
49
+ var WorkflowRunRepository = class extends import_core.EntityRepository {
50
+ static {
51
+ __name(this, "WorkflowRunRepository");
52
+ }
53
+ };
54
+ var StepCheckpointRepository = class extends import_core.EntityRepository {
55
+ static {
56
+ __name(this, "StepCheckpointRepository");
57
+ }
58
+ };
59
+ var RunAttributeRepository = class extends import_core.EntityRepository {
60
+ static {
61
+ __name(this, "RunAttributeRepository");
62
+ }
63
+ };
64
+ var SignalWaiterRepository = class extends import_core.EntityRepository {
65
+ static {
66
+ __name(this, "SignalWaiterRepository");
67
+ }
68
+ };
69
+ var BufferedSignalRepository = class extends import_core.EntityRepository {
70
+ static {
71
+ __name(this, "BufferedSignalRepository");
72
+ }
73
+ };
74
+ var BufferedEventRepository = class extends import_core.EntityRepository {
75
+ static {
76
+ __name(this, "BufferedEventRepository");
77
+ }
78
+ };
79
+
80
+ // src/entities.ts
40
81
  function snakeCase(property) {
41
82
  return property.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
42
83
  }
@@ -51,6 +92,7 @@ var WorkflowRunEntity = class {
51
92
  static {
52
93
  __name(this, "WorkflowRunEntity");
53
94
  }
95
+ [import_core2.EntityRepositoryType];
54
96
  id;
55
97
  workflow;
56
98
  workflowVersion;
@@ -75,6 +117,7 @@ var StepCheckpointEntity = class {
75
117
  static {
76
118
  __name(this, "StepCheckpointEntity");
77
119
  }
120
+ [import_core2.EntityRepositoryType];
78
121
  runId;
79
122
  seq;
80
123
  name;
@@ -97,6 +140,7 @@ var RunAttributeEntity = class {
97
140
  static {
98
141
  __name(this, "RunAttributeEntity");
99
142
  }
143
+ [import_core2.EntityRepositoryType];
100
144
  runId;
101
145
  key;
102
146
  strValue;
@@ -106,6 +150,7 @@ var SignalWaiterEntity = class {
106
150
  static {
107
151
  __name(this, "SignalWaiterEntity");
108
152
  }
153
+ [import_core2.EntityRepositoryType];
109
154
  token;
110
155
  runId;
111
156
  seq;
@@ -115,6 +160,7 @@ var BufferedSignalEntity = class {
115
160
  static {
116
161
  __name(this, "BufferedSignalEntity");
117
162
  }
163
+ [import_core2.EntityRepositoryType];
118
164
  id;
119
165
  token;
120
166
  payload;
@@ -123,6 +169,7 @@ var BufferedEventEntity = class {
123
169
  static {
124
170
  __name(this, "BufferedEventEntity");
125
171
  }
172
+ [import_core2.EntityRepositoryType];
126
173
  id;
127
174
  name;
128
175
  payload;
@@ -137,9 +184,12 @@ function namespaceFilterCond(args) {
137
184
  __name(namespaceFilterCond, "namespaceFilterCond");
138
185
  function durableEntities(options = {}) {
139
186
  const col = columnNamer(options.naming ?? "snake_case");
140
- const workflowRuns = new import_core.EntitySchema({
187
+ const workflowRuns = new import_core2.EntitySchema({
141
188
  class: WorkflowRunEntity,
142
189
  tableName: "durable_workflow_runs",
190
+ // Wired per schema rather than once on the class: `durableEntities` can be called again with a
191
+ // different `naming`, and each call builds a distinct EntitySchema over the same class.
192
+ repository: /* @__PURE__ */ __name(() => WorkflowRunRepository, "repository"),
143
193
  // Global filter: confines every read to the tenant's namespace when the store is scoped.
144
194
  // The `cond` returns `{}` (no-op) when the `namespace` arg is `undefined`, so an unscoped
145
195
  // (operator / control-plane) store sees all rows unchanged — existing behaviour is preserved.
@@ -300,9 +350,10 @@ function durableEntities(options = {}) {
300
350
  }
301
351
  }
302
352
  });
303
- const stepCheckpoints = new import_core.EntitySchema({
353
+ const stepCheckpoints = new import_core2.EntitySchema({
304
354
  class: StepCheckpointEntity,
305
355
  tableName: "durable_step_checkpoints",
356
+ repository: /* @__PURE__ */ __name(() => StepCheckpointRepository, "repository"),
306
357
  properties: {
307
358
  runId: {
308
359
  type: "string",
@@ -388,9 +439,10 @@ function durableEntities(options = {}) {
388
439
  }
389
440
  }
390
441
  });
391
- const runAttributes = new import_core.EntitySchema({
442
+ const runAttributes = new import_core2.EntitySchema({
392
443
  class: RunAttributeEntity,
393
444
  tableName: "durable_run_attributes",
445
+ repository: /* @__PURE__ */ __name(() => RunAttributeRepository, "repository"),
394
446
  // The search-attribute EXISTS join (see the class docstring) pushes equality/range predicates down
395
447
  // onto `(key, numValue)` / `(key, strValue)`; mirror the Prisma adapter so those scans stay indexed.
396
448
  indexes: [
@@ -433,9 +485,10 @@ function durableEntities(options = {}) {
433
485
  }
434
486
  }
435
487
  });
436
- const signalWaiters = new import_core.EntitySchema({
488
+ const signalWaiters = new import_core2.EntitySchema({
437
489
  class: SignalWaiterEntity,
438
490
  tableName: "durable_signal_waiters",
491
+ repository: /* @__PURE__ */ __name(() => SignalWaiterRepository, "repository"),
439
492
  properties: {
440
493
  token: {
441
494
  type: "string",
@@ -460,9 +513,10 @@ function durableEntities(options = {}) {
460
513
  }
461
514
  }
462
515
  });
463
- const bufferedSignals = new import_core.EntitySchema({
516
+ const bufferedSignals = new import_core2.EntitySchema({
464
517
  class: BufferedSignalEntity,
465
518
  tableName: "durable_buffered_signals",
519
+ repository: /* @__PURE__ */ __name(() => BufferedSignalRepository, "repository"),
466
520
  properties: {
467
521
  id: {
468
522
  type: "integer",
@@ -482,9 +536,10 @@ function durableEntities(options = {}) {
482
536
  }
483
537
  }
484
538
  });
485
- const bufferedEvents = new import_core.EntitySchema({
539
+ const bufferedEvents = new import_core2.EntitySchema({
486
540
  class: BufferedEventEntity,
487
541
  tableName: "durable_buffered_events",
542
+ repository: /* @__PURE__ */ __name(() => BufferedEventRepository, "repository"),
488
543
  // `(name, publishedAt)` serves both `listBufferedEvents`'s name-scoped scan and its oldest-first
489
544
  // ordering in one index.
490
545
  indexes: [
@@ -533,7 +588,7 @@ var ENTITIES = durableEntities();
533
588
 
534
589
  // src/mikro-orm-state-store.ts
535
590
  var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
536
- var import_core2 = require("@mikro-orm/core");
591
+ var import_core3 = require("@mikro-orm/core");
537
592
 
538
593
  // src/schema.ts
539
594
  var import_node_crypto = require("crypto");
@@ -1016,20 +1071,47 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1016
1071
  * applied by the QueryBuilder path in both callers. */
1017
1072
  runWhere(query) {
1018
1073
  const where = {};
1074
+ const and = [];
1019
1075
  if (query.workflow) where.workflow = query.workflow;
1076
+ if (query.workflows) and.push({
1077
+ workflow: {
1078
+ $in: query.workflows
1079
+ }
1080
+ });
1020
1081
  if (query.namespace !== void 0) where.namespace = query.namespace;
1082
+ if (query.namespaces) and.push({
1083
+ namespace: {
1084
+ $in: query.namespaces
1085
+ }
1086
+ });
1021
1087
  if (query.origin !== void 0) where.origin = query.origin;
1088
+ if (query.origins) {
1089
+ const named = query.origins.filter((o) => o !== null);
1090
+ const branches = [];
1091
+ if (named.length) branches.push({
1092
+ origin: {
1093
+ $in: named
1094
+ }
1095
+ });
1096
+ if (query.origins.some((o) => o === null)) branches.push({
1097
+ origin: null
1098
+ });
1099
+ and.push(branches.length ? {
1100
+ $or: branches
1101
+ } : {
1102
+ origin: {
1103
+ $in: []
1104
+ }
1105
+ });
1106
+ }
1022
1107
  if (query.status && query.statuses) {
1023
- where.$and = [
1024
- {
1025
- status: query.status
1026
- },
1027
- {
1028
- status: {
1029
- $in: query.statuses
1030
- }
1108
+ and.push({
1109
+ status: query.status
1110
+ }, {
1111
+ status: {
1112
+ $in: query.statuses
1031
1113
  }
1032
- ];
1114
+ });
1033
1115
  } else if (query.status) {
1034
1116
  where.status = query.status;
1035
1117
  } else if (query.statuses) {
@@ -1037,6 +1119,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1037
1119
  $in: query.statuses
1038
1120
  };
1039
1121
  }
1122
+ if (and.length) where.$and = and;
1040
1123
  return where;
1041
1124
  }
1042
1125
  async listRuns(query) {
@@ -1045,7 +1128,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1045
1128
  const orderBy = {
1046
1129
  createdAt: "desc"
1047
1130
  };
1048
- if (query.tag || query.attributes?.length) {
1131
+ if (query.tag || query.tags || query.attributes?.length) {
1049
1132
  const qb = this.runQueryBuilder(em, query, where).orderBy(orderBy);
1050
1133
  if (query.limit != null) qb.limit(query.limit);
1051
1134
  if (query.offset != null) qb.offset(query.offset);
@@ -1071,17 +1154,23 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1071
1154
  const quote = this.idQuote(em);
1072
1155
  const cols = this.attributeColumns(em);
1073
1156
  const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where);
1074
- if (query.tag) {
1075
- const tagsCol = this.tagsColumn(em);
1076
- const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
1077
- const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`);
1078
- qb.andWhere(`${colExpr} LIKE '${pattern}'`);
1157
+ if (query.tag) qb.andWhere(this.tagLikeSql(em, query.tag, quote));
1158
+ if (query.tags) {
1159
+ const branches = query.tags.map((t) => this.tagLikeSql(em, t, quote));
1160
+ qb.andWhere(branches.length ? `(${branches.join(" OR ")})` : "1 = 0");
1079
1161
  }
1080
1162
  for (const f of query.attributes ?? []) {
1081
1163
  qb.andWhere(this.attributeExistsSql(f, quote, "r", cols));
1082
1164
  }
1083
1165
  return qb;
1084
1166
  }
1167
+ /** `tags LIKE '%"etl"%'` — the quoted token, so `etl` doesn't match `etl-foo`. See the note in
1168
+ * {@link listRuns} for why this is raw SQL rather than a MikroORM condition. */
1169
+ tagLikeSql(em, tag, quote) {
1170
+ const pattern = `%"${tag.replace(/'/g, "''")}"%`;
1171
+ const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(this.tagsColumn(em))}`);
1172
+ return `${colExpr} LIKE '${pattern}'`;
1173
+ }
1085
1174
  /** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
1086
1175
  * console can show whole-set counts next to a bounded page instead of downloading every run to
1087
1176
  * count them in the browser. */
@@ -1093,7 +1182,7 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1093
1182
  const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([
1094
1183
  `r.${statusCol}`,
1095
1184
  `r.${originCol}`
1096
- ]).addSelect((0, import_core2.raw)("count(*) as count")).groupBy([
1185
+ ]).addSelect((0, import_core3.raw)("count(*) as count")).groupBy([
1097
1186
  `r.${statusCol}`,
1098
1187
  `r.${originCol}`
1099
1188
  ]).execute();
@@ -1102,20 +1191,50 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1102
1191
  count: Number(r.count)
1103
1192
  })));
1104
1193
  }
1194
+ /** Distinct values of one filter axis over the same predicates {@link listRuns} pages. A run-table
1195
+ * column is a `GROUP BY` like {@link runFacets}, exact over the whole matching set; the tag and
1196
+ * attribute axes live outside the row (json array / side table) and are counted from a bounded
1197
+ * page of runs instead — see {@link RunValueFacetOptions.scan}. */
1198
+ async runValueFacets(axis, query, opts) {
1199
+ if (!(0, import_nestjs_durable_core.axisIsRunColumn)(axis)) {
1200
+ const runs = await this.listRuns({
1201
+ ...query,
1202
+ limit: opts?.scan ?? import_nestjs_durable_core.RUN_VALUE_FACET_SCAN
1203
+ });
1204
+ return (0, import_nestjs_durable_core.runValueFacetsFromRuns)(runs, axis, opts);
1205
+ }
1206
+ const em = this.fork();
1207
+ const meta = em.getMetadata().get(WorkflowRunEntity);
1208
+ const col = meta.properties[axis.field]?.fieldNames?.[0] ?? axis.field;
1209
+ const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([
1210
+ `r.${col}`
1211
+ ]).addSelect((0, import_core3.raw)("count(*) as count")).groupBy([
1212
+ `r.${col}`
1213
+ ]).execute();
1214
+ return (0, import_nestjs_durable_core.mergeRunValueFacetRows)(rows.map((row) => ({
1215
+ value: row[axis.field] ?? null,
1216
+ count: Number(row.count)
1217
+ })), opts);
1218
+ }
1105
1219
  /** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
1106
1220
  * alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
1107
1221
  * EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
1108
- * operands compare the num column, everything else the str column. Identifiers are quoted per
1109
- * driver and operands are inlined as literals (string operands are escaped by doubling quotes). */
1222
+ * operands compare the num column, everything else the str column. `in` contributes one comparison
1223
+ * per member, ORed inside the single EXISTS — one subquery either way, and an `in` over no values
1224
+ * yields no comparison and so matches nothing. Identifiers are quoted per driver and operands are
1225
+ * inlined as literals (string operands are escaped by doubling quotes). */
1110
1226
  attributeExistsSql(f, quote, alias, cols) {
1111
- const col = (0, import_nestjs_durable_core.attributeColumnFor)(f) === "numValue" ? cols.numValue : cols.strValue;
1112
- const cmp = (0, import_nestjs_durable_core.sqlComparator)(f.op);
1113
- const operand = (0, import_nestjs_durable_core.attributeOperand)(f);
1114
- const literal = typeof operand === "number" ? String(operand) : `'${String(operand).replace(/'/g, "''")}'`;
1227
+ const comparisons = (0, import_nestjs_durable_core.attributePredicateOperands)(f).map(({ column, comparator, operand }) => {
1228
+ const col = column === "numValue" ? cols.numValue : cols.strValue;
1229
+ const literal = typeof operand === "number" ? String(operand) : `'${String(operand).replace(/'/g, "''")}'`;
1230
+ return `${quote("a")}.${quote(col)} ${comparator} ${literal}`;
1231
+ });
1232
+ if (comparisons.length === 0) return "1 = 0";
1115
1233
  const a = quote(cols.table);
1116
1234
  const sub = quote("a");
1117
1235
  const outerId = `${quote(alias)}.${quote(cols.runPk)}`;
1118
- return `EXISTS (SELECT 1 FROM ${a} ${sub} WHERE ${sub}.${quote(cols.runId)} = ${outerId} AND ${sub}.${quote(cols.key)} = '${f.key.replace(/'/g, "''")}' AND ${sub}.${quote(col)} ${cmp} ${literal})`;
1236
+ const match = comparisons.length === 1 ? comparisons[0] : `(${comparisons.join(" OR ")})`;
1237
+ return `EXISTS (SELECT 1 FROM ${a} ${sub} WHERE ${sub}.${quote(cols.runId)} = ${outerId} AND ${sub}.${quote(cols.key)} = '${f.key.replace(/'/g, "''")}' AND ${match})`;
1119
1238
  }
1120
1239
  /** Resolve the side-table's actual column names from MikroORM metadata, so the raw EXISTS matches
1121
1240
  * the active naming strategy (underscore vs camelCase) rather than guessing. The run PK column is
@@ -1396,13 +1515,19 @@ __name(fromCheckpointEntity, "fromCheckpointEntity");
1396
1515
  // Annotate the CommonJS export names for ESM import in node:
1397
1516
  0 && (module.exports = {
1398
1517
  BufferedEventEntity,
1518
+ BufferedEventRepository,
1399
1519
  BufferedSignalEntity,
1520
+ BufferedSignalRepository,
1400
1521
  ENTITIES,
1401
1522
  MikroOrmStateStore,
1402
1523
  RunAttributeEntity,
1524
+ RunAttributeRepository,
1403
1525
  SignalWaiterEntity,
1526
+ SignalWaiterRepository,
1404
1527
  StepCheckpointEntity,
1528
+ StepCheckpointRepository,
1405
1529
  WorkflowRunEntity,
1530
+ WorkflowRunRepository,
1406
1531
  durableEntities,
1407
1532
  durableManagedTables,
1408
1533
  ensureMikroOrmDurableSchema