@hatchet-dev/typescript-sdk 1.33.2 → 1.34.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/clients/admin/admin-client.d.ts +6 -1
  2. package/clients/admin/admin-client.js +9 -4
  3. package/clients/admin/rpc.d.ts +13 -0
  4. package/clients/admin/rpc.js +23 -0
  5. package/clients/event/event-client.d.ts +2 -1
  6. package/clients/event/event-client.js +6 -3
  7. package/clients/event/rpc.d.ts +7 -0
  8. package/clients/event/rpc.js +13 -0
  9. package/clients/hatchet-client/hatchet-logger.js +2 -1
  10. package/clients/rest/generated/data-contracts.d.ts +2 -0
  11. package/clients/transport/grpc-target.d.ts +19 -0
  12. package/clients/transport/grpc-target.js +85 -0
  13. package/clients/transport/index.d.ts +4 -0
  14. package/clients/transport/index.js +15 -0
  15. package/clients/transport/message-bridge.d.ts +32 -0
  16. package/clients/transport/message-bridge.js +20 -0
  17. package/clients/transport/node-transport.d.ts +50 -0
  18. package/clients/transport/node-transport.js +123 -0
  19. package/clients/transport/transport.d.ts +14 -0
  20. package/clients/transport/transport.js +24 -0
  21. package/clients/transport/ts-proto-client.d.ts +41 -0
  22. package/clients/transport/ts-proto-client.js +143 -0
  23. package/core/index.d.ts +9 -0
  24. package/core/index.js +12 -0
  25. package/dist/check-exports.mjs +171 -0
  26. package/legacy/legacy-client.d.ts +4 -0
  27. package/legacy/legacy-client.js +5 -3
  28. package/package.json +95 -2
  29. package/protoc-es/dispatcher/dispatcher_pb.d.ts +1357 -0
  30. package/protoc-es/dispatcher/dispatcher_pb.js +371 -0
  31. package/protoc-es/events/events_pb.d.ts +294 -0
  32. package/protoc-es/events/events_pb.js +61 -0
  33. package/protoc-es/google/rpc/status_pb.d.ts +48 -0
  34. package/protoc-es/google/rpc/status_pb.js +27 -0
  35. package/protoc-es/v1/dispatcher_pb.d.ts +676 -0
  36. package/protoc-es/v1/dispatcher_pb.js +155 -0
  37. package/protoc-es/v1/shared/condition_pb.d.ts +166 -0
  38. package/protoc-es/v1/shared/condition_pb.js +68 -0
  39. package/protoc-es/v1/shared/trigger_pb.d.ts +160 -0
  40. package/protoc-es/v1/shared/trigger_pb.js +55 -0
  41. package/protoc-es/v1/workflows_pb.d.ts +995 -0
  42. package/protoc-es/v1/workflows_pb.js +280 -0
  43. package/protoc-es/workflows/workflows_pb.d.ts +715 -0
  44. package/protoc-es/workflows/workflows_pb.js +216 -0
  45. package/scripts/check-exports.mjs +171 -0
  46. package/util/grpc-error.d.ts +3 -1
  47. package/util/grpc-error.js +13 -4
  48. package/v1/client/admin.d.ts +2 -1
  49. package/v1/client/admin.js +34 -114
  50. package/v1/client/client.d.ts +1 -0
  51. package/v1/client/client.js +8 -5
  52. package/version.d.ts +1 -1
  53. package/version.js +1 -1
@@ -5,6 +5,7 @@ import { Logger } from '../../util/logger';
5
5
  import WorkflowRunRef from '../../util/workflow-run-ref';
6
6
  import { AdminServiceClient, CreateWorkflowVersionRequest } from '../../protoc/v1/workflows';
7
7
  import { Priority, RunsClient, WorkerLabelComparator } from '../../v1';
8
+ import { type Transport } from '../transport';
8
9
  import { Api } from '../rest';
9
10
  import { WebhookWorkerCreateRequest, WorkflowRunStatus, WorkflowRunStatusList } from '../rest/generated/data-contracts';
10
11
  import { RunListenerClient } from '../listeners/run-listener/child-listener-client';
@@ -50,7 +51,11 @@ export declare class AdminClient {
50
51
  logger: Logger;
51
52
  listenerClient: RunListenerClient;
52
53
  workflows: RunsClient | undefined;
53
- constructor(config: ClientConfig, channel: Channel, factory: ClientFactory, api: Api, tenantId: string, listenerClient: RunListenerClient, workflows: RunsClient | undefined);
54
+ /**
55
+ * The positional `channel` and `factory` parameters are part of the public constructor
56
+ * signature; the unary calls go over `transport` and the streaming clients own the channel.
57
+ */
58
+ constructor(config: ClientConfig, _channel: Channel, _factory: ClientFactory, api: Api, tenantId: string, listenerClient: RunListenerClient, workflows: RunsClient | undefined, transport?: Transport);
54
59
  /**
55
60
  * @deprecated use putWorkflow instead
56
61
  */
@@ -29,8 +29,9 @@ const hatchet_error_1 = require("../../util/errors/hatchet-error");
29
29
  const v0_deprecation_warning_1 = require("../../util/v0-deprecation-warning");
30
30
  const retrier_1 = require("../../util/retrier");
31
31
  const workflow_run_ref_1 = __importDefault(require("../../util/workflow-run-ref"));
32
- const workflows_2 = require("../../protoc/v1/workflows");
33
32
  const apply_namespace_1 = require("../../util/apply-namespace");
33
+ const transport_1 = require("../transport");
34
+ const rpc_1 = require("./rpc");
34
35
  function convertDesiredWorkerLabels(labels) {
35
36
  return Object.fromEntries(Object.entries(labels).map(([key, label]) => [
36
37
  key,
@@ -44,10 +45,14 @@ function convertDesiredWorkerLabels(labels) {
44
45
  ]));
45
46
  }
46
47
  class AdminClient {
47
- constructor(config, channel, factory, api, tenantId, listenerClient, workflows) {
48
+ /**
49
+ * The positional `channel` and `factory` parameters are part of the public constructor
50
+ * signature; the unary calls go over `transport` and the streaming clients own the channel.
51
+ */
52
+ constructor(config, _channel, _factory, api, tenantId, listenerClient, workflows, transport = (0, transport_1.createNodeTransport)(config)) {
48
53
  this.config = config;
49
- this.client = factory.create(workflows_1.WorkflowServiceDefinition, channel);
50
- this.v1Client = factory.create(workflows_2.AdminServiceDefinition, channel);
54
+ this.client = (0, rpc_1.createWorkflowsRpc)(transport);
55
+ this.v1Client = (0, rpc_1.createV1AdminRpc)(transport);
51
56
  this.api = api;
52
57
  this.tenantId = tenantId;
53
58
  this.logger = config.logger(`Admin`, config.log_level);
@@ -0,0 +1,13 @@
1
+ import { WorkflowServiceClient } from '../../protoc/workflows/workflows';
2
+ import { AdminServiceClient } from '../../protoc/v1/workflows';
3
+ import { type Transport } from '../transport';
4
+ /**
5
+ * The `WorkflowService` client, every RPC of the generated `WorkflowServiceClient` interface,
6
+ * served by a Connect client on the given transport.
7
+ */
8
+ export declare function createWorkflowsRpc(transport: Transport): WorkflowServiceClient;
9
+ /**
10
+ * The v1 `AdminService` client, every RPC of the generated `AdminServiceClient` interface,
11
+ * served by a Connect client on the given transport.
12
+ */
13
+ export declare function createV1AdminRpc(transport: Transport): AdminServiceClient;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWorkflowsRpc = createWorkflowsRpc;
4
+ exports.createV1AdminRpc = createV1AdminRpc;
5
+ const workflows_1 = require("../../protoc/workflows/workflows");
6
+ const workflows_pb_1 = require("../../protoc-es/workflows/workflows_pb");
7
+ const workflows_2 = require("../../protoc/v1/workflows");
8
+ const workflows_pb_2 = require("../../protoc-es/v1/workflows_pb");
9
+ const transport_1 = require("../transport");
10
+ /**
11
+ * The `WorkflowService` client, every RPC of the generated `WorkflowServiceClient` interface,
12
+ * served by a Connect client on the given transport.
13
+ */
14
+ function createWorkflowsRpc(transport) {
15
+ return (0, transport_1.createTsProtoClient)(workflows_1.WorkflowServiceDefinition, workflows_pb_1.WorkflowService, transport);
16
+ }
17
+ /**
18
+ * The v1 `AdminService` client, every RPC of the generated `AdminServiceClient` interface,
19
+ * served by a Connect client on the given transport.
20
+ */
21
+ function createV1AdminRpc(transport) {
22
+ return (0, transport_1.createTsProtoClient)(workflows_2.AdminServiceDefinition, workflows_pb_2.AdminService, transport);
23
+ }
@@ -1,5 +1,6 @@
1
1
  import { Channel, ClientFactory } from 'nice-grpc';
2
2
  import { EventsServiceClient } from '../../protoc/events/events';
3
+ import { type Transport } from '../transport';
3
4
  import { ClientConfig } from '../hatchet-client/client-config';
4
5
  import { Logger } from '../../util/logger';
5
6
  import { retrier } from '../../util/retrier';
@@ -28,7 +29,7 @@ export declare class EventClient {
28
29
  api: HatchetClient['api'];
29
30
  tenantId: string;
30
31
  logger: Logger;
31
- constructor(config: ClientConfig, channel: Channel, factory: ClientFactory, api: HatchetClient['api']);
32
+ constructor(config: ClientConfig, _channel: Channel, _factory: ClientFactory, api: HatchetClient['api'], transport?: Transport);
32
33
  /**
33
34
  * @important This method is instrumented by HatchetInstrumentor._patchPushEvent.
34
35
  * Keep the signature in sync with the instrumentor wrapper.
@@ -10,7 +10,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.EventClient = exports.LogLevel = void 0;
13
- const events_1 = require("../../protoc/events/events");
13
+ const transport_1 = require("../transport");
14
+ const rpc_1 = require("./rpc");
14
15
  const hatchet_error_1 = require("../../util/errors/hatchet-error");
15
16
  const retrier_1 = require("../../util/retrier");
16
17
  const apply_namespace_1 = require("../../util/apply-namespace");
@@ -30,9 +31,11 @@ function injectSourceInfo(metadata) {
30
31
  return Object.assign(Object.assign({}, metadata), { hatchet__source_workflow_run_id: ctx.parentId, hatchet__source_step_run_id: ctx.parentTaskRunExternalId });
31
32
  }
32
33
  class EventClient {
33
- constructor(config, channel, factory, api) {
34
+ // The positional channel and factory parameters are part of the public constructor signature;
35
+ // unary event calls go over the transport and do not use them.
36
+ constructor(config, _channel, _factory, api, transport = (0, transport_1.createNodeTransport)(config)) {
34
37
  this.config = config;
35
- this.client = factory.create(events_1.EventsServiceDefinition, channel);
38
+ this.client = (0, rpc_1.createEventsRpc)(transport);
36
39
  this.logger = config.logger(`Dispatcher`, config.log_level);
37
40
  this.retrier = retrier_1.retrier;
38
41
  this.api = api;
@@ -0,0 +1,7 @@
1
+ import { EventsServiceClient } from '../../protoc/events/events';
2
+ import { type Transport } from '../transport';
3
+ /**
4
+ * The `EventsService` client, every RPC of the generated `EventsServiceClient` interface,
5
+ * served by a Connect client on the given transport.
6
+ */
7
+ export declare function createEventsRpc(transport: Transport): EventsServiceClient;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createEventsRpc = createEventsRpc;
4
+ const events_1 = require("../../protoc/events/events");
5
+ const events_pb_1 = require("../../protoc-es/events/events_pb");
6
+ const transport_1 = require("../transport");
7
+ /**
8
+ * The `EventsService` client, every RPC of the generated `EventsServiceClient` interface,
9
+ * served by a Connect client on the given transport.
10
+ */
11
+ function createEventsRpc(transport) {
12
+ return (0, transport_1.createTsProtoClient)(events_1.EventsServiceDefinition, events_pb_1.EventsService, transport);
13
+ }
@@ -20,7 +20,8 @@ class HatchetLogger {
20
20
  }
21
21
  log(level, message, color = '37') {
22
22
  var _a;
23
- if (logger_1.LogLevelEnum[level] >= logger_1.LogLevelEnum[this.logLevel]) {
23
+ // OFF disables all logger output, regardless of message level.
24
+ if (this.logLevel !== 'OFF' && logger_1.LogLevelEnum[level] >= logger_1.LogLevelEnum[this.logLevel]) {
24
25
  const time = new Date().toLocaleString('en-US', {
25
26
  month: '2-digit',
26
27
  day: '2-digit',
@@ -900,6 +900,8 @@ export interface Tenant {
900
900
  serverUrl?: string;
901
901
  /** Control-plane shard region for the tenant (e.g. aws:us-west-2). */
902
902
  region?: string;
903
+ /** The data retention period for the tenant, e.g. 720h. */
904
+ dataRetentionPeriod?: string;
903
905
  }
904
906
  export interface V1EventWorkflowRunSummary {
905
907
  /**
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Where a gRPC target points: the host and port to dial and the `:authority` grpc-js sends for
3
+ * it, which is the target's path as written (the first address for the IP schemes).
4
+ */
5
+ export interface GrpcTarget {
6
+ host: string;
7
+ port: number;
8
+ authority: string;
9
+ }
10
+ /**
11
+ * Parses the target forms grpc-js accepts for `host_port`, so the unary transport dials the
12
+ * same endpoint the streaming channel does: `host:port`, `dns:host:port`, `dns:///host:port`,
13
+ * `ipv4:address:port` and `ipv6:[address]:port` (the first address of an address list). A
14
+ * missing port is grpc-js's default, 443. `unix:` sockets and a `dns://nameserver/` authority
15
+ * have no HTTP/2 equivalent here and are refused with a clear error.
16
+ */
17
+ export declare function parseGrpcTarget(target: string): GrpcTarget;
18
+ /** The base URL Connect dials for a target: an IPv6 host is bracketed as a URL requires. */
19
+ export declare function grpcTargetBaseUrl(target: GrpcTarget, protocol: 'http' | 'https'): string;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseGrpcTarget = parseGrpcTarget;
4
+ exports.grpcTargetBaseUrl = grpcTargetBaseUrl;
5
+ const net_1 = require("net");
6
+ /** The port grpc-js dials when a target names none. */
7
+ const DEFAULT_PORT = 443;
8
+ /*
9
+ * grpc-js parses a target as `scheme:` `//authority/` `path`, all but the path optional; a
10
+ * scheme it has no resolver for (which is what `host:port` looks like) makes the whole string a
11
+ * `dns` path.
12
+ */
13
+ const URI_REGEX = /^(?:([A-Za-z0-9+.-]+):)?(?:\/\/([^/]*)\/)?(.+)$/;
14
+ const RESOLVER_SCHEMES = new Set(['dns', 'ipv4', 'ipv6', 'unix']);
15
+ /**
16
+ * Parses the target forms grpc-js accepts for `host_port`, so the unary transport dials the
17
+ * same endpoint the streaming channel does: `host:port`, `dns:host:port`, `dns:///host:port`,
18
+ * `ipv4:address:port` and `ipv6:[address]:port` (the first address of an address list). A
19
+ * missing port is grpc-js's default, 443. `unix:` sockets and a `dns://nameserver/` authority
20
+ * have no HTTP/2 equivalent here and are refused with a clear error.
21
+ */
22
+ function parseGrpcTarget(target) {
23
+ var _a;
24
+ const match = URI_REGEX.exec(target);
25
+ if (!match) {
26
+ throw new Error(`host_port ${JSON.stringify(target)} is not a gRPC target`);
27
+ }
28
+ const [, rawScheme, authority, rawPath] = match;
29
+ const known = rawScheme !== undefined && RESOLVER_SCHEMES.has(rawScheme);
30
+ const scheme = known ? rawScheme : 'dns';
31
+ const path = known ? rawPath : target;
32
+ if (scheme === 'unix') {
33
+ throw new Error(`host_port ${JSON.stringify(target)}: unix domain socket targets are not supported by the unary transport; use a host:port target`);
34
+ }
35
+ if (known && authority) {
36
+ throw new Error(`host_port ${JSON.stringify(target)}: a target-specific DNS server is not supported by the unary transport; use dns:///host:port`);
37
+ }
38
+ const address = scheme === 'dns' ? path : path.split(',')[0];
39
+ const hostPort = splitHostPort(address);
40
+ if (!hostPort) {
41
+ throw new Error(`host_port ${JSON.stringify(target)} is not a valid host and port`);
42
+ }
43
+ if (scheme === 'ipv4' && !(0, net_1.isIPv4)(hostPort.host)) {
44
+ throw new Error(`host_port ${JSON.stringify(target)} is not an IPv4 address`);
45
+ }
46
+ if (scheme === 'ipv6' && !(0, net_1.isIPv6)(hostPort.host)) {
47
+ throw new Error(`host_port ${JSON.stringify(target)} is not an IPv6 address`);
48
+ }
49
+ return { host: hostPort.host, port: (_a = hostPort.port) !== null && _a !== void 0 ? _a : DEFAULT_PORT, authority: address };
50
+ }
51
+ /** The base URL Connect dials for a target: an IPv6 host is bracketed as a URL requires. */
52
+ function grpcTargetBaseUrl(target, protocol) {
53
+ const host = target.host.includes(':') ? `[${target.host}]` : target.host;
54
+ return `${protocol}://${host}:${target.port}`;
55
+ }
56
+ /*
57
+ * grpc-js's host:port split: a bracketed host is IPv6 with an optional `:port`, exactly one
58
+ * colon separates a host from a numeric port, and any other number of colons is a bare IPv6
59
+ * address with no port.
60
+ */
61
+ function splitHostPort(address) {
62
+ if (address.startsWith('[')) {
63
+ const end = address.indexOf(']');
64
+ if (end === -1) {
65
+ return null;
66
+ }
67
+ const host = address.slice(1, end);
68
+ if (!host.includes(':')) {
69
+ return null;
70
+ }
71
+ const rest = address.slice(end + 1);
72
+ if (rest === '') {
73
+ return { host };
74
+ }
75
+ if (rest.startsWith(':') && /^\d+$/.test(rest.slice(1))) {
76
+ return { host, port: Number(rest.slice(1)) };
77
+ }
78
+ return null;
79
+ }
80
+ const parts = address.split(':');
81
+ if (parts.length === 2) {
82
+ return /^\d+$/.test(parts[1]) ? { host: parts[0], port: Number(parts[1]) } : null;
83
+ }
84
+ return { host: address };
85
+ }
@@ -0,0 +1,4 @@
1
+ export { createAuthInterceptor, type Transport } from './transport';
2
+ export { createNodeTransport } from './node-transport';
3
+ export { fromProtobufEs, toProtobufEs, type DeepPartial, type TsProtoCodec, } from './message-bridge';
4
+ export { createTsProtoClient, headersToMetadata, metadataToHeaders, toConnectCallOptions, type UnaryCallOptions, type UnaryCallOptionsExt, } from './ts-proto-client';
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toConnectCallOptions = exports.metadataToHeaders = exports.headersToMetadata = exports.createTsProtoClient = exports.toProtobufEs = exports.fromProtobufEs = exports.createNodeTransport = exports.createAuthInterceptor = void 0;
4
+ var transport_1 = require("./transport");
5
+ Object.defineProperty(exports, "createAuthInterceptor", { enumerable: true, get: function () { return transport_1.createAuthInterceptor; } });
6
+ var node_transport_1 = require("./node-transport");
7
+ Object.defineProperty(exports, "createNodeTransport", { enumerable: true, get: function () { return node_transport_1.createNodeTransport; } });
8
+ var message_bridge_1 = require("./message-bridge");
9
+ Object.defineProperty(exports, "fromProtobufEs", { enumerable: true, get: function () { return message_bridge_1.fromProtobufEs; } });
10
+ Object.defineProperty(exports, "toProtobufEs", { enumerable: true, get: function () { return message_bridge_1.toProtobufEs; } });
11
+ var ts_proto_client_1 = require("./ts-proto-client");
12
+ Object.defineProperty(exports, "createTsProtoClient", { enumerable: true, get: function () { return ts_proto_client_1.createTsProtoClient; } });
13
+ Object.defineProperty(exports, "headersToMetadata", { enumerable: true, get: function () { return ts_proto_client_1.headersToMetadata; } });
14
+ Object.defineProperty(exports, "metadataToHeaders", { enumerable: true, get: function () { return ts_proto_client_1.metadataToHeaders; } });
15
+ Object.defineProperty(exports, "toConnectCallOptions", { enumerable: true, get: function () { return ts_proto_client_1.toConnectCallOptions; } });
@@ -0,0 +1,32 @@
1
+ import { type DescMessage, type MessageShape } from '@bufbuild/protobuf';
2
+ type Builtin = Date | ((...args: never[]) => unknown) | Uint8Array | string | number | boolean | undefined;
3
+ /**
4
+ * The partial message shape the SDK's clients accept, matching the `DeepPartial` type each
5
+ * `src/protoc` module generates for its own messages.
6
+ */
7
+ 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 {} ? {
8
+ [K in keyof T]?: DeepPartial<T[K]>;
9
+ } : Partial<T>;
10
+ /**
11
+ * The part of a `ts-proto` message codec (what `src/protoc` exports under a message's name)
12
+ * the bridge needs: the wire codec and the partial-to-full normalizer.
13
+ */
14
+ export interface TsProtoCodec<T> {
15
+ encode(message: T): {
16
+ finish(): Uint8Array;
17
+ };
18
+ decode(input: Uint8Array): T;
19
+ fromPartial(object: DeepPartial<T>): T;
20
+ }
21
+ /**
22
+ * Converts a `ts-proto` message to the protobuf-es message a Connect client sends. The two
23
+ * bindings share the wire format, so an encode/decode round trip is an exact translation and
24
+ * the SDK keeps its `ts-proto` types as the public request types.
25
+ */
26
+ export declare function toProtobufEs<T, S extends DescMessage>(schema: S, codec: TsProtoCodec<T>, message: DeepPartial<T>): MessageShape<S>;
27
+ /**
28
+ * Converts a protobuf-es message a Connect client received to the `ts-proto` message callers
29
+ * expect, the inverse of `toProtobufEs`.
30
+ */
31
+ export declare function fromProtobufEs<T, S extends DescMessage>(codec: TsProtoCodec<T>, schema: S, message: MessageShape<S>): T;
32
+ export {};
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toProtobufEs = toProtobufEs;
4
+ exports.fromProtobufEs = fromProtobufEs;
5
+ const protobuf_1 = require("@bufbuild/protobuf");
6
+ /**
7
+ * Converts a `ts-proto` message to the protobuf-es message a Connect client sends. The two
8
+ * bindings share the wire format, so an encode/decode round trip is an exact translation and
9
+ * the SDK keeps its `ts-proto` types as the public request types.
10
+ */
11
+ function toProtobufEs(schema, codec, message) {
12
+ return (0, protobuf_1.fromBinary)(schema, codec.encode(codec.fromPartial(message)).finish());
13
+ }
14
+ /**
15
+ * Converts a protobuf-es message a Connect client received to the `ts-proto` message callers
16
+ * expect, the inverse of `toProtobufEs`.
17
+ */
18
+ function fromProtobufEs(codec, schema, message) {
19
+ return codec.decode((0, protobuf_1.toBinary)(schema, message));
20
+ }
@@ -0,0 +1,50 @@
1
+ import type { SecureClientSessionOptions } from 'http2';
2
+ import { type GrpcTransportOptions } from '@connectrpc/connect-node';
3
+ import type { ClientConfig } from '../hatchet-client/client-config';
4
+ import { type Transport } from './transport';
5
+ /**
6
+ * The largest message size Connect can enforce (a 32-bit length prefix). The config schema
7
+ * accepts any positive integer, as grpc-js does, so a larger configured limit is clamped to it
8
+ * rather than refused at the first call.
9
+ */
10
+ export declare const MAX_MESSAGE_BYTES = 4294967295;
11
+ /**
12
+ * The connection settings the `nice-grpc` channel uses (`grpc.keepalive_time_ms`,
13
+ * `grpc.keepalive_timeout_ms`, `grpc.keepalive_permit_without_calls` and
14
+ * `grpc.client_idle_timeout_ms` in `util/grpc-helpers.ts`), so a unary connection is kept alive,
15
+ * given up on and idled exactly as a streaming one.
16
+ */
17
+ export declare const SESSION_OPTIONS: {
18
+ readonly pingIntervalMs: number;
19
+ readonly pingTimeoutMs: number;
20
+ readonly pingIdleConnection: true;
21
+ readonly idleConnectionTimeoutMs: number;
22
+ };
23
+ /**
24
+ * Builds the TLS session options for the engine connection from the client's `tls_config`:
25
+ * `none` means plaintext, `tls` verifies the server against the trusted roots, and `mtls` (the
26
+ * default when no strategy is set) additionally presents `cert_file` and `key_file`.
27
+ * `server_name` overrides the hostname used for SNI and certificate verification, which is what
28
+ * lets a client reach an engine through an address its certificate does not name.
29
+ *
30
+ * The roots and cipher suites follow grpc-js, which the streaming channel uses: `ca_file`, else
31
+ * the bundle `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH` names, else the platform roots; and
32
+ * `GRPC_SSL_CIPHER_SUITES` when set.
33
+ */
34
+ export declare function tlsSessionOptions(tls: ClientConfig['tls_config']): SecureClientSessionOptions;
35
+ /**
36
+ * Creates the Node transport: the gRPC protocol over HTTP/2 (`createGrpcTransport` speaks
37
+ * nothing else), the same wire the SDK's `nice-grpc` clients speak, so an engine sees no
38
+ * difference between the two. The bearer token from the config is attached to every call and
39
+ * the connection is kept alive with pings the way the `nice-grpc` channel is.
40
+ *
41
+ * The transport is built on the first call so that constructing a client reads no TLS files;
42
+ * a client whose unary RPCs are never used never touches them.
43
+ */
44
+ export declare function createNodeTransport(config: ClientConfig): Transport;
45
+ /**
46
+ * The options `createNodeTransport` builds its transport from, derived from the client config.
47
+ * The session manager is built here from `nodeOptions` and the session settings; Connect takes
48
+ * the connection from it and leaves those transport-level copies as the record of what it uses.
49
+ */
50
+ export declare function nodeTransportOptions(config: ClientConfig): GrpcTransportOptions;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SESSION_OPTIONS = exports.MAX_MESSAGE_BYTES = void 0;
4
+ exports.tlsSessionOptions = tlsSessionOptions;
5
+ exports.createNodeTransport = createNodeTransport;
6
+ exports.nodeTransportOptions = nodeTransportOptions;
7
+ const fs_1 = require("fs");
8
+ const connect_node_1 = require("@connectrpc/connect-node");
9
+ const grpc_target_1 = require("./grpc-target");
10
+ const transport_1 = require("./transport");
11
+ const DEFAULT_MAX_MESSAGE_BYTES = 4 * 1024 * 1024;
12
+ /**
13
+ * The largest message size Connect can enforce (a 32-bit length prefix). The config schema
14
+ * accepts any positive integer, as grpc-js does, so a larger configured limit is clamped to it
15
+ * rather than refused at the first call.
16
+ */
17
+ exports.MAX_MESSAGE_BYTES = 0xffffffff;
18
+ function messageLimit(configured) {
19
+ return Math.min(configured !== null && configured !== void 0 ? configured : DEFAULT_MAX_MESSAGE_BYTES, exports.MAX_MESSAGE_BYTES);
20
+ }
21
+ /**
22
+ * The connection settings the `nice-grpc` channel uses (`grpc.keepalive_time_ms`,
23
+ * `grpc.keepalive_timeout_ms`, `grpc.keepalive_permit_without_calls` and
24
+ * `grpc.client_idle_timeout_ms` in `util/grpc-helpers.ts`), so a unary connection is kept alive,
25
+ * given up on and idled exactly as a streaming one.
26
+ */
27
+ exports.SESSION_OPTIONS = {
28
+ pingIntervalMs: 10 * 1000,
29
+ pingTimeoutMs: 60 * 1000,
30
+ pingIdleConnection: true,
31
+ idleConnectionTimeoutMs: 60 * 1000,
32
+ };
33
+ /**
34
+ * Builds the TLS session options for the engine connection from the client's `tls_config`:
35
+ * `none` means plaintext, `tls` verifies the server against the trusted roots, and `mtls` (the
36
+ * default when no strategy is set) additionally presents `cert_file` and `key_file`.
37
+ * `server_name` overrides the hostname used for SNI and certificate verification, which is what
38
+ * lets a client reach an engine through an address its certificate does not name.
39
+ *
40
+ * The roots and cipher suites follow grpc-js, which the streaming channel uses: `ca_file`, else
41
+ * the bundle `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH` names, else the platform roots; and
42
+ * `GRPC_SSL_CIPHER_SUITES` when set.
43
+ */
44
+ function tlsSessionOptions(tls) {
45
+ // grpc-js verifies the peer regardless of NODE_TLS_REJECT_UNAUTHORIZED, which is Node's
46
+ // process-wide switch; set explicitly, the switch cannot weaken this connection either.
47
+ const options = { rejectUnauthorized: true };
48
+ const rootsFile = tls.ca_file || process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
49
+ if (rootsFile) {
50
+ options.ca = (0, fs_1.readFileSync)(rootsFile);
51
+ }
52
+ const ciphers = process.env.GRPC_SSL_CIPHER_SUITES;
53
+ if (ciphers) {
54
+ options.ciphers = ciphers;
55
+ }
56
+ if (tls.tls_strategy !== 'tls') {
57
+ // grpc-js refuses half a client identity with these messages rather than connecting without
58
+ // one, which an endpoint that does not require client certificates would silently accept.
59
+ if (tls.key_file && !tls.cert_file) {
60
+ throw new Error('Private key must be given with accompanying certificate chain');
61
+ }
62
+ if (tls.cert_file && !tls.key_file) {
63
+ throw new Error('Certificate chain must be given with accompanying private key');
64
+ }
65
+ if (tls.key_file && tls.cert_file) {
66
+ options.key = (0, fs_1.readFileSync)(tls.key_file);
67
+ options.cert = (0, fs_1.readFileSync)(tls.cert_file);
68
+ }
69
+ }
70
+ if (tls.server_name) {
71
+ options.servername = tls.server_name;
72
+ }
73
+ return options;
74
+ }
75
+ /**
76
+ * Creates the Node transport: the gRPC protocol over HTTP/2 (`createGrpcTransport` speaks
77
+ * nothing else), the same wire the SDK's `nice-grpc` clients speak, so an engine sees no
78
+ * difference between the two. The bearer token from the config is attached to every call and
79
+ * the connection is kept alive with pings the way the `nice-grpc` channel is.
80
+ *
81
+ * The transport is built on the first call so that constructing a client reads no TLS files;
82
+ * a client whose unary RPCs are never used never touches them.
83
+ */
84
+ function createNodeTransport(config) {
85
+ let transport;
86
+ const get = () => {
87
+ if (!transport) {
88
+ transport = (0, connect_node_1.createGrpcTransport)(nodeTransportOptions(config));
89
+ }
90
+ return transport;
91
+ };
92
+ return {
93
+ unary: (...args) => get().unary(...args),
94
+ stream: (...args) => get().stream(...args),
95
+ };
96
+ }
97
+ /**
98
+ * The options `createNodeTransport` builds its transport from, derived from the client config.
99
+ * The session manager is built here from `nodeOptions` and the session settings; Connect takes
100
+ * the connection from it and leaves those transport-level copies as the record of what it uses.
101
+ */
102
+ function nodeTransportOptions(config) {
103
+ const insecure = config.tls_config.tls_strategy === 'none';
104
+ const target = (0, grpc_target_1.parseGrpcTarget)(config.host_port);
105
+ const baseUrl = (0, grpc_target_1.grpcTargetBaseUrl)(target, insecure ? 'http' : 'https');
106
+ const nodeOptions = insecure ? undefined : tlsSessionOptions(config.tls_config);
107
+ return Object.assign({ baseUrl,
108
+ nodeOptions, sessionManager: createSessionManager(baseUrl, target.authority, nodeOptions), interceptors: [(0, transport_1.createAuthInterceptor)(config.token)], sendCompression: connect_node_1.compressionGzip, readMaxBytes: messageLimit(config.grpc_max_recv_message_length), writeMaxBytes: messageLimit(config.grpc_max_send_message_length) }, exports.SESSION_OPTIONS);
109
+ }
110
+ /**
111
+ * The HTTP/2 session for the transport, sending `:authority` as grpc-js does: the target as
112
+ * written, so `server_name` changes only SNI and certificate verification. Node otherwise
113
+ * derives the authority from `servername` when it is set, which would also change the virtual
114
+ * host a proxy in front of the engine routes on.
115
+ */
116
+ function createSessionManager(baseUrl, authority, nodeOptions) {
117
+ const manager = new connect_node_1.Http2SessionManager(baseUrl, exports.SESSION_OPTIONS, nodeOptions);
118
+ return {
119
+ authority: manager.authority,
120
+ request: (method, path, headers, options) => manager.request(method, path, Object.assign({ ':authority': authority }, headers), options),
121
+ notifyResponseByteRead: (stream) => manager.notifyResponseByteRead(stream),
122
+ };
123
+ }
@@ -0,0 +1,14 @@
1
+ import type { Interceptor, Transport } from '@connectrpc/connect';
2
+ /**
3
+ * The seam every Connect-backed client calls through. A transport carries the protocol
4
+ * (gRPC over HTTP/2 in Node, Connect over fetch elsewhere), the connection settings and the
5
+ * interceptors that apply to every call.
6
+ */
7
+ export type { Transport };
8
+ /**
9
+ * Sets the bearer token the engine authenticates every call with. The transport applies it to
10
+ * each request, so callers never pass credentials per call. A token that cannot be carried in a
11
+ * header is refused here with a fixed message, so the token's value never reaches a log or an
12
+ * error.
13
+ */
14
+ export declare function createAuthInterceptor(token: string): Interceptor;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createAuthInterceptor = createAuthInterceptor;
4
+ /**
5
+ * The characters a token may contain and still travel in an HTTP header: the printable ASCII
6
+ * range, which is also what grpc-js accepts in metadata. Anything else would make the header
7
+ * layer reject the request with an error quoting the token.
8
+ */
9
+ const HEADER_SAFE = /^[ -~]*$/;
10
+ /**
11
+ * Sets the bearer token the engine authenticates every call with. The transport applies it to
12
+ * each request, so callers never pass credentials per call. A token that cannot be carried in a
13
+ * header is refused here with a fixed message, so the token's value never reaches a log or an
14
+ * error.
15
+ */
16
+ function createAuthInterceptor(token) {
17
+ if (!HEADER_SAFE.test(token)) {
18
+ throw new Error('The Hatchet client token contains characters that cannot be sent in an HTTP header; check HATCHET_CLIENT_TOKEN for stray whitespace or line breaks');
19
+ }
20
+ return (next) => (req) => {
21
+ req.header.set('authorization', `Bearer ${token}`);
22
+ return next(req);
23
+ };
24
+ }
@@ -0,0 +1,41 @@
1
+ import type { DescService } from '@bufbuild/protobuf';
2
+ import { type CallOptions as ConnectCallOptions, type Transport } from '@connectrpc/connect';
3
+ import type { Client, TsProtoServiceDefinition } from 'nice-grpc';
4
+ import { Metadata, type CallOptions } from 'nice-grpc-common';
5
+ /**
6
+ * The per-call options the Connect-backed clients accept: `nice-grpc`'s `CallOptions`
7
+ * (`signal`, `metadata`, `onHeader`, `onTrailer`) plus the `deadline` its deadline middleware
8
+ * adds, an absolute time as a `Date` or epoch milliseconds.
9
+ */
10
+ export interface UnaryCallOptionsExt {
11
+ deadline?: Date | number;
12
+ }
13
+ export type UnaryCallOptions = CallOptions & UnaryCallOptionsExt;
14
+ /**
15
+ * Builds the client a `ts-proto` service definition describes, with every method served by a
16
+ * Connect client for the matching protobuf-es service on `transport`. The result has the exact
17
+ * surface `nice-grpc`'s `ClientFactory.create(definition, channel)` produced: every RPC of the
18
+ * service as `(request, options?) => Promise<response>` with the SDK's `ts-proto` message types,
19
+ * so a caller holding one of the public client properties sees no difference.
20
+ *
21
+ * Both bindings are generated from the same proto, so a method's client name is the same in
22
+ * each and the protobuf-es descriptor can be looked up by it.
23
+ */
24
+ export declare function createTsProtoClient<Def extends TsProtoServiceDefinition>(definition: Def, service: DescService, transport: Transport): Client<Def, UnaryCallOptionsExt>;
25
+ /**
26
+ * Translates `nice-grpc` call options to Connect's: the signal passes through, an absolute
27
+ * deadline becomes the remaining time, request metadata becomes headers and the header and
28
+ * trailer callbacks receive `Metadata` built from the response headers.
29
+ */
30
+ export declare function toConnectCallOptions(options?: UnaryCallOptions): ConnectCallOptions | undefined;
31
+ /**
32
+ * gRPC metadata as HTTP headers: binary values (keys ending in `-bin`) are base64 encoded,
33
+ * which is how they travel on the wire in every gRPC implementation.
34
+ */
35
+ export declare function metadataToHeaders(metadata: Metadata): Headers;
36
+ /**
37
+ * Response headers as gRPC metadata, the inverse of `metadataToHeaders`. `Headers` joins
38
+ * repeated values with a comma, so a binary header is split back into its values before each
39
+ * is decoded.
40
+ */
41
+ export declare function headersToMetadata(headers: Headers): Metadata;