@trigger.dev/redis-worker 0.0.0-prerelease-20260220162801 → 0.0.0-prerelease-20260302145933

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.cts CHANGED
@@ -97,6 +97,10 @@ declare const CronSchema: z.ZodObject<{
97
97
  lastTimestamp?: number | undefined;
98
98
  }>;
99
99
  type CronSchema = z.infer<typeof CronSchema>;
100
+ type BatchConfig = {
101
+ maxSize: number;
102
+ maxWaitMs: number;
103
+ };
100
104
  type WorkerCatalog = {
101
105
  [key: string]: {
102
106
  schema: z.ZodFirstPartySchemaTypes | z.ZodDiscriminatedUnion<any, any>;
@@ -106,6 +110,8 @@ type WorkerCatalog = {
106
110
  jitterInMs?: number;
107
111
  /** Defaults to true. If false, errors will not be logged. */
108
112
  logErrors?: boolean;
113
+ /** When set, items are accumulated and delivered in batches to the handler. */
114
+ batch?: BatchConfig;
109
115
  };
110
116
  };
111
117
  type QueueCatalogFromWorkerCatalog<Catalog extends WorkerCatalog> = {
@@ -119,6 +125,9 @@ type JobHandlerParams<Catalog extends WorkerCatalog, K extends keyof Catalog> =
119
125
  deduplicationKey?: string;
120
126
  };
121
127
  type JobHandler<Catalog extends WorkerCatalog, K extends keyof Catalog> = (params: JobHandlerParams<Catalog, K>) => Promise<void>;
128
+ type JobHandlerFor<Catalog extends WorkerCatalog, K extends keyof Catalog> = Catalog[K] extends {
129
+ batch: BatchConfig;
130
+ } ? (items: Array<JobHandlerParams<Catalog, K>>) => Promise<void> : (params: JobHandlerParams<Catalog, K>) => Promise<void>;
122
131
  type WorkerConcurrencyOptions = {
123
132
  workers?: number;
124
133
  tasksPerWorker?: number;
@@ -129,7 +138,7 @@ type WorkerOptions<TCatalog extends WorkerCatalog> = {
129
138
  redisOptions: RedisOptions;
130
139
  catalog: TCatalog;
131
140
  jobs: {
132
- [K in keyof TCatalog]: JobHandler<TCatalog, K>;
141
+ [K in keyof TCatalog]: JobHandlerFor<TCatalog, K>;
133
142
  };
134
143
  concurrency?: WorkerConcurrencyOptions;
135
144
  pollIntervalMs?: number;
@@ -154,6 +163,7 @@ declare class Worker<TCatalog extends WorkerCatalog> {
154
163
  private concurrency;
155
164
  private shutdownTimeoutMs;
156
165
  private limiters;
166
+ private batchAccumulators;
157
167
  constructor(options: WorkerOptions<TCatalog>);
158
168
  start(): this;
159
169
  /**
@@ -206,11 +216,45 @@ declare class Worker<TCatalog extends WorkerCatalog> {
206
216
  cancel(cancellationKey: string): Promise<void>;
207
217
  ack(id: string): Promise<void>;
208
218
  getJob(id: string): Promise<QueueItem<QueueCatalogFromWorkerCatalog<TCatalog>> | null>;
219
+ /**
220
+ * Returns true if the given job type has batch config in the catalog.
221
+ */
222
+ private isBatchJob;
223
+ /**
224
+ * Returns the batch config for a job type, or undefined if not batch-enabled.
225
+ */
226
+ private getBatchConfig;
227
+ /**
228
+ * The max dequeue count: the largest batch maxSize across catalog entries,
229
+ * falling back to tasksPerWorker for non-batch catalogs.
230
+ */
231
+ private get maxDequeueCount();
209
232
  /**
210
233
  * The main loop that each worker runs. It repeatedly polls for items,
211
234
  * processes them, and then waits before the next iteration.
212
235
  */
213
236
  private runWorkerLoop;
237
+ /**
238
+ * Adds an item to the batch accumulator for its job type.
239
+ */
240
+ private addToAccumulator;
241
+ /**
242
+ * Flushes any batch accumulators that have exceeded their maxWaitMs.
243
+ */
244
+ private flushTimedOutBatches;
245
+ /**
246
+ * Flushes all batch accumulators (used during shutdown).
247
+ */
248
+ private flushAllBatches;
249
+ /**
250
+ * Flushes the batch accumulator for a specific job type.
251
+ * Removes items from the accumulator and submits them to the limiter as a single batch.
252
+ */
253
+ private flushBatch;
254
+ /**
255
+ * Processes a batch of items for a batch-enabled job type.
256
+ */
257
+ private processBatch;
214
258
  /**
215
259
  * Processes a single item.
216
260
  */
@@ -485,6 +529,13 @@ interface SchedulerContext {
485
529
  /** Get queue descriptor by ID */
486
530
  getQueueDescriptor(queueId: string): QueueDescriptor;
487
531
  }
532
+ /**
533
+ * Extended context for two-level dispatch scheduling.
534
+ */
535
+ interface DispatchSchedulerContext extends SchedulerContext {
536
+ /** Get queues for a specific tenant from the per-tenant queue index (Level 2) */
537
+ getQueuesForTenant(tenantId: string, limit?: number): Promise<QueueWithScore[]>;
538
+ }
488
539
  /**
489
540
  * Pluggable scheduler interface for fair queue selection.
490
541
  */
@@ -499,6 +550,13 @@ interface FairScheduler {
499
550
  * @returns Queues grouped by tenant in priority order
500
551
  */
501
552
  selectQueues(masterQueueShard: string, consumerId: string, context: SchedulerContext): Promise<TenantQueues[]>;
553
+ /**
554
+ * Select queues using the two-level tenant dispatch index.
555
+ * Level 1: reads tenantIds from dispatch shard.
556
+ * Level 2: reads queueIds from per-tenant index.
557
+ * Optional - falls back to selectQueues with flat queue list if not implemented.
558
+ */
559
+ selectQueuesFromDispatch?(dispatchShardKey: string, consumerId: string, context: DispatchSchedulerContext): Promise<TenantQueues[]>;
502
560
  /**
503
561
  * Called after processing a message to update scheduler state.
504
562
  * Optional - not all schedulers need to track state.
@@ -566,6 +624,10 @@ interface FairQueueKeyProducer {
566
624
  deadLetterQueueKey(tenantId: string): string;
567
625
  /** Get the dead letter queue data hash key for a tenant */
568
626
  deadLetterQueueDataKey(tenantId: string): string;
627
+ /** Get the dispatch index key for a shard (Level 1: tenantIds with capacity) */
628
+ dispatchKey(shardId: number): string;
629
+ /** Get the per-tenant queue index key (Level 2: queueIds for a tenant) */
630
+ tenantQueueIndexKey(tenantId: string): string;
569
631
  /** Extract tenant ID from a queue ID */
570
632
  extractTenantId(queueId: string): string;
571
633
  /** Extract a specific group ID from a queue ID */
@@ -822,7 +884,9 @@ interface WeightedSchedulerConfig {
822
884
  * Uses a configurable prefix and standard key structure.
823
885
  *
824
886
  * Key structure:
825
- * - Master queue: {prefix}:master:{shardId}
887
+ * - Master queue: {prefix}:master:{shardId} (legacy, drain-only)
888
+ * - Dispatch index: {prefix}:dispatch:{shardId} (Level 1: tenantIds)
889
+ * - Tenant queue index: {prefix}:tenantq:{tenantId} (Level 2: queueIds)
826
890
  * - Queue: {prefix}:queue:{queueId}
827
891
  * - Queue items: {prefix}:queue:{queueId}:items
828
892
  * - Concurrency: {prefix}:concurrency:{groupName}:{groupId}
@@ -845,6 +909,8 @@ declare class DefaultFairQueueKeyProducer implements FairQueueKeyProducer {
845
909
  inflightKey(shardId: number): string;
846
910
  inflightDataKey(shardId: number): string;
847
911
  workerQueueKey(consumerId: string): string;
912
+ dispatchKey(shardId: number): string;
913
+ tenantQueueIndexKey(tenantId: string): string;
848
914
  deadLetterQueueKey(tenantId: string): string;
849
915
  deadLetterQueueDataKey(tenantId: string): string;
850
916
  /**
@@ -1148,10 +1214,12 @@ declare class VisibilityManager {
1148
1214
  * @param queueId - The queue ID
1149
1215
  * @param queueKey - The Redis key for the queue
1150
1216
  * @param queueItemsKey - The Redis key for the queue items hash
1151
- * @param masterQueueKey - The Redis key for the master queue
1217
+ * @param tenantQueueIndexKey - The Redis key for the tenant queue index (Level 2)
1218
+ * @param dispatchKey - The Redis key for the dispatch index (Level 1)
1219
+ * @param tenantId - The tenant ID
1152
1220
  * @param score - Optional score for the message (defaults to now)
1153
1221
  */
1154
- release<TPayload = unknown>(messageId: string, queueId: string, queueKey: string, queueItemsKey: string, masterQueueKey: string, score?: number): Promise<void>;
1222
+ release<TPayload = unknown>(messageId: string, queueId: string, queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, tenantId: string, score?: number, updatedData?: string): Promise<void>;
1155
1223
  /**
1156
1224
  * Release multiple messages back to their queue in a single operation.
1157
1225
  * Used when processing fails or consumer wants to retry later.
@@ -1161,12 +1229,14 @@ declare class VisibilityManager {
1161
1229
  * @param queueId - The queue ID
1162
1230
  * @param queueKey - The Redis key for the queue
1163
1231
  * @param queueItemsKey - The Redis key for the queue items hash
1164
- * @param masterQueueKey - The Redis key for the master queue
1232
+ * @param tenantQueueIndexKey - The Redis key for the tenant queue index (Level 2)
1233
+ * @param dispatchKey - The Redis key for the dispatch index (Level 1)
1234
+ * @param tenantId - The tenant ID
1165
1235
  * @param score - Optional score for the messages (defaults to now)
1166
1236
  */
1167
1237
  releaseBatch(messages: Array<{
1168
1238
  messageId: string;
1169
- }>, queueId: string, queueKey: string, queueItemsKey: string, masterQueueKey: string, score?: number): Promise<void>;
1239
+ }>, queueId: string, queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, tenantId: string, score?: number): Promise<void>;
1170
1240
  /**
1171
1241
  * Reclaim timed-out messages from a shard.
1172
1242
  * Returns messages to their original queues.
@@ -1178,7 +1248,9 @@ declare class VisibilityManager {
1178
1248
  reclaimTimedOut(shardId: number, getQueueKeys: (queueId: string) => {
1179
1249
  queueKey: string;
1180
1250
  queueItemsKey: string;
1181
- masterQueueKey: string;
1251
+ tenantQueueIndexKey: string;
1252
+ dispatchKey: string;
1253
+ tenantId: string;
1182
1254
  }): Promise<ReclaimedMessageInfo[]>;
1183
1255
  /**
1184
1256
  * Get all in-flight messages for a shard.
@@ -1205,8 +1277,8 @@ declare module "@internal/redis" {
1205
1277
  interface RedisCommander<Context> {
1206
1278
  claimMessage(queueKey: string, queueItemsKey: string, inflightKey: string, inflightDataKey: string, queueId: string, consumerId: string, deadline: string): Promise<[string, string] | null>;
1207
1279
  claimMessageBatch(queueKey: string, queueItemsKey: string, inflightKey: string, inflightDataKey: string, queueId: string, deadline: string, maxCount: string): Promise<string[]>;
1208
- releaseMessage(inflightKey: string, inflightDataKey: string, queueKey: string, queueItemsKey: string, masterQueueKey: string, member: string, messageId: string, score: string, queueId: string): Promise<number>;
1209
- releaseMessageBatch(inflightKey: string, inflightDataKey: string, queueKey: string, queueItemsKey: string, masterQueueKey: string, score: string, queueId: string, ...membersAndMessageIds: string[]): Promise<number>;
1280
+ releaseMessage(inflightKey: string, inflightDataKey: string, queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, member: string, messageId: string, score: string, queueId: string, updatedData: string, tenantId: string): Promise<number>;
1281
+ releaseMessageBatch(inflightKey: string, inflightDataKey: string, queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, score: string, queueId: string, tenantId: string, ...membersAndMessageIds: string[]): Promise<number>;
1210
1282
  heartbeatMessage(inflightKey: string, member: string, newDeadline: string): Promise<number>;
1211
1283
  }
1212
1284
  }
@@ -1403,6 +1475,17 @@ declare class DRRScheduler extends BaseScheduler {
1403
1475
  * 6. Order tenants by deficit (highest first for fairness)
1404
1476
  */
1405
1477
  selectQueues(masterQueueShard: string, consumerId: string, context: SchedulerContext): Promise<TenantQueues[]>;
1478
+ /**
1479
+ * Select queues using the two-level tenant dispatch index.
1480
+ *
1481
+ * Algorithm:
1482
+ * 1. ZRANGEBYSCORE on dispatch index (gets only tenants with queues - much smaller)
1483
+ * 2. Add quantum to each tenant's deficit (atomically)
1484
+ * 3. Check capacity as safety net (dispatch should only have tenants with capacity)
1485
+ * 4. Select tenants with deficit >= 1, sorted by deficit (highest first)
1486
+ * 5. For each tenant, fetch their queues from Level 2 index
1487
+ */
1488
+ selectQueuesFromDispatch(dispatchShardKey: string, consumerId: string, context: DispatchSchedulerContext): Promise<TenantQueues[]>;
1406
1489
  /**
1407
1490
  * Record that a message was processed from a tenant.
1408
1491
  * Decrements the tenant's deficit.
@@ -1531,6 +1614,7 @@ interface FairQueueMetrics {
1531
1614
  queueTime: Histogram;
1532
1615
  queueLength: ObservableGauge;
1533
1616
  masterQueueLength: ObservableGauge;
1617
+ dispatchLength: ObservableGauge;
1534
1618
  inflightCount: ObservableGauge;
1535
1619
  dlqLength: ObservableGauge;
1536
1620
  }
@@ -1616,6 +1700,7 @@ declare class FairQueueTelemetry {
1616
1700
  registerGaugeCallbacks(callbacks: {
1617
1701
  getQueueLength?: (queueId: string) => Promise<number>;
1618
1702
  getMasterQueueLength?: (shardId: number) => Promise<number>;
1703
+ getDispatchLength?: (shardId: number) => Promise<number>;
1619
1704
  getInflightCount?: (shardId: number) => Promise<number>;
1620
1705
  getDLQLength?: (tenantId: string) => Promise<number>;
1621
1706
  shardCount?: number;
@@ -1749,6 +1834,83 @@ declare class BatchedSpanManager {
1749
1834
  */
1750
1835
  declare const noopTelemetry: FairQueueTelemetry;
1751
1836
 
1837
+ interface TenantDispatchOptions {
1838
+ redis: RedisOptions;
1839
+ keys: FairQueueKeyProducer;
1840
+ shardCount: number;
1841
+ }
1842
+ interface TenantWithScore {
1843
+ tenantId: string;
1844
+ score: number;
1845
+ }
1846
+ /**
1847
+ * TenantDispatch manages the two-level tenant dispatch index.
1848
+ *
1849
+ * Level 1 - Dispatch Index (per shard):
1850
+ * Key: {prefix}:dispatch:{shardId}
1851
+ * ZSET of tenantIds scored by oldest message timestamp across their queues.
1852
+ * Only tenants with queues containing messages appear here.
1853
+ *
1854
+ * Level 2 - Per-Tenant Queue Index:
1855
+ * Key: {prefix}:tenantq:{tenantId}
1856
+ * ZSET of queueIds scored by oldest message timestamp in that queue.
1857
+ *
1858
+ * This replaces the flat master queue for new enqueues, isolating each tenant's
1859
+ * queue backlog so the scheduler iterates tenants (not queues) at Level 1.
1860
+ */
1861
+ declare class TenantDispatch {
1862
+ private options;
1863
+ private redis;
1864
+ private keys;
1865
+ private shardCount;
1866
+ constructor(options: TenantDispatchOptions);
1867
+ /**
1868
+ * Get the dispatch shard ID for a tenant.
1869
+ * Uses jump consistent hash on the tenant ID so each tenant
1870
+ * always maps to exactly one dispatch shard.
1871
+ */
1872
+ getShardForTenant(tenantId: string): number;
1873
+ /**
1874
+ * Get eligible tenants from a dispatch shard (Level 1).
1875
+ * Returns tenants ordered by oldest message (lowest score first).
1876
+ */
1877
+ getTenantsFromShard(shardId: number, limit?: number, maxScore?: number): Promise<TenantWithScore[]>;
1878
+ /**
1879
+ * Get queues for a specific tenant (Level 2).
1880
+ * Returns queues ordered by oldest message (lowest score first).
1881
+ */
1882
+ getQueuesForTenant(tenantId: string, limit?: number, maxScore?: number): Promise<QueueWithScore[]>;
1883
+ /**
1884
+ * Get the number of tenants in a dispatch shard.
1885
+ */
1886
+ getShardTenantCount(shardId: number): Promise<number>;
1887
+ /**
1888
+ * Get total tenant count across all dispatch shards.
1889
+ * Note: tenants may appear in multiple shards, so this may overcount.
1890
+ */
1891
+ getTotalTenantCount(): Promise<number>;
1892
+ /**
1893
+ * Get the number of queues for a tenant.
1894
+ */
1895
+ getTenantQueueCount(tenantId: string): Promise<number>;
1896
+ /**
1897
+ * Remove a tenant from a specific dispatch shard.
1898
+ */
1899
+ removeTenantFromShard(shardId: number, tenantId: string): Promise<void>;
1900
+ /**
1901
+ * Add a tenant to a dispatch shard with the given score.
1902
+ */
1903
+ addTenantToShard(shardId: number, tenantId: string, score: number): Promise<void>;
1904
+ /**
1905
+ * Remove a queue from a tenant's queue index.
1906
+ */
1907
+ removeQueueFromTenant(tenantId: string, queueId: string): Promise<void>;
1908
+ /**
1909
+ * Close the Redis connection.
1910
+ */
1911
+ close(): Promise<void>;
1912
+ }
1913
+
1752
1914
  /**
1753
1915
  * FairQueue is the main orchestrator for fair queue message routing.
1754
1916
  *
@@ -1805,6 +1967,7 @@ declare class FairQueue<TPayloadSchema extends z.ZodTypeAny = z.ZodUnknown> {
1805
1967
  private masterQueueConsumerLoops;
1806
1968
  private reclaimLoop?;
1807
1969
  private queueDescriptorCache;
1970
+ private tenantDispatch;
1808
1971
  constructor(options: FairQueueOptions<TPayloadSchema>);
1809
1972
  /**
1810
1973
  * Register observable gauge callbacks for telemetry.
@@ -1882,7 +2045,7 @@ declare class FairQueue<TPayloadSchema extends z.ZodTypeAny = z.ZodUnknown> {
1882
2045
  */
1883
2046
  getQueueLength(queueId: string): Promise<number>;
1884
2047
  /**
1885
- * Get total queue count across all shards.
2048
+ * Get total tenant count across dispatch shards plus any legacy queues still draining.
1886
2049
  */
1887
2050
  getTotalQueueCount(): Promise<number>;
1888
2051
  /**
@@ -1942,7 +2105,10 @@ declare module "@internal/redis" {
1942
2105
  enqueueMessageAtomic(queueKey: string, queueItemsKey: string, masterQueueKey: string, queueId: string, messageId: string, timestamp: string, payload: string): Promise<number>;
1943
2106
  enqueueBatchAtomic(queueKey: string, queueItemsKey: string, masterQueueKey: string, queueId: string, ...args: string[]): Promise<number>;
1944
2107
  updateMasterQueueIfEmpty(masterQueueKey: string, queueKey: string, queueId: string): Promise<number>;
2108
+ enqueueMessageAtomicV2(queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, queueId: string, messageId: string, timestamp: string, payload: string, tenantId: string): Promise<number>;
2109
+ enqueueBatchAtomicV2(queueKey: string, queueItemsKey: string, tenantQueueIndexKey: string, dispatchKey: string, queueId: string, tenantId: string, ...args: string[]): Promise<number>;
2110
+ updateDispatchIndexes(queueKey: string, tenantQueueIndexKey: string, dispatchKey: string, queueId: string, tenantId: string): Promise<number>;
1945
2111
  }
1946
2112
  }
1947
2113
 
1948
- export { type AnyMessageCatalog, type AnyQueueItem, BaseScheduler, BatchedSpanManager, type BatchedSpanManagerOptions, CallbackFairQueueKeyProducer, type ClaimResult, type ConcurrencyCheckResult, type ConcurrencyGroupConfig, ConcurrencyManager, type ConcurrencyManagerOptions, type ConcurrencyState, type ConsumerLoopState, type CooloffOptions, CronSchema, CustomRetry, DRRScheduler, type DRRSchedulerConfig, type DeadLetterMessage, DefaultFairQueueKeyProducer, type EnqueueBatchOptions, type EnqueueOptions, ExponentialBackoffRetry, FairQueue, FairQueueAttributes, type FairQueueKeyProducer, type FairQueueMetrics, type FairQueueOptions, FairQueueTelemetry, type FairScheduler, FixedDelayRetry, type GlobalRateLimiter, ImmediateRetry, type InFlightMessage, type JobHandler, type JobHandlerParams, LinearBackoffRetry, MasterQueue, type MasterQueueOptions, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type MessageHandler, type MessageHandlerContext, MessagingAttributes, NoRetry, NoopScheduler, type QueueCooloffState, type QueueDescriptor, type QueueItem, type QueueMessage, type QueueWithScore, type ReclaimedMessageInfo, type RetryOptions, type RetryStrategy, RoundRobinScheduler, type SchedulerContext, SimpleQueue, type StoredMessage, type TelemetryOptions, type TenantQueues, VisibilityManager, type VisibilityManagerOptions, WeightedScheduler, type WeightedSchedulerBiases, type WeightedSchedulerConfig, Worker, type WorkerCatalog, type WorkerConcurrencyOptions, WorkerQueueManager, type WorkerQueueManagerOptions, type WorkerQueueOptions, createDefaultRetryStrategy, defaultRetryOptions, isAbortError, noopTelemetry };
2114
+ export { type AnyMessageCatalog, type AnyQueueItem, BaseScheduler, type BatchConfig, BatchedSpanManager, type BatchedSpanManagerOptions, CallbackFairQueueKeyProducer, type ClaimResult, type ConcurrencyCheckResult, type ConcurrencyGroupConfig, ConcurrencyManager, type ConcurrencyManagerOptions, type ConcurrencyState, type ConsumerLoopState, type CooloffOptions, CronSchema, CustomRetry, DRRScheduler, type DRRSchedulerConfig, type DeadLetterMessage, DefaultFairQueueKeyProducer, type DispatchSchedulerContext, type EnqueueBatchOptions, type EnqueueOptions, ExponentialBackoffRetry, FairQueue, FairQueueAttributes, type FairQueueKeyProducer, type FairQueueMetrics, type FairQueueOptions, FairQueueTelemetry, type FairScheduler, FixedDelayRetry, type GlobalRateLimiter, ImmediateRetry, type InFlightMessage, type JobHandler, type JobHandlerParams, LinearBackoffRetry, MasterQueue, type MasterQueueOptions, type MessageCatalogKey, type MessageCatalogSchema, type MessageCatalogValue, type MessageHandler, type MessageHandlerContext, MessagingAttributes, NoRetry, NoopScheduler, type QueueCooloffState, type QueueDescriptor, type QueueItem, type QueueMessage, type QueueWithScore, type ReclaimedMessageInfo, type RetryOptions, type RetryStrategy, RoundRobinScheduler, type SchedulerContext, SimpleQueue, type StoredMessage, type TelemetryOptions, TenantDispatch, type TenantDispatchOptions, type TenantQueues, type TenantWithScore, VisibilityManager, type VisibilityManagerOptions, WeightedScheduler, type WeightedSchedulerBiases, type WeightedSchedulerConfig, Worker, type WorkerCatalog, type WorkerConcurrencyOptions, WorkerQueueManager, type WorkerQueueManagerOptions, type WorkerQueueOptions, createDefaultRetryStrategy, defaultRetryOptions, isAbortError, noopTelemetry };