Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions sdks/typescript/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to Hatchet's TypeScript SDK will be documented in this chang
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.22.4] - 2026-05-19

### Added

- Adds `grpc_max_recv_message_length` and `grpc_max_send_message_length` to client config, also configurable via env vars. Defaults to 4MB.

## [1.22.3] - 2026-05-18

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion sdks/typescript/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hatchet-dev/typescript-sdk",
"version": "1.22.3",
"version": "1.22.4",
"description": "Background task orchestration & visibility for developers",
"types": "dist/index.d.ts",
"files": [
Expand Down
45 changes: 45 additions & 0 deletions sdks/typescript/src/clients/hatchet-client/client-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,48 @@ describe('ClientConfigSchema cancellation timing', () => {
).toThrow();
});
});

describe('ClientConfigSchema gRPC max message length', () => {
it('applies 4MB defaults for both recv and send', () => {
const cfg = ClientConfigSchema.parse(baseConfig());
expect(cfg.grpc_max_recv_message_length).toBe(4 * 1024 * 1024);
expect(cfg.grpc_max_send_message_length).toBe(4 * 1024 * 1024);
});

it('accepts custom positive integer values', () => {
const cfg = ClientConfigSchema.parse({
...baseConfig(),
grpc_max_recv_message_length: 8 * 1024 * 1024,
grpc_max_send_message_length: 16 * 1024 * 1024,
});
expect(cfg.grpc_max_recv_message_length).toBe(8 * 1024 * 1024);
expect(cfg.grpc_max_send_message_length).toBe(16 * 1024 * 1024);
});

it('rejects invalid values', () => {
expect(() =>
ClientConfigSchema.parse({
...baseConfig(),
grpc_max_recv_message_length: 0,
})
).toThrow();
expect(() =>
ClientConfigSchema.parse({
...baseConfig(),
grpc_max_send_message_length: -1,
})
).toThrow();
expect(() =>
ClientConfigSchema.parse({
...baseConfig(),
grpc_max_recv_message_length: 1.5,
})
).toThrow();
expect(() =>
ClientConfigSchema.parse({
...baseConfig(),
grpc_max_send_message_length: '4mb' as any,
})
).toThrow();
});
});
19 changes: 18 additions & 1 deletion sdks/typescript/src/clients/hatchet-client/client-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ export const ClientConfigSchema = z.object({
middleware: TaskMiddlewareSchema,
cancellation_grace_period: DurationMsSchema.optional().default(1000),
cancellation_warning_threshold: DurationMsSchema.optional().default(300),
grpc_max_recv_message_length: z
.number()
.int()
.positive()
.optional()
.default(4 * 1024 * 1024),
grpc_max_send_message_length: z
.number()
.int()
.positive()
.optional()
.default(4 * 1024 * 1024),
});

export type LogConstructor = (context: string, logLevel?: LogLevel) => Logger;
Expand Down Expand Up @@ -127,10 +139,15 @@ type ClientConfigInferred = z.infer<typeof ClientConfigSchema>;

export type ClientConfig = Omit<
ClientConfigInferred,
'cancellation_grace_period' | 'cancellation_warning_threshold'
| 'cancellation_grace_period'
| 'cancellation_warning_threshold'
| 'grpc_max_recv_message_length'
| 'grpc_max_send_message_length'
> & {
cancellation_grace_period?: number;
cancellation_warning_threshold?: number;
grpc_max_recv_message_length?: number;
grpc_max_send_message_length?: number;
} & {
credentials?: ChannelCredentials;
} & {
Expand Down
22 changes: 22 additions & 0 deletions sdks/typescript/src/util/config-loader/config-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ describe('ConfigLoader', () => {
beforeEach(() => {
// Clear env vars that might leak from other tests
delete process.env.HATCHET_CLIENT_TLS_STRATEGY;
delete process.env.HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH;
delete process.env.HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH;

process.env.HATCHET_CLIENT_TOKEN =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJncnBjX2Jyb2FkY2FzdF9hZGRyZXNzIjoiMTI3LjAuMC4xOjgwODAiLCJzZXJ2ZXJfdXJsIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgwIiwic3ViIjoiNzA3ZDA4NTUtODBhYi00ZTFmLWExNTYtZjFjNDU0NmNiZjUyIn0K.abcdef';
Expand All @@ -14,6 +16,8 @@ describe('ConfigLoader', () => {
process.env.HATCHET_CLIENT_TLS_SERVER_NAME = 'TLS_SERVER_NAME';
process.env.HATCHET_CLIENT_WORKER_HEALTHCHECK_ENABLED = 'true';
process.env.HATCHET_CLIENT_WORKER_HEALTHCHECK_PORT = '8001';
process.env.HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH = String(8 * 1024 * 1024);
process.env.HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH = String(16 * 1024 * 1024);
});

it('should load from environment variables', () => {
Expand Down Expand Up @@ -41,9 +45,25 @@ describe('ConfigLoader', () => {
excludedAttributes: [],
includeTaskNameInSpanName: false,
},
grpc_max_recv_message_length: 8 * 1024 * 1024,
grpc_max_send_message_length: 16 * 1024 * 1024,
});
});

it('should throw on a malformed grpc max recv message length env var', () => {
process.env.HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH = '4mb';
expect(() => ConfigLoader.loadClientConfig()).toThrow(
/HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH.*"4mb".*positive integer/
);
});

it('should throw on a malformed grpc max send message length env var', () => {
process.env.HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH = '4mb';
expect(() => ConfigLoader.loadClientConfig()).toThrow(
/HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH.*"4mb".*positive integer/
);
});

it('should throw an error if the file is not found', () => {
expect(() =>
ConfigLoader.loadClientConfig(
Expand Down Expand Up @@ -97,6 +117,8 @@ describe('ConfigLoader', () => {
excludedAttributes: ['additional_metadata'],
includeTaskNameInSpanName: true,
},
grpc_max_recv_message_length: 8 * 1024 * 1024,
grpc_max_send_message_length: 16 * 1024 * 1024,
});
});

Expand Down
29 changes: 28 additions & 1 deletion sdks/typescript/src/util/config-loader/config-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ type EnvVars =
| 'HATCHET_CLIENT_WORKER_HEALTHCHECK_ENABLED'
| 'HATCHET_CLIENT_WORKER_HEALTHCHECK_PORT'
| 'HATCHET_CLIENT_OPENTELEMETRY_EXCLUDED_ATTRIBUTES'
| 'HATCHET_CLIENT_OPENTELEMETRY_INCLUDE_TASK_NAME_IN_SPAN_NAME';
| 'HATCHET_CLIENT_OPENTELEMETRY_INCLUDE_TASK_NAME_IN_SPAN_NAME'
| 'HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH'
| 'HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH';

type TLSStrategy = 'tls' | 'mtls';

Expand Down Expand Up @@ -105,6 +107,18 @@ export class ConfigLoader {
this.env('HATCHET_CLIENT_OPENTELEMETRY_INCLUDE_TASK_NAME_IN_SPAN_NAME') === 'true',
};

const grpcMaxRecvMessageLength =
override?.grpc_max_recv_message_length ??
yaml?.grpc_max_recv_message_length ??
this.parseIntEnv('HATCHET_CLIENT_GRPC_MAX_RECV_MESSAGE_LENGTH') ??
4 * 1024 * 1024;

const grpcMaxSendMessageLength =
override?.grpc_max_send_message_length ??
yaml?.grpc_max_send_message_length ??
this.parseIntEnv('HATCHET_CLIENT_GRPC_MAX_SEND_MESSAGE_LENGTH') ??
4 * 1024 * 1024;

return {
token: override?.token ?? yaml?.token ?? this.env('HATCHET_CLIENT_TOKEN'),
host_port: grpcBroadcastAddress,
Expand All @@ -119,9 +133,22 @@ export class ConfigLoader {
tenant_id: tenantId,
namespace: namespace ? `${namespace}`.toLowerCase() : '',
otel: otelConfig,
grpc_max_recv_message_length: grpcMaxRecvMessageLength,
grpc_max_send_message_length: grpcMaxSendMessageLength,
};
}

private static parseIntEnv(envName: EnvVars): number | undefined {
const value = this.env(envName);
if (value === undefined || value === '') return undefined;
if (!/^\d+$/.test(value.trim())) {
throw new Error(
`Invalid value for ${envName}: "${value}". Expected a positive integer.`
);
}
return parseInt(value, 10);
}

private static parseJsonArray(value: string): string[] {
try {
const parsed = JSON.parse(value);
Expand Down
2 changes: 2 additions & 0 deletions sdks/typescript/src/util/grpc-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export const channelFactory = (config: ClientConfig, credentials: ChannelCredent
'grpc.keepalive_permit_without_calls': 1,
// Enable gzip compression for all calls on this channel
'grpc.default_compression_algorithm': 2, // 2 = Gzip compression
'grpc.max_send_message_length': config.grpc_max_send_message_length ?? 4 * 1024 * 1024,
'grpc.max_receive_message_length': config.grpc_max_recv_message_length ?? 4 * 1024 * 1024,
});

export const addTokenMiddleware = (token: string) =>
Expand Down
8 changes: 5 additions & 3 deletions sdks/typescript/src/v1/client/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,9 +248,11 @@ export class AdminClient {
};
});

const limit = 4 * 1024 * 1024; // FIXME configurable GRPC limit

const batches = batch(workflowRequests, batchSize, limit);
const batches = batch(
workflowRequests,
batchSize,
this.config.grpc_max_send_message_length ?? 4 * 1024 * 1024
);

this.logger.debug(`batching ${batches.length} batches`);

Expand Down
2 changes: 1 addition & 1 deletion sdks/typescript/src/version.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const HATCHET_VERSION = '1.22.1';
export const HATCHET_VERSION = '1.22.4';
Loading