ToolJet/ToolJet · error · GrpcOperationError

Service ${serviceName} is not a valid constructor function

Error message

Service ${serviceName} is not a valid constructor function

What it means

Thrown in buildReflectionClient when findServiceInPackage returns a value but typeof service !== 'function'. A gRPC service constructor must be a function; getting a non-function (typically an object or namespace) means the resolved symbol is a nested package/namespace, not a service constructor. The traversal matched a container, not the service itself.

Source

Thrown at plugins/packages/grpcv2/lib/operations.ts:91

    if (!descriptor) {
      throw new GrpcOperationError(`Service descriptor not found for ${serviceName}`);
    }

    const packageObject = descriptor.getPackageObject({
      keepCase: true,
      enums: String,
      longs: String,
      defaults: true,
      oneofs: true
    });

    const service = findServiceInPackage(packageObject, serviceName);
    if (!service) {
      throw new GrpcOperationError(`Service ${serviceName} not found in proto descriptor`);
    }

    if (typeof service !== 'function') {
      throw new GrpcOperationError(`Service ${serviceName} is not a valid constructor function`);
    }

    const credentials = buildChannelCredentials(sourceOptions);
    const cleanUrl = sanitizeGrpcServerUrl(sourceOptions.url, sourceOptions.ssl_enabled);
    const ServiceConstructor = service as new (url: string, credentials: any) => GrpcClient;
    const grpcClient = new ServiceConstructor(cleanUrl, credentials);

    return grpcClient;
  } catch (error: unknown) {
    const err = toError(error);
    throw new GrpcOperationError(`Failed to create reflection client for service ${serviceName}: ${err.message}`, error);
  }
};

export const buildProtoFileClient = async (sourceOptions: SourceOptions, serviceName: string): Promise<GrpcClient> => {
  try {
    const packageDefinition = await loadProtoFromRemoteUrl(sourceOptions.proto_file_url!);
    const grpcObject = grpc.loadPackageDefinition(packageDefinition);

View on GitHub (pinned to 20602a8e10)

Solutions

  1. Target the fully-qualified service symbol that resolves to the constructor, not an enclosing namespace.
  2. Verify the proto: a service keyword defines the callable; ensure the name maps to that, not a package or message.
  3. Retry after refreshing the reflection descriptor cache.
Defensive patterns

Strategy: type-guard

Type guard

const isServiceConstructor = (v: unknown): v is new (url: string, creds: any) => GrpcClient =>
  typeof v === 'function';
// usage: if (!isServiceConstructor(sym)) throw new Error(`${name} is a namespace, not a service`);

Prevention

When it happens

Trigger: serviceName resolves to a package or namespace object rather than the service constructor — e.g. the name matches a package that contains the service. A proto where a message or enum name shadows the service name.

Common situations: Service and package sharing a name; pointing at an intermediate namespace; descriptor version where the service symbol is wrapped differently; stale descriptor cached by reflection.

Related errors


AI-assisted analysis of ToolJet/ToolJet@20602a8e10 (2026-08-13). Data as JSON: /api/errors/41fb3653c1fe7e4e. Report an issue: GitHub.