mirror of
https://github.com/ToolJet/ToolJet
synced 2026-04-21 21:47:17 +00:00
refactor: make code more strict in typing
This commit is contained in:
parent
d44f755df9
commit
88fa169af8
2 changed files with 57 additions and 67 deletions
|
|
@ -1,7 +1,6 @@
|
|||
import { SourceOptions, QueryOptions, GrpcService, GrpcMethod, GrpcClient, UnaryMethodFunction, GrpcOperationError, toError, isRecord } from './types';
|
||||
import { sanitizeHeaders } from '@tooljet-plugins/common';
|
||||
import { SourceOptions, QueryOptions, GrpcService, GrpcMethod, GrpcClient, GrpcOperationError, toError, isRecord } from './types';
|
||||
import got from 'got';
|
||||
import { GrpcReflection, serviceHelper } from 'grpc-js-reflection-client';
|
||||
import { GrpcReflection, serviceHelper, ServiceHelperOptionsType } from 'grpc-js-reflection-client';
|
||||
import type { ListMethodsType } from 'grpc-js-reflection-client/dist/Types/ListMethodsType';
|
||||
import * as grpc from '@grpc/grpc-js';
|
||||
import * as protoLoader from '@grpc/proto-loader';
|
||||
|
|
@ -14,8 +13,7 @@ export const buildReflectionClient = async (sourceOptions: SourceOptions, servic
|
|||
try {
|
||||
const credentials = buildChannelCredentials(sourceOptions);
|
||||
const cleanUrl = sanitizeGrpcServerUrl(sourceOptions.url, sourceOptions.ssl_enabled);
|
||||
|
||||
const client = await serviceHelper({
|
||||
const serviceHelperOptions: ServiceHelperOptionsType = {
|
||||
host: cleanUrl,
|
||||
servicePath: serviceName,
|
||||
credentials,
|
||||
|
|
@ -27,7 +25,9 @@ export const buildReflectionClient = async (sourceOptions: SourceOptions, servic
|
|||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const client = await serviceHelper(serviceHelperOptions) as GrpcClient;
|
||||
|
||||
return client;
|
||||
} catch (error: unknown) {
|
||||
|
|
@ -368,21 +368,17 @@ export const buildMetadataForNonTlsConnection = (
|
|||
|
||||
// 1. Add datasource metadata (client-id, cp-env, etc.)
|
||||
if (sourceOptions.metadata && Array.isArray(sourceOptions.metadata) && sourceOptions.metadata.length > 0) {
|
||||
const sanitizedDatasourceMetadata = extractDatasourceHeaders(sourceOptions);
|
||||
const sanitizedDatasourceMetadata = extractSanitizedMetadata(sourceOptions.metadata);
|
||||
Object.entries(sanitizedDatasourceMetadata).forEach(([key, value]) => {
|
||||
if (key && value) {
|
||||
metadata.set(key, String(value));
|
||||
}
|
||||
metadata.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Add query metadata
|
||||
if (queryOptions?.metadata && Array.isArray(queryOptions.metadata) && queryOptions.metadata.length > 0) {
|
||||
const sanitizedQueryMetadata = extractQueryHeaders(queryOptions);
|
||||
const sanitizedQueryMetadata = extractSanitizedMetadata(queryOptions.metadata);
|
||||
Object.entries(sanitizedQueryMetadata).forEach(([key, value]) => {
|
||||
if (key && value) {
|
||||
metadata.set(key, String(value));
|
||||
}
|
||||
metadata.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +405,7 @@ export const buildMetadataForNonTlsConnection = (
|
|||
|
||||
case 'oauth2':
|
||||
// For OAuth2, access_token is in datasource metadata (already added above)
|
||||
const sanitizedAuth = extractDatasourceHeaders(sourceOptions);
|
||||
const sanitizedAuth = extractSanitizedMetadata(sourceOptions.metadata || []);
|
||||
if (sanitizedAuth?.access_token) {
|
||||
if (sourceOptions.add_token_to === 'header') {
|
||||
const prefix = sourceOptions.header_prefix || 'Bearer ';
|
||||
|
|
@ -464,23 +460,19 @@ export const buildMetadataForTlsConnection = (
|
|||
|
||||
// 1. Add datasource metadata (client-id, tenant-id, etc.)
|
||||
if (sourceOptions.metadata && Array.isArray(sourceOptions.metadata) && sourceOptions.metadata.length > 0) {
|
||||
const sanitizedDatasourceMetadata = extractDatasourceHeaders(sourceOptions);
|
||||
const sanitizedDatasourceMetadata = extractSanitizedMetadata(sourceOptions.metadata);
|
||||
|
||||
Object.entries(sanitizedDatasourceMetadata).forEach(([key, value]) => {
|
||||
if (key && value) {
|
||||
combinedMetadata.set(key, String(value));
|
||||
}
|
||||
combinedMetadata.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Add query metadata
|
||||
if (queryOptions?.metadata && Array.isArray(queryOptions.metadata) && queryOptions.metadata.length > 0) {
|
||||
const sanitizedQueryMetadata = extractQueryHeaders(queryOptions);
|
||||
const sanitizedQueryMetadata = extractSanitizedMetadata(queryOptions.metadata);
|
||||
|
||||
Object.entries(sanitizedQueryMetadata).forEach(([key, value]) => {
|
||||
if (key && value) {
|
||||
combinedMetadata.set(key, String(value));
|
||||
}
|
||||
combinedMetadata.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -488,41 +480,46 @@ export const buildMetadataForTlsConnection = (
|
|||
};
|
||||
|
||||
/**
|
||||
* Extract and sanitize datasource metadata only (client-id, cp-env, etc.)
|
||||
* Used for critical metadata that needs reliable delivery
|
||||
* Extract and sanitize metadata from a metadata array
|
||||
* Returns sanitized metadata as key-value pairs
|
||||
*/
|
||||
const extractDatasourceHeaders = (sourceOptions: SourceOptions): { [k: string]: string } => {
|
||||
const ensureArrayFormat = (metadata: any) => {
|
||||
if (!metadata) return [];
|
||||
if (Array.isArray(metadata)) return metadata;
|
||||
const extractSanitizedMetadata = (metadata: unknown): { [k: string]: string } => {
|
||||
type MetadataEntry = [string, string];
|
||||
|
||||
const cleanMetadata = (metadata: [string, unknown][]): [string, unknown][] =>
|
||||
metadata.filter(([k, _]) => k !== '').map(([k, v]) => [k.trim(), v]);
|
||||
|
||||
const filterValidMetadataEntries = (metadata: [string, unknown][]): MetadataEntry[] => {
|
||||
return metadata.filter((entry): entry is MetadataEntry => {
|
||||
const [_, value] = entry;
|
||||
if (value == null) return false;
|
||||
if (typeof value === 'string') return true;
|
||||
// Convert array to string by joining
|
||||
if (Array.isArray(value) && value.every((v) => typeof v === 'string')) {
|
||||
entry[1] = value.join(', ');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const processMetadata = (rawMetadata: [string, unknown][]): { [k: string]: string } => {
|
||||
const cleaned = cleanMetadata(rawMetadata || []);
|
||||
const validMetadata = filterValidMetadataEntries(cleaned);
|
||||
return Object.fromEntries(validMetadata);
|
||||
};
|
||||
|
||||
const ensureArrayFormat = (data: unknown): [string, unknown][] => {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) {
|
||||
// Ensure each item is a tuple with at least 2 elements
|
||||
return data.filter(item => Array.isArray(item) && item.length >= 2)
|
||||
.map(item => [String(item[0]), item[1]]);
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const sourceOptionsWithHeaders = {
|
||||
...sourceOptions,
|
||||
headers: ensureArrayFormat(sourceOptions.metadata)
|
||||
};
|
||||
|
||||
return sanitizeHeaders(sourceOptionsWithHeaders, {}, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract and sanitize query metadata only (request-id, trace-id, etc.)
|
||||
* Used for query-specific headers that follow gRPC standards
|
||||
*/
|
||||
const extractQueryHeaders = (queryOptions: QueryOptions): { [k: string]: string } => {
|
||||
const ensureArrayFormat = (metadata: any) => {
|
||||
if (!metadata) return [];
|
||||
if (Array.isArray(metadata)) return metadata;
|
||||
return [];
|
||||
};
|
||||
|
||||
const queryOptionsWithHeaders = {
|
||||
...queryOptions,
|
||||
headers: ensureArrayFormat(queryOptions.metadata)
|
||||
};
|
||||
|
||||
return sanitizeHeaders({}, queryOptionsWithHeaders, false);
|
||||
return processMetadata(ensureArrayFormat(metadata));
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -573,7 +570,7 @@ export const buildAuthCallCredentials = (sourceOptions: SourceOptions): grpc.Cal
|
|||
break;
|
||||
|
||||
case 'oauth2':
|
||||
const sanitizedMetadata = extractDatasourceHeaders(sourceOptions);
|
||||
const sanitizedMetadata = extractSanitizedMetadata(sourceOptions.metadata || []);
|
||||
if (sanitizedMetadata?.access_token) {
|
||||
if (sourceOptions.add_token_to === 'header') {
|
||||
const prefix = sourceOptions.header_prefix || 'Bearer ';
|
||||
|
|
@ -611,7 +608,7 @@ export const executeGrpcMethod = async (
|
|||
sourceOptions: SourceOptions,
|
||||
queryOptions: QueryOptions
|
||||
): Promise<Record<string, unknown>> => {
|
||||
const methodFunction = client[methodName] as UnaryMethodFunction;
|
||||
const methodFunction = client[methodName] as unknown;
|
||||
|
||||
if (typeof methodFunction !== 'function') {
|
||||
throw new GrpcOperationError(`Method ${methodName} not found on client`);
|
||||
|
|
|
|||
|
|
@ -12,18 +12,11 @@ function hasProperty<T extends PropertyKey>(obj: unknown, prop: T): obj is Recor
|
|||
return isRecord(obj) && prop in obj;
|
||||
}
|
||||
|
||||
// Type for gRPC unary method functions based on grpc-js implementation
|
||||
export type UnaryMethodFunction = (
|
||||
message: any,
|
||||
metadataOrCallback: grpc.Metadata | grpc.CallOptions | Function,
|
||||
optionsOrCallback?: grpc.CallOptions | Function,
|
||||
callback?: Function
|
||||
) => void;
|
||||
// Use SDK's Client class as the base type for all gRPC clients
|
||||
export type GrpcClient = grpc.Client;
|
||||
|
||||
export interface GrpcClient {
|
||||
[methodName: string]: UnaryMethodFunction | any;
|
||||
waitForReady?(deadline: Date, callback: (error?: Error) => void): void;
|
||||
}
|
||||
// Re-export useful SDK types
|
||||
export type { UnaryCallback } from '@grpc/grpc-js/build/src/client';
|
||||
|
||||
export { toError, isRecord, hasProperty };
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue