@dudousxd/nestjs-durable-store-mikro-orm 0.17.0 → 0.18.1

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.js CHANGED
@@ -84,11 +84,35 @@ var BufferedSignalEntity = class {
84
84
  token;
85
85
  payload;
86
86
  };
87
+ function namespaceFilterCond(args) {
88
+ if (args?.namespace === void 0) return {};
89
+ return {
90
+ namespace: args.namespace
91
+ };
92
+ }
93
+ __name(namespaceFilterCond, "namespaceFilterCond");
87
94
  function durableEntities(options = {}) {
88
95
  const col = columnNamer(options.naming ?? "snake_case");
89
96
  const workflowRuns = new EntitySchema({
90
97
  class: WorkflowRunEntity,
91
98
  tableName: "durable_workflow_runs",
99
+ // Global filter: confines every read to the tenant's namespace when the store is scoped.
100
+ // The `cond` returns `{}` (no-op) when the `namespace` arg is `undefined`, so an unscoped
101
+ // (operator / control-plane) store sees all rows unchanged — existing behaviour is preserved.
102
+ filters: {
103
+ namespace: {
104
+ name: "namespace",
105
+ cond: namespaceFilterCond,
106
+ default: true,
107
+ // Optional args: the store's own forks set the scope via `setFilterParams` (undefined =
108
+ // operator, sees all), but an external consumer that shares this ORM (an operator app reading
109
+ // WorkflowRunEntity through its own EntityManager) never sets it. Without this, MikroORM
110
+ // throws "No arguments provided for filter 'namespace'" on those queries. `args: false` makes
111
+ // the param optional — cond then receives `undefined` and no-ops (sees all), which is exactly
112
+ // the operator view, while a scoped store still passes its namespace through.
113
+ args: false
114
+ }
115
+ },
92
116
  // Indexes mirror the Prisma adapter so a store swap keeps the same plan. The poller hits these
93
117
  // every tick: `(status, wakeAt)` serves both the due-timer scan (status='suspended' AND wakeAt<=now)
94
118
  // and, via its `status` prefix, the pending/incomplete status lookups; `(workflow, status)` serves
@@ -614,25 +638,51 @@ async function alignDurableCollation(orm) {
614
638
  __name(alignDurableCollation, "alignDurableCollation");
615
639
 
616
640
  // src/mikro-orm-state-store.ts
617
- var MikroOrmStateStore = class {
641
+ var MikroOrmStateStore = class _MikroOrmStateStore {
618
642
  static {
619
643
  __name(this, "MikroOrmStateStore");
620
644
  }
621
645
  orm;
622
- constructor(orm) {
646
+ scopeNamespace;
647
+ constructor(orm, opts) {
623
648
  this.orm = orm;
649
+ this.scopeNamespace = opts?.scope?.namespace;
650
+ }
651
+ /**
652
+ * Derive a store confined to a single tenant `scope.namespace`, sharing this store's ORM (no new
653
+ * connection). Turns the operator (unscoped) store into a tenant-boundary view at wiring time —
654
+ * used by the NestJS module's `scopeReads` option, which receives a pre-built store and can't
655
+ * reconstruct it. Pass `{ namespace: undefined }` for the unscoped operator view.
656
+ */
657
+ withScope(scope) {
658
+ return new _MikroOrmStateStore(this.orm, {
659
+ scope
660
+ });
661
+ }
662
+ /**
663
+ * Create a forked EntityManager and activate the `namespace` global filter with the store's
664
+ * scope (if any). All read operations go through this so the tenant boundary is applied
665
+ * uniformly. Write operations (nativeDelete, nativeUpdate, upsert, insertMany) bypass global
666
+ * filters in MikroORM and are unaffected.
667
+ */
668
+ fork() {
669
+ const em = this.orm.em.fork();
670
+ em.setFilterParams("namespace", {
671
+ namespace: this.scopeNamespace
672
+ });
673
+ return em;
624
674
  }
625
675
  async ensureSchema() {
626
676
  await ensureMikroOrmDurableSchema(this.orm);
627
677
  }
628
678
  async createRun(run) {
629
- const em = this.orm.em.fork();
679
+ const em = this.fork();
630
680
  em.create(WorkflowRunEntity, toRunEntity(run));
631
681
  await em.flush();
632
682
  await this.reindexAttributes(run.id, run.searchAttributes);
633
683
  }
634
684
  async updateRun(runId, patch) {
635
- const em = this.orm.em.fork();
685
+ const em = this.fork();
636
686
  const entity = await em.findOneOrFail(WorkflowRunEntity, {
637
687
  id: runId
638
688
  });
@@ -646,7 +696,7 @@ var MikroOrmStateStore = class {
646
696
  /** Rewrite a run's normalized attribute rows: delete the old set, insert the current one. Mirrors
647
697
  * the in-memory store's reindex so the side-table always reflects the run's live searchAttributes. */
648
698
  async reindexAttributes(runId, attributes, forked) {
649
- const em = forked ?? this.orm.em.fork();
699
+ const em = forked ?? this.fork();
650
700
  await em.nativeDelete(RunAttributeEntity, {
651
701
  runId
652
702
  });
@@ -654,14 +704,14 @@ var MikroOrmStateStore = class {
654
704
  if (rows.length) await em.insertMany(RunAttributeEntity, rows);
655
705
  }
656
706
  async getRun(runId) {
657
- const em = this.orm.em.fork();
707
+ const em = this.fork();
658
708
  const entity = await em.findOne(WorkflowRunEntity, {
659
709
  id: runId
660
710
  });
661
711
  return entity ? fromRunEntity(entity) : null;
662
712
  }
663
713
  async deleteRun(runId) {
664
- const em = this.orm.em.fork();
714
+ const em = this.fork();
665
715
  await em.nativeDelete(StepCheckpointEntity, {
666
716
  runId
667
717
  });
@@ -677,7 +727,7 @@ var MikroOrmStateStore = class {
677
727
  }
678
728
  async pruneTerminalRuns(policy, nowMs, limit) {
679
729
  if (policy.statuses.length === 0 || limit <= 0) return 0;
680
- const em = this.orm.em.fork();
730
+ const em = this.fork();
681
731
  const status = {
682
732
  $in: policy.statuses
683
733
  };
@@ -743,7 +793,7 @@ var MikroOrmStateStore = class {
743
793
  return idList.length;
744
794
  }
745
795
  async getCheckpoint(runId, seq) {
746
- const em = this.orm.em.fork();
796
+ const em = this.fork();
747
797
  const entity = await em.findOne(StepCheckpointEntity, {
748
798
  runId,
749
799
  seq
@@ -751,12 +801,12 @@ var MikroOrmStateStore = class {
751
801
  return entity ? fromCheckpointEntity(entity) : null;
752
802
  }
753
803
  async saveCheckpoint(checkpoint) {
754
- const em = this.orm.em.fork();
804
+ const em = this.fork();
755
805
  await em.upsert(StepCheckpointEntity, toCheckpointEntity(checkpoint));
756
806
  await em.flush();
757
807
  }
758
808
  async transaction(work) {
759
- return this.orm.em.fork().transactional(async (em) => work({
809
+ return this.fork().transactional(async (em) => work({
760
810
  raw: em,
761
811
  saveCheckpoint: /* @__PURE__ */ __name(async (cp) => {
762
812
  await em.upsert(StepCheckpointEntity, toCheckpointEntity(cp));
@@ -764,7 +814,7 @@ var MikroOrmStateStore = class {
764
814
  }));
765
815
  }
766
816
  async listIncompleteRuns(namespace) {
767
- const em = this.orm.em.fork();
817
+ const em = this.fork();
768
818
  const rows = await em.find(WorkflowRunEntity, {
769
819
  status: {
770
820
  $in: [
@@ -779,7 +829,7 @@ var MikroOrmStateStore = class {
779
829
  return rows.map(fromRunEntity);
780
830
  }
781
831
  async listPendingRuns(limit, namespace) {
782
- const em = this.orm.em.fork();
832
+ const em = this.fork();
783
833
  const rows = await em.find(WorkflowRunEntity, {
784
834
  status: "pending",
785
835
  ...namespace !== void 0 ? {
@@ -794,7 +844,7 @@ var MikroOrmStateStore = class {
794
844
  return rows.map(fromRunEntity);
795
845
  }
796
846
  async listDueTimers(nowMs, namespace) {
797
- const em = this.orm.em.fork();
847
+ const em = this.fork();
798
848
  const rows = await em.find(WorkflowRunEntity, {
799
849
  status: "suspended",
800
850
  wakeAt: {
@@ -808,7 +858,7 @@ var MikroOrmStateStore = class {
808
858
  return rows.map(fromRunEntity);
809
859
  }
810
860
  async tryLockRun(runId, owner, leaseUntilMs, nowMs) {
811
- const em = this.orm.em.fork();
861
+ const em = this.fork();
812
862
  const affected = await em.nativeUpdate(WorkflowRunEntity, {
813
863
  id: runId,
814
864
  $or: [
@@ -828,7 +878,7 @@ var MikroOrmStateStore = class {
828
878
  return affected === 1;
829
879
  }
830
880
  async releaseRunLock(runId) {
831
- const em = this.orm.em.fork();
881
+ const em = this.fork();
832
882
  await em.nativeUpdate(WorkflowRunEntity, {
833
883
  id: runId
834
884
  }, {
@@ -837,7 +887,7 @@ var MikroOrmStateStore = class {
837
887
  });
838
888
  }
839
889
  async renewRunLock(runId, owner, leaseUntilMs) {
840
- const em = this.orm.em.fork();
890
+ const em = this.fork();
841
891
  const affected = await em.nativeUpdate(WorkflowRunEntity, {
842
892
  id: runId,
843
893
  lockedBy: owner
@@ -847,7 +897,7 @@ var MikroOrmStateStore = class {
847
897
  return affected === 1;
848
898
  }
849
899
  async listRuns(query) {
850
- const em = this.orm.em.fork();
900
+ const em = this.fork();
851
901
  const where = {};
852
902
  if (query.workflow) where.workflow = query.workflow;
853
903
  if (query.namespace !== void 0) where.namespace = query.namespace;
@@ -873,6 +923,7 @@ var MikroOrmStateStore = class {
873
923
  createdAt: "desc"
874
924
  };
875
925
  if (query.tag || query.attributes?.length) {
926
+ if (this.scopeNamespace !== void 0) where.namespace = this.scopeNamespace;
876
927
  const quote = this.idQuote(em);
877
928
  const cols = this.attributeColumns(em);
878
929
  const qb = em.createQueryBuilder(WorkflowRunEntity, "r").where(where).orderBy(orderBy);
@@ -955,7 +1006,7 @@ var MikroOrmStateStore = class {
955
1006
  return (id) => `${ch}${id}${ch}`;
956
1007
  }
957
1008
  async listCheckpoints(runId) {
958
- const em = this.orm.em.fork();
1009
+ const em = this.fork();
959
1010
  const rows = await em.find(StepCheckpointEntity, {
960
1011
  runId
961
1012
  }, {
@@ -966,7 +1017,7 @@ var MikroOrmStateStore = class {
966
1017
  return rows.map(fromCheckpointEntity);
967
1018
  }
968
1019
  async getLatestCheckpointByName(runId, name) {
969
- const em = this.orm.em.fork();
1020
+ const em = this.fork();
970
1021
  const entity = await em.findOne(StepCheckpointEntity, {
971
1022
  runId,
972
1023
  name
@@ -979,7 +1030,7 @@ var MikroOrmStateStore = class {
979
1030
  }
980
1031
  async listCheckpointsByNamePrefix(runId, prefixes) {
981
1032
  if (prefixes.length === 0) return [];
982
- const em = this.orm.em.fork();
1033
+ const em = this.fork();
983
1034
  const rows = await em.find(StepCheckpointEntity, {
984
1035
  runId,
985
1036
  $or: prefixes.map((p) => ({
@@ -995,7 +1046,7 @@ var MikroOrmStateStore = class {
995
1046
  return rows.map(fromCheckpointEntity);
996
1047
  }
997
1048
  async putSignalWaiter(waiter) {
998
- const em = this.orm.em.fork();
1049
+ const em = this.fork();
999
1050
  await em.upsert(SignalWaiterEntity, {
1000
1051
  token: waiter.token,
1001
1052
  runId: waiter.runId,
@@ -1005,7 +1056,7 @@ var MikroOrmStateStore = class {
1005
1056
  await em.flush();
1006
1057
  }
1007
1058
  async takeSignalWaiter(token) {
1008
- const em = this.orm.em.fork();
1059
+ const em = this.fork();
1009
1060
  const entity = await em.findOne(SignalWaiterEntity, {
1010
1061
  token
1011
1062
  });
@@ -1020,7 +1071,7 @@ var MikroOrmStateStore = class {
1020
1071
  return waiter;
1021
1072
  }
1022
1073
  async listSignalWaiters(prefix) {
1023
- const em = this.orm.em.fork();
1074
+ const em = this.fork();
1024
1075
  const rows = await em.find(SignalWaiterEntity, {
1025
1076
  token: {
1026
1077
  $like: `${prefix}%`
@@ -1034,7 +1085,7 @@ var MikroOrmStateStore = class {
1034
1085
  }));
1035
1086
  }
1036
1087
  async bufferSignal(token, payload) {
1037
- const em = this.orm.em.fork();
1088
+ const em = this.fork();
1038
1089
  const e = new BufferedSignalEntity();
1039
1090
  e.token = token;
1040
1091
  e.payload = payload ?? null;
@@ -1042,7 +1093,7 @@ var MikroOrmStateStore = class {
1042
1093
  await em.flush();
1043
1094
  }
1044
1095
  async takeBufferedSignal(token) {
1045
- const em = this.orm.em.fork();
1096
+ const em = this.fork();
1046
1097
  const entity = await em.findOne(BufferedSignalEntity, {
1047
1098
  token
1048
1099
  }, {