From ed599d5216c01a40dc5024522930645a364acefa Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 8 Aug 2023 17:10:47 +0200 Subject: [PATCH 1/3] feat: Scaffold plugin server --- .eslintrc | 3 +- package-lock.json | 39 +++++++++-- package.json | 3 +- src/grpc/discovery.ts | 1 - src/grpc/plugin.ts | 82 +++++++++++++++++++--- src/grpc/server.test.ts | 8 --- src/grpc/server.ts | 11 ++- src/main.ts | 5 +- src/plugin/plugin.ts | 101 ++++++++++++++++++++++++++++ src/{serve => plugin}/serve.test.ts | 5 +- src/plugin/serve.ts | 88 ++++++++++++++++++++++++ src/serve/serve.ts | 84 ----------------------- 12 files changed, 311 insertions(+), 119 deletions(-) delete mode 100644 src/grpc/server.test.ts create mode 100644 src/plugin/plugin.ts rename src/{serve => plugin}/serve.test.ts (75%) create mode 100644 src/plugin/serve.ts delete mode 100644 src/serve/serve.ts diff --git a/.eslintrc b/.eslintrc index 8a70fb8..c09c176 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,7 @@ { "root": true, "parser": "@typescript-eslint/parser", - "plugins": ["@typescript-eslint", "prettier", "unicorn"], + "plugins": ["@typescript-eslint", "prettier", "unicorn", "unused-imports"], "parserOptions": { "project": "./tsconfig.json" }, @@ -19,6 +19,7 @@ "plugin:you-dont-need-lodash-underscore/all" ], "rules": { + "unused-imports/no-unused-imports": "error", "no-console": "error", "@typescript-eslint/no-unused-vars": 0, "require-await": "off", diff --git a/package-lock.json b/package-lock.json index a3af22d..fe61dd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MPL-2.0", "dependencies": { "@apache-arrow/esnext-esm": "^12.0.1", - "@cloudquery/plugin-pb-javascript": "^0.0.6", + "@cloudquery/plugin-pb-javascript": "^0.0.7", "boolean": "^3.2.0", "winston": "^3.10.0", "yargs": "^17.7.2" @@ -33,6 +33,7 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unicorn": "^48.0.1", + "eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", "prettier": "^3.0.1", "ts-node": "^10.9.1", @@ -268,9 +269,9 @@ } }, "node_modules/@cloudquery/plugin-pb-javascript": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@cloudquery/plugin-pb-javascript/-/plugin-pb-javascript-0.0.6.tgz", - "integrity": "sha512-ElYCkzSR2ZaylIHcqW2ja81JkaRc2sHOzyw2tBHV0Rm0Tc0u6RXNg3pBMUWAXoWuPiebgv/HaKMm+wpHR7MW2w==", + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@cloudquery/plugin-pb-javascript/-/plugin-pb-javascript-0.0.7.tgz", + "integrity": "sha512-EqcmnTEgnLYETtvDpJj+yGEE7Bq+MSGH2eioYd+zvGZLUOkF/VN8FT9XyIJlZqL70vstnMxet8s1XFM3TabSGg==", "dependencies": { "google-protobuf": "^3.21.2" }, @@ -2568,6 +2569,27 @@ "node": ">=8" } }, + "node_modules/eslint-plugin-unused-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz", + "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0", + "eslint": "^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, "node_modules/eslint-plugin-you-dont-need-lodash-underscore": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/eslint-plugin-you-dont-need-lodash-underscore/-/eslint-plugin-you-dont-need-lodash-underscore-6.12.0.tgz", @@ -2580,6 +2602,15 @@ "node": ">=4.0" } }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", diff --git a/package.json b/package.json index 549cce9..2af66dd 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", "eslint-plugin-unicorn": "^48.0.1", + "eslint-plugin-unused-imports": "^3.0.0", "eslint-plugin-you-dont-need-lodash-underscore": "^6.12.0", "prettier": "^3.0.1", "ts-node": "^10.9.1", @@ -78,7 +79,7 @@ }, "dependencies": { "@apache-arrow/esnext-esm": "^12.0.1", - "@cloudquery/plugin-pb-javascript": "^0.0.6", + "@cloudquery/plugin-pb-javascript": "^0.0.7", "boolean": "^3.2.0", "winston": "^3.10.0", "yargs": "^17.7.2" diff --git a/src/grpc/discovery.ts b/src/grpc/discovery.ts index 800cfb8..b77c44e 100644 --- a/src/grpc/discovery.ts +++ b/src/grpc/discovery.ts @@ -1,6 +1,5 @@ import { discovery1 } from '@cloudquery/plugin-pb-javascript'; import grpc = require('@grpc/grpc-js'); -import winston from 'winston'; const SUPPORTED_VERSIONS = [3]; diff --git a/src/grpc/plugin.ts b/src/grpc/plugin.ts index 7136a0b..633074f 100644 --- a/src/grpc/plugin.ts +++ b/src/grpc/plugin.ts @@ -1,8 +1,17 @@ import { pluginV3 } from '@cloudquery/plugin-pb-javascript'; import grpc = require('@grpc/grpc-js'); -import winston from 'winston'; + +import { Plugin } from '../plugin/plugin.js'; export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPluginService { + // Needed due to some TypeScript nonsense + private plugin: Plugin & grpc.UntypedHandleCall; + + constructor(plugin: Plugin) { + super(); + this.plugin = plugin as Plugin & grpc.UntypedHandleCall; + } + GetName( call: grpc.ServerUnaryCall< pluginV3.cloudquery.plugin.v3.GetName.Request, @@ -10,7 +19,7 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu >, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + return callback(null, new pluginV3.cloudquery.plugin.v3.GetName.Response({ name: this.plugin.name() })); } GetVersion( call: grpc.ServerUnaryCall< @@ -19,13 +28,27 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu >, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + return callback(null, new pluginV3.cloudquery.plugin.v3.GetVersion.Response({ version: this.plugin.version() })); } Init( call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + const { + request: { spec, no_connection: noConnection }, + } = call; + + const stringSpec = new TextDecoder().decode(spec); + this.plugin + .init(stringSpec, { noConnection }) + .then(() => { + // eslint-disable-next-line promise/no-callback-in-promise + return callback(null, new pluginV3.cloudquery.plugin.v3.Init.Response()); + }) + .catch((error) => { + // eslint-disable-next-line promise/no-callback-in-promise + return callback(error, null); + }); } GetTables( call: grpc.ServerUnaryCall< @@ -34,7 +57,21 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu >, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + const { + request: { tables, skip_tables: skipTables, skip_dependent_tables: skipDependentTables }, + } = call; + + this.plugin + .tables({ tables, skipTables, skipDependentTables }) + .then((tables) => { + const encodedTables = tables.map((table) => new TextEncoder().encode(table)); + // eslint-disable-next-line promise/no-callback-in-promise + return callback(null, new pluginV3.cloudquery.plugin.v3.GetTables.Response({ tables: encodedTables })); + }) + .catch((error) => { + // eslint-disable-next-line promise/no-callback-in-promise + return callback(error, null); + }); } Sync( call: grpc.ServerWritableStream< @@ -42,7 +79,24 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu pluginV3.cloudquery.plugin.v3.Sync.Response >, ): void { - throw new Error('Method not implemented.'); + const { + request: { + tables, + skip_tables: skipTables, + skip_dependent_tables: skipDependentTables, + deterministic_cq_id: deterministicCQId, + backend: { connection, table_name: tableName }, + }, + } = call; + + this.plugin.sync({ + tables, + skipTables, + skipDependentTables, + deterministicCQId, + backendOptions: { connection, tableName }, + stream: call, + }); } Read( call: grpc.ServerWritableStream< @@ -50,7 +104,7 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu pluginV3.cloudquery.plugin.v3.Read.Response >, ): void { - throw new Error('Method not implemented.'); + this.plugin.read(call); } Write( call: grpc.ServerReadableStream< @@ -59,7 +113,8 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu >, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + this.plugin.write(call); + callback(null, new pluginV3.cloudquery.plugin.v3.Write.Response()); } Close( call: grpc.ServerUnaryCall< @@ -68,6 +123,15 @@ export class PluginServer extends pluginV3.cloudquery.plugin.v3.UnimplementedPlu >, callback: grpc.sendUnaryData, ): void { - throw new Error('Method not implemented.'); + this.plugin + .close() + .then(() => { + // eslint-disable-next-line promise/no-callback-in-promise + return callback(null, new pluginV3.cloudquery.plugin.v3.Close.Response()); + }) + .catch((error) => { + // eslint-disable-next-line promise/no-callback-in-promise + return callback(error, null); + }); } } diff --git a/src/grpc/server.test.ts b/src/grpc/server.test.ts deleted file mode 100644 index c413f9f..0000000 --- a/src/grpc/server.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import test from 'ava'; - -import { getServer } from './server.js'; - -test('getServer', (t) => { - const serve = getServer(); - t.not(serve, undefined); -}); diff --git a/src/grpc/server.ts b/src/grpc/server.ts index 9a7c1e8..9bc7d2b 100644 --- a/src/grpc/server.ts +++ b/src/grpc/server.ts @@ -2,6 +2,8 @@ import { pluginV3, discovery1 } from '@cloudquery/plugin-pb-javascript'; import grpc = require('@grpc/grpc-js'); import winston from 'winston'; +import { Plugin } from '../plugin/plugin.js'; + import { DiscoveryServer } from './discovery.js'; import { PluginServer } from './plugin.js'; @@ -9,16 +11,11 @@ export enum Network { TCP = 'tcp', } -export const getServer = () => { +export const startServer = (logger: winston.Logger, address: string, plugin: Plugin) => { const server = new grpc.Server(); - server.addService(pluginV3.cloudquery.plugin.v3.UnimplementedPluginService.definition, new PluginServer()); + server.addService(pluginV3.cloudquery.plugin.v3.UnimplementedPluginService.definition, new PluginServer(plugin)); server.addService(discovery1.cloudquery.discovery.v1.UnimplementedDiscoveryService.definition, new DiscoveryServer()); - return server; -}; - -export const startServer = (logger: winston.Logger, address: string) => { - const server = getServer(); server.bindAsync(address, grpc.ServerCredentials.createInsecure(), (error, port) => { if (error) { logger.error(error); diff --git a/src/main.ts b/src/main.ts index 8619bc0..1960522 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,3 +1,4 @@ -import { serve } from './serve/serve.js'; +import { newPlugin, newUnimplementedClient } from './plugin/plugin.js'; +import { createServeCommand } from './plugin/serve.js'; -serve.parse(); +createServeCommand(newPlugin('test', 'v1.0.0', newUnimplementedClient)).parse(); diff --git a/src/plugin/plugin.ts b/src/plugin/plugin.ts new file mode 100644 index 0000000..1af03e4 --- /dev/null +++ b/src/plugin/plugin.ts @@ -0,0 +1,101 @@ +import { Readable, Writable } from 'node:stream'; + +import { Logger } from 'winston'; + +export type BackendOptions = { + tableName: string; + connection: string; +}; + +export type TableOptions = { + tables: string[]; + skipTables: string[]; + skipDependentTables: boolean; +}; + +export type SyncOptions = { + tables: string[]; + skipTables: string[]; + skipDependentTables: boolean; + deterministicCQId: boolean; + backendOptions: BackendOptions; + stream: Writable; +}; + +export type NewClientOptions = { + noConnection: boolean; +}; + +export type NewClientFunction = (logger: Logger, spec: string, options: NewClientOptions) => Promise; + +export interface SourceClient { + close: () => Promise; + tables: (options: TableOptions) => Promise; + sync: (options: SyncOptions) => void; +} + +export interface DestinationClient { + close: () => Promise; + read: (stream: Writable) => void; + write: (stream: Readable) => void; +} + +export interface Client extends SourceClient, DestinationClient {} + +export interface Plugin { + name: () => string; + version: () => string; + write: (stream: Readable) => void; + read: (stream: Writable) => void; + setLogger: (logger: Logger) => void; + sync: (options: SyncOptions) => void; + tables: (options: TableOptions) => Promise; + init: (spec: string, options: NewClientOptions) => Promise; + close: () => Promise; +} + +export const newUnimplementedSourceClient = (): SourceClient => { + return { + close: () => Promise.reject(new Error('unimplemented')), + tables: () => Promise.reject(new Error('unimplemented')), + sync: () => Promise.reject(new Error('unimplemented')), + }; +}; + +export const newUnimplementedDestinationClient = (): DestinationClient => { + return { + close: () => Promise.reject(new Error('unimplemented')), + read: () => Promise.reject(new Error('unimplemented')), + write: () => Promise.reject(new Error('unimplemented')), + }; +}; + +export const newUnimplementedClient: NewClientFunction = (logger: Logger, spec: string, options: NewClientOptions) => { + return Promise.resolve({ + ...newUnimplementedSourceClient(), + ...newUnimplementedDestinationClient(), + }); +}; + +export const newPlugin = (name: string, version: string, newClient: NewClientFunction): Plugin => { + const plugin = { + client: undefined as Client | undefined, + logger: undefined as Logger | undefined, + name: () => name, + version: () => version, + write: (stream: Readable) => plugin.client?.write(stream) ?? new Error('client not initialized'), + read: (stream: Writable) => plugin.client?.read(stream) ?? new Error('client not initialized'), + setLogger: (logger: Logger) => { + plugin.logger = logger; + }, + sync: (options: SyncOptions) => plugin.client?.sync(options) ?? new Error('client not initialized'), + tables: (options: TableOptions) => + plugin.client?.tables(options) ?? Promise.reject(new Error('client not initialized')), + init: async (spec: string, options: NewClientOptions) => { + plugin.client = await newClient(plugin.logger!, spec, options); + }, + close: () => plugin.client?.close() ?? Promise.reject(new Error('client not initialized')), + }; + + return plugin; +}; diff --git a/src/serve/serve.test.ts b/src/plugin/serve.test.ts similarity index 75% rename from src/serve/serve.test.ts rename to src/plugin/serve.test.ts index ef9d7ff..64a7353 100644 --- a/src/serve/serve.test.ts +++ b/src/plugin/serve.test.ts @@ -1,8 +1,9 @@ import test from 'ava'; -import { serve as serveWithExit, ServeArguments } from './serve.js'; +import { newPlugin, newUnimplementedClient } from './plugin.js'; +import { createServeCommand, ServeArguments } from './serve.js'; -const serve = serveWithExit.exitProcess(false); +const serve = createServeCommand(newPlugin('test', 'v1.0.0', newUnimplementedClient)).exitProcess(false); test('should return error without command', (t) => { t.throws(() => serve.parse([]), { message: 'Specify a command to run' }); diff --git a/src/plugin/serve.ts b/src/plugin/serve.ts new file mode 100644 index 0000000..ee309fd --- /dev/null +++ b/src/plugin/serve.ts @@ -0,0 +1,88 @@ +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +import { startServer, Network } from '../grpc/server.js'; +import { LogFormat, LogLevel, createLogger } from '../logger/logger.js'; + +import { Plugin } from './plugin.js'; + +const TELEMETRY_LEVEL_CHOICES = ['none', 'errors', 'stats', 'all'] as const; + +export type ServeArguments = { + address: string; + network: Network; + logLevel: LogLevel; + logFormat: LogFormat; + sentry: boolean; + otelEndpoint: string; + otelEndpointInsecure: boolean; + telemetryLevel: (typeof TELEMETRY_LEVEL_CHOICES)[number]; +}; + +export const createServeCommand = (plugin: Plugin) => { + return yargs(hideBin(process.argv)) + .command( + 'serve', + 'start plugin gRPC server', + () => {}, + ({ address, network, logLevel, logFormat, sentry: sentry, otelEndpoint, telemetryLevel }: ServeArguments) => { + const logger = createLogger(logLevel, logFormat); + logger.info(JSON.stringify({ address, network, logLevel, logFormat, sentry, otelEndpoint, telemetryLevel })); + startServer(logger, address, plugin); + }, + ) + .options({ + address: { + alias: 'a', + type: 'string', + description: 'address to bind to', + default: 'localhost:7777', + }, + network: { + alias: 'n', + type: 'string', + choices: Object.values(Network), + description: 'network to bind to', + default: 'tcp', + }, + 'log-level': { + alias: 'l', + type: 'string', + choices: Object.values(LogLevel), + description: 'log level', + default: 'info', + }, + 'log-format': { + alias: 'f', + type: 'string', + choices: Object.values(LogFormat), + description: 'log format', + default: 'text', + }, + sentry: { + type: 'boolean', + description: 'enable sentry reporting. Pass `--no-sentry` to disable.', + default: true, + }, + 'otel-endpoint': { + type: 'string', + description: 'OpenTelemetry collector endpoint', + default: '', + }, + 'otel-endpoint-insecure': { + type: 'boolean', + description: 'use Open Telemetry HTTP endpoint (for development only)', + default: false, + }, + 'telemetry-level': { + type: 'string', + description: 'CQ Telemetry level', + hidden: true, + choices: TELEMETRY_LEVEL_CHOICES, + default: 'all', + }, + }) + .env('CQ_') + .strict() + .demandCommand(1, 1, 'Specify a command to run'); +}; diff --git a/src/serve/serve.ts b/src/serve/serve.ts deleted file mode 100644 index 9cbebee..0000000 --- a/src/serve/serve.ts +++ /dev/null @@ -1,84 +0,0 @@ -import yargs from 'yargs'; -import { hideBin } from 'yargs/helpers'; - -import { startServer, Network } from '../grpc/server.js'; -import { LogFormat, LogLevel, createLogger } from '../logger/logger.js'; - -const TELEMETRY_LEVEL_CHOICES = ['none', 'errors', 'stats', 'all'] as const; - -export type ServeArguments = { - address: string; - network: Network; - logLevel: LogLevel; - logFormat: LogFormat; - sentry: boolean; - otelEndpoint: string; - otelEndpointInsecure: boolean; - telemetryLevel: (typeof TELEMETRY_LEVEL_CHOICES)[number]; -}; - -export const serve = yargs(hideBin(process.argv)) - .command( - 'serve', - 'start plugin gRPC server', - () => {}, - ({ address, network, logLevel, logFormat, sentry: sentry, otelEndpoint, telemetryLevel }: ServeArguments) => { - const logger = createLogger(logLevel, logFormat); - logger.info(JSON.stringify({ address, network, logLevel, logFormat, sentry, otelEndpoint, telemetryLevel })); - startServer(logger, address); - }, - ) - .options({ - address: { - alias: 'a', - type: 'string', - description: 'address to bind to', - default: 'localhost:7777', - }, - network: { - alias: 'n', - type: 'string', - choices: Object.values(Network), - description: 'network to bind to', - default: 'tcp', - }, - 'log-level': { - alias: 'l', - type: 'string', - choices: Object.values(LogLevel), - description: 'log level', - default: 'info', - }, - 'log-format': { - alias: 'f', - type: 'string', - choices: Object.values(LogFormat), - description: 'log format', - default: 'text', - }, - sentry: { - type: 'boolean', - description: 'enable sentry reporting. Pass `--no-sentry` to disable.', - default: true, - }, - 'otel-endpoint': { - type: 'string', - description: 'OpenTelemetry collector endpoint', - default: '', - }, - 'otel-endpoint-insecure': { - type: 'boolean', - description: 'use Open Telemetry HTTP endpoint (for development only)', - default: false, - }, - 'telemetry-level': { - type: 'string', - description: 'CQ Telemetry level', - hidden: true, - choices: TELEMETRY_LEVEL_CHOICES, - default: 'all', - }, - }) - .env('CQ_') - .strict() - .demandCommand(1, 1, 'Specify a command to run'); From c60ec36b543f851bb03d0e1aaaf80fec36a60b8c Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 8 Aug 2023 17:18:45 +0200 Subject: [PATCH 2/3] fix: Log --- src/grpc/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grpc/server.ts b/src/grpc/server.ts index 9bc7d2b..fa8b3a3 100644 --- a/src/grpc/server.ts +++ b/src/grpc/server.ts @@ -22,6 +22,6 @@ export const startServer = (logger: winston.Logger, address: string, plugin: Plu return; } server.start(); - logger.info('server running on port', port); + logger.info(`server running on port: ${port}`); }); }; From a3fc4da7ecf672f291b74cccd3ad45e1c01f9996 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 8 Aug 2023 17:21:46 +0200 Subject: [PATCH 3/3] fix: Lint --- src/transformers/openapi.test.ts | 2 +- src/transformers/openapi.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/transformers/openapi.test.ts b/src/transformers/openapi.test.ts index 62937d5..08343db 100644 --- a/src/transformers/openapi.test.ts +++ b/src/transformers/openapi.test.ts @@ -1,4 +1,4 @@ -import { DataType, Utf8, Int64, Bool } from '@apache-arrow/esnext-esm'; +import { Utf8, Int64, Bool } from '@apache-arrow/esnext-esm'; import test from 'ava'; import { Column } from '../schema/column.js'; diff --git a/src/transformers/openapi.ts b/src/transformers/openapi.ts index 7f44071..4744147 100644 --- a/src/transformers/openapi.ts +++ b/src/transformers/openapi.ts @@ -1,4 +1,4 @@ -import { DataType, Field, Utf8, Int64, Bool } from '@apache-arrow/esnext-esm'; +import { DataType, Utf8, Int64, Bool } from '@apache-arrow/esnext-esm'; import { Column } from '../schema/column.js'; import { JSONType } from '../types/json.js';