@dudousxd/nestjs-durable-core 0.67.1 → 0.69.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
@@ -1042,6 +1042,20 @@ interface StateStore {
1042
1042
  * fall back to counting a {@link listRuns} result, which is correct but unbounded.
1043
1043
  */
1044
1044
  runFacets?(query: RunFacetQuery): Promise<RunFacetRow[]>;
1045
+ /**
1046
+ * The distinct values of ONE filter axis over the runs matching `query`, with counts — what a
1047
+ * console's tenant/tag/attribute pickers list instead of asking an operator to type a value blind.
1048
+ *
1049
+ * Scoped by the SAME predicates the list is under (`query`), so the options narrow as the operator
1050
+ * narrows: picking a tenant leaves the tag picker offering only tags that tenant's runs actually
1051
+ * carry, and a picker never offers a value that would return an empty list.
1052
+ *
1053
+ * Bounded by {@link RunValueFacetOptions} — read its `scan` note before trusting a `tag` count.
1054
+ *
1055
+ * Optional: a store that omits it still works — the console falls back to free-text entry for these
1056
+ * filters, which is what it had before pickers existed.
1057
+ */
1058
+ runValueFacets?(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
1045
1059
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
1046
1060
  /**
1047
1061
  * The LATEST checkpoint for `runId` whose `name` equals `name` exactly (highest `seq` wins), or
@@ -1121,15 +1135,40 @@ type SearchAttributesSchema<A extends SearchAttributes = SearchAttributes> = Sta
1121
1135
  * workflow declares via `@Workflow({ searchAttributes })`. Feed it straight into `WorkflowCtx<A>`:
1122
1136
  * `WorkflowCtx<InferSearchAttributes<typeof mySchema>>`. */
1123
1137
  type InferSearchAttributes<S extends StandardSchemaV1<unknown, SearchAttributes>> = StandardSchemaV1.InferOutput<S>;
1124
- type AttributeOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte';
1125
- /** One predicate over a run's {@link SearchAttributes}; a {@link RunQuery} ANDs them all. */
1126
- interface AttributeFilter {
1138
+ type AttributeOp = 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte' | 'in';
1139
+ /** One value a search attribute can hold, and so one operand a predicate compares against. */
1140
+ type AttributeValue = string | number | boolean;
1141
+ /**
1142
+ * One predicate over a run's {@link SearchAttributes}; a {@link RunQuery} ANDs them all.
1143
+ *
1144
+ * The scalar ops carry a single `value`. `in` carries a SET of values matched as OR, and it needs to
1145
+ * exist as its own op because ORing inside ONE predicate is the only way to express "tier is pro or
1146
+ * enterprise": two `eq` predicates on the same key are ANDed like every other pair, which no run can
1147
+ * satisfy. An empty `values` matches nothing, mirroring {@link RunQuery.statuses}.
1148
+ */
1149
+ type AttributeFilter = {
1127
1150
  key: string;
1128
- op: AttributeOp;
1129
- value: string | number | boolean;
1130
- }
1151
+ op: Exclude<AttributeOp, 'in'>;
1152
+ value: AttributeValue;
1153
+ values?: never;
1154
+ } | {
1155
+ key: string;
1156
+ op: 'in';
1157
+ values: AttributeValue[];
1158
+ value?: never;
1159
+ };
1160
+ /** The single-operand members of {@link AttributeFilter} — every op except the `in` set. */
1161
+ type ScalarAttributeFilter = Exclude<AttributeFilter, {
1162
+ op: 'in';
1163
+ }>;
1131
1164
  interface RunQuery {
1132
1165
  workflow?: string | undefined;
1166
+ /**
1167
+ * Match any of these workflows (`workflow IN (...)`). Same shape as {@link statuses}: ORed with
1168
+ * each other, ANDed with everything else, and further narrowed by a concurrent single `workflow`.
1169
+ * Empty array = matches nothing.
1170
+ */
1171
+ workflows?: string[] | undefined;
1133
1172
  status?: RunStatus | undefined;
1134
1173
  /**
1135
1174
  * Match any of these statuses (a `status IN (...)` filter). ORed together, and ANDed with the other
@@ -1141,6 +1180,13 @@ interface RunQuery {
1141
1180
  statuses?: RunStatus[] | undefined;
1142
1181
  /** Only runs carrying this tag (exact match against {@link WorkflowRun.tags}). */
1143
1182
  tag?: string | undefined;
1183
+ /**
1184
+ * Only runs carrying ANY of these tags. A run's tags are already a set, so the useful multi-value
1185
+ * question is "in this set", not "has all of them" — an operator picking `etl` and `nightly` from a
1186
+ * facet list means the union, and asking for their intersection through this field would return
1187
+ * nothing for tags that never co-occur. ANDed with a concurrent single `tag`; empty = matches nothing.
1188
+ */
1189
+ tags?: string[] | undefined;
1144
1190
  /**
1145
1191
  * Typed/range predicates over {@link WorkflowRun.searchAttributes}, ANDed together (e.g. `amount`
1146
1192
  * >= 200 and `tier` = 'pro'). Applied in-process after the coarse filters, so pair with
@@ -1149,6 +1195,9 @@ interface RunQuery {
1149
1195
  attributes?: AttributeFilter[] | undefined;
1150
1196
  /** Restrict to runs in this namespace (exact match), ANDed with the other predicates. */
1151
1197
  namespace?: string | undefined;
1198
+ /** Restrict to runs in ANY of these namespaces (`namespace IN (...)`), so a console can compare a
1199
+ * few tenants side by side. ANDed with a concurrent single `namespace`; empty = matches nothing. */
1200
+ namespaces?: string[] | undefined;
1152
1201
  /**
1153
1202
  * Restrict to runs whose workflow was declared by this package (exact match against
1154
1203
  * {@link WorkflowRun.origin}), ANDed with the other predicates.
@@ -1160,6 +1209,13 @@ interface RunQuery {
1160
1209
  * facet without having to hold every run in the browser to find them.
1161
1210
  */
1162
1211
  origin?: string | null | undefined;
1212
+ /**
1213
+ * Restrict to runs from ANY of these origins. `null` is a member like any other — it selects the
1214
+ * unattributed bucket — so `['@acme/billing', null]` is "this package's runs plus the ones nothing
1215
+ * could attribute", which the single {@link origin} cannot express at all. ANDed with a concurrent
1216
+ * single `origin`; empty = matches nothing.
1217
+ */
1218
+ origins?: Array<string | null> | undefined;
1163
1219
  limit?: number | undefined;
1164
1220
  offset?: number | undefined;
1165
1221
  }
@@ -1181,6 +1237,46 @@ interface RunFacetRow {
1181
1237
  origin: string | null;
1182
1238
  count: number;
1183
1239
  }
1240
+ /**
1241
+ * Which axis {@link StateStore.runValueFacets} enumerates the distinct VALUES of. Every member is an
1242
+ * axis {@link RunQuery} can then filter by, which is the point: the answer to "what can I pick here"
1243
+ * has to be spendable as a predicate, or a console is offering choices that return nothing.
1244
+ *
1245
+ * `attributeKey` lists the search-attribute keys in use (the left-hand side of a predicate);
1246
+ * `attributeValue` lists the values recorded under ONE key (its right-hand side).
1247
+ */
1248
+ type RunValueAxis = {
1249
+ field: 'workflow' | 'status' | 'origin' | 'namespace' | 'tag' | 'attributeKey';
1250
+ } | {
1251
+ field: 'attributeValue';
1252
+ key: string;
1253
+ };
1254
+ /**
1255
+ * One distinct value of a {@link RunValueAxis} and how many of the matching runs carry it — the rows
1256
+ * behind a console's value picker. `value` is `null` only where the axis itself has an absent bucket
1257
+ * (a run with no `origin`); a count is never zero, since a value with no runs produces no row.
1258
+ */
1259
+ interface RunValueFacetRow {
1260
+ value: string | null;
1261
+ count: number;
1262
+ }
1263
+ /**
1264
+ * How much of the store {@link StateStore.runValueFacets} may read to answer.
1265
+ *
1266
+ * `limit` bounds the ROWS RETURNED (highest count first): tag and attribute-value cardinality is
1267
+ * unbounded in principle — a run tagged `singleton:<key>` mints a new tag per key — so a picker asks
1268
+ * for the top slice rather than the whole domain, and keeps free text for everything else.
1269
+ *
1270
+ * `scan` bounds the RUNS READ, and applies to the axes whose values are not a column of the run row
1271
+ * being counted — `tag` (inside a JSON array column) and the two attribute axes (a side table). Those
1272
+ * report counts over the newest `scan` matching runs rather than over all of them: a bounded,
1273
+ * deliberately approximate answer, where the alternative is a full scan on every keystroke. The
1274
+ * column axes (`workflow`, `status`, `origin`, `namespace`) are exact and ignore `scan`.
1275
+ */
1276
+ interface RunValueFacetOptions {
1277
+ limit?: number | undefined;
1278
+ scan?: number | undefined;
1279
+ }
1184
1280
  /** The transaction handle `StateStore.transaction` hands to its work callback. */
1185
1281
  interface StoreTransaction {
1186
1282
  /** The store-native transaction handle (TypeORM `EntityManager`, Prisma tx client, MikroORM `EntityManager`,
@@ -1247,6 +1343,11 @@ type RunRequestKind = {
1247
1343
  } | {
1248
1344
  kind: 'runFacets';
1249
1345
  query: RunFacetQuery;
1346
+ } | {
1347
+ kind: 'runValueFacets';
1348
+ axis: RunValueAxis;
1349
+ query: RunFacetQuery;
1350
+ opts?: RunValueFacetOptions | undefined;
1250
1351
  } | {
1251
1352
  kind: 'workerHealth';
1252
1353
  } | {
@@ -4396,6 +4497,62 @@ declare function mergeRunFacetRows(rows: readonly {
4396
4497
  count: number;
4397
4498
  }[]): RunFacetRow[];
4398
4499
 
4500
+ /** Rows a value picker gets when the caller names no bound — enough to fill a dropdown several times
4501
+ * over, small enough that an axis with unbounded cardinality (tags) can't return a listing. */
4502
+ declare const RUN_VALUE_FACET_LIMIT = 100;
4503
+ /** Runs read when an axis has to be counted in-process (see {@link RunValueFacetOptions.scan}). */
4504
+ declare const RUN_VALUE_FACET_SCAN = 5000;
4505
+ /**
4506
+ * Tag prefixes the ENGINE mints one of PER KEY — `singleton:<key>` is stamped on every run of a
4507
+ * singleton workflow so the admission gate can find its siblings.
4508
+ *
4509
+ * A picker ranks these last. They are machine bookkeeping with cardinality that grows with the data,
4510
+ * and on a real deployment they crowd out everything an operator would actually choose: measured on
4511
+ * a 12k-run control plane, 82 of the top 100 tags were `singleton:*`, pushing genuine tags like
4512
+ * `type:mvr` to the edge of the list and past it.
4513
+ *
4514
+ * Ranked, not REMOVED: a singleton tag is still a legitimate thing to filter by (a run row's tag chip
4515
+ * sets exactly that), so it stays offered — just after the tags a human wrote. `version:undeclared`
4516
+ * is engine-minted too but is a single fixed value, so it competes fairly and is not listed here.
4517
+ */
4518
+ declare const ENGINE_MINTED_TAG_PREFIXES: readonly ["singleton:"];
4519
+ /** Is this a tag the engine mints one of per key? See {@link ENGINE_MINTED_TAG_PREFIXES}. */
4520
+ declare function isEngineMintedTag(value: string | null): boolean;
4521
+ /**
4522
+ * Is this axis a plain column on the runs table, and so countable with a `GROUP BY` over the whole
4523
+ * matching set?
4524
+ *
4525
+ * The other three are not, in the same way for the same reason — the value isn't in a column of the
4526
+ * row being counted. A run's tags live inside ONE json array column that no `GROUP BY` portable
4527
+ * across sqlite/MySQL/Postgres reaches into, and its search attributes live in a side table whose
4528
+ * join back to the filtered run set differs per adapter. Those axes are counted in-process over a
4529
+ * bounded scan instead ({@link runValueFacetsFromRuns}), so their counts describe that window rather
4530
+ * than the whole store — which is why {@link RunValueFacetOptions.scan} exists.
4531
+ */
4532
+ declare function axisIsRunColumn(axis: RunValueAxis): axis is {
4533
+ field: 'workflow' | 'status' | 'origin' | 'namespace';
4534
+ };
4535
+ /**
4536
+ * Fold, order and bound raw `(value, count)` rows into the answer callers see: highest count first,
4537
+ * ties broken alphabetically so a picker's order is stable between polls, the unattributed bucket
4538
+ * (`null`) last, and no more than `limit` rows.
4539
+ *
4540
+ * Every store pipes its `GROUP BY` through this — the same role {@link mergeRunFacetRows} plays for
4541
+ * status/origin counts — so a picker lists the same values in the same order whichever adapter is
4542
+ * underneath, rather than each SQL dialect's default ordering leaking into the UI.
4543
+ */
4544
+ declare function mergeRunValueFacetRows(rows: readonly {
4545
+ value: string | null | undefined;
4546
+ count: number;
4547
+ }[], opts?: RunValueFacetOptions): RunValueFacetRow[];
4548
+ /**
4549
+ * Count an axis's distinct values across runs already narrowed by the caller's predicates — the
4550
+ * in-process path, used by the in-memory store for every axis and by the SQL stores for `tag` (see
4551
+ * {@link axisIsGroupable}). `runs` must be ordered newest-first and already bounded to
4552
+ * {@link RunValueFacetOptions.scan}, since that window is what the counts then describe.
4553
+ */
4554
+ declare function runValueFacetsFromRuns(runs: readonly WorkflowRun[], axis: RunValueAxis, opts?: RunValueFacetOptions): RunValueFacetRow[];
4555
+
4399
4556
  /** A run as returned by the gateway's list — the durable run plus an optional {@link RunWaiting}
4400
4557
  * descriptor the control plane resolves (what a suspended run is parked on), so the dashboard can
4401
4558
  * name the wait in a list row without fetching each run's timeline. Additive: `waiting` is absent
@@ -4459,6 +4616,16 @@ declare abstract class RunGateway {
4459
4616
  * page bounds what is rendered, these counts stay whole-set exact. Zero-count cells are absent.
4460
4617
  */
4461
4618
  abstract runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
4619
+ /**
4620
+ * The distinct values of one filter axis over the runs matching `query`, with counts — what fills a
4621
+ * console's tenant/tag/attribute pickers. On the port (not control-plane-only) because the pickers
4622
+ * belong to the same panel as {@link listRuns}: a tenant that can list its runs has to be able to
4623
+ * see what it may filter them by, or its console offers a blank dropdown.
4624
+ *
4625
+ * Returns `[]` when the underlying store does not implement the enumeration — an empty picker with
4626
+ * free-text entry still standing, not an error.
4627
+ */
4628
+ abstract runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
4462
4629
  /**
4463
4630
  * Bulk-resolve {@link RunWaiting} for an arbitrary set of run ids — for a consumer with its OWN
4464
4631
  * filtered/paginated run listing (so it isn't just `listRuns` under another name) that needs to know
@@ -4544,6 +4711,12 @@ declare class CodecStateStore implements StateStore {
4544
4711
  * of resolving to a silently wrong empty result.
4545
4712
  */
4546
4713
  readonly runFacets?: (query: RunFacetQuery) => Promise<RunFacetRow[]>;
4714
+ /**
4715
+ * Forwarded verbatim, and safe to: the axes a value facet enumerates are searchable metadata
4716
+ * (workflow, status, origin, namespace, tags, search attributes), none of which the codec encodes.
4717
+ * Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
4718
+ */
4719
+ readonly runValueFacets?: (axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions) => Promise<RunValueFacetRow[]>;
4547
4720
  constructor(inner: StateStore, codec: PayloadCodec);
4548
4721
  private enc;
4549
4722
  private dec;
@@ -4772,16 +4945,39 @@ interface RunAttributeRow {
4772
4945
  * in-process `compare` treats `eq`/`ne` on booleans). Returns `[]` for a run with no attributes.
4773
4946
  */
4774
4947
  declare function normalizeAttributeRows(runId: string, attributes: SearchAttributes | undefined): RunAttributeRow[];
4775
- /** SQL comparison operator for an {@link AttributeOp} (used to build pushdown predicates). */
4776
- declare function sqlComparator(op: AttributeOp): string;
4948
+ /**
4949
+ * SQL comparison operator for a single-operand {@link AttributeOp} (used to build pushdown
4950
+ * predicates). `in` is absent by construction: it compares against a SET, which a store emits as an
4951
+ * OR of equalities over {@link attributePredicateOperands} rather than as one comparator.
4952
+ */
4953
+ declare function sqlComparator(op: Exclude<AttributeOp, 'in'>): string;
4777
4954
  /**
4778
4955
  * Which side-table column an attribute filter compares against: numeric operands (and range ops on
4779
4956
  * numbers) hit `numValue`; everything else hits `strValue` (booleans are stored as `"true"`/`"false"`).
4780
4957
  * Keeping this in core means every SQL adapter pushes predicates down identically.
4781
4958
  */
4782
- declare function attributeColumnFor(filter: AttributeFilter): 'numValue' | 'strValue';
4959
+ declare function attributeColumnFor(filter: ScalarAttributeFilter): 'numValue' | 'strValue';
4783
4960
  /** The literal a side-table predicate compares against (booleans → `"true"`/`"false"` strings). */
4784
- declare function attributeOperand(filter: AttributeFilter): string | number;
4961
+ declare function attributeOperand(filter: ScalarAttributeFilter): string | number;
4962
+ /** One comparison a side-table predicate makes: which typed column, with which operator, against
4963
+ * which literal. */
4964
+ interface AttributePredicateOperand {
4965
+ column: 'numValue' | 'strValue';
4966
+ comparator: string;
4967
+ operand: string | number;
4968
+ }
4969
+ /**
4970
+ * The comparisons one {@link AttributeFilter} makes, for a store to OR together inside its
4971
+ * `EXISTS`/join on the normalized attribute side-table. A scalar op yields exactly ONE comparison
4972
+ * (the same column/operator/literal {@link attributeColumnFor} and {@link attributeOperand} give);
4973
+ * `in` yields one per member, each an equality, since its operands may be of mixed types and so land
4974
+ * in different typed columns.
4975
+ *
4976
+ * An EMPTY list is a real answer, not a missing one: `in` over no values can match no run. A caller
4977
+ * that ORs an empty list must emit a false predicate rather than dropping the filter, or the query
4978
+ * silently widens to every run.
4979
+ */
4980
+ declare function attributePredicateOperands(filter: AttributeFilter): AttributePredicateOperand[];
4785
4981
 
4786
4982
  /**
4787
4983
  * Read a run's `searchAttributes` through a {@link SearchAttributesSchema}, typed to the schema's
@@ -5002,6 +5198,10 @@ declare class InMemoryStateStore implements StateStore {
5002
5198
  private matching;
5003
5199
  listRuns(query: RunQuery): Promise<WorkflowRun[]>;
5004
5200
  runFacets(query: RunFacetQuery): Promise<RunFacetRow[]>;
5201
+ /** Counted over the newest `scan` matching runs for EVERY axis, not just `tag`: this store holds
5202
+ * its runs in a Map with no index to group by, so the bound that the SQL stores need only for tags
5203
+ * is the honest one to apply here throughout. */
5204
+ runValueFacets(axis: RunValueAxis, query: RunFacetQuery, opts?: RunValueFacetOptions): Promise<RunValueFacetRow[]>;
5005
5205
  listCheckpoints(runId: string): Promise<StepCheckpoint[]>;
5006
5206
  getLatestCheckpointByName(runId: string, name: string): Promise<StepCheckpoint | undefined>;
5007
5207
  listCheckpointsByNamePrefix(runId: string, prefixes: string[]): Promise<StepCheckpoint[]>;
@@ -5033,4 +5233,4 @@ declare class InMemoryTransport implements Transport, ControlPlane {
5033
5233
  onControl(handler: (msg: ControlMessage) => void): void;
5034
5234
  }
5035
5235
 
5036
- export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AnnouncedWorkflow, type AnnouncementEvidence, type AttributeFilter, type AttributeOp, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DEFAULT_PARTITION, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatSighting, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PartitionSighting, type PayloadCodec, type QueueConfig, QueueController, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunFacetQuery, type RunFacetRow, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, VERSION_UNDECLARED_TAG, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, type WorkflowDirectory, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, announcementsOf, applyAttributeQuery, attributeColumnFor, attributeOperand, bindWorkflowClass, breakpointToken, buildWorkflowDirectory, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, coveredTokens, createStepLogger, currentStep, currentWorkflowCtx, debug, describeWorker, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, facetOrigin, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, mergeRunFacetRows, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, observedAnnouncements, parseDuration, partitionOfToken, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
5236
+ export { type AccumulatorHost, type Admission, type AdmissionBackend, type AdmissionItem, type AnnouncedWorkflow, type AnnouncementEvidence, type AttributeFilter, type AttributeOp, type AttributePredicateOperand, type AttributeValue, type BackoffStrategy, type BlockedDispatch, type BlockedResolution, CONTROL_FLOW_SIGNAL, CURRENT_PROTOCOL_VERSION, type CapabilityRequirement, type ChildCallOptions, CodecStateStore, ContinueAsNew, type ControlMessage, type ControlPlane, DEFAULT_PARTITION, DURABLE_OPTIONS, DURABLE_OPTIONS_CANONICAL, DURABLE_STEP_CONFIG, DURABLE_STEP_NAME, type Disagreement, type DispatchDiagnostics, type DispatchPlan, type DurableExecution, type DurableExecutionWrapper, type DurableTopology, type DurableWebhook, DurableWorkflow, ENGINE_MINTED_TAG_PREFIXES, EVENT_BUFFER_SCAN_LIMIT, EVENT_TOKEN_PREFIX, type EngineEvent, type EngineEventType, type EngineListener, Entities, type EntityConfig, type EntityHandler, type EntityHost, EventAccumulators, type EventBatchConfig, FatalError, GatherError, type GroupHealth, type Heartbeat, type HeartbeatSighting, type HeartbeatStatus, type HistoryEvent, InMemoryAdmissionBackend, InMemoryStateStore, InMemoryTransport, type InferSearchAttributes, LEGACY_V1_CAPABILITIES, LEGACY_V1_PROTOCOL, type MetricsCollector, type MetricsSnapshot, type NamedTransport, type NegotiateOptions, type NegotiationOutcome, type NegotiationReason, type NegotiationResult, NonDeterminismError, type PartitionSighting, type PayloadCodec, type QueueConfig, QueueController, RUN_VALUE_FACET_LIMIT, RUN_VALUE_FACET_SCAN, type RawWorkerDescriptor, RemoteStepError, RemoteStepTimeout, type RemoteTask, RemoteWorkflowExecutor, RemoteWorkflowTimeout, type RetentionPolicy, type RoutableDispatch, type RoutableResolution, type RoutingResolution, type RunAttributeRow, type RunDetail, type RunDispatcher, type RunFacetQuery, type RunFacetRow, RunGateway, type RunListItem, type RunQuery, type RunReply, type RunReplyResult, type RunRequest, type RunRequestKind, type RunResult, type RunSchedulesOptions, type RunStatus, type RunValueAxis, type RunValueFacetOptions, type RunValueFacetRow, type RunWaiting, STATE_STORE, STATE_STORE_CANONICAL, type ScalarAttributeFilter, type ScheduledWorkflow, type SearchAttributes, type SearchAttributesSchema, SignalTimeoutError, type SignalWaiter, type SingletonConfig, SingletonQueueFullError, StandardSchemaV1, type StartOptions, type StartRunMessage, type StateStore, type StepCheckpoint, type StepConfig, type StepDef, type StepDispatchOpts, type StepError, type StepEvent, type StepHandler, type StepInterceptor, type StepInvocation, type StepKind, type StepLogger, type StepOptions, type StepRef, type StepResult, type StepUndo, type StoreTransaction, type SubProcessHandle, TERMINAL_RUN_STATUSES, TRANSPORT, TRANSPORT_CANONICAL, type TenantEvent, type Transport, type UndoOf, type UpdateResult, type UpdateValidator, VERSION_UNDECLARED_TAG, WORKFLOW_NAME_KEY, type WireDates, type WorkerAdjust, type WorkerConcurrencyStatus, type WorkerDescriptor, type WorkerHeartbeat, type WorkerLifecycle, type WorkerStatus, type WorkflowAnnouncement, WorkflowBlocked, type WorkflowClass, type WorkflowCommand, type WorkflowCtx, type WorkflowDecision, type WorkflowDirectory, WorkflowEngine, type WorkflowEngineDeps, type WorkflowExecutor, type WorkflowHandler, type WorkflowInputOf, type WorkflowOutputOf, type WorkflowRef, type WorkflowRegistration, type WorkflowRun, type WorkflowStartClient, type WorkflowStepEvent, WorkflowSuspended, type WorkflowTask, aggregateAnnouncements, announcementsOf, applyAttributeQuery, attributeColumnFor, attributeOperand, attributePredicateOperands, axisIsRunColumn, bindWorkflowClass, breakpointToken, buildWorkflowDirectory, canRoute, capableWorkers, classifyWaiterToken, clearDurableExecutionWrappers, collectMetrics, controlPlaneDescriptor, coveredTokens, createStepLogger, currentStep, currentWorkflowCtx, debug, describeWorker, descriptorHash, error, eventMatchOf, eventMatches, eventNameOf, eventPrefix, eventToken, facetOrigin, hasDurableExecutionWrappers, heartbeatStatus, indexWaitersByRun, info, isEngineMintedTag, isEventToken, isLegacyDescriptor, isWorkflowControlFlowSignal, log, matchesAttributes, mergeRunFacetRows, mergeRunValueFacetRows, missingCapabilities, negotiate, normalizeAttributeRows, normalizeDescriptor, observedAnnouncements, parseDuration, partitionOfToken, planDispatch, prevCronFireMs, readSearchAttributes, requiredCapabilities, resolveRouting, resolveRunWaiting, runDurableExecution, runInStepLogger, runInWorkflowCtx, runSchedules, runStepHandler, runValueFacetsFromRuns, sanitizeQueueToken, scheduledRunId, sqlComparator, stepConfigOf, stepId, stepNameOf, sub, subEvent, subProcess, tenantGroup, useDurableExecution, warn, workflowName };
package/dist/index.js CHANGED
@@ -6000,6 +6000,86 @@ function mergeRunFacetRows(rows) {
6000
6000
  }
6001
6001
  __name(mergeRunFacetRows, "mergeRunFacetRows");
6002
6002
 
6003
+ // src/run-value-facets.ts
6004
+ var RUN_VALUE_FACET_LIMIT = 100;
6005
+ var RUN_VALUE_FACET_SCAN = 5e3;
6006
+ var ENGINE_MINTED_TAG_PREFIXES = [
6007
+ "singleton:"
6008
+ ];
6009
+ function isEngineMintedTag(value) {
6010
+ return value !== null && ENGINE_MINTED_TAG_PREFIXES.some((prefix) => value.startsWith(prefix));
6011
+ }
6012
+ __name(isEngineMintedTag, "isEngineMintedTag");
6013
+ function axisIsRunColumn(axis) {
6014
+ return axis.field === "workflow" || axis.field === "status" || axis.field === "origin" || axis.field === "namespace";
6015
+ }
6016
+ __name(axisIsRunColumn, "axisIsRunColumn");
6017
+ function valuesOf(run, axis) {
6018
+ switch (axis.field) {
6019
+ case "workflow":
6020
+ return [
6021
+ run.workflow
6022
+ ];
6023
+ case "status":
6024
+ return [
6025
+ run.status
6026
+ ];
6027
+ case "origin":
6028
+ return [
6029
+ facetOrigin(run.origin)
6030
+ ];
6031
+ case "namespace":
6032
+ return run.namespace === void 0 ? [] : [
6033
+ run.namespace
6034
+ ];
6035
+ case "tag":
6036
+ return run.tags ?? [];
6037
+ case "attributeKey":
6038
+ return Object.keys(run.searchAttributes ?? {});
6039
+ case "attributeValue": {
6040
+ const value = run.searchAttributes?.[axis.key];
6041
+ return value === void 0 ? [] : [
6042
+ String(value)
6043
+ ];
6044
+ }
6045
+ }
6046
+ }
6047
+ __name(valuesOf, "valuesOf");
6048
+ function mergeRunValueFacetRows(rows, opts) {
6049
+ const counts = /* @__PURE__ */ new Map();
6050
+ for (const row of rows) {
6051
+ const value = row.value ?? null;
6052
+ counts.set(value, (counts.get(value) ?? 0) + Number(row.count));
6053
+ }
6054
+ const merged = [
6055
+ ...counts
6056
+ ].map(([value, count]) => ({
6057
+ value,
6058
+ count
6059
+ })).filter((row) => row.count > 0).sort((a, b) => {
6060
+ const engineA = isEngineMintedTag(a.value);
6061
+ const engineB = isEngineMintedTag(b.value);
6062
+ if (engineA !== engineB) return engineA ? 1 : -1;
6063
+ if (b.count !== a.count) return b.count - a.count;
6064
+ if (a.value === null) return 1;
6065
+ if (b.value === null) return -1;
6066
+ return a.value.localeCompare(b.value);
6067
+ });
6068
+ return merged.slice(0, opts?.limit ?? RUN_VALUE_FACET_LIMIT);
6069
+ }
6070
+ __name(mergeRunValueFacetRows, "mergeRunValueFacetRows");
6071
+ function runValueFacetsFromRuns(runs, axis, opts) {
6072
+ const rows = [];
6073
+ for (const run of runs) {
6074
+ for (const value of valuesOf(run, axis)) rows.push({
6075
+ value,
6076
+ count: 1
6077
+ });
6078
+ }
6079
+ return mergeRunValueFacetRows(rows, opts);
6080
+ }
6081
+ __name(runValueFacetsFromRuns, "runValueFacetsFromRuns");
6082
+
6003
6083
  // src/run-gateway.ts
6004
6084
  var RunGateway = class {
6005
6085
  static {
@@ -6021,11 +6101,21 @@ var CodecStateStore = class {
6021
6101
  * of resolving to a silently wrong empty result.
6022
6102
  */
6023
6103
  runFacets;
6104
+ /**
6105
+ * Forwarded verbatim, and safe to: the axes a value facet enumerates are searchable metadata
6106
+ * (workflow, status, origin, namespace, tags, search attributes), none of which the codec encodes.
6107
+ * Bound the same conditional way as {@link runFacets} so absence is reported, not faked.
6108
+ */
6109
+ runValueFacets;
6024
6110
  constructor(inner, codec) {
6025
6111
  this.inner = inner;
6026
6112
  this.codec = codec;
6027
6113
  const facets = inner.runFacets;
6028
6114
  if (facets) this.runFacets = (query) => facets.call(inner, query);
6115
+ const valueFacets = inner.runValueFacets;
6116
+ if (valueFacets) {
6117
+ this.runValueFacets = (axis, query, opts) => valueFacets.call(inner, axis, query, opts);
6118
+ }
6029
6119
  }
6030
6120
  enc(v) {
6031
6121
  return v === void 0 ? void 0 : this.codec.encode(v);
@@ -6380,8 +6470,10 @@ async function runSchedules(engine, schedules, nowMs, opts) {
6380
6470
  __name(runSchedules, "runSchedules");
6381
6471
 
6382
6472
  // src/search-attributes.ts
6383
- function compare(actual, op, expected) {
6384
- switch (op) {
6473
+ function compare(actual, filter) {
6474
+ if (filter.op === "in") return filter.values.some((v) => actual === v);
6475
+ const expected = filter.value;
6476
+ switch (filter.op) {
6385
6477
  case "eq":
6386
6478
  return actual === expected;
6387
6479
  case "ne":
@@ -6400,7 +6492,7 @@ __name(compare, "compare");
6400
6492
  function matchesAttributes(attributes, filters) {
6401
6493
  if (!filters?.length) return true;
6402
6494
  if (!attributes) return false;
6403
- return filters.every((f) => f.key in attributes && compare(attributes[f.key], f.op, f.value));
6495
+ return filters.every((f) => f.key in attributes && compare(attributes[f.key], f));
6404
6496
  }
6405
6497
  __name(matchesAttributes, "matchesAttributes");
6406
6498
  function applyAttributeQuery(rows, query) {
@@ -6466,6 +6558,30 @@ function attributeOperand(filter) {
6466
6558
  return filter.value;
6467
6559
  }
6468
6560
  __name(attributeOperand, "attributeOperand");
6561
+ function attributePredicateOperands(filter) {
6562
+ if (filter.op === "in") {
6563
+ return filter.values.map((value) => {
6564
+ const scalar = {
6565
+ key: filter.key,
6566
+ op: "eq",
6567
+ value
6568
+ };
6569
+ return {
6570
+ column: attributeColumnFor(scalar),
6571
+ comparator: "=",
6572
+ operand: attributeOperand(scalar)
6573
+ };
6574
+ });
6575
+ }
6576
+ return [
6577
+ {
6578
+ column: attributeColumnFor(filter),
6579
+ comparator: sqlComparator(filter.op),
6580
+ operand: attributeOperand(filter)
6581
+ }
6582
+ ];
6583
+ }
6584
+ __name(attributePredicateOperands, "attributePredicateOperands");
6469
6585
 
6470
6586
  // src/search-attributes-schema.ts
6471
6587
  function readSearchAttributes(schema, run) {
@@ -6693,33 +6809,27 @@ var InMemoryStateStore = class {
6693
6809
  const byRun = this.attributeIndex.get(f.key);
6694
6810
  const out = /* @__PURE__ */ new Set();
6695
6811
  if (!byRun) return out;
6696
- const numeric = typeof f.value === "number";
6697
- const operand = typeof f.value === "boolean" ? f.value ? "true" : "false" : f.value;
6812
+ const comparisons = attributePredicateOperands(f);
6698
6813
  for (const row of byRun.values()) {
6699
- const actual = numeric ? row.numValue : row.strValue;
6700
- if (actual == null) continue;
6701
- let ok = false;
6702
- switch (f.op) {
6703
- case "eq":
6704
- ok = actual === operand;
6705
- break;
6706
- case "ne":
6707
- ok = actual !== operand;
6708
- break;
6709
- case "gt":
6710
- ok = actual > operand;
6711
- break;
6712
- case "gte":
6713
- ok = actual >= operand;
6714
- break;
6715
- case "lt":
6716
- ok = actual < operand;
6717
- break;
6718
- case "lte":
6719
- ok = actual <= operand;
6720
- break;
6721
- }
6722
- if (ok) out.add(row.runId);
6814
+ const matched = comparisons.some(({ column, comparator, operand }) => {
6815
+ const actual = column === "numValue" ? row.numValue : row.strValue;
6816
+ if (actual == null) return false;
6817
+ switch (comparator) {
6818
+ case "=":
6819
+ return actual === operand;
6820
+ case "<>":
6821
+ return actual !== operand;
6822
+ case ">":
6823
+ return actual > operand;
6824
+ case ">=":
6825
+ return actual >= operand;
6826
+ case "<":
6827
+ return actual < operand;
6828
+ default:
6829
+ return actual <= operand;
6830
+ }
6831
+ });
6832
+ if (matched) out.add(row.runId);
6723
6833
  }
6724
6834
  return out;
6725
6835
  }
@@ -6730,12 +6840,20 @@ var InMemoryStateStore = class {
6730
6840
  ...this.runs.values()
6731
6841
  ];
6732
6842
  if (query.workflow) runs = runs.filter((r) => r.workflow === query.workflow);
6843
+ if (query.workflows) runs = runs.filter((r) => query.workflows?.includes(r.workflow));
6733
6844
  if (query.status) runs = runs.filter((r) => r.status === query.status);
6734
6845
  if (query.statuses) runs = runs.filter((r) => query.statuses?.includes(r.status));
6735
6846
  if (query.tag) runs = runs.filter((r) => r.tags?.includes(query.tag));
6847
+ if (query.tags) runs = runs.filter((r) => r.tags?.some((t) => query.tags?.includes(t)));
6736
6848
  if (query.namespace !== void 0) runs = runs.filter((r) => r.namespace === query.namespace);
6849
+ if (query.namespaces) {
6850
+ runs = runs.filter((r) => r.namespace !== void 0 && query.namespaces?.includes(r.namespace));
6851
+ }
6737
6852
  if (query.origin === null) runs = runs.filter((r) => r.origin == null);
6738
6853
  else if (query.origin !== void 0) runs = runs.filter((r) => r.origin === query.origin);
6854
+ if (query.origins) {
6855
+ runs = runs.filter((r) => query.origins?.includes(r.origin ?? null));
6856
+ }
6739
6857
  if (query.attributes?.length) {
6740
6858
  let candidates = null;
6741
6859
  for (const f of query.attributes) {
@@ -6772,6 +6890,16 @@ var InMemoryStateStore = class {
6772
6890
  count: 1
6773
6891
  })));
6774
6892
  }
6893
+ /** Counted over the newest `scan` matching runs for EVERY axis, not just `tag`: this store holds
6894
+ * its runs in a Map with no index to group by, so the bound that the SQL stores need only for tags
6895
+ * is the honest one to apply here throughout. */
6896
+ async runValueFacets(axis, query, opts) {
6897
+ const runs = await this.listRuns({
6898
+ ...query,
6899
+ limit: opts?.scan ?? RUN_VALUE_FACET_SCAN
6900
+ });
6901
+ return runValueFacetsFromRuns(runs, axis, opts);
6902
+ }
6775
6903
  async listCheckpoints(runId) {
6776
6904
  const rows = [
6777
6905
  ...this.checkpoints.values()
@@ -6859,6 +6987,7 @@ export {
6859
6987
  DURABLE_STEP_CONFIG,
6860
6988
  DURABLE_STEP_NAME,
6861
6989
  DurableWorkflow,
6990
+ ENGINE_MINTED_TAG_PREFIXES,
6862
6991
  EVENT_BUFFER_SCAN_LIMIT,
6863
6992
  EVENT_TOKEN_PREFIX,
6864
6993
  Entities,
@@ -6872,6 +7001,8 @@ export {
6872
7001
  LEGACY_V1_PROTOCOL,
6873
7002
  NonDeterminismError,
6874
7003
  QueueController,
7004
+ RUN_VALUE_FACET_LIMIT,
7005
+ RUN_VALUE_FACET_SCAN,
6875
7006
  RemoteStepError,
6876
7007
  RemoteStepTimeout,
6877
7008
  RemoteWorkflowExecutor,
@@ -6894,6 +7025,8 @@ export {
6894
7025
  applyAttributeQuery,
6895
7026
  attributeColumnFor,
6896
7027
  attributeOperand,
7028
+ attributePredicateOperands,
7029
+ axisIsRunColumn,
6897
7030
  bindWorkflowClass,
6898
7031
  breakpointToken,
6899
7032
  buildWorkflowDirectory,
@@ -6921,12 +7054,14 @@ export {
6921
7054
  heartbeatStatus,
6922
7055
  indexWaitersByRun,
6923
7056
  info,
7057
+ isEngineMintedTag,
6924
7058
  isEventToken,
6925
7059
  isLegacyDescriptor,
6926
7060
  isWorkflowControlFlowSignal,
6927
7061
  log,
6928
7062
  matchesAttributes,
6929
7063
  mergeRunFacetRows,
7064
+ mergeRunValueFacetRows,
6930
7065
  missingCapabilities,
6931
7066
  negotiate,
6932
7067
  normalizeAttributeRows,
@@ -6945,6 +7080,7 @@ export {
6945
7080
  runInWorkflowCtx,
6946
7081
  runSchedules,
6947
7082
  runStepHandler,
7083
+ runValueFacetsFromRuns,
6948
7084
  sanitizeQueueToken,
6949
7085
  scheduledRunId,
6950
7086
  sqlComparator,