@dudousxd/nestjs-durable-core 0.65.0 → 0.67.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
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  CURRENT_PROTOCOL_VERSION: () => CURRENT_PROTOCOL_VERSION,
27
27
  CodecStateStore: () => CodecStateStore,
28
28
  ContinueAsNew: () => ContinueAsNew,
29
+ DEFAULT_PARTITION: () => DEFAULT_PARTITION,
29
30
  DURABLE_OPTIONS: () => DURABLE_OPTIONS,
30
31
  DURABLE_OPTIONS_CANONICAL: () => DURABLE_OPTIONS_CANONICAL,
31
32
  DURABLE_STEP_CONFIG: () => DURABLE_STEP_CONFIG,
@@ -56,6 +57,7 @@ __export(index_exports, {
56
57
  TERMINAL_RUN_STATUSES: () => TERMINAL_RUN_STATUSES,
57
58
  TRANSPORT: () => TRANSPORT,
58
59
  TRANSPORT_CANONICAL: () => TRANSPORT_CANONICAL,
60
+ VERSION_UNDECLARED_TAG: () => VERSION_UNDECLARED_TAG,
59
61
  WORKFLOW_NAME_KEY: () => WORKFLOW_NAME_KEY,
60
62
  WorkflowBlocked: () => WorkflowBlocked,
61
63
  WorkflowEngine: () => WorkflowEngine,
@@ -67,12 +69,14 @@ __export(index_exports, {
67
69
  attributeOperand: () => attributeOperand,
68
70
  bindWorkflowClass: () => bindWorkflowClass,
69
71
  breakpointToken: () => breakpointToken,
72
+ buildWorkflowDirectory: () => buildWorkflowDirectory,
70
73
  canRoute: () => canRoute,
71
74
  capableWorkers: () => capableWorkers,
72
75
  classifyWaiterToken: () => classifyWaiterToken,
73
76
  clearDurableExecutionWrappers: () => clearDurableExecutionWrappers,
74
77
  collectMetrics: () => collectMetrics,
75
78
  controlPlaneDescriptor: () => controlPlaneDescriptor,
79
+ coveredTokens: () => coveredTokens,
76
80
  createStepLogger: () => createStepLogger,
77
81
  currentStep: () => currentStep,
78
82
  currentWorkflowCtx: () => currentWorkflowCtx,
@@ -85,6 +89,7 @@ __export(index_exports, {
85
89
  eventNameOf: () => eventNameOf,
86
90
  eventPrefix: () => eventPrefix,
87
91
  eventToken: () => eventToken,
92
+ facetOrigin: () => facetOrigin,
88
93
  hasDurableExecutionWrappers: () => hasDurableExecutionWrappers,
89
94
  heartbeatStatus: () => heartbeatStatus,
90
95
  indexWaitersByRun: () => indexWaitersByRun,
@@ -94,11 +99,14 @@ __export(index_exports, {
94
99
  isWorkflowControlFlowSignal: () => isWorkflowControlFlowSignal,
95
100
  log: () => log,
96
101
  matchesAttributes: () => matchesAttributes,
102
+ mergeRunFacetRows: () => mergeRunFacetRows,
97
103
  missingCapabilities: () => missingCapabilities,
98
104
  negotiate: () => negotiate,
99
105
  normalizeAttributeRows: () => normalizeAttributeRows,
100
106
  normalizeDescriptor: () => normalizeDescriptor,
107
+ observedAnnouncements: () => observedAnnouncements,
101
108
  parseDuration: () => parseDuration,
109
+ partitionOfToken: () => partitionOfToken,
102
110
  planDispatch: () => planDispatch,
103
111
  prevCronFireMs: () => prevCronFireMs,
104
112
  readSearchAttributes: () => readSearchAttributes,
@@ -1310,6 +1318,7 @@ function aggregateAnnouncements(descriptors) {
1310
1318
  const first = announcements[0];
1311
1319
  if (!first) continue;
1312
1320
  out.push({
1321
+ evidence: "declared",
1313
1322
  key,
1314
1323
  name: first.name,
1315
1324
  ...first.version !== void 0 ? {
@@ -1330,6 +1339,137 @@ function aggregateAnnouncements(descriptors) {
1330
1339
  return out.sort((a, b) => a.key.localeCompare(b.key));
1331
1340
  }
1332
1341
  __name(aggregateAnnouncements, "aggregateAnnouncements");
1342
+ function observedAnnouncements(heartbeats, covered) {
1343
+ const byToken = /* @__PURE__ */ new Map();
1344
+ for (const beat of heartbeats) {
1345
+ if (!beat.group || covered.has(beat.group)) continue;
1346
+ const instances = byToken.get(beat.group);
1347
+ if (instances) instances.add(beat.instanceId);
1348
+ else byToken.set(beat.group, /* @__PURE__ */ new Set([
1349
+ beat.instanceId
1350
+ ]));
1351
+ }
1352
+ return [
1353
+ ...byToken.entries()
1354
+ ].map(([group, instances]) => ({
1355
+ evidence: "observed",
1356
+ key: group,
1357
+ name: group,
1358
+ groups: [
1359
+ group
1360
+ ],
1361
+ origins: [],
1362
+ requires: [],
1363
+ runtimes: [],
1364
+ instances: [
1365
+ ...instances
1366
+ ].sort(),
1367
+ disagreements: []
1368
+ })).sort((a, b) => a.key.localeCompare(b.key));
1369
+ }
1370
+ __name(observedAnnouncements, "observedAnnouncements");
1371
+ function coveredTokens(announced, descriptors, sanitize) {
1372
+ const tokens = /* @__PURE__ */ new Set();
1373
+ for (const entry of announced) {
1374
+ tokens.add(sanitize(entry.name));
1375
+ for (const group of entry.groups) tokens.add(group);
1376
+ }
1377
+ for (const raw of descriptors) {
1378
+ const d = normalizeDescriptor(raw);
1379
+ for (const step of d.steps) {
1380
+ const token = sanitize(step);
1381
+ tokens.add(token);
1382
+ if (d.partition) tokens.add(`${token}@${d.partition}`);
1383
+ }
1384
+ }
1385
+ return tokens;
1386
+ }
1387
+ __name(coveredTokens, "coveredTokens");
1388
+
1389
+ // src/handshake/directory.ts
1390
+ var DEFAULT_PARTITION = "default";
1391
+ function partitionOfToken(token) {
1392
+ const at = token.lastIndexOf("@");
1393
+ return at === -1 ? DEFAULT_PARTITION : token.slice(at + 1);
1394
+ }
1395
+ __name(partitionOfToken, "partitionOfToken");
1396
+ function buildWorkflowDirectory(input) {
1397
+ const { descriptors, heartbeats, namespace, supported, sanitize } = input;
1398
+ if (!supported) {
1399
+ return {
1400
+ supported: false,
1401
+ workflows: [],
1402
+ otherPartitions: [],
1403
+ detail: "No transport in this process can read what the fleet advertises, so nothing here was asked and this list is empty for that reason alone. Workers may well be running. A workflow can still be called by name."
1404
+ };
1405
+ }
1406
+ const mine = namespace === void 0 ? void 0 : namespace;
1407
+ const ownDescriptors = mine === void 0 ? descriptors : descriptors.filter((d) => (d.namespace ?? DEFAULT_PARTITION) === mine);
1408
+ const ownBeats = [];
1409
+ const foreign = /* @__PURE__ */ new Map();
1410
+ for (const beat of heartbeats) {
1411
+ if (!beat.group) continue;
1412
+ const partition = partitionOfToken(beat.group);
1413
+ if (mine === void 0 || partition === mine) {
1414
+ ownBeats.push(beat);
1415
+ continue;
1416
+ }
1417
+ const seen = foreign.get(partition);
1418
+ if (seen) {
1419
+ seen.groups.add(beat.group);
1420
+ seen.instances.add(beat.instanceId);
1421
+ } else {
1422
+ foreign.set(partition, {
1423
+ groups: /* @__PURE__ */ new Set([
1424
+ beat.group
1425
+ ]),
1426
+ instances: /* @__PURE__ */ new Set([
1427
+ beat.instanceId
1428
+ ])
1429
+ });
1430
+ }
1431
+ }
1432
+ const declared = aggregateAnnouncements(ownDescriptors);
1433
+ const observed = observedAnnouncements(ownBeats, coveredTokens(declared, ownDescriptors, sanitize));
1434
+ const workflows = [
1435
+ ...declared,
1436
+ ...observed
1437
+ ].sort((a, b) => a.key.localeCompare(b.key));
1438
+ const otherPartitions = [
1439
+ ...foreign.entries()
1440
+ ].map(([partition, seen]) => ({
1441
+ partition,
1442
+ groups: [
1443
+ ...seen.groups
1444
+ ].sort(),
1445
+ instances: [
1446
+ ...seen.instances
1447
+ ].sort()
1448
+ })).sort((a, b) => a.partition.localeCompare(b.partition));
1449
+ return {
1450
+ supported: true,
1451
+ workflows,
1452
+ otherPartitions,
1453
+ detail: describeDirectory(workflows, otherPartitions, mine)
1454
+ };
1455
+ }
1456
+ __name(buildWorkflowDirectory, "buildWorkflowDirectory");
1457
+ function summarize(names, limit = 3) {
1458
+ if (names.length <= limit) return names.join(", ");
1459
+ return `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
1460
+ }
1461
+ __name(summarize, "summarize");
1462
+ function describeDirectory(workflows, otherPartitions, namespace) {
1463
+ const here = namespace === void 0 ? "this deployment" : `partition "${namespace}"`;
1464
+ const elsewhere = otherPartitions.length === 0 ? "" : ` Live workers were seen on ${otherPartitions.map((p) => `"${p.partition}" (${summarize(p.groups)})`).join(", ")}, which ${here} does not route to \u2014 a worker on the wrong partition looks exactly like no worker at all, so it is named here rather than left out.`;
1465
+ if (workflows.length === 0) {
1466
+ return `The fleet was asked just now and nothing is live in ${here}, so this list is empty because the deployment is empty \u2014 not because the question could not be put.${elsewhere}`;
1467
+ }
1468
+ const observed = workflows.filter((w) => w.evidence === "observed").length;
1469
+ const declaredNote = observed === 0 ? "" : ` ${observed} of them ${observed === 1 ? "is" : "are"} known only from a live queue: the worker serving ${observed === 1 ? "it" : "them"} publishes no description, so ${observed === 1 ? "its" : "their"} version, origin and runtime are genuinely unknown and a version pinned against ${observed === 1 ? "it" : "them"} cannot be checked.`;
1470
+ return `Every workflow a live worker in ${here} can execute, read just now. A worker that stops beating drops off this list within about half a minute.${declaredNote}${elsewhere}`;
1471
+ }
1472
+ __name(describeDirectory, "describeDirectory");
1333
1473
 
1334
1474
  // src/handshake/negotiate.ts
1335
1475
  var rangesOverlap = /* @__PURE__ */ __name((a, b) => Math.max(a[0], b[0]) <= Math.min(a[1], b[1]), "rangesOverlap");
@@ -1881,6 +2021,36 @@ var TransportPool = class {
1881
2021
  ...byInstance.values()
1882
2022
  ];
1883
2023
  }
2024
+ /**
2025
+ * Every live worker heartbeat in the deployment, merged across every transport that can report
2026
+ * them and de-duplicated by `(group, instanceId)` — the same worker is one beat per token, and two
2027
+ * transports scanning the same keyspace must not double-count it.
2028
+ *
2029
+ * Returns `[]` when no transport implements {@link Transport.readAllWorkerHeartbeats}, which is
2030
+ * indistinguishable from "nothing is beating" on its own — {@link introspectsFleet} is what tells
2031
+ * those apart, and the directory reads it rather than guessing from an empty array.
2032
+ */
2033
+ async readAllWorkerHeartbeats() {
2034
+ const seen = /* @__PURE__ */ new Map();
2035
+ for (const { transport } of this.transports) {
2036
+ if (!transport.readAllWorkerHeartbeats) continue;
2037
+ for (const beat of await transport.readAllWorkerHeartbeats()) {
2038
+ const key = `${beat.group}\0${beat.instanceId}`;
2039
+ if (!seen.has(key)) seen.set(key, beat);
2040
+ }
2041
+ }
2042
+ return [
2043
+ ...seen.values()
2044
+ ];
2045
+ }
2046
+ /**
2047
+ * Can ANY transport here introspect the fleet — descriptors or heartbeats? This is the difference
2048
+ * between "the fleet is empty" and "nobody asked", and it is a property of the POOL rather than of
2049
+ * a read's result, so it stays answerable when both reads legitimately come back empty.
2050
+ */
2051
+ get introspectsFleet() {
2052
+ return this.transports.some(({ transport }) => transport.readAllWorkerDescriptors || transport.readAllWorkerHeartbeats);
2053
+ }
1884
2054
  /** Distinct worker groups with a live heartbeat, merged across every transport that can report it. */
1885
2055
  async listWorkerGroups() {
1886
2056
  const groups = /* @__PURE__ */ new Set();
@@ -2645,6 +2815,7 @@ function mergeTags(staticTags, runTags) {
2645
2815
  __name(mergeTags, "mergeTags");
2646
2816
  var BREAKPOINT = "breakpoint";
2647
2817
  var isBreakpoint = /* @__PURE__ */ __name((cp) => cp.status === "pending" && cp.name.startsWith(BREAKPOINT), "isBreakpoint");
2818
+ var VERSION_UNDECLARED_TAG = "version:undeclared";
2648
2819
  var versionKey = /* @__PURE__ */ __name((name, version) => `${name}@${version}`, "versionKey");
2649
2820
  function stepGroup(step, runNamespace) {
2650
2821
  return tenantGroup(sanitizeQueueToken(step.name), step.partition ?? runNamespace);
@@ -3136,7 +3307,9 @@ var WorkflowEngine = class {
3136
3307
  createdAt: /* @__PURE__ */ new Date(),
3137
3308
  updatedAt: /* @__PURE__ */ new Date()
3138
3309
  };
3139
- registered = await this.resolveRemoteByConvention(conventionRun);
3310
+ registered = await this.resolveRemoteByConvention(conventionRun, {
3311
+ askFleetForVersion: true
3312
+ });
3140
3313
  }
3141
3314
  if (!registered) {
3142
3315
  throw new Error(pinned === void 0 ? `workflow ${name} is not registered` : `workflow ${versionKey(name, pinned)} is not registered \u2014 a pinned start never falls back to the newest version (registered: ${this.registeredVersions(name)})`);
@@ -3152,10 +3325,16 @@ var WorkflowEngine = class {
3152
3325
  };
3153
3326
  }
3154
3327
  const now = /* @__PURE__ */ new Date();
3155
- const tags = mergeTags(registered.tags, registered.singleton ? [
3328
+ const stampedTags = [
3156
3329
  ...opts?.tags ?? [],
3157
- this.singletons.tag(registered.singleton, input)
3158
- ] : opts?.tags);
3330
+ ...registered.singleton ? [
3331
+ this.singletons.tag(registered.singleton, input)
3332
+ ] : [],
3333
+ ...registered.versionDeclared === false ? [
3334
+ VERSION_UNDECLARED_TAG
3335
+ ] : []
3336
+ ];
3337
+ const tags = mergeTags(registered.tags, stampedTags);
3159
3338
  if (registered.singleton) {
3160
3339
  await this.singletons.assertCapacity(name, registered.singleton, input);
3161
3340
  }
@@ -3265,14 +3444,15 @@ var WorkflowEngine = class {
3265
3444
  * Recomputed per call (never memoized into `this.workflows`) so it stays correct if group
3266
3445
  * liveness changes between calls, exactly as {@link synthesizeRemoteChild} does.
3267
3446
  */
3268
- async resolveRemoteByConvention(run) {
3447
+ async resolveRemoteByConvention(run, opts = {}) {
3269
3448
  const group = tenantGroup(sanitizeQueueToken(run.workflow), run.namespace);
3270
3449
  const liveTransport = await this.pool.transportWithLiveGroup(group);
3271
3450
  if (!liveTransport) return void 0;
3272
3451
  const executor = new RemoteWorkflowExecutor(liveTransport, run.workflow, run.namespace);
3452
+ const declared = opts.askFleetForVersion ? await this.declaredVersion(liveTransport, group, run.workflow) : void 0;
3273
3453
  return {
3274
3454
  name: run.workflow,
3275
- version: run.workflowVersion,
3455
+ version: declared ?? run.workflowVersion,
3276
3456
  fn: /* @__PURE__ */ __name(() => {
3277
3457
  throw new Error(`remote workflow ${run.workflow} has no local body`);
3278
3458
  }, "fn"),
@@ -3280,10 +3460,46 @@ var WorkflowEngine = class {
3280
3460
  remote: {
3281
3461
  group,
3282
3462
  executor
3283
- }
3463
+ },
3464
+ ...opts.askFleetForVersion ? {
3465
+ versionDeclared: declared !== void 0
3466
+ } : {}
3284
3467
  };
3285
3468
  }
3286
3469
  /**
3470
+ * The version the LIVE fleet declares for `workflow` on `group`, or `undefined` when it declares
3471
+ * none — which is a fact about the fleet, not a failure to read it.
3472
+ *
3473
+ * The read is the descriptor advertisement, scoped to the one group being resolved: the workers on
3474
+ * that queue, their `registrations`, the entry whose `name` matches. That is where a version can
3475
+ * come from at all — a heartbeat has no room for one, so a worker on an SDK too old to publish a
3476
+ * descriptor declares nothing and the caller is told so rather than handed a default.
3477
+ *
3478
+ * `undefined` when the announcers DISAGREE, and deliberately: two live workers claiming different
3479
+ * versions of one name is a deployment in the middle of something, and picking either would stamp
3480
+ * a run with a version half the fleet does not serve. Undeclared is the honest reading of a
3481
+ * contradiction, and it is the reading that refuses to let a pin pass on a coin flip.
3482
+ */
3483
+ async declaredVersion(transport, group, workflow) {
3484
+ if (!transport.readWorkerDescriptors) return void 0;
3485
+ let descriptors;
3486
+ try {
3487
+ descriptors = await transport.readWorkerDescriptors(group);
3488
+ } catch {
3489
+ return void 0;
3490
+ }
3491
+ const versions = /* @__PURE__ */ new Set();
3492
+ for (const announced of aggregateAnnouncements(descriptors)) {
3493
+ if (announced.name === workflow && announced.version !== void 0) {
3494
+ versions.add(announced.version);
3495
+ }
3496
+ }
3497
+ const only = [
3498
+ ...versions
3499
+ ];
3500
+ return only.length === 1 ? only[0] : void 0;
3501
+ }
3502
+ /**
3287
3503
  * Walk the parent chain of `childRunId` to the nearest ancestor registered as REMOTE, returning its
3288
3504
  * registration (or `undefined` if there is none). The parent of an awaited child is found via its
3289
3505
  * live `child:<childId>` signal waiter — present for as long as the parent is suspended awaiting the
@@ -4238,9 +4454,30 @@ var WorkflowEngine = class {
4238
4454
  * Workflows only, and deliberately: see the "steps are out of scope" note in `handshake/announced`.
4239
4455
  */
4240
4456
  async announcedWorkflows() {
4241
- const descriptors = await this.pool.readAllWorkerDescriptors();
4242
- const mine = this.namespace === void 0 ? descriptors : descriptors.filter((d) => (d.namespace ?? "default") === this.namespace);
4243
- return aggregateAnnouncements(mine);
4457
+ return (await this.workflowDirectory()).workflows;
4458
+ }
4459
+ /**
4460
+ * {@link announcedWorkflows}, plus the thing a list of workflows can never say: why it is the
4461
+ * length it is. See {@link WorkflowDirectory} for the three states an empty array used to collapse
4462
+ * into one — "nobody asked", "nothing is live", and the partition mismatch that reads exactly like
4463
+ * the second from the caller's side.
4464
+ *
4465
+ * Two live reads, both on demand and neither retained: the descriptor advertisement (what workers
4466
+ * SAY they serve) and the heartbeat keyspace (what is demonstrably consuming a queue right now).
4467
+ * The second is why a worker on an SDK too old to publish a descriptor appears here at all — it
4468
+ * beats and never describes itself, and reading only descriptors reported such a fleet as empty
4469
+ * while `resolveRemoteByConvention`, a few lines away in this same class, was routing calls to it
4470
+ * off those very keys. Two answers out of one Redis, disagreeing. Now they agree, and the entry
4471
+ * says which of the two kinds of evidence it rests on rather than flattening them together.
4472
+ */
4473
+ async workflowDirectory() {
4474
+ return buildWorkflowDirectory({
4475
+ descriptors: await this.pool.readAllWorkerDescriptors(),
4476
+ heartbeats: await this.pool.readAllWorkerHeartbeats(),
4477
+ namespace: this.namespace,
4478
+ supported: this.pool.introspectsFleet,
4479
+ sanitize: sanitizeQueueToken
4480
+ });
4244
4481
  }
4245
4482
  /**
4246
4483
  * The routing groups of every in-flight run's still-`pending` remote step — the queues where work is
@@ -5871,6 +6108,33 @@ var TERMINAL_RUN_STATUSES = [
5871
6108
  "dead"
5872
6109
  ];
5873
6110
 
6111
+ // src/run-facets.ts
6112
+ function facetOrigin(origin) {
6113
+ return origin?.trim() ? origin : null;
6114
+ }
6115
+ __name(facetOrigin, "facetOrigin");
6116
+ function mergeRunFacetRows(rows) {
6117
+ const cells = /* @__PURE__ */ new Map();
6118
+ for (const row of rows) {
6119
+ const origin = facetOrigin(row.origin);
6120
+ const key = JSON.stringify([
6121
+ row.status,
6122
+ origin
6123
+ ]);
6124
+ const cell = cells.get(key);
6125
+ if (cell) cell.count += row.count;
6126
+ else cells.set(key, {
6127
+ status: row.status,
6128
+ origin,
6129
+ count: row.count
6130
+ });
6131
+ }
6132
+ return [
6133
+ ...cells.values()
6134
+ ];
6135
+ }
6136
+ __name(mergeRunFacetRows, "mergeRunFacetRows");
6137
+
5874
6138
  // src/run-gateway.ts
5875
6139
  var RunGateway = class {
5876
6140
  static {
@@ -5885,9 +6149,18 @@ var CodecStateStore = class {
5885
6149
  }
5886
6150
  inner;
5887
6151
  codec;
6152
+ /**
6153
+ * Forwarded verbatim — a facet cell counts `status`/`origin`, neither of which the codec touches
6154
+ * (it encodes payloads: input/output/error). Bound as a PROPERTY, and only when the inner store
6155
+ * actually aggregates, so `store.runFacets?.()` still reports absence through the wrapper instead
6156
+ * of resolving to a silently wrong empty result.
6157
+ */
6158
+ runFacets;
5888
6159
  constructor(inner, codec) {
5889
6160
  this.inner = inner;
5890
6161
  this.codec = codec;
6162
+ const facets = inner.runFacets;
6163
+ if (facets) this.runFacets = (query) => facets.call(inner, query);
5891
6164
  }
5892
6165
  enc(v) {
5893
6166
  return v === void 0 ? void 0 : this.codec.encode(v);
@@ -6585,7 +6858,9 @@ var InMemoryStateStore = class {
6585
6858
  }
6586
6859
  return out;
6587
6860
  }
6588
- async listRuns(query) {
6861
+ /** Every run satisfying `query`'s predicates, unsorted and unpaged — the shared body of
6862
+ * {@link listRuns} (which then sorts + pages it) and {@link runFacets} (which groups it). */
6863
+ matching(query) {
6589
6864
  let runs = [
6590
6865
  ...this.runs.values()
6591
6866
  ];
@@ -6594,7 +6869,8 @@ var InMemoryStateStore = class {
6594
6869
  if (query.statuses) runs = runs.filter((r) => query.statuses?.includes(r.status));
6595
6870
  if (query.tag) runs = runs.filter((r) => r.tags?.includes(query.tag));
6596
6871
  if (query.namespace !== void 0) runs = runs.filter((r) => r.namespace === query.namespace);
6597
- if (query.origin !== void 0) runs = runs.filter((r) => r.origin === query.origin);
6872
+ if (query.origin === null) runs = runs.filter((r) => r.origin == null);
6873
+ else if (query.origin !== void 0) runs = runs.filter((r) => r.origin === query.origin);
6598
6874
  if (query.attributes?.length) {
6599
6875
  let candidates = null;
6600
6876
  for (const f of query.attributes) {
@@ -6613,6 +6889,10 @@ var InMemoryStateStore = class {
6613
6889
  this.lastAttributeCandidates = ids.size;
6614
6890
  runs = runs.filter((r) => ids.has(r.id));
6615
6891
  }
6892
+ return runs;
6893
+ }
6894
+ async listRuns(query) {
6895
+ const runs = this.matching(query);
6616
6896
  runs.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
6617
6897
  const offset = query.offset ?? 0;
6618
6898
  const limit = query.limit ?? runs.length;
@@ -6620,6 +6900,13 @@ var InMemoryStateStore = class {
6620
6900
  ...r
6621
6901
  }));
6622
6902
  }
6903
+ async runFacets(query) {
6904
+ return mergeRunFacetRows(this.matching(query).map((run) => ({
6905
+ status: run.status,
6906
+ origin: run.origin,
6907
+ count: 1
6908
+ })));
6909
+ }
6623
6910
  async listCheckpoints(runId) {
6624
6911
  const rows = [
6625
6912
  ...this.checkpoints.values()
@@ -6702,6 +6989,7 @@ var InMemoryTransport = class {
6702
6989
  CURRENT_PROTOCOL_VERSION,
6703
6990
  CodecStateStore,
6704
6991
  ContinueAsNew,
6992
+ DEFAULT_PARTITION,
6705
6993
  DURABLE_OPTIONS,
6706
6994
  DURABLE_OPTIONS_CANONICAL,
6707
6995
  DURABLE_STEP_CONFIG,
@@ -6732,6 +7020,7 @@ var InMemoryTransport = class {
6732
7020
  TERMINAL_RUN_STATUSES,
6733
7021
  TRANSPORT,
6734
7022
  TRANSPORT_CANONICAL,
7023
+ VERSION_UNDECLARED_TAG,
6735
7024
  WORKFLOW_NAME_KEY,
6736
7025
  WorkflowBlocked,
6737
7026
  WorkflowEngine,
@@ -6743,12 +7032,14 @@ var InMemoryTransport = class {
6743
7032
  attributeOperand,
6744
7033
  bindWorkflowClass,
6745
7034
  breakpointToken,
7035
+ buildWorkflowDirectory,
6746
7036
  canRoute,
6747
7037
  capableWorkers,
6748
7038
  classifyWaiterToken,
6749
7039
  clearDurableExecutionWrappers,
6750
7040
  collectMetrics,
6751
7041
  controlPlaneDescriptor,
7042
+ coveredTokens,
6752
7043
  createStepLogger,
6753
7044
  currentStep,
6754
7045
  currentWorkflowCtx,
@@ -6761,6 +7052,7 @@ var InMemoryTransport = class {
6761
7052
  eventNameOf,
6762
7053
  eventPrefix,
6763
7054
  eventToken,
7055
+ facetOrigin,
6764
7056
  hasDurableExecutionWrappers,
6765
7057
  heartbeatStatus,
6766
7058
  indexWaitersByRun,
@@ -6770,11 +7062,14 @@ var InMemoryTransport = class {
6770
7062
  isWorkflowControlFlowSignal,
6771
7063
  log,
6772
7064
  matchesAttributes,
7065
+ mergeRunFacetRows,
6773
7066
  missingCapabilities,
6774
7067
  negotiate,
6775
7068
  normalizeAttributeRows,
6776
7069
  normalizeDescriptor,
7070
+ observedAnnouncements,
6777
7071
  parseDuration,
7072
+ partitionOfToken,
6778
7073
  planDispatch,
6779
7074
  prevCronFireMs,
6780
7075
  readSearchAttributes,