@hatchet-dev/typescript-sdk 1.35.0 → 1.36.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.
@@ -225,8 +225,6 @@ class ActionListener {
225
225
  }
226
226
  });
227
227
  }
228
- // Leaves the heartbeat running: tasks that are already in flight still need
229
- // the engine to see this worker as alive until they finish.
230
228
  stopStream() {
231
229
  this.done = true;
232
230
  // Abort the gRPC stream to immediately cancel the generator
@@ -1,4 +1,4 @@
1
- import { APIErrors, APIMeta, AcceptInviteRequest, BulkCreateEventRequest, CancelEventRequest, CreateAPITokenRequest, CreateAPITokenResponse, CreateCronWorkflowTriggerRequest, CreateEventRequest, CreateSNSIntegrationRequest, CreateTenantAlertEmailGroupRequest, CreateTenantInviteRequest, CreateTenantRequest, CronWorkflows, CronWorkflowsList, CronWorkflowsOrderByField, Event, EventData, EventKey, EventKeyList, EventList, EventOrderByDirection, EventOrderByField, EventSearch, Events, FeatureFlagEvaluationResult, FeatureFlagId, ListAPIMetaIntegration, ListAPITokensResponse, ListSNSIntegrations, ListSlackWebhooks, OtelSpanList, RateLimitList, RateLimitOrderByDirection, RateLimitOrderByField, RejectInviteRequest, ReplayEventRequest, ReplayWorkflowRunsRequest, ReplayWorkflowRunsResponse, RerunStepRunRequest, SNSIntegration, ScheduleWorkflowRunRequest, ScheduledRunStatus, ScheduledWorkflows, ScheduledWorkflowsBulkDeleteRequest, ScheduledWorkflowsBulkDeleteResponse, ScheduledWorkflowsBulkUpdateRequest, ScheduledWorkflowsBulkUpdateResponse, ScheduledWorkflowsList, ScheduledWorkflowsOrderByField, StepRun, StepRunArchiveList, StepRunEventList, TaskStats, Tenant, TenantAlertEmailGroup, TenantAlertEmailGroupList, TenantAlertingSettings, TenantInvite, TenantInviteList, TenantMember, TenantMemberList, TenantQueueMetrics, TenantResourcePolicy, TenantStepRunQueueMetrics, TriggerRunResult, TriggerWorkflowRunRequest, UpdateCronWorkflowTriggerRequest, UpdateScheduledWorkflowRunRequest, UpdateTenantAlertEmailGroupRequest, UpdateTenantInviteRequest, UpdateTenantMemberRequest, UpdateTenantRequest, UpdateWorkerRequest, User, UserChangePasswordRequest, UserLoginRequest, UserRegisterRequest, UserTenantMembershipsList, V1AdditionalMetadataOperator, V1BranchDurableTaskRequest, V1BranchDurableTaskResponse, V1CELDebugRequest, V1CELDebugResponse, V1CancelTaskRequest, V1CancelledTasks, V1CreateFilterRequest, V1CreateWebhookRequest, V1DagChildren, V1DurableEventLogList, V1Event, V1EventList, V1Filter, V1FilterList, V1LogLineLevel, V1LogLineList, V1LogLineOrderByDirection, V1LogsPointMetrics, V1ReplayTaskRequest, V1ReplayedTasks, V1RestoreTaskResponse, V1RunningFilter, V1TaskEventList, V1TaskPointMetrics, V1TaskRunMetrics, V1TaskStatus, V1TaskSummary, V1TaskSummaryList, V1TaskTimingList, V1TriggerWorkflowRunRequest, V1UpdateFilterRequest, V1UpdateWebhookRequest, V1Webhook, V1WebhookList, V1WebhookResponse, V1WebhookSourceName, V1WorkflowRunDetails, V1WorkflowRunDisplayNameList, V1WorkflowRunExternalIdList, WebhookWorkerCreateRequest, WebhookWorkerCreated, WebhookWorkerListResponse, WebhookWorkerRequestListResponse, Worker, WorkerList, WorkerStatus, Workflow, WorkflowID, WorkflowKindList, WorkflowList, WorkflowMetrics, WorkflowRun, WorkflowRunList, WorkflowRunOrderByDirection, WorkflowRunOrderByField, WorkflowRunShape, WorkflowRunStatus, WorkflowRunStatusList, WorkflowRunsCancelRequest, WorkflowRunsMetrics, WorkflowUpdateRequest, WorkflowVersion, WorkflowWorkersCount } from './data-contracts';
1
+ import { APIErrors, APIMeta, AcceptInviteRequest, BulkCreateEventRequest, CancelEventRequest, CreateAPITokenRequest, CreateAPITokenResponse, CreateCronWorkflowTriggerRequest, CreateEventRequest, CreateSNSIntegrationRequest, CreateTenantAlertEmailGroupRequest, CreateTenantInviteRequest, CreateTenantRequest, CronWorkflows, CronWorkflowsList, CronWorkflowsOrderByField, Event, EventData, EventKey, EventKeyList, EventList, EventOrderByDirection, EventOrderByField, EventSearch, Events, FeatureFlagEvaluationResult, FeatureFlagId, ListAPIMetaIntegration, ListAPITokensResponse, ListSNSIntegrations, ListSlackWebhooks, OtelSpanList, RateLimitList, RateLimitOrderByDirection, RateLimitOrderByField, RejectInviteRequest, ReplayEventRequest, ReplayWorkflowRunsRequest, ReplayWorkflowRunsResponse, RerunStepRunRequest, SNSIntegration, ScheduleWorkflowRunRequest, ScheduledRunStatus, ScheduledWorkflows, ScheduledWorkflowsBulkDeleteRequest, ScheduledWorkflowsBulkDeleteResponse, ScheduledWorkflowsBulkUpdateRequest, ScheduledWorkflowsBulkUpdateResponse, ScheduledWorkflowsList, ScheduledWorkflowsOrderByField, StepRun, StepRunArchiveList, StepRunEventList, TaskStats, Tenant, TenantAlertEmailGroup, TenantAlertEmailGroupList, TenantAlertingSettings, TenantInvite, TenantInviteList, TenantMember, TenantMemberList, TenantQueueMetrics, TenantResourcePolicy, TenantStepRunQueueMetrics, TriggerRunResult, TriggerWorkflowRunRequest, UpdateCronWorkflowTriggerRequest, UpdateScheduledWorkflowRunRequest, UpdateTenantAlertEmailGroupRequest, UpdateTenantInviteRequest, UpdateTenantMemberRequest, UpdateTenantRequest, UpdateWorkerRequest, User, UserChangePasswordRequest, UserLoginRequest, UserRegisterRequest, UserTenantMembershipsList, V1AdditionalMetadataOperator, V1BranchDurableTaskRequest, V1BranchDurableTaskResponse, V1CELDebugRequest, V1CELDebugResponse, V1CancelTaskRequest, V1CancelledTasks, V1CreateFilterRequest, V1CreateWebhookRequest, V1DagChildren, V1DurableEventLogList, V1Event, V1EventList, V1Filter, V1FilterList, V1LogLineLevel, V1LogLineList, V1LogLineOrderByDirection, V1LogsPointMetrics, V1ReplayTaskRequest, V1ReplayedTasks, V1RestoreTaskResponse, V1RunningFilter, V1StreamPayloadRef, V1TaskEventList, V1TaskPointMetrics, V1TaskRunMetrics, V1TaskStatus, V1TaskSummary, V1TaskSummaryList, V1TaskTimingList, V1TriggerWorkflowRunRequest, V1UpdateFilterRequest, V1UpdateWebhookRequest, V1Webhook, V1WebhookList, V1WebhookResponse, V1WebhookSourceName, V1WorkflowRunDetails, V1WorkflowRunDisplayNameList, V1WorkflowRunExternalIdList, WebhookWorkerCreateRequest, WebhookWorkerCreated, WebhookWorkerListResponse, WebhookWorkerRequestListResponse, Worker, WorkerList, WorkerStatus, Workflow, WorkflowID, WorkflowKindList, WorkflowList, WorkflowMetrics, WorkflowRun, WorkflowRunList, WorkflowRunOrderByDirection, WorkflowRunOrderByField, WorkflowRunShape, WorkflowRunStatus, WorkflowRunStatusList, WorkflowRunsCancelRequest, WorkflowRunsMetrics, WorkflowUpdateRequest, WorkflowVersion, WorkflowWorkersCount } from './data-contracts';
2
2
  import { HttpClient, RequestParams } from './http-client';
3
3
  export declare class Api<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {
4
4
  /**
@@ -341,6 +341,32 @@ export declare class Api<SecurityDataType = unknown> extends HttpClient<Security
341
341
  */
342
342
  limit?: number;
343
343
  }, params?: RequestParams) => Promise<import("axios").AxiosResponse<V1DurableEventLogList, any, {}>>;
344
+ /**
345
+ * @description Get a durable stream payload uploaded ahead of its publish, by the payload_ref of the message that references it.
346
+ *
347
+ * @tags Streams
348
+ * @name V1StreamPayloadGet
349
+ * @summary Get stream payload
350
+ * @request GET:/api/v1/stable/tenants/{tenant}/streams/payloads
351
+ * @secure
352
+ */
353
+ v1StreamPayloadGet: (tenant: string, query: {
354
+ /** The payload_ref of the stream message */
355
+ ref: string;
356
+ }, params?: RequestParams) => Promise<import("axios").AxiosResponse<File, any, {}>>;
357
+ /**
358
+ * @description Upload a payload too large for a gRPC publish, then publish the message with the returned ref as its payload_ref.
359
+ *
360
+ * @tags Streams
361
+ * @name V1StreamPayloadUpload
362
+ * @summary Upload stream payload
363
+ * @request POST:/api/v1/stable/tenants/{tenant}/streams/payloads
364
+ * @secure
365
+ */
366
+ v1StreamPayloadUpload: (tenant: string, data: {
367
+ /** @format binary */
368
+ payload: File;
369
+ }, params?: RequestParams) => Promise<import("axios").AxiosResponse<V1StreamPayloadRef, any, {}>>;
344
370
  /**
345
371
  * @description Get a workflow run and its metadata to display on the "detail" page
346
372
  *
@@ -166,6 +166,26 @@ class Api extends http_client_1.HttpClient {
166
166
  * @secure
167
167
  */
168
168
  this.v1DurableTaskEventLogList = (tenant, durableTask, query, params = {}) => this.request(Object.assign({ path: `/api/v1/stable/tenants/${tenant}/durable-tasks/${durableTask}`, method: 'GET', query: query, secure: true, format: 'json' }, params));
169
+ /**
170
+ * @description Get a durable stream payload uploaded ahead of its publish, by the payload_ref of the message that references it.
171
+ *
172
+ * @tags Streams
173
+ * @name V1StreamPayloadGet
174
+ * @summary Get stream payload
175
+ * @request GET:/api/v1/stable/tenants/{tenant}/streams/payloads
176
+ * @secure
177
+ */
178
+ this.v1StreamPayloadGet = (tenant, query, params = {}) => this.request(Object.assign({ path: `/api/v1/stable/tenants/${tenant}/streams/payloads`, method: 'GET', query: query, secure: true }, params));
179
+ /**
180
+ * @description Upload a payload too large for a gRPC publish, then publish the message with the returned ref as its payload_ref.
181
+ *
182
+ * @tags Streams
183
+ * @name V1StreamPayloadUpload
184
+ * @summary Upload stream payload
185
+ * @request POST:/api/v1/stable/tenants/{tenant}/streams/payloads
186
+ * @secure
187
+ */
188
+ this.v1StreamPayloadUpload = (tenant, data, params = {}) => this.request(Object.assign({ path: `/api/v1/stable/tenants/${tenant}/streams/payloads`, method: 'POST', body: data, secure: true, type: http_client_1.ContentType.FormData, format: 'json' }, params));
169
189
  /**
170
190
  * @description Get a workflow run and its metadata to display on the "detail" page
171
191
  *
@@ -766,6 +766,10 @@ export interface V1DurableEventLogEntry {
766
766
  taskDisplayName: string;
767
767
  }
768
768
  export type V1DurableEventLogList = V1DurableEventLogEntry[];
769
+ export interface V1StreamPayloadRef {
770
+ /** Publish the message with this as its payload_ref */
771
+ ref: string;
772
+ }
769
773
  export interface OtelSpan {
770
774
  traceId: string;
771
775
  spanId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchet-dev/typescript-sdk",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "engines": {
5
5
  "node": ">=20"
6
6
  },
@@ -7,6 +7,11 @@ export interface PublishStreamMessageRequest {
7
7
  payload: Uint8Array;
8
8
  producerId: string;
9
9
  producerSeq: number;
10
+ /**
11
+ * instead of payload: a ref returned by the stream payload upload endpoint,
12
+ * for payloads too large for one gRPC message
13
+ */
14
+ payloadRef: string;
10
15
  }
11
16
  export interface PublishStreamMessageResponse {
12
17
  }
@@ -24,12 +29,31 @@ export interface StreamEntry {
24
29
  payload: Uint8Array;
25
30
  cursor: string;
26
31
  createdAt: Date | undefined;
32
+ /** instead of payload: fetch it from the stream payload endpoint by this ref */
33
+ payloadRef: string;
34
+ }
35
+ export interface GetStreamTopicMetadataRequest {
36
+ namespace: string;
37
+ topic: string;
38
+ }
39
+ /** Counts and the latest message cover only what's within the tenant's retention. */
40
+ export interface StreamTopicMetadata {
41
+ namespace: string;
42
+ topic: string;
43
+ tenantId: string;
44
+ /** resumes a subscription after the newest retained message; unset when none is retained */
45
+ latestCursor?: string | undefined;
46
+ messageCount: number;
47
+ /** when the newest retained message was stored; unset when none is retained */
48
+ lastPublishedAt: Date | undefined;
27
49
  }
28
50
  export declare const PublishStreamMessageRequest: MessageFns<PublishStreamMessageRequest>;
29
51
  export declare const PublishStreamMessageResponse: MessageFns<PublishStreamMessageResponse>;
30
52
  export declare const SubscribeStreamRequest: MessageFns<SubscribeStreamRequest>;
31
53
  export declare const StreamMessage: MessageFns<StreamMessage>;
32
54
  export declare const StreamEntry: MessageFns<StreamEntry>;
55
+ export declare const GetStreamTopicMetadataRequest: MessageFns<GetStreamTopicMetadataRequest>;
56
+ export declare const StreamTopicMetadata: MessageFns<StreamTopicMetadata>;
33
57
  export type V1StreamsDefinition = typeof V1StreamsDefinition;
34
58
  export declare const V1StreamsDefinition: {
35
59
  readonly name: "V1Streams";
@@ -51,15 +75,25 @@ export declare const V1StreamsDefinition: {
51
75
  readonly responseStream: true;
52
76
  readonly options: {};
53
77
  };
78
+ readonly getTopicMetadata: {
79
+ readonly name: "GetTopicMetadata";
80
+ readonly requestType: typeof GetStreamTopicMetadataRequest;
81
+ readonly requestStream: false;
82
+ readonly responseType: typeof StreamTopicMetadata;
83
+ readonly responseStream: false;
84
+ readonly options: {};
85
+ };
54
86
  };
55
87
  };
56
88
  export interface V1StreamsServiceImplementation<CallContextExt = {}> {
57
89
  publish(request: PublishStreamMessageRequest, context: CallContext & CallContextExt): Promise<DeepPartial<PublishStreamMessageResponse>>;
58
90
  subscribe(request: SubscribeStreamRequest, context: CallContext & CallContextExt): ServerStreamingMethodResult<DeepPartial<StreamMessage>>;
91
+ getTopicMetadata(request: GetStreamTopicMetadataRequest, context: CallContext & CallContextExt): Promise<DeepPartial<StreamTopicMetadata>>;
59
92
  }
60
93
  export interface V1StreamsClient<CallOptionsExt = {}> {
61
94
  publish(request: DeepPartial<PublishStreamMessageRequest>, options?: CallOptions & CallOptionsExt): Promise<PublishStreamMessageResponse>;
62
95
  subscribe(request: DeepPartial<SubscribeStreamRequest>, options?: CallOptions & CallOptionsExt): AsyncIterable<StreamMessage>;
96
+ getTopicMetadata(request: DeepPartial<GetStreamTopicMetadataRequest>, options?: CallOptions & CallOptionsExt): Promise<StreamTopicMetadata>;
63
97
  }
64
98
  type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined;
65
99
  export type DeepPartial<T> = T extends Builtin ? T : T extends globalThis.Array<infer U> ? globalThis.Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends {} ? {
@@ -5,13 +5,20 @@
5
5
  // protoc v3.19.1
6
6
  // source: v1/streams.proto
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.V1StreamsDefinition = exports.StreamEntry = exports.StreamMessage = exports.SubscribeStreamRequest = exports.PublishStreamMessageResponse = exports.PublishStreamMessageRequest = exports.protobufPackage = void 0;
8
+ exports.V1StreamsDefinition = exports.StreamTopicMetadata = exports.GetStreamTopicMetadataRequest = exports.StreamEntry = exports.StreamMessage = exports.SubscribeStreamRequest = exports.PublishStreamMessageResponse = exports.PublishStreamMessageRequest = exports.protobufPackage = void 0;
9
9
  /* eslint-disable */
10
10
  const wire_1 = require("@bufbuild/protobuf/wire");
11
11
  const timestamp_1 = require("../google/protobuf/timestamp");
12
12
  exports.protobufPackage = 'v1';
13
13
  function createBasePublishStreamMessageRequest() {
14
- return { namespace: '', topic: '', payload: new Uint8Array(0), producerId: '', producerSeq: 0 };
14
+ return {
15
+ namespace: '',
16
+ topic: '',
17
+ payload: new Uint8Array(0),
18
+ producerId: '',
19
+ producerSeq: 0,
20
+ payloadRef: '',
21
+ };
15
22
  }
16
23
  exports.PublishStreamMessageRequest = {
17
24
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -30,6 +37,9 @@ exports.PublishStreamMessageRequest = {
30
37
  if (message.producerSeq !== 0) {
31
38
  writer.uint32(40).int64(message.producerSeq);
32
39
  }
40
+ if (message.payloadRef !== '') {
41
+ writer.uint32(50).string(message.payloadRef);
42
+ }
33
43
  return writer;
34
44
  },
35
45
  decode(input, length) {
@@ -74,6 +84,13 @@ exports.PublishStreamMessageRequest = {
74
84
  message.producerSeq = longToNumber(reader.int64());
75
85
  continue;
76
86
  }
87
+ case 6: {
88
+ if (tag !== 50) {
89
+ break;
90
+ }
91
+ message.payloadRef = reader.string();
92
+ continue;
93
+ }
77
94
  }
78
95
  if ((tag & 7) === 4 || tag === 0) {
79
96
  break;
@@ -97,6 +114,11 @@ exports.PublishStreamMessageRequest = {
97
114
  : isSet(object.producer_seq)
98
115
  ? globalThis.Number(object.producer_seq)
99
116
  : 0,
117
+ payloadRef: isSet(object.payloadRef)
118
+ ? globalThis.String(object.payloadRef)
119
+ : isSet(object.payload_ref)
120
+ ? globalThis.String(object.payload_ref)
121
+ : '',
100
122
  };
101
123
  },
102
124
  toJSON(message) {
@@ -116,19 +138,23 @@ exports.PublishStreamMessageRequest = {
116
138
  if (message.producerSeq !== 0) {
117
139
  obj.producerSeq = Math.round(message.producerSeq);
118
140
  }
141
+ if (message.payloadRef !== '') {
142
+ obj.payloadRef = message.payloadRef;
143
+ }
119
144
  return obj;
120
145
  },
121
146
  create(base) {
122
147
  return exports.PublishStreamMessageRequest.fromPartial(base !== null && base !== void 0 ? base : {});
123
148
  },
124
149
  fromPartial(object) {
125
- var _a, _b, _c, _d, _e;
150
+ var _a, _b, _c, _d, _e, _f;
126
151
  const message = createBasePublishStreamMessageRequest();
127
152
  message.namespace = (_a = object.namespace) !== null && _a !== void 0 ? _a : '';
128
153
  message.topic = (_b = object.topic) !== null && _b !== void 0 ? _b : '';
129
154
  message.payload = (_c = object.payload) !== null && _c !== void 0 ? _c : new Uint8Array(0);
130
155
  message.producerId = (_d = object.producerId) !== null && _d !== void 0 ? _d : '';
131
156
  message.producerSeq = (_e = object.producerSeq) !== null && _e !== void 0 ? _e : 0;
157
+ message.payloadRef = (_f = object.payloadRef) !== null && _f !== void 0 ? _f : '';
132
158
  return message;
133
159
  },
134
160
  };
@@ -341,7 +367,7 @@ exports.StreamMessage = {
341
367
  },
342
368
  };
343
369
  function createBaseStreamEntry() {
344
- return { payload: new Uint8Array(0), cursor: '', createdAt: undefined };
370
+ return { payload: new Uint8Array(0), cursor: '', createdAt: undefined, payloadRef: '' };
345
371
  }
346
372
  exports.StreamEntry = {
347
373
  encode(message, writer = new wire_1.BinaryWriter()) {
@@ -354,6 +380,9 @@ exports.StreamEntry = {
354
380
  if (message.createdAt !== undefined) {
355
381
  timestamp_1.Timestamp.encode(toTimestamp(message.createdAt), writer.uint32(26).fork()).join();
356
382
  }
383
+ if (message.payloadRef !== '') {
384
+ writer.uint32(34).string(message.payloadRef);
385
+ }
357
386
  return writer;
358
387
  },
359
388
  decode(input, length) {
@@ -384,6 +413,13 @@ exports.StreamEntry = {
384
413
  message.createdAt = fromTimestamp(timestamp_1.Timestamp.decode(reader, reader.uint32()));
385
414
  continue;
386
415
  }
416
+ case 4: {
417
+ if (tag !== 34) {
418
+ break;
419
+ }
420
+ message.payloadRef = reader.string();
421
+ continue;
422
+ }
387
423
  }
388
424
  if ((tag & 7) === 4 || tag === 0) {
389
425
  break;
@@ -401,6 +437,11 @@ exports.StreamEntry = {
401
437
  : isSet(object.created_at)
402
438
  ? fromJsonTimestamp(object.created_at)
403
439
  : undefined,
440
+ payloadRef: isSet(object.payloadRef)
441
+ ? globalThis.String(object.payloadRef)
442
+ : isSet(object.payload_ref)
443
+ ? globalThis.String(object.payload_ref)
444
+ : '',
404
445
  };
405
446
  },
406
447
  toJSON(message) {
@@ -414,17 +455,242 @@ exports.StreamEntry = {
414
455
  if (message.createdAt !== undefined) {
415
456
  obj.createdAt = message.createdAt.toISOString();
416
457
  }
458
+ if (message.payloadRef !== '') {
459
+ obj.payloadRef = message.payloadRef;
460
+ }
417
461
  return obj;
418
462
  },
419
463
  create(base) {
420
464
  return exports.StreamEntry.fromPartial(base !== null && base !== void 0 ? base : {});
421
465
  },
422
466
  fromPartial(object) {
423
- var _a, _b, _c;
467
+ var _a, _b, _c, _d;
424
468
  const message = createBaseStreamEntry();
425
469
  message.payload = (_a = object.payload) !== null && _a !== void 0 ? _a : new Uint8Array(0);
426
470
  message.cursor = (_b = object.cursor) !== null && _b !== void 0 ? _b : '';
427
471
  message.createdAt = (_c = object.createdAt) !== null && _c !== void 0 ? _c : undefined;
472
+ message.payloadRef = (_d = object.payloadRef) !== null && _d !== void 0 ? _d : '';
473
+ return message;
474
+ },
475
+ };
476
+ function createBaseGetStreamTopicMetadataRequest() {
477
+ return { namespace: '', topic: '' };
478
+ }
479
+ exports.GetStreamTopicMetadataRequest = {
480
+ encode(message, writer = new wire_1.BinaryWriter()) {
481
+ if (message.namespace !== '') {
482
+ writer.uint32(10).string(message.namespace);
483
+ }
484
+ if (message.topic !== '') {
485
+ writer.uint32(18).string(message.topic);
486
+ }
487
+ return writer;
488
+ },
489
+ decode(input, length) {
490
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
491
+ const end = length === undefined ? reader.len : reader.pos + length;
492
+ const message = createBaseGetStreamTopicMetadataRequest();
493
+ while (reader.pos < end) {
494
+ const tag = reader.uint32();
495
+ switch (tag >>> 3) {
496
+ case 1: {
497
+ if (tag !== 10) {
498
+ break;
499
+ }
500
+ message.namespace = reader.string();
501
+ continue;
502
+ }
503
+ case 2: {
504
+ if (tag !== 18) {
505
+ break;
506
+ }
507
+ message.topic = reader.string();
508
+ continue;
509
+ }
510
+ }
511
+ if ((tag & 7) === 4 || tag === 0) {
512
+ break;
513
+ }
514
+ reader.skip(tag & 7);
515
+ }
516
+ return message;
517
+ },
518
+ fromJSON(object) {
519
+ return {
520
+ namespace: isSet(object.namespace) ? globalThis.String(object.namespace) : '',
521
+ topic: isSet(object.topic) ? globalThis.String(object.topic) : '',
522
+ };
523
+ },
524
+ toJSON(message) {
525
+ const obj = {};
526
+ if (message.namespace !== '') {
527
+ obj.namespace = message.namespace;
528
+ }
529
+ if (message.topic !== '') {
530
+ obj.topic = message.topic;
531
+ }
532
+ return obj;
533
+ },
534
+ create(base) {
535
+ return exports.GetStreamTopicMetadataRequest.fromPartial(base !== null && base !== void 0 ? base : {});
536
+ },
537
+ fromPartial(object) {
538
+ var _a, _b;
539
+ const message = createBaseGetStreamTopicMetadataRequest();
540
+ message.namespace = (_a = object.namespace) !== null && _a !== void 0 ? _a : '';
541
+ message.topic = (_b = object.topic) !== null && _b !== void 0 ? _b : '';
542
+ return message;
543
+ },
544
+ };
545
+ function createBaseStreamTopicMetadata() {
546
+ return {
547
+ namespace: '',
548
+ topic: '',
549
+ tenantId: '',
550
+ latestCursor: undefined,
551
+ messageCount: 0,
552
+ lastPublishedAt: undefined,
553
+ };
554
+ }
555
+ exports.StreamTopicMetadata = {
556
+ encode(message, writer = new wire_1.BinaryWriter()) {
557
+ if (message.namespace !== '') {
558
+ writer.uint32(10).string(message.namespace);
559
+ }
560
+ if (message.topic !== '') {
561
+ writer.uint32(18).string(message.topic);
562
+ }
563
+ if (message.tenantId !== '') {
564
+ writer.uint32(26).string(message.tenantId);
565
+ }
566
+ if (message.latestCursor !== undefined) {
567
+ writer.uint32(34).string(message.latestCursor);
568
+ }
569
+ if (message.messageCount !== 0) {
570
+ writer.uint32(40).int64(message.messageCount);
571
+ }
572
+ if (message.lastPublishedAt !== undefined) {
573
+ timestamp_1.Timestamp.encode(toTimestamp(message.lastPublishedAt), writer.uint32(50).fork()).join();
574
+ }
575
+ return writer;
576
+ },
577
+ decode(input, length) {
578
+ const reader = input instanceof wire_1.BinaryReader ? input : new wire_1.BinaryReader(input);
579
+ const end = length === undefined ? reader.len : reader.pos + length;
580
+ const message = createBaseStreamTopicMetadata();
581
+ while (reader.pos < end) {
582
+ const tag = reader.uint32();
583
+ switch (tag >>> 3) {
584
+ case 1: {
585
+ if (tag !== 10) {
586
+ break;
587
+ }
588
+ message.namespace = reader.string();
589
+ continue;
590
+ }
591
+ case 2: {
592
+ if (tag !== 18) {
593
+ break;
594
+ }
595
+ message.topic = reader.string();
596
+ continue;
597
+ }
598
+ case 3: {
599
+ if (tag !== 26) {
600
+ break;
601
+ }
602
+ message.tenantId = reader.string();
603
+ continue;
604
+ }
605
+ case 4: {
606
+ if (tag !== 34) {
607
+ break;
608
+ }
609
+ message.latestCursor = reader.string();
610
+ continue;
611
+ }
612
+ case 5: {
613
+ if (tag !== 40) {
614
+ break;
615
+ }
616
+ message.messageCount = longToNumber(reader.int64());
617
+ continue;
618
+ }
619
+ case 6: {
620
+ if (tag !== 50) {
621
+ break;
622
+ }
623
+ message.lastPublishedAt = fromTimestamp(timestamp_1.Timestamp.decode(reader, reader.uint32()));
624
+ continue;
625
+ }
626
+ }
627
+ if ((tag & 7) === 4 || tag === 0) {
628
+ break;
629
+ }
630
+ reader.skip(tag & 7);
631
+ }
632
+ return message;
633
+ },
634
+ fromJSON(object) {
635
+ return {
636
+ namespace: isSet(object.namespace) ? globalThis.String(object.namespace) : '',
637
+ topic: isSet(object.topic) ? globalThis.String(object.topic) : '',
638
+ tenantId: isSet(object.tenantId)
639
+ ? globalThis.String(object.tenantId)
640
+ : isSet(object.tenant_id)
641
+ ? globalThis.String(object.tenant_id)
642
+ : '',
643
+ latestCursor: isSet(object.latestCursor)
644
+ ? globalThis.String(object.latestCursor)
645
+ : isSet(object.latest_cursor)
646
+ ? globalThis.String(object.latest_cursor)
647
+ : undefined,
648
+ messageCount: isSet(object.messageCount)
649
+ ? globalThis.Number(object.messageCount)
650
+ : isSet(object.message_count)
651
+ ? globalThis.Number(object.message_count)
652
+ : 0,
653
+ lastPublishedAt: isSet(object.lastPublishedAt)
654
+ ? fromJsonTimestamp(object.lastPublishedAt)
655
+ : isSet(object.last_published_at)
656
+ ? fromJsonTimestamp(object.last_published_at)
657
+ : undefined,
658
+ };
659
+ },
660
+ toJSON(message) {
661
+ const obj = {};
662
+ if (message.namespace !== '') {
663
+ obj.namespace = message.namespace;
664
+ }
665
+ if (message.topic !== '') {
666
+ obj.topic = message.topic;
667
+ }
668
+ if (message.tenantId !== '') {
669
+ obj.tenantId = message.tenantId;
670
+ }
671
+ if (message.latestCursor !== undefined) {
672
+ obj.latestCursor = message.latestCursor;
673
+ }
674
+ if (message.messageCount !== 0) {
675
+ obj.messageCount = Math.round(message.messageCount);
676
+ }
677
+ if (message.lastPublishedAt !== undefined) {
678
+ obj.lastPublishedAt = message.lastPublishedAt.toISOString();
679
+ }
680
+ return obj;
681
+ },
682
+ create(base) {
683
+ return exports.StreamTopicMetadata.fromPartial(base !== null && base !== void 0 ? base : {});
684
+ },
685
+ fromPartial(object) {
686
+ var _a, _b, _c, _d, _e, _f;
687
+ const message = createBaseStreamTopicMetadata();
688
+ message.namespace = (_a = object.namespace) !== null && _a !== void 0 ? _a : '';
689
+ message.topic = (_b = object.topic) !== null && _b !== void 0 ? _b : '';
690
+ message.tenantId = (_c = object.tenantId) !== null && _c !== void 0 ? _c : '';
691
+ message.latestCursor = (_d = object.latestCursor) !== null && _d !== void 0 ? _d : undefined;
692
+ message.messageCount = (_e = object.messageCount) !== null && _e !== void 0 ? _e : 0;
693
+ message.lastPublishedAt = (_f = object.lastPublishedAt) !== null && _f !== void 0 ? _f : undefined;
428
694
  return message;
429
695
  },
430
696
  };
@@ -448,6 +714,14 @@ exports.V1StreamsDefinition = {
448
714
  responseStream: true,
449
715
  options: {},
450
716
  },
717
+ getTopicMetadata: {
718
+ name: 'GetTopicMetadata',
719
+ requestType: exports.GetStreamTopicMetadataRequest,
720
+ requestStream: false,
721
+ responseType: exports.StreamTopicMetadata,
722
+ responseStream: false,
723
+ options: {},
724
+ },
451
725
  },
452
726
  };
453
727
  function bytesFromBase64(b64) {
@@ -29,6 +29,13 @@ export type PublishStreamMessageRequest = Message<"v1.PublishStreamMessageReques
29
29
  * @generated from field: int64 producer_seq = 5;
30
30
  */
31
31
  producerSeq: bigint;
32
+ /**
33
+ * instead of payload: a ref returned by the stream payload upload endpoint,
34
+ * for payloads too large for one gRPC message
35
+ *
36
+ * @generated from field: string payload_ref = 6;
37
+ */
38
+ payloadRef: string;
32
39
  };
33
40
  /**
34
41
  * Describes the message v1.PublishStreamMessageRequest.
@@ -104,12 +111,76 @@ export type StreamEntry = Message<"v1.StreamEntry"> & {
104
111
  * @generated from field: google.protobuf.Timestamp created_at = 3;
105
112
  */
106
113
  createdAt?: Timestamp | undefined;
114
+ /**
115
+ * instead of payload: fetch it from the stream payload endpoint by this ref
116
+ *
117
+ * @generated from field: string payload_ref = 4;
118
+ */
119
+ payloadRef: string;
107
120
  };
108
121
  /**
109
122
  * Describes the message v1.StreamEntry.
110
123
  * Use `create(StreamEntrySchema)` to create a new message.
111
124
  */
112
125
  export declare const StreamEntrySchema: GenMessage<StreamEntry>;
126
+ /**
127
+ * @generated from message v1.GetStreamTopicMetadataRequest
128
+ */
129
+ export type GetStreamTopicMetadataRequest = Message<"v1.GetStreamTopicMetadataRequest"> & {
130
+ /**
131
+ * @generated from field: string namespace = 1;
132
+ */
133
+ namespace: string;
134
+ /**
135
+ * @generated from field: string topic = 2;
136
+ */
137
+ topic: string;
138
+ };
139
+ /**
140
+ * Describes the message v1.GetStreamTopicMetadataRequest.
141
+ * Use `create(GetStreamTopicMetadataRequestSchema)` to create a new message.
142
+ */
143
+ export declare const GetStreamTopicMetadataRequestSchema: GenMessage<GetStreamTopicMetadataRequest>;
144
+ /**
145
+ * Counts and the latest message cover only what's within the tenant's retention.
146
+ *
147
+ * @generated from message v1.StreamTopicMetadata
148
+ */
149
+ export type StreamTopicMetadata = Message<"v1.StreamTopicMetadata"> & {
150
+ /**
151
+ * @generated from field: string namespace = 1;
152
+ */
153
+ namespace: string;
154
+ /**
155
+ * @generated from field: string topic = 2;
156
+ */
157
+ topic: string;
158
+ /**
159
+ * @generated from field: string tenant_id = 3;
160
+ */
161
+ tenantId: string;
162
+ /**
163
+ * resumes a subscription after the newest retained message; unset when none is retained
164
+ *
165
+ * @generated from field: optional string latest_cursor = 4;
166
+ */
167
+ latestCursor?: string | undefined;
168
+ /**
169
+ * @generated from field: int64 message_count = 5;
170
+ */
171
+ messageCount: bigint;
172
+ /**
173
+ * when the newest retained message was stored; unset when none is retained
174
+ *
175
+ * @generated from field: google.protobuf.Timestamp last_published_at = 6;
176
+ */
177
+ lastPublishedAt?: Timestamp | undefined;
178
+ };
179
+ /**
180
+ * Describes the message v1.StreamTopicMetadata.
181
+ * Use `create(StreamTopicMetadataSchema)` to create a new message.
182
+ */
183
+ export declare const StreamTopicMetadataSchema: GenMessage<StreamTopicMetadata>;
113
184
  /**
114
185
  * @generated from service v1.V1Streams
115
186
  */
@@ -130,4 +201,12 @@ export declare const V1Streams: GenService<{
130
201
  input: typeof SubscribeStreamRequestSchema;
131
202
  output: typeof StreamMessageSchema;
132
203
  };
204
+ /**
205
+ * @generated from rpc v1.V1Streams.GetTopicMetadata
206
+ */
207
+ getTopicMetadata: {
208
+ methodKind: "unary";
209
+ input: typeof GetStreamTopicMetadataRequestSchema;
210
+ output: typeof StreamTopicMetadataSchema;
211
+ };
133
212
  }>;
@@ -3,13 +3,13 @@
3
3
  // @generated from file v1/streams.proto (package v1, syntax proto3)
4
4
  /* eslint-disable */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.V1Streams = exports.StreamEntrySchema = exports.StreamMessageSchema = exports.SubscribeStreamRequestSchema = exports.PublishStreamMessageResponseSchema = exports.PublishStreamMessageRequestSchema = exports.file_v1_streams = void 0;
6
+ exports.V1Streams = exports.StreamTopicMetadataSchema = exports.GetStreamTopicMetadataRequestSchema = exports.StreamEntrySchema = exports.StreamMessageSchema = exports.SubscribeStreamRequestSchema = exports.PublishStreamMessageResponseSchema = exports.PublishStreamMessageRequestSchema = exports.file_v1_streams = void 0;
7
7
  const codegenv2_1 = require("@bufbuild/protobuf/codegenv2");
8
8
  const wkt_1 = require("@bufbuild/protobuf/wkt");
9
9
  /**
10
10
  * Describes the file v1/streams.proto.
11
11
  */
12
- exports.file_v1_streams = (0, codegenv2_1.fileDesc)("ChB2MS9zdHJlYW1zLnByb3RvEgJ2MSJ7ChtQdWJsaXNoU3RyZWFtTWVzc2FnZVJlcXVlc3QSEQoJbmFtZXNwYWNlGAEgASgJEg0KBXRvcGljGAIgASgJEg8KB3BheWxvYWQYAyABKAwSEwoLcHJvZHVjZXJfaWQYBCABKAkSFAoMcHJvZHVjZXJfc2VxGAUgASgDIh4KHFB1Ymxpc2hTdHJlYW1NZXNzYWdlUmVzcG9uc2UiWgoWU3Vic2NyaWJlU3RyZWFtUmVxdWVzdBIRCgluYW1lc3BhY2UYASABKAkSDQoFdG9waWMYAiABKAkSEwoGY3Vyc29yGAMgASgJSACIAQFCCQoHX2N1cnNvciJRCg1TdHJlYW1NZXNzYWdlEiAKB2VudHJpZXMYASADKAsyDy52MS5TdHJlYW1FbnRyeRIOCgZoYW5ndXAYAiABKAgSDgoGY3Vyc29yGAMgASgJIl4KC1N0cmVhbUVudHJ5Eg8KB3BheWxvYWQYASABKAwSDgoGY3Vyc29yGAIgASgJEi4KCmNyZWF0ZWRfYXQYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wMpsBCglWMVN0cmVhbXMSTgoHUHVibGlzaBIfLnYxLlB1Ymxpc2hTdHJlYW1NZXNzYWdlUmVxdWVzdBogLnYxLlB1Ymxpc2hTdHJlYW1NZXNzYWdlUmVzcG9uc2UiABI+CglTdWJzY3JpYmUSGi52MS5TdWJzY3JpYmVTdHJlYW1SZXF1ZXN0GhEudjEuU3RyZWFtTWVzc2FnZSIAMAFCQlpAZ2l0aHViLmNvbS9oYXRjaGV0LWRldi9oYXRjaGV0L2ludGVybmFsL3NlcnZpY2VzL3NoYXJlZC9wcm90by92MWIGcHJvdG8z", [wkt_1.file_google_protobuf_timestamp]);
12
+ exports.file_v1_streams = (0, codegenv2_1.fileDesc)("ChB2MS9zdHJlYW1zLnByb3RvEgJ2MSKQAQobUHVibGlzaFN0cmVhbU1lc3NhZ2VSZXF1ZXN0EhEKCW5hbWVzcGFjZRgBIAEoCRINCgV0b3BpYxgCIAEoCRIPCgdwYXlsb2FkGAMgASgMEhMKC3Byb2R1Y2VyX2lkGAQgASgJEhQKDHByb2R1Y2VyX3NlcRgFIAEoAxITCgtwYXlsb2FkX3JlZhgGIAEoCSIeChxQdWJsaXNoU3RyZWFtTWVzc2FnZVJlc3BvbnNlIloKFlN1YnNjcmliZVN0cmVhbVJlcXVlc3QSEQoJbmFtZXNwYWNlGAEgASgJEg0KBXRvcGljGAIgASgJEhMKBmN1cnNvchgDIAEoCUgAiAEBQgkKB19jdXJzb3IiUQoNU3RyZWFtTWVzc2FnZRIgCgdlbnRyaWVzGAEgAygLMg8udjEuU3RyZWFtRW50cnkSDgoGaGFuZ3VwGAIgASgIEg4KBmN1cnNvchgDIAEoCSJzCgtTdHJlYW1FbnRyeRIPCgdwYXlsb2FkGAEgASgMEg4KBmN1cnNvchgCIAEoCRIuCgpjcmVhdGVkX2F0GAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBITCgtwYXlsb2FkX3JlZhgEIAEoCSJBCh1HZXRTdHJlYW1Ub3BpY01ldGFkYXRhUmVxdWVzdBIRCgluYW1lc3BhY2UYASABKAkSDQoFdG9waWMYAiABKAkixgEKE1N0cmVhbVRvcGljTWV0YWRhdGESEQoJbmFtZXNwYWNlGAEgASgJEg0KBXRvcGljGAIgASgJEhEKCXRlbmFudF9pZBgDIAEoCRIaCg1sYXRlc3RfY3Vyc29yGAQgASgJSACIAQESFQoNbWVzc2FnZV9jb3VudBgFIAEoAxI1ChFsYXN0X3B1Ymxpc2hlZF9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXBCEAoOX2xhdGVzdF9jdXJzb3Iy7QEKCVYxU3RyZWFtcxJOCgdQdWJsaXNoEh8udjEuUHVibGlzaFN0cmVhbU1lc3NhZ2VSZXF1ZXN0GiAudjEuUHVibGlzaFN0cmVhbU1lc3NhZ2VSZXNwb25zZSIAEj4KCVN1YnNjcmliZRIaLnYxLlN1YnNjcmliZVN0cmVhbVJlcXVlc3QaES52MS5TdHJlYW1NZXNzYWdlIgAwARJQChBHZXRUb3BpY01ldGFkYXRhEiEudjEuR2V0U3RyZWFtVG9waWNNZXRhZGF0YVJlcXVlc3QaFy52MS5TdHJlYW1Ub3BpY01ldGFkYXRhIgBCQlpAZ2l0aHViLmNvbS9oYXRjaGV0LWRldi9oYXRjaGV0L2ludGVybmFsL3NlcnZpY2VzL3NoYXJlZC9wcm90by92MWIGcHJvdG8z", [wkt_1.file_google_protobuf_timestamp]);
13
13
  /**
14
14
  * Describes the message v1.PublishStreamMessageRequest.
15
15
  * Use `create(PublishStreamMessageRequestSchema)` to create a new message.
@@ -35,6 +35,16 @@ exports.StreamMessageSchema = (0, codegenv2_1.messageDesc)(exports.file_v1_strea
35
35
  * Use `create(StreamEntrySchema)` to create a new message.
36
36
  */
37
37
  exports.StreamEntrySchema = (0, codegenv2_1.messageDesc)(exports.file_v1_streams, 4);
38
+ /**
39
+ * Describes the message v1.GetStreamTopicMetadataRequest.
40
+ * Use `create(GetStreamTopicMetadataRequestSchema)` to create a new message.
41
+ */
42
+ exports.GetStreamTopicMetadataRequestSchema = (0, codegenv2_1.messageDesc)(exports.file_v1_streams, 5);
43
+ /**
44
+ * Describes the message v1.StreamTopicMetadata.
45
+ * Use `create(StreamTopicMetadataSchema)` to create a new message.
46
+ */
47
+ exports.StreamTopicMetadataSchema = (0, codegenv2_1.messageDesc)(exports.file_v1_streams, 6);
38
48
  /**
39
49
  * @generated from service v1.V1Streams
40
50
  */
@@ -7,6 +7,17 @@ export type StreamEvent = {
7
7
  /** when the message was stored */
8
8
  createdAt?: Date;
9
9
  };
10
+ /** Covers only messages within the tenant's retention. */
11
+ export type StreamTopicMetadata = {
12
+ namespace: string;
13
+ topic: string;
14
+ tenantId: string;
15
+ messageCount: number;
16
+ /** resumes events() after the newest retained message; undefined when none is retained */
17
+ latestCursor?: string;
18
+ /** when the newest retained message was stored; undefined when none is retained */
19
+ lastPublishedAt?: Date;
20
+ };
10
21
  export type StreamCallOptions = {
11
22
  namespace?: string;
12
23
  cursor?: string;
@@ -20,11 +31,15 @@ export type StreamCallOptions = {
20
31
  */
21
32
  export declare class StreamsClient {
22
33
  private _config;
34
+ private _client;
23
35
  private _grpc;
24
36
  private producers;
25
37
  private publishChains;
26
38
  constructor(client: HatchetClient);
27
39
  private get grpc();
40
+ private static withRetries;
41
+ private uploadPayload;
42
+ private fetchPayload;
28
43
  private publishOrdered;
29
44
  /**
30
45
  * Resolves once the message is stored. A client's messages to a topic are
@@ -34,6 +49,13 @@ export declare class StreamsClient {
34
49
  * @param options - optional namespace override
35
50
  */
36
51
  publish(topic: string, message: Uint8Array | string, options?: StreamCallOptions): Promise<void>;
52
+ /**
53
+ * Describes topic's retained messages. Rejects with a NOT_FOUND gRPC error
54
+ * for a topic nothing was published to.
55
+ * @param topic - the topic to describe
56
+ * @param options - optional namespace override
57
+ */
58
+ topicMetadata(topic: string, options?: Pick<StreamCallOptions, 'namespace'>): Promise<StreamTopicMetadata>;
37
59
  /**
38
60
  * Yields topic's messages after options.cursor, or from the oldest retained.
39
61
  * If the connection drops, it resubscribes from the last message yielded, so
@@ -34,6 +34,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  Object.defineProperty(exports, "__esModule", { value: true });
35
35
  exports.StreamsClient = void 0;
36
36
  const crypto_1 = require("crypto");
37
+ const axios_1 = require("axios");
37
38
  const nice_grpc_1 = require("nice-grpc");
38
39
  const grpc_helpers_1 = require("../../../util/grpc-helpers");
39
40
  const grpc_error_1 = require("../../../util/grpc-error");
@@ -58,7 +59,27 @@ const permanentSubscribeErrors = new Set([
58
59
  ]);
59
60
  const resubscribeMinDelayMs = 250;
60
61
  const resubscribeMaxDelayMs = 10000;
62
+ // the server's limit for a payload sent over gRPC (MaxStreamMessagePayloadBytes)
63
+ const maxInlinePayloadBytes = 4 * 1024 * 1024 - 5 * 1024;
64
+ // the server's limit for an uploaded payload (MaxStreamUploadedPayloadBytes)
65
+ const maxUploadedPayloadBytes = 64 * 1024 * 1024;
66
+ const payloadRequestAttempts = 3;
67
+ function httpStatus(err) {
68
+ var _a;
69
+ return (0, axios_1.isAxiosError)(err) ? (_a = err.response) === null || _a === void 0 ? void 0 : _a.status : undefined;
70
+ }
71
+ // a payload fetch that got no response, a 5xx or a 429 may succeed later
72
+ function isTemporaryHttpError(err) {
73
+ if (!(0, axios_1.isAxiosError)(err)) {
74
+ return false;
75
+ }
76
+ const status = httpStatus(err);
77
+ return status === undefined || status >= 500 || status === 429;
78
+ }
61
79
  function shouldResubscribe(err, attempt) {
80
+ if (isTemporaryHttpError(err)) {
81
+ return true;
82
+ }
62
83
  const code = (0, grpc_error_1.getGrpcErrorCode)(err);
63
84
  if (code === undefined || permanentSubscribeErrors.has(code)) {
64
85
  return false;
@@ -82,6 +103,7 @@ class StreamsClient {
82
103
  // stored promise always resolves, so one failure doesn't block later calls
83
104
  this.publishChains = new Map();
84
105
  this._config = client.config;
106
+ this._client = client;
85
107
  }
86
108
  get grpc() {
87
109
  if (!this._grpc) {
@@ -90,21 +112,46 @@ class StreamsClient {
90
112
  }
91
113
  return this._grpc;
92
114
  }
93
- publishOrdered(key_1, namespace_1, topic_1, payload_1) {
94
- return __awaiter(this, arguments, void 0, function* (key, namespace, topic, payload, retryGap = true) {
115
+ // retries a 5xx or network failure; a 4xx won't succeed on retry
116
+ static withRetries(request) {
117
+ return __awaiter(this, void 0, void 0, function* () {
118
+ for (let attempt = 1;; attempt += 1) {
119
+ try {
120
+ return yield request();
121
+ }
122
+ catch (err) {
123
+ const status = httpStatus(err);
124
+ if ((status !== undefined && status < 500) || attempt >= payloadRequestAttempts) {
125
+ throw err;
126
+ }
127
+ }
128
+ }
129
+ });
130
+ }
131
+ uploadPayload(payload) {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const res = yield StreamsClient.withRetries(() => this._client.api.v1StreamPayloadUpload(this._config.tenant_id, {
134
+ payload: new Blob([payload]),
135
+ }));
136
+ return res.data.ref;
137
+ });
138
+ }
139
+ fetchPayload(payloadRef, signal) {
140
+ return __awaiter(this, void 0, void 0, function* () {
141
+ const res = yield StreamsClient.withRetries(() => this._client.api.v1StreamPayloadGet(this._config.tenant_id, { ref: payloadRef }, { format: 'arraybuffer', signal }));
142
+ return new Uint8Array(res.data);
143
+ });
144
+ }
145
+ publishOrdered(key_1, namespace_1, topic_1, body_1) {
146
+ return __awaiter(this, arguments, void 0, function* (key, namespace, topic, body, retryGap = true) {
95
147
  let producer = this.producers.get(key);
96
148
  if (!producer) {
97
149
  producer = { producerId: (0, crypto_1.randomUUID)(), seq: 0 };
98
150
  this.producers.set(key, producer);
99
151
  }
100
152
  try {
101
- yield this.grpc.publish({
102
- namespace,
103
- topic,
104
- payload,
105
- producerId: producer.producerId,
106
- producerSeq: producer.seq,
107
- });
153
+ yield this.grpc.publish(Object.assign(Object.assign({ namespace,
154
+ topic }, body), { producerId: producer.producerId, producerSeq: producer.seq }));
108
155
  }
109
156
  catch (err) {
110
157
  // it may still land, and a different payload under its seq would be dropped as a duplicate
@@ -113,7 +160,7 @@ class StreamsClient {
113
160
  }
114
161
  // a gap stored nothing (e.g. the watermark passed cursor retention), so resend as the new producer
115
162
  if (retryGap && (0, grpc_error_1.getGrpcErrorCode)(err) === nice_grpc_1.Status.FAILED_PRECONDITION) {
116
- return this.publishOrdered(key, namespace, topic, payload, false);
163
+ return this.publishOrdered(key, namespace, topic, body, false);
117
164
  }
118
165
  throw err;
119
166
  }
@@ -133,12 +180,41 @@ class StreamsClient {
133
180
  const payload = typeof message === 'string' ? new TextEncoder().encode(message) : message;
134
181
  const namespace = (_a = options === null || options === void 0 ? void 0 : options.namespace) !== null && _a !== void 0 ? _a : '';
135
182
  const key = `${namespace}\u0000${topic}`;
183
+ if (payload.byteLength > maxUploadedPayloadBytes) {
184
+ throw new Error(`payload exceeds maximum size of ${maxUploadedPayloadBytes} bytes`);
185
+ }
186
+ // uploaded before taking its place in the topic's order, so uploads overlap
187
+ const uploaded = payload.byteLength > maxInlinePayloadBytes ? this.uploadPayload(payload) : undefined;
188
+ // awaited in order below; this only keeps an early failure from reporting as unhandled
189
+ uploaded === null || uploaded === void 0 ? void 0 : uploaded.catch(() => { });
136
190
  const previous = (_b = this.publishChains.get(key)) !== null && _b !== void 0 ? _b : Promise.resolve();
137
- const publishPromise = previous.then(() => this.publishOrdered(key, namespace, topic, payload));
191
+ const publishPromise = previous.then(() => __awaiter(this, void 0, void 0, function* () {
192
+ return this.publishOrdered(key, namespace, topic, uploaded ? { payloadRef: yield uploaded } : { payload });
193
+ }));
138
194
  this.publishChains.set(key, publishPromise.catch(() => { }));
139
195
  return publishPromise;
140
196
  });
141
197
  }
198
+ /**
199
+ * Describes topic's retained messages. Rejects with a NOT_FOUND gRPC error
200
+ * for a topic nothing was published to.
201
+ * @param topic - the topic to describe
202
+ * @param options - optional namespace override
203
+ */
204
+ topicMetadata(topic, options) {
205
+ return __awaiter(this, void 0, void 0, function* () {
206
+ var _a;
207
+ const res = yield this.grpc.getTopicMetadata({ namespace: (_a = options === null || options === void 0 ? void 0 : options.namespace) !== null && _a !== void 0 ? _a : '', topic });
208
+ return {
209
+ namespace: res.namespace,
210
+ topic: res.topic,
211
+ tenantId: res.tenantId,
212
+ messageCount: res.messageCount,
213
+ latestCursor: res.latestCursor,
214
+ lastPublishedAt: res.lastPublishedAt,
215
+ };
216
+ });
217
+ }
142
218
  /**
143
219
  * Yields topic's messages after options.cursor, or from the oldest retained.
144
220
  * If the connection drops, it resubscribes from the last message yielded, so
@@ -167,11 +243,15 @@ class StreamsClient {
167
243
  if (msg.hangup) {
168
244
  return yield __await(void 0);
169
245
  }
170
- failures = 0;
171
246
  for (const entry of msg.entries) {
247
+ const payload = entry.payloadRef
248
+ ? yield __await(this.fetchPayload(entry.payloadRef, signal))
249
+ : entry.payload;
172
250
  ({ cursor } = entry);
251
+ // reset only on delivery, so a payload fetch failing on every reconnect still backs off
252
+ failures = 0;
173
253
  yield yield __await({
174
- payload: entry.payload,
254
+ payload,
175
255
  cursor: entry.cursor,
176
256
  createdAt: entry.createdAt,
177
257
  });
@@ -639,7 +639,7 @@ class InternalWorker {
639
639
  }
640
640
  shutdown() {
641
641
  return __awaiter(this, void 0, void 0, function* () {
642
- var _a, _b;
642
+ var _a;
643
643
  this.killing = true;
644
644
  this.setStatus(health_server_1.workerStatus.UNHEALTHY);
645
645
  this.logger.info('Starting to exit...');
@@ -670,15 +670,17 @@ class InternalWorker {
670
670
  catch (e) {
671
671
  this.logger.error(`Could not stop durable listener: ${e.message}`);
672
672
  }
673
- (_a = this.listener) === null || _a === void 0 ? void 0 : _a.stopStream();
674
673
  this.logger.info('Gracefully exiting hatchet worker, running tasks will attempt to finish...');
675
- // attempt to wait for futures to finish
676
- yield Promise.all(Object.values(this.futures).map(({ promise }) => promise));
674
+ // Awaits the underlying work rather than the cancelable wrapper, which rejects as soon as a
675
+ // cancellation arrives while the task is still winding down.
676
+ yield Promise.allSettled(Object.values(this.futures).map(({ inner }) => inner));
677
677
  this.logger.info('Successfully finished pending tasks.');
678
- // Unregistering stops the heartbeat, so it has to wait until the running tasks are done:
679
- // otherwise the engine sees a dead worker and reassigns tasks that are about to complete.
678
+ // Unregistering closes the action stream and stops the heartbeat, so it has to wait until
679
+ // the running tasks are done. Stopping the heartbeat earlier lets the engine treat the worker
680
+ // as dead and reassign tasks that are about to complete, and closing the stream earlier makes
681
+ // the engine reject every heartbeat sent while those tasks finish.
680
682
  try {
681
- yield ((_b = this.listener) === null || _b === void 0 ? void 0 : _b.unregister());
683
+ yield ((_a = this.listener) === null || _a === void 0 ? void 0 : _a.unregister());
682
684
  }
683
685
  catch (e) {
684
686
  this.logger.error(`Could not unregister listener: ${e.message}`);
@@ -0,0 +1,34 @@
1
+ import { HatchetClient } from '../..';
2
+ import { StreamEvent } from '../../client/features/streams';
3
+ export declare const MiB: number;
4
+ export declare const maxInlinePayloadBytes: number;
5
+ export declare const describeBenchmark: jest.Describe;
6
+ export type LatencyStats = {
7
+ count: number;
8
+ avgMs: number;
9
+ p50Ms: number;
10
+ p99Ms: number;
11
+ maxMs: number;
12
+ };
13
+ export declare function latencyStats(durationsMs: number[]): LatencyStats;
14
+ export declare function formatLatency(stats: LatencyStats, digits?: number): string;
15
+ export declare function formatSize(bytes: number): string;
16
+ export type PublishLatencyStats = LatencyStats & {
17
+ throughputPerSec: number;
18
+ };
19
+ /**
20
+ * Times `count` sequential publishes, after `warmupCount` unmeasured ones
21
+ * absorb connection setup and topic creation.
22
+ */
23
+ export declare function measurePublishLatency(hatchet: HatchetClient, topic: string, payload: Uint8Array | string, count: number, warmupCount?: number): Promise<PublishLatencyStats>;
24
+ export declare function formatPublishLatency(stats: PublishLatencyStats): string;
25
+ export type LiveSubscription = {
26
+ /** resolves with the next message delivered after the call */
27
+ nextEvent: () => Promise<StreamEvent>;
28
+ close: () => Promise<void>;
29
+ };
30
+ /**
31
+ * Subscribes to topic and resolves once the subscription is live, so a
32
+ * benchmark's timings don't include subscribe setup.
33
+ */
34
+ export declare function openLiveSubscription(hatchet: HatchetClient, topic: string): Promise<LiveSubscription>;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __asyncValues = (this && this.__asyncValues) || function (o) {
12
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
13
+ var m = o[Symbol.asyncIterator], i;
14
+ return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
15
+ function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
16
+ function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.describeBenchmark = exports.maxInlinePayloadBytes = exports.MiB = void 0;
20
+ exports.latencyStats = latencyStats;
21
+ exports.formatLatency = formatLatency;
22
+ exports.formatSize = formatSize;
23
+ exports.measurePublishLatency = measurePublishLatency;
24
+ exports.formatPublishLatency = formatPublishLatency;
25
+ exports.openLiveSubscription = openLiveSubscription;
26
+ const perf_hooks_1 = require("perf_hooks");
27
+ exports.MiB = 1024 * 1024;
28
+ // the server's per-message gRPC limit (MaxStreamMessagePayloadBytes); larger payloads are uploaded
29
+ exports.maxInlinePayloadBytes = 4 * exports.MiB - 5 * 1024;
30
+ // benchmarks run only when asked for (test_durable_streams.sh --bench)
31
+ exports.describeBenchmark = process.env.HATCHET_E2E_BENCHMARKS ? describe : describe.skip;
32
+ function percentile(sorted, p) {
33
+ return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
34
+ }
35
+ function latencyStats(durationsMs) {
36
+ const sorted = [...durationsMs].sort((a, b) => a - b);
37
+ return {
38
+ count: durationsMs.length,
39
+ avgMs: durationsMs.reduce((sum, d) => sum + d, 0) / durationsMs.length,
40
+ p50Ms: percentile(sorted, 0.5),
41
+ p99Ms: percentile(sorted, 0.99),
42
+ maxMs: sorted[sorted.length - 1],
43
+ };
44
+ }
45
+ function formatLatency(stats, digits = 2) {
46
+ const fmt = (ms) => ms.toFixed(digits);
47
+ return `avg=${fmt(stats.avgMs)}ms p50=${fmt(stats.p50Ms)}ms p99=${fmt(stats.p99Ms)}ms max=${fmt(stats.maxMs)}ms`;
48
+ }
49
+ function formatSize(bytes) {
50
+ if (bytes % exports.MiB === 0)
51
+ return `${bytes / exports.MiB}MiB`;
52
+ if (bytes % 1024 === 0)
53
+ return `${bytes / 1024}KiB`;
54
+ return `${bytes}B`;
55
+ }
56
+ /**
57
+ * Times `count` sequential publishes, after `warmupCount` unmeasured ones
58
+ * absorb connection setup and topic creation.
59
+ */
60
+ function measurePublishLatency(hatchet_1, topic_1, payload_1, count_1) {
61
+ return __awaiter(this, arguments, void 0, function* (hatchet, topic, payload, count, warmupCount = 5) {
62
+ for (let i = 0; i < warmupCount; i += 1) {
63
+ yield hatchet.streams.publish(topic, payload);
64
+ }
65
+ const durationsMs = [];
66
+ const start = perf_hooks_1.performance.now();
67
+ for (let i = 0; i < count; i += 1) {
68
+ const before = perf_hooks_1.performance.now();
69
+ yield hatchet.streams.publish(topic, payload);
70
+ durationsMs.push(perf_hooks_1.performance.now() - before);
71
+ }
72
+ const totalMs = perf_hooks_1.performance.now() - start;
73
+ return Object.assign(Object.assign({}, latencyStats(durationsMs)), { throughputPerSec: (count / totalMs) * 1000 });
74
+ });
75
+ }
76
+ function formatPublishLatency(stats) {
77
+ return `${formatLatency(stats)} throughput=${stats.throughputPerSec.toFixed(2)}/s`;
78
+ }
79
+ /**
80
+ * Subscribes to topic and resolves once the subscription is live, so a
81
+ * benchmark's timings don't include subscribe setup.
82
+ */
83
+ function openLiveSubscription(hatchet, topic) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ const abort = new AbortController();
86
+ let onEvent;
87
+ const consumer = (() => __awaiter(this, void 0, void 0, function* () {
88
+ var _a, e_1, _b, _c;
89
+ try {
90
+ for (var _d = true, _e = __asyncValues(hatchet.streams.events(topic, { signal: abort.signal })), _f; _f = yield _e.next(), _a = _f.done, !_a; _d = true) {
91
+ _c = _f.value;
92
+ _d = false;
93
+ const ev = _c;
94
+ onEvent === null || onEvent === void 0 ? void 0 : onEvent(ev);
95
+ }
96
+ }
97
+ catch (e_1_1) { e_1 = { error: e_1_1 }; }
98
+ finally {
99
+ try {
100
+ if (!_d && !_a && (_b = _e.return)) yield _b.call(_e);
101
+ }
102
+ finally { if (e_1) throw e_1.error; }
103
+ }
104
+ }))();
105
+ const nextEvent = () => new Promise((resolve) => {
106
+ onEvent = resolve;
107
+ });
108
+ // the subscription is live once a first message comes through
109
+ const ready = nextEvent();
110
+ yield hatchet.streams.publish(topic, 'ready');
111
+ yield ready;
112
+ return {
113
+ nextEvent,
114
+ close: () => __awaiter(this, void 0, void 0, function* () {
115
+ abort.abort();
116
+ yield consumer;
117
+ }),
118
+ };
119
+ });
120
+ }
package/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const HATCHET_VERSION = "1.35.0";
1
+ export declare const HATCHET_VERSION = "1.36.0";
package/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HATCHET_VERSION = void 0;
4
- exports.HATCHET_VERSION = '1.35.0';
4
+ exports.HATCHET_VERSION = '1.36.0';
@@ -1,15 +0,0 @@
1
- import { HatchetClient } from '../..';
2
- export type PublishLatencyStats = {
3
- count: number;
4
- avgMs: number;
5
- p50Ms: number;
6
- p99Ms: number;
7
- maxMs: number;
8
- throughputPerSec: number;
9
- };
10
- /**
11
- * Times `count` sequential publishes, after `warmupCount` unmeasured ones
12
- * absorb connection setup and topic creation.
13
- */
14
- export declare function measurePublishLatency(hatchet: HatchetClient, topic: string, payload: Uint8Array | string, count: number, warmupCount?: number): Promise<PublishLatencyStats>;
15
- export declare function formatPublishLatency(stats: PublishLatencyStats): string;
@@ -1,50 +0,0 @@
1
- "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.measurePublishLatency = measurePublishLatency;
13
- exports.formatPublishLatency = formatPublishLatency;
14
- const perf_hooks_1 = require("perf_hooks");
15
- function percentile(sorted, p) {
16
- return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * p))];
17
- }
18
- /**
19
- * Times `count` sequential publishes, after `warmupCount` unmeasured ones
20
- * absorb connection setup and topic creation.
21
- */
22
- function measurePublishLatency(hatchet_1, topic_1, payload_1, count_1) {
23
- return __awaiter(this, arguments, void 0, function* (hatchet, topic, payload, count, warmupCount = 5) {
24
- for (let i = 0; i < warmupCount; i += 1) {
25
- yield hatchet.streams.publish(topic, payload);
26
- }
27
- const durationsMs = [];
28
- const start = perf_hooks_1.performance.now();
29
- for (let i = 0; i < count; i += 1) {
30
- const before = perf_hooks_1.performance.now();
31
- yield hatchet.streams.publish(topic, payload);
32
- durationsMs.push(perf_hooks_1.performance.now() - before);
33
- }
34
- const totalMs = perf_hooks_1.performance.now() - start;
35
- const sorted = [...durationsMs].sort((a, b) => a - b);
36
- return {
37
- count,
38
- avgMs: durationsMs.reduce((sum, d) => sum + d, 0) / count,
39
- p50Ms: percentile(sorted, 0.5),
40
- p99Ms: percentile(sorted, 0.99),
41
- maxMs: sorted[sorted.length - 1],
42
- throughputPerSec: (count / totalMs) * 1000,
43
- };
44
- });
45
- }
46
- function formatPublishLatency(stats) {
47
- const fmt = (ms) => ms.toFixed(2);
48
- return (`avg=${fmt(stats.avgMs)}ms p50=${fmt(stats.p50Ms)}ms p99=${fmt(stats.p99Ms)}ms ` +
49
- `max=${fmt(stats.maxMs)}ms throughput=${fmt(stats.throughputPerSec)}/s`);
50
- }