@dudousxd/nestjs-durable-store-mikro-orm 0.21.0 → 0.23.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,6 +1,34 @@
1
1
  import * as _mikro_orm_core from '@mikro-orm/core';
2
- import { EntitySchema, MikroORM } from '@mikro-orm/core';
3
- import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, SignalWaiter } from '@dudousxd/nestjs-durable-core';
2
+ import { EntityRepository, EntityRepositoryType, EntitySchema, MikroORM } from '@mikro-orm/core';
3
+ import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepCheckpoint, RunQuery, RunFacetQuery, RunFacetRow, SignalWaiter } from '@dudousxd/nestjs-durable-core';
4
+
5
+ /**
6
+ * Custom repositories for the durable entities, so a host app can `@InjectRepository`/inject a
7
+ * `WorkflowRunRepository` by type instead of repeating `em.find(WorkflowRunEntity, …)` with the
8
+ * entity class as the first argument everywhere.
9
+ *
10
+ * They are wired per-schema inside `durableEntities()` (not with a decorator), because that factory
11
+ * can be called more than once with different column naming and each call must produce schemas that
12
+ * still point at these classes. Each entity also declares `[EntityRepositoryType]`, which is what
13
+ * makes `em.getRepository(WorkflowRunEntity)` resolve to the specific type rather than the base
14
+ * `EntityRepository`.
15
+ *
16
+ * The bodies are intentionally empty: the durable engine's own queries go through
17
+ * `MikroOrmStateStore`, and a durable-owned query added here would be a second, unversioned read
18
+ * path over the same tables. They exist as an injectable, correctly-typed handle for host code.
19
+ */
20
+ declare class WorkflowRunRepository extends EntityRepository<WorkflowRunEntity> {
21
+ }
22
+ declare class StepCheckpointRepository extends EntityRepository<StepCheckpointEntity> {
23
+ }
24
+ declare class RunAttributeRepository extends EntityRepository<RunAttributeEntity> {
25
+ }
26
+ declare class SignalWaiterRepository extends EntityRepository<SignalWaiterEntity> {
27
+ }
28
+ declare class BufferedSignalRepository extends EntityRepository<BufferedSignalEntity> {
29
+ }
30
+ declare class BufferedEventRepository extends EntityRepository<BufferedEventEntity> {
31
+ }
4
32
 
5
33
  /**
6
34
  * How property names map to physical column names. The durable tables are adapter-agnostic: a run
@@ -16,6 +44,7 @@ import { StepKind, RunStatus, StateStore, WorkflowRun, RetentionPolicy, StepChec
16
44
  */
17
45
  type DurableColumnNaming = 'snake_case' | 'preserve' | ((property: string) => string);
18
46
  declare class WorkflowRunEntity {
47
+ [EntityRepositoryType]?: WorkflowRunRepository;
19
48
  id: string;
20
49
  workflow: string;
21
50
  workflowVersion: string;
@@ -37,6 +66,7 @@ declare class WorkflowRunEntity {
37
66
  updatedAt: Date;
38
67
  }
39
68
  declare class StepCheckpointEntity {
69
+ [EntityRepositoryType]?: StepCheckpointRepository;
40
70
  runId: string;
41
71
  seq: number;
42
72
  name: string;
@@ -62,18 +92,21 @@ declare class StepCheckpointEntity {
62
92
  * `numValue`, strings/booleans in `strValue` (booleans as "true"/"false"); see normalizeAttributeRows.
63
93
  */
64
94
  declare class RunAttributeEntity {
95
+ [EntityRepositoryType]?: RunAttributeRepository;
65
96
  runId: string;
66
97
  key: string;
67
98
  strValue?: string | null;
68
99
  numValue?: number | null;
69
100
  }
70
101
  declare class SignalWaiterEntity {
102
+ [EntityRepositoryType]?: SignalWaiterRepository;
71
103
  token: string;
72
104
  runId: string;
73
105
  seq: number;
74
106
  parallelGroup?: string | null;
75
107
  }
76
108
  declare class BufferedSignalEntity {
109
+ [EntityRepositoryType]?: BufferedSignalRepository;
77
110
  id: number;
78
111
  token: string;
79
112
  payload?: unknown;
@@ -82,6 +115,7 @@ declare class BufferedSignalEntity {
82
115
  * caller-minted `id` (a uuid, not autoincrement — `removeBufferedEvent` targets it directly), not by
83
116
  * `runId`: events are name-based pub/sub, not tied to any one run. */
84
117
  declare class BufferedEventEntity {
118
+ [EntityRepositoryType]?: BufferedEventRepository;
85
119
  id: string;
86
120
  name: string;
87
121
  payload?: unknown;
@@ -153,7 +187,19 @@ declare class MikroOrmStateStore implements StateStore {
153
187
  tryLockRun(runId: string, owner: string, leaseUntilMs: number, nowMs: number): Promise<boolean>;
154
188
  releaseRunLock(runId: string): Promise<void>;
155
189
  renewRunLock(runId: string, owner: string, leaseUntilMs: number): Promise<boolean>;
190
+ /** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
191
+ * Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
192
+ * applied by the QueryBuilder path in both callers. */
193
+ private runWhere;
156
194
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
195
+ /** A QueryBuilder over `durable_workflow_runs` (root alias `r`) carrying `where` plus the two
196
+ * predicates only raw SQL can express — the `tag` JSON LIKE and one EXISTS per attribute filter.
197
+ * Shared so a facet count and the page it labels are taken over the SAME set. */
198
+ private runQueryBuilder;
199
+ /** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
200
+ * console can show whole-set counts next to a bounded page instead of downloading every run to
201
+ * count them in the browser. */
202
+ runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
157
203
  /** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
158
204
  * alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
159
205
  * EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
@@ -242,4 +288,4 @@ declare function durableManagedTables(): string[];
242
288
  */
243
289
  declare function ensureMikroOrmDurableSchema(orm: MikroORM): Promise<void>;
244
290
 
245
- export { BufferedEventEntity, BufferedSignalEntity, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, SignalWaiterEntity, StepCheckpointEntity, WorkflowRunEntity, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
291
+ export { BufferedEventEntity, BufferedEventRepository, BufferedSignalEntity, BufferedSignalRepository, type DurableColumnNaming, ENTITIES, MikroOrmStateStore, type MikroOrmStateStoreOptions, RunAttributeEntity, RunAttributeRepository, SignalWaiterEntity, SignalWaiterRepository, StepCheckpointEntity, StepCheckpointRepository, WorkflowRunEntity, WorkflowRunRepository, durableEntities, durableManagedTables, ensureMikroOrmDurableSchema };
package/dist/index.js CHANGED
@@ -2,7 +2,42 @@ var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
4
  // src/entities.ts
5
- import { EntitySchema } from "@mikro-orm/core";
5
+ import { EntityRepositoryType, EntitySchema } from "@mikro-orm/core";
6
+
7
+ // src/repositories.ts
8
+ import { EntityRepository } from "@mikro-orm/core";
9
+ var WorkflowRunRepository = class extends EntityRepository {
10
+ static {
11
+ __name(this, "WorkflowRunRepository");
12
+ }
13
+ };
14
+ var StepCheckpointRepository = class extends EntityRepository {
15
+ static {
16
+ __name(this, "StepCheckpointRepository");
17
+ }
18
+ };
19
+ var RunAttributeRepository = class extends EntityRepository {
20
+ static {
21
+ __name(this, "RunAttributeRepository");
22
+ }
23
+ };
24
+ var SignalWaiterRepository = class extends EntityRepository {
25
+ static {
26
+ __name(this, "SignalWaiterRepository");
27
+ }
28
+ };
29
+ var BufferedSignalRepository = class extends EntityRepository {
30
+ static {
31
+ __name(this, "BufferedSignalRepository");
32
+ }
33
+ };
34
+ var BufferedEventRepository = class extends EntityRepository {
35
+ static {
36
+ __name(this, "BufferedEventRepository");
37
+ }
38
+ };
39
+
40
+ // src/entities.ts
6
41
  function snakeCase(property) {
7
42
  return property.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
8
43
  }
@@ -17,6 +52,7 @@ var WorkflowRunEntity = class {
17
52
  static {
18
53
  __name(this, "WorkflowRunEntity");
19
54
  }
55
+ [EntityRepositoryType];
20
56
  id;
21
57
  workflow;
22
58
  workflowVersion;
@@ -41,6 +77,7 @@ var StepCheckpointEntity = class {
41
77
  static {
42
78
  __name(this, "StepCheckpointEntity");
43
79
  }
80
+ [EntityRepositoryType];
44
81
  runId;
45
82
  seq;
46
83
  name;
@@ -63,6 +100,7 @@ var RunAttributeEntity = class {
63
100
  static {
64
101
  __name(this, "RunAttributeEntity");
65
102
  }
103
+ [EntityRepositoryType];
66
104
  runId;
67
105
  key;
68
106
  strValue;
@@ -72,6 +110,7 @@ var SignalWaiterEntity = class {
72
110
  static {
73
111
  __name(this, "SignalWaiterEntity");
74
112
  }
113
+ [EntityRepositoryType];
75
114
  token;
76
115
  runId;
77
116
  seq;
@@ -81,6 +120,7 @@ var BufferedSignalEntity = class {
81
120
  static {
82
121
  __name(this, "BufferedSignalEntity");
83
122
  }
123
+ [EntityRepositoryType];
84
124
  id;
85
125
  token;
86
126
  payload;
@@ -89,6 +129,7 @@ var BufferedEventEntity = class {
89
129
  static {
90
130
  __name(this, "BufferedEventEntity");
91
131
  }
132
+ [EntityRepositoryType];
92
133
  id;
93
134
  name;
94
135
  payload;
@@ -106,6 +147,9 @@ function durableEntities(options = {}) {
106
147
  const workflowRuns = new EntitySchema({
107
148
  class: WorkflowRunEntity,
108
149
  tableName: "durable_workflow_runs",
150
+ // Wired per schema rather than once on the class: `durableEntities` can be called again with a
151
+ // different `naming`, and each call builds a distinct EntitySchema over the same class.
152
+ repository: /* @__PURE__ */ __name(() => WorkflowRunRepository, "repository"),
109
153
  // Global filter: confines every read to the tenant's namespace when the store is scoped.
110
154
  // The `cond` returns `{}` (no-op) when the `namespace` arg is `undefined`, so an unscoped
111
155
  // (operator / control-plane) store sees all rows unchanged — existing behaviour is preserved.
@@ -269,6 +313,7 @@ function durableEntities(options = {}) {
269
313
  const stepCheckpoints = new EntitySchema({
270
314
  class: StepCheckpointEntity,
271
315
  tableName: "durable_step_checkpoints",
316
+ repository: /* @__PURE__ */ __name(() => StepCheckpointRepository, "repository"),
272
317
  properties: {
273
318
  runId: {
274
319
  type: "string",
@@ -357,6 +402,7 @@ function durableEntities(options = {}) {
357
402
  const runAttributes = new EntitySchema({
358
403
  class: RunAttributeEntity,
359
404
  tableName: "durable_run_attributes",
405
+ repository: /* @__PURE__ */ __name(() => RunAttributeRepository, "repository"),
360
406
  // The search-attribute EXISTS join (see the class docstring) pushes equality/range predicates down
361
407
  // onto `(key, numValue)` / `(key, strValue)`; mirror the Prisma adapter so those scans stay indexed.
362
408
  indexes: [
@@ -402,6 +448,7 @@ function durableEntities(options = {}) {
402
448
  const signalWaiters = new EntitySchema({
403
449
  class: SignalWaiterEntity,
404
450
  tableName: "durable_signal_waiters",
451
+ repository: /* @__PURE__ */ __name(() => SignalWaiterRepository, "repository"),
405
452
  properties: {
406
453
  token: {
407
454
  type: "string",
@@ -429,6 +476,7 @@ function durableEntities(options = {}) {
429
476
  const bufferedSignals = new EntitySchema({
430
477
  class: BufferedSignalEntity,
431
478
  tableName: "durable_buffered_signals",
479
+ repository: /* @__PURE__ */ __name(() => BufferedSignalRepository, "repository"),
432
480
  properties: {
433
481
  id: {
434
482
  type: "integer",
@@ -451,6 +499,7 @@ function durableEntities(options = {}) {
451
499
  const bufferedEvents = new EntitySchema({
452
500
  class: BufferedEventEntity,
453
501
  tableName: "durable_buffered_events",
502
+ repository: /* @__PURE__ */ __name(() => BufferedEventRepository, "repository"),
454
503
  // `(name, publishedAt)` serves both `listBufferedEvents`'s name-scoped scan and its oldest-first
455
504
  // ordering in one index.
456
505
  indexes: [
@@ -498,7 +547,8 @@ __name(durableEntities, "durableEntities");
498
547
  var ENTITIES = durableEntities();
499
548
 
500
549
  // src/mikro-orm-state-store.ts
501
- import { attributeColumnFor, attributeOperand, normalizeAttributeRows, parseDuration, sqlComparator } from "@dudousxd/nestjs-durable-core";
550
+ import { attributeColumnFor, attributeOperand, mergeRunFacetRows, normalizeAttributeRows, parseDuration, sqlComparator } from "@dudousxd/nestjs-durable-core";
551
+ import { raw } from "@mikro-orm/core";
502
552
 
503
553
  // src/schema.ts
504
554
  import { createHash } from "node:crypto";
@@ -976,8 +1026,10 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
976
1026
  });
977
1027
  return affected === 1;
978
1028
  }
979
- async listRuns(query) {
980
- const em = this.fork();
1029
+ /** The `where` every run query shares — {@link listRuns} pages it, {@link runFacets} groups it.
1030
+ * Only the predicates MikroORM can express declaratively; `tag`/`attributes` need raw SQL and are
1031
+ * applied by the QueryBuilder path in both callers. */
1032
+ runWhere(query) {
981
1033
  const where = {};
982
1034
  if (query.workflow) where.workflow = query.workflow;
983
1035
  if (query.namespace !== void 0) where.namespace = query.namespace;
@@ -1000,23 +1052,16 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1000
1052
  $in: query.statuses
1001
1053
  };
1002
1054
  }
1055
+ return where;
1056
+ }
1057
+ async listRuns(query) {
1058
+ const em = this.fork();
1059
+ const where = this.runWhere(query);
1003
1060
  const orderBy = {
1004
1061
  createdAt: "desc"
1005
1062
  };
1006
1063
  if (query.tag || query.attributes?.length) {
1007
- if (this.scopeNamespace !== void 0) where.namespace = this.scopeNamespace;
1008
- const quote = this.idQuote(em);
1009
- const cols = this.attributeColumns(em);
1010
- const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where).orderBy(orderBy);
1011
- if (query.tag) {
1012
- const tagsCol = this.tagsColumn(em);
1013
- const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
1014
- const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`);
1015
- qb.andWhere(`${colExpr} LIKE '${pattern}'`);
1016
- }
1017
- for (const f of query.attributes ?? []) {
1018
- qb.andWhere(this.attributeExistsSql(f, quote, "r", cols));
1019
- }
1064
+ const qb = this.runQueryBuilder(em, query, where).orderBy(orderBy);
1020
1065
  if (query.limit != null) qb.limit(query.limit);
1021
1066
  if (query.offset != null) qb.offset(query.offset);
1022
1067
  const rows2 = await qb.getResultList();
@@ -1033,6 +1078,45 @@ var MikroOrmStateStore = class _MikroOrmStateStore {
1033
1078
  });
1034
1079
  return rows.map(fromRunEntity);
1035
1080
  }
1081
+ /** A QueryBuilder over `durable_workflow_runs` (root alias `r`) carrying `where` plus the two
1082
+ * predicates only raw SQL can express — the `tag` JSON LIKE and one EXISTS per attribute filter.
1083
+ * Shared so a facet count and the page it labels are taken over the SAME set. */
1084
+ runQueryBuilder(em, query, where) {
1085
+ if (this.scopeNamespace !== void 0) where.namespace = this.scopeNamespace;
1086
+ const quote = this.idQuote(em);
1087
+ const cols = this.attributeColumns(em);
1088
+ const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where);
1089
+ if (query.tag) {
1090
+ const tagsCol = this.tagsColumn(em);
1091
+ const pattern = `%"${query.tag.replace(/'/g, "''")}"%`;
1092
+ const colExpr = this.jsonAsText(em, `${quote("r")}.${quote(tagsCol)}`);
1093
+ qb.andWhere(`${colExpr} LIKE '${pattern}'`);
1094
+ }
1095
+ for (const f of query.attributes ?? []) {
1096
+ qb.andWhere(this.attributeExistsSql(f, quote, "r", cols));
1097
+ }
1098
+ return qb;
1099
+ }
1100
+ /** `GROUP BY status, origin` over the same predicates {@link listRuns} pages — one aggregate, so a
1101
+ * console can show whole-set counts next to a bounded page instead of downloading every run to
1102
+ * count them in the browser. */
1103
+ async runFacets(query) {
1104
+ const em = this.fork();
1105
+ const meta = em.getMetadata().get(WorkflowRunEntity);
1106
+ const statusCol = meta.properties.status?.fieldNames?.[0] ?? "status";
1107
+ const originCol = meta.properties.origin?.fieldNames?.[0] ?? "origin";
1108
+ const rows = await this.runQueryBuilder(em, query, this.runWhere(query)).select([
1109
+ `r.${statusCol}`,
1110
+ `r.${originCol}`
1111
+ ]).addSelect(raw("count(*) as count")).groupBy([
1112
+ `r.${statusCol}`,
1113
+ `r.${originCol}`
1114
+ ]).execute();
1115
+ return mergeRunFacetRows(rows.map((r) => ({
1116
+ ...r,
1117
+ count: Number(r.count)
1118
+ })));
1119
+ }
1036
1120
  /** One attribute predicate as a raw EXISTS subquery on the side-table, correlated to the outer run
1037
1121
  * alias. `<>` (ne) also excludes runs where the attribute is absent (missing-key-never-matches):
1038
1122
  * EXISTS already requires a present row, so EXISTS(... <> ...) is exactly ne-with-present. Numeric
@@ -1326,13 +1410,19 @@ function fromCheckpointEntity(e) {
1326
1410
  __name(fromCheckpointEntity, "fromCheckpointEntity");
1327
1411
  export {
1328
1412
  BufferedEventEntity,
1413
+ BufferedEventRepository,
1329
1414
  BufferedSignalEntity,
1415
+ BufferedSignalRepository,
1330
1416
  ENTITIES,
1331
1417
  MikroOrmStateStore,
1332
1418
  RunAttributeEntity,
1419
+ RunAttributeRepository,
1333
1420
  SignalWaiterEntity,
1421
+ SignalWaiterRepository,
1334
1422
  StepCheckpointEntity,
1423
+ StepCheckpointRepository,
1335
1424
  WorkflowRunEntity,
1425
+ WorkflowRunRepository,
1336
1426
  durableEntities,
1337
1427
  durableManagedTables,
1338
1428
  ensureMikroOrmDurableSchema