@whaly/connector-sdk 0.3.0 → 0.3.2
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.
- package/dist/index.d.mts +19 -7
- package/dist/index.d.ts +19 -7
- package/dist/index.js +77 -89
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +76 -89
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxiosInstance, AxiosRequestHeaders, AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';
|
|
2
|
-
import {
|
|
2
|
+
import Ajv, { ValidateFunction, ErrorObject } from 'ajv';
|
|
3
3
|
import { Dayjs } from 'dayjs';
|
|
4
4
|
import { WriteStream } from 'fs';
|
|
5
5
|
import { WorkBook } from 'xlsx';
|
|
@@ -273,9 +273,12 @@ declare class StreamState$1 {
|
|
|
273
273
|
keyProperties: string[];
|
|
274
274
|
_hasBeenLoadedYetDuringThisSync: boolean;
|
|
275
275
|
replicationMethod: ReplicationMethod;
|
|
276
|
+
compiledValidateFn?: ValidateFunction;
|
|
276
277
|
constructor(streamId: StreamId, dbSync: StreamWarehouseSyncService, replicationMethod: ReplicationMethod);
|
|
277
278
|
setSchema(schema: FlattenedSchema): void;
|
|
278
279
|
getSchema(): FlattenedSchema | undefined;
|
|
280
|
+
setCompiledValidateFn(fn: ValidateFunction): void;
|
|
281
|
+
getCompiledValidateFn(): ValidateFunction | undefined;
|
|
279
282
|
getBatchDate(): string;
|
|
280
283
|
getBatchedRowCount(): number;
|
|
281
284
|
incrementBatchedRowCount(): void;
|
|
@@ -308,7 +311,7 @@ declare abstract class ITarget<C extends BaseConfig = BaseConfig> {
|
|
|
308
311
|
streams: {
|
|
309
312
|
[streamName: string]: StreamState$1;
|
|
310
313
|
};
|
|
311
|
-
|
|
314
|
+
ajv: Ajv;
|
|
312
315
|
constructor(config: C, stateProvider: StateProvider);
|
|
313
316
|
static requiredConfigKeys: string[];
|
|
314
317
|
abstract newDBSyncInstance: (streamId: StreamId, database: string, schema: string, table: string) => StreamWarehouseSyncService;
|
|
@@ -414,6 +417,7 @@ declare abstract class Stream<O = any, C = any, P = undefined> {
|
|
|
414
417
|
isSorted: boolean;
|
|
415
418
|
isSilent: boolean;
|
|
416
419
|
childConcurrency: number;
|
|
420
|
+
totalRows?: number;
|
|
417
421
|
rowsSyncedMetricsConf: MetricConfiguration;
|
|
418
422
|
executionTimeMetricsConf: MetricConfiguration;
|
|
419
423
|
constructor(config: C, tapState: InputTapState, target: ITarget, childConcurrency?: number);
|
|
@@ -595,8 +599,8 @@ declare abstract class Tap<C> {
|
|
|
595
599
|
|
|
596
600
|
declare class SchemaValidationError extends Error {
|
|
597
601
|
streamId: StreamId;
|
|
598
|
-
validationErrors:
|
|
599
|
-
constructor(message: string, streamId: StreamId, errors:
|
|
602
|
+
validationErrors: ErrorObject[];
|
|
603
|
+
constructor(message: string, streamId: StreamId, errors: ErrorObject[]);
|
|
600
604
|
}
|
|
601
605
|
declare class MissingFieldInSchemaError extends Error {
|
|
602
606
|
streamId: StreamId;
|
|
@@ -761,7 +765,7 @@ declare const createCellReference: (columnIndex: number, rowIndex: number) => st
|
|
|
761
765
|
/**
|
|
762
766
|
* Low-level Excel extractor: returns array of row records from a workbook.
|
|
763
767
|
*/
|
|
764
|
-
declare const extractExcelRows: (localFilePath: string, conf: ExcelExtractionConfig) => Promise<Record<string, string>[]>;
|
|
768
|
+
declare const extractExcelRows: (localFilePath: string, conf: ExcelExtractionConfig) => Promise<Record<string, string | null>[]>;
|
|
765
769
|
/**
|
|
766
770
|
* Validates an Excel file configuration.
|
|
767
771
|
*/
|
|
@@ -773,7 +777,7 @@ declare const findAllMatchingConfigs: (filename: string, configs: ExcelExtractio
|
|
|
773
777
|
/**
|
|
774
778
|
* Creates an async generator from Excel data.
|
|
775
779
|
*/
|
|
776
|
-
declare function createExcelGenerator(data: Record<string, string>[]): AsyncGenerator<Record<string, any>, void, unknown>;
|
|
780
|
+
declare function createExcelGenerator(data: Record<string, string | null>[]): AsyncGenerator<Record<string, any>, void, unknown>;
|
|
777
781
|
|
|
778
782
|
/**
|
|
779
783
|
* Builder pattern for creating Excel configurations.
|
|
@@ -808,6 +812,11 @@ declare class ExcelExtractionConfigBuilder {
|
|
|
808
812
|
* Supports custom separators, encoding, BOM stripping, and field transformers.
|
|
809
813
|
*/
|
|
810
814
|
declare function rowGeneratorFromCsv(path: string, fileConfig: CsvFileConfig): AsyncGenerator<Record<string, any>>;
|
|
815
|
+
/**
|
|
816
|
+
* Counts data rows in a CSV file by scanning for newline bytes.
|
|
817
|
+
* Subtracts 1 for the header row. Fast: no CSV parsing required.
|
|
818
|
+
*/
|
|
819
|
+
declare function countCsvLines(filePath: string): Promise<number>;
|
|
811
820
|
/**
|
|
812
821
|
* Validates CSV header row against expected configuration.
|
|
813
822
|
*/
|
|
@@ -885,6 +894,7 @@ declare class FileStream extends Stream<Record<string, any>, FileStreamConfig> {
|
|
|
885
894
|
/**
|
|
886
895
|
* Yield records from all file(s).
|
|
887
896
|
* When multiple file paths are provided, records are yielded sequentially from each file.
|
|
897
|
+
* Pre-counts total rows across all files to enable percentage progress logging.
|
|
888
898
|
*/
|
|
889
899
|
_getRecords(): AsyncIterable<Record<string, any>>;
|
|
890
900
|
}
|
|
@@ -1047,6 +1057,8 @@ declare class CloudStorageService {
|
|
|
1047
1057
|
writeStringObject(objectPath: string, contents: string): Promise<void>;
|
|
1048
1058
|
/**
|
|
1049
1059
|
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
1060
|
+
* Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
|
|
1061
|
+
* or `<prefix>/default/` otherwise.
|
|
1050
1062
|
* Returns the GCS File reference.
|
|
1051
1063
|
*/
|
|
1052
1064
|
uploadFileWithUniqueName(filePath: string, prefix: string, streamId: string): Promise<File>;
|
|
@@ -1131,4 +1143,4 @@ declare class GCSStateProvider implements StateProvider {
|
|
|
1131
1143
|
writeState(state: string): Promise<void>;
|
|
1132
1144
|
}
|
|
1133
1145
|
|
|
1134
|
-
export { ALL_STREAM_ID_KEYWORD, API_CALLS_METRIC_NAME, Authenticator, BATCH_INTERVAL_MS, type BaseConfig, type BigQueryConfig, BigQueryTarget, type BigqueryLocation, type Bookmark, type Bookmarks, CloudStorageService, type CloudStorageServiceOptions, type ColumnMappingStoreUnsafeToSafe, CounterMetric, CsvExtractionConfigBuilder, type CsvFieldsArrayConfig, type CsvFieldsConfig, type CsvFieldsDictConfig, type CsvFileConfig, type CsvStreamConfig, DEFAULT_MAX_CONCURRENT_STREAMS, EXECUTION_TIME_METRIC_NAME, type ErrorType, type ExcelCustomExtractorConfig, type ExcelDerivedField, type ExcelExtractionBaseConfig, type ExcelExtractionConfig, ExcelExtractionConfigBuilder, type ExcelFieldMapping, type ExcelFieldSpec, type ExcelSingleSheetExtractionConfig, type ExcelSourceColumn, type ExcelStreamConfig, type FieldType, FileFormat, FilePatterns, FileStream, type FileStreamConfig, type FileStreamEntry, FileTap, type FileTapConfig, type FlattenedSchema, GCSStateProvider, type GetConfig, type HTTPHeaders, type HTTPMethod, ITarget, type InputTapState, type InternalTableField, type JSONSchemaFieldDefinition, type Message, type MessageType, type MetricConfiguration, MissingFieldInSchemaError, MissingSchemaError, type PostConfig, type ProgressMarkers, type PropertiesMetadata, RESTStream, ROWS_SYNCED_METRIC_NAME, RecordMessage, RenameColumnStore, ReplicationMethod, ReplicationMethodMessage, type SafeColumnNameConverterFn, type Schema, SchemaMessage, SchemaValidationError, SftpService, type State, type StateHolder, StateMessage, type StateProvider, StateService, Stream, type StreamId, type StreamState, StreamWarehouseSyncService, type StreamWithTempFile, type SyncOptions, Tap, type TapState, type TargetSchemaHook, type TargetSchemaHookInput, type TempFile, type URLParams, VariableExtractors, type WarehouseTableField, _greaterThan, addWhalyFields, checkCsvHeaderRow, createCellReference, createCsvStreamConfig, createExcelGenerator, createExcelStreamConfig, createTemporaryFileStream, csvFieldsToJsonSchema, excelColumnToIndex, excelFieldsToJsonSchema, excelRowToIndex, extractExcelRows, extractPrimaryKeysFromCsvConfig, extractPrimaryKeysFromExcelFields, extractStateForStream, fieldTypeToJsonSchema, finalizeStateProgressMarkers, findAllMatchingConfigs, flattenSchema, getAllMetrics, getAxiosInstance, getCounterMetric, getCounterMetrics, getDownloadFileApiCall, getJSONApiCall, getJSONApiCallWithFullResponse, getRawJSONApiCall, gracefulExit, haltAndCatchFire, incrementStreamState, indexToExcelColumn, indexToExcelRow, loadJson, loadSchema, logger, parseCellReference, postFormDataApiCall, postJSONApiCall, postUrlEncodedApiCall, printMemoryFootprint, processFileStreams, removeParasiteProperties, rowGeneratorFromCsv, safePath, unzip, validateExcelConfig, writeDataToCsv, writeGeneratorToCSV };
|
|
1146
|
+
export { ALL_STREAM_ID_KEYWORD, API_CALLS_METRIC_NAME, Authenticator, BATCH_INTERVAL_MS, type BaseConfig, type BigQueryConfig, BigQueryTarget, type BigqueryLocation, type Bookmark, type Bookmarks, CloudStorageService, type CloudStorageServiceOptions, type ColumnMappingStoreUnsafeToSafe, CounterMetric, CsvExtractionConfigBuilder, type CsvFieldsArrayConfig, type CsvFieldsConfig, type CsvFieldsDictConfig, type CsvFileConfig, type CsvStreamConfig, DEFAULT_MAX_CONCURRENT_STREAMS, EXECUTION_TIME_METRIC_NAME, type ErrorType, type ExcelCustomExtractorConfig, type ExcelDerivedField, type ExcelExtractionBaseConfig, type ExcelExtractionConfig, ExcelExtractionConfigBuilder, type ExcelFieldMapping, type ExcelFieldSpec, type ExcelSingleSheetExtractionConfig, type ExcelSourceColumn, type ExcelStreamConfig, type FieldType, FileFormat, FilePatterns, FileStream, type FileStreamConfig, type FileStreamEntry, FileTap, type FileTapConfig, type FlattenedSchema, GCSStateProvider, type GetConfig, type HTTPHeaders, type HTTPMethod, ITarget, type InputTapState, type InternalTableField, type JSONSchemaFieldDefinition, type Message, type MessageType, type MetricConfiguration, MissingFieldInSchemaError, MissingSchemaError, type PostConfig, type ProgressMarkers, type PropertiesMetadata, RESTStream, ROWS_SYNCED_METRIC_NAME, RecordMessage, RenameColumnStore, ReplicationMethod, ReplicationMethodMessage, type SafeColumnNameConverterFn, type Schema, SchemaMessage, SchemaValidationError, SftpService, type State, type StateHolder, StateMessage, type StateProvider, StateService, Stream, type StreamId, type StreamState, StreamWarehouseSyncService, type StreamWithTempFile, type SyncOptions, Tap, type TapState, type TargetSchemaHook, type TargetSchemaHookInput, type TempFile, type URLParams, VariableExtractors, type WarehouseTableField, _greaterThan, addWhalyFields, checkCsvHeaderRow, countCsvLines, createCellReference, createCsvStreamConfig, createExcelGenerator, createExcelStreamConfig, createTemporaryFileStream, csvFieldsToJsonSchema, excelColumnToIndex, excelFieldsToJsonSchema, excelRowToIndex, extractExcelRows, extractPrimaryKeysFromCsvConfig, extractPrimaryKeysFromExcelFields, extractStateForStream, fieldTypeToJsonSchema, finalizeStateProgressMarkers, findAllMatchingConfigs, flattenSchema, getAllMetrics, getAxiosInstance, getCounterMetric, getCounterMetrics, getDownloadFileApiCall, getJSONApiCall, getJSONApiCallWithFullResponse, getRawJSONApiCall, gracefulExit, haltAndCatchFire, incrementStreamState, indexToExcelColumn, indexToExcelRow, loadJson, loadSchema, logger, parseCellReference, postFormDataApiCall, postJSONApiCall, postUrlEncodedApiCall, printMemoryFootprint, processFileStreams, removeParasiteProperties, rowGeneratorFromCsv, safePath, unzip, validateExcelConfig, writeDataToCsv, writeGeneratorToCSV };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxiosInstance, AxiosRequestHeaders, AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';
|
|
2
|
-
import {
|
|
2
|
+
import Ajv, { ValidateFunction, ErrorObject } from 'ajv';
|
|
3
3
|
import { Dayjs } from 'dayjs';
|
|
4
4
|
import { WriteStream } from 'fs';
|
|
5
5
|
import { WorkBook } from 'xlsx';
|
|
@@ -273,9 +273,12 @@ declare class StreamState$1 {
|
|
|
273
273
|
keyProperties: string[];
|
|
274
274
|
_hasBeenLoadedYetDuringThisSync: boolean;
|
|
275
275
|
replicationMethod: ReplicationMethod;
|
|
276
|
+
compiledValidateFn?: ValidateFunction;
|
|
276
277
|
constructor(streamId: StreamId, dbSync: StreamWarehouseSyncService, replicationMethod: ReplicationMethod);
|
|
277
278
|
setSchema(schema: FlattenedSchema): void;
|
|
278
279
|
getSchema(): FlattenedSchema | undefined;
|
|
280
|
+
setCompiledValidateFn(fn: ValidateFunction): void;
|
|
281
|
+
getCompiledValidateFn(): ValidateFunction | undefined;
|
|
279
282
|
getBatchDate(): string;
|
|
280
283
|
getBatchedRowCount(): number;
|
|
281
284
|
incrementBatchedRowCount(): void;
|
|
@@ -308,7 +311,7 @@ declare abstract class ITarget<C extends BaseConfig = BaseConfig> {
|
|
|
308
311
|
streams: {
|
|
309
312
|
[streamName: string]: StreamState$1;
|
|
310
313
|
};
|
|
311
|
-
|
|
314
|
+
ajv: Ajv;
|
|
312
315
|
constructor(config: C, stateProvider: StateProvider);
|
|
313
316
|
static requiredConfigKeys: string[];
|
|
314
317
|
abstract newDBSyncInstance: (streamId: StreamId, database: string, schema: string, table: string) => StreamWarehouseSyncService;
|
|
@@ -414,6 +417,7 @@ declare abstract class Stream<O = any, C = any, P = undefined> {
|
|
|
414
417
|
isSorted: boolean;
|
|
415
418
|
isSilent: boolean;
|
|
416
419
|
childConcurrency: number;
|
|
420
|
+
totalRows?: number;
|
|
417
421
|
rowsSyncedMetricsConf: MetricConfiguration;
|
|
418
422
|
executionTimeMetricsConf: MetricConfiguration;
|
|
419
423
|
constructor(config: C, tapState: InputTapState, target: ITarget, childConcurrency?: number);
|
|
@@ -595,8 +599,8 @@ declare abstract class Tap<C> {
|
|
|
595
599
|
|
|
596
600
|
declare class SchemaValidationError extends Error {
|
|
597
601
|
streamId: StreamId;
|
|
598
|
-
validationErrors:
|
|
599
|
-
constructor(message: string, streamId: StreamId, errors:
|
|
602
|
+
validationErrors: ErrorObject[];
|
|
603
|
+
constructor(message: string, streamId: StreamId, errors: ErrorObject[]);
|
|
600
604
|
}
|
|
601
605
|
declare class MissingFieldInSchemaError extends Error {
|
|
602
606
|
streamId: StreamId;
|
|
@@ -761,7 +765,7 @@ declare const createCellReference: (columnIndex: number, rowIndex: number) => st
|
|
|
761
765
|
/**
|
|
762
766
|
* Low-level Excel extractor: returns array of row records from a workbook.
|
|
763
767
|
*/
|
|
764
|
-
declare const extractExcelRows: (localFilePath: string, conf: ExcelExtractionConfig) => Promise<Record<string, string>[]>;
|
|
768
|
+
declare const extractExcelRows: (localFilePath: string, conf: ExcelExtractionConfig) => Promise<Record<string, string | null>[]>;
|
|
765
769
|
/**
|
|
766
770
|
* Validates an Excel file configuration.
|
|
767
771
|
*/
|
|
@@ -773,7 +777,7 @@ declare const findAllMatchingConfigs: (filename: string, configs: ExcelExtractio
|
|
|
773
777
|
/**
|
|
774
778
|
* Creates an async generator from Excel data.
|
|
775
779
|
*/
|
|
776
|
-
declare function createExcelGenerator(data: Record<string, string>[]): AsyncGenerator<Record<string, any>, void, unknown>;
|
|
780
|
+
declare function createExcelGenerator(data: Record<string, string | null>[]): AsyncGenerator<Record<string, any>, void, unknown>;
|
|
777
781
|
|
|
778
782
|
/**
|
|
779
783
|
* Builder pattern for creating Excel configurations.
|
|
@@ -808,6 +812,11 @@ declare class ExcelExtractionConfigBuilder {
|
|
|
808
812
|
* Supports custom separators, encoding, BOM stripping, and field transformers.
|
|
809
813
|
*/
|
|
810
814
|
declare function rowGeneratorFromCsv(path: string, fileConfig: CsvFileConfig): AsyncGenerator<Record<string, any>>;
|
|
815
|
+
/**
|
|
816
|
+
* Counts data rows in a CSV file by scanning for newline bytes.
|
|
817
|
+
* Subtracts 1 for the header row. Fast: no CSV parsing required.
|
|
818
|
+
*/
|
|
819
|
+
declare function countCsvLines(filePath: string): Promise<number>;
|
|
811
820
|
/**
|
|
812
821
|
* Validates CSV header row against expected configuration.
|
|
813
822
|
*/
|
|
@@ -885,6 +894,7 @@ declare class FileStream extends Stream<Record<string, any>, FileStreamConfig> {
|
|
|
885
894
|
/**
|
|
886
895
|
* Yield records from all file(s).
|
|
887
896
|
* When multiple file paths are provided, records are yielded sequentially from each file.
|
|
897
|
+
* Pre-counts total rows across all files to enable percentage progress logging.
|
|
888
898
|
*/
|
|
889
899
|
_getRecords(): AsyncIterable<Record<string, any>>;
|
|
890
900
|
}
|
|
@@ -1047,6 +1057,8 @@ declare class CloudStorageService {
|
|
|
1047
1057
|
writeStringObject(objectPath: string, contents: string): Promise<void>;
|
|
1048
1058
|
/**
|
|
1049
1059
|
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
1060
|
+
* Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
|
|
1061
|
+
* or `<prefix>/default/` otherwise.
|
|
1050
1062
|
* Returns the GCS File reference.
|
|
1051
1063
|
*/
|
|
1052
1064
|
uploadFileWithUniqueName(filePath: string, prefix: string, streamId: string): Promise<File>;
|
|
@@ -1131,4 +1143,4 @@ declare class GCSStateProvider implements StateProvider {
|
|
|
1131
1143
|
writeState(state: string): Promise<void>;
|
|
1132
1144
|
}
|
|
1133
1145
|
|
|
1134
|
-
export { ALL_STREAM_ID_KEYWORD, API_CALLS_METRIC_NAME, Authenticator, BATCH_INTERVAL_MS, type BaseConfig, type BigQueryConfig, BigQueryTarget, type BigqueryLocation, type Bookmark, type Bookmarks, CloudStorageService, type CloudStorageServiceOptions, type ColumnMappingStoreUnsafeToSafe, CounterMetric, CsvExtractionConfigBuilder, type CsvFieldsArrayConfig, type CsvFieldsConfig, type CsvFieldsDictConfig, type CsvFileConfig, type CsvStreamConfig, DEFAULT_MAX_CONCURRENT_STREAMS, EXECUTION_TIME_METRIC_NAME, type ErrorType, type ExcelCustomExtractorConfig, type ExcelDerivedField, type ExcelExtractionBaseConfig, type ExcelExtractionConfig, ExcelExtractionConfigBuilder, type ExcelFieldMapping, type ExcelFieldSpec, type ExcelSingleSheetExtractionConfig, type ExcelSourceColumn, type ExcelStreamConfig, type FieldType, FileFormat, FilePatterns, FileStream, type FileStreamConfig, type FileStreamEntry, FileTap, type FileTapConfig, type FlattenedSchema, GCSStateProvider, type GetConfig, type HTTPHeaders, type HTTPMethod, ITarget, type InputTapState, type InternalTableField, type JSONSchemaFieldDefinition, type Message, type MessageType, type MetricConfiguration, MissingFieldInSchemaError, MissingSchemaError, type PostConfig, type ProgressMarkers, type PropertiesMetadata, RESTStream, ROWS_SYNCED_METRIC_NAME, RecordMessage, RenameColumnStore, ReplicationMethod, ReplicationMethodMessage, type SafeColumnNameConverterFn, type Schema, SchemaMessage, SchemaValidationError, SftpService, type State, type StateHolder, StateMessage, type StateProvider, StateService, Stream, type StreamId, type StreamState, StreamWarehouseSyncService, type StreamWithTempFile, type SyncOptions, Tap, type TapState, type TargetSchemaHook, type TargetSchemaHookInput, type TempFile, type URLParams, VariableExtractors, type WarehouseTableField, _greaterThan, addWhalyFields, checkCsvHeaderRow, createCellReference, createCsvStreamConfig, createExcelGenerator, createExcelStreamConfig, createTemporaryFileStream, csvFieldsToJsonSchema, excelColumnToIndex, excelFieldsToJsonSchema, excelRowToIndex, extractExcelRows, extractPrimaryKeysFromCsvConfig, extractPrimaryKeysFromExcelFields, extractStateForStream, fieldTypeToJsonSchema, finalizeStateProgressMarkers, findAllMatchingConfigs, flattenSchema, getAllMetrics, getAxiosInstance, getCounterMetric, getCounterMetrics, getDownloadFileApiCall, getJSONApiCall, getJSONApiCallWithFullResponse, getRawJSONApiCall, gracefulExit, haltAndCatchFire, incrementStreamState, indexToExcelColumn, indexToExcelRow, loadJson, loadSchema, logger, parseCellReference, postFormDataApiCall, postJSONApiCall, postUrlEncodedApiCall, printMemoryFootprint, processFileStreams, removeParasiteProperties, rowGeneratorFromCsv, safePath, unzip, validateExcelConfig, writeDataToCsv, writeGeneratorToCSV };
|
|
1146
|
+
export { ALL_STREAM_ID_KEYWORD, API_CALLS_METRIC_NAME, Authenticator, BATCH_INTERVAL_MS, type BaseConfig, type BigQueryConfig, BigQueryTarget, type BigqueryLocation, type Bookmark, type Bookmarks, CloudStorageService, type CloudStorageServiceOptions, type ColumnMappingStoreUnsafeToSafe, CounterMetric, CsvExtractionConfigBuilder, type CsvFieldsArrayConfig, type CsvFieldsConfig, type CsvFieldsDictConfig, type CsvFileConfig, type CsvStreamConfig, DEFAULT_MAX_CONCURRENT_STREAMS, EXECUTION_TIME_METRIC_NAME, type ErrorType, type ExcelCustomExtractorConfig, type ExcelDerivedField, type ExcelExtractionBaseConfig, type ExcelExtractionConfig, ExcelExtractionConfigBuilder, type ExcelFieldMapping, type ExcelFieldSpec, type ExcelSingleSheetExtractionConfig, type ExcelSourceColumn, type ExcelStreamConfig, type FieldType, FileFormat, FilePatterns, FileStream, type FileStreamConfig, type FileStreamEntry, FileTap, type FileTapConfig, type FlattenedSchema, GCSStateProvider, type GetConfig, type HTTPHeaders, type HTTPMethod, ITarget, type InputTapState, type InternalTableField, type JSONSchemaFieldDefinition, type Message, type MessageType, type MetricConfiguration, MissingFieldInSchemaError, MissingSchemaError, type PostConfig, type ProgressMarkers, type PropertiesMetadata, RESTStream, ROWS_SYNCED_METRIC_NAME, RecordMessage, RenameColumnStore, ReplicationMethod, ReplicationMethodMessage, type SafeColumnNameConverterFn, type Schema, SchemaMessage, SchemaValidationError, SftpService, type State, type StateHolder, StateMessage, type StateProvider, StateService, Stream, type StreamId, type StreamState, StreamWarehouseSyncService, type StreamWithTempFile, type SyncOptions, Tap, type TapState, type TargetSchemaHook, type TargetSchemaHookInput, type TempFile, type URLParams, VariableExtractors, type WarehouseTableField, _greaterThan, addWhalyFields, checkCsvHeaderRow, countCsvLines, createCellReference, createCsvStreamConfig, createExcelGenerator, createExcelStreamConfig, createTemporaryFileStream, csvFieldsToJsonSchema, excelColumnToIndex, excelFieldsToJsonSchema, excelRowToIndex, extractExcelRows, extractPrimaryKeysFromCsvConfig, extractPrimaryKeysFromExcelFields, extractStateForStream, fieldTypeToJsonSchema, finalizeStateProgressMarkers, findAllMatchingConfigs, flattenSchema, getAllMetrics, getAxiosInstance, getCounterMetric, getCounterMetrics, getDownloadFileApiCall, getJSONApiCall, getJSONApiCallWithFullResponse, getRawJSONApiCall, gracefulExit, haltAndCatchFire, incrementStreamState, indexToExcelColumn, indexToExcelRow, loadJson, loadSchema, logger, parseCellReference, postFormDataApiCall, postJSONApiCall, postUrlEncodedApiCall, printMemoryFootprint, processFileStreams, removeParasiteProperties, rowGeneratorFromCsv, safePath, unzip, validateExcelConfig, writeDataToCsv, writeGeneratorToCSV };
|
package/dist/index.js
CHANGED
|
@@ -68,6 +68,7 @@ __export(index_exports, {
|
|
|
68
68
|
_greaterThan: () => _greaterThan,
|
|
69
69
|
addWhalyFields: () => addWhalyFields,
|
|
70
70
|
checkCsvHeaderRow: () => checkCsvHeaderRow,
|
|
71
|
+
countCsvLines: () => countCsvLines,
|
|
71
72
|
createCellReference: () => createCellReference,
|
|
72
73
|
createCsvStreamConfig: () => createCsvStreamConfig,
|
|
73
74
|
createExcelGenerator: () => createExcelGenerator,
|
|
@@ -847,6 +848,8 @@ var Stream = class {
|
|
|
847
848
|
// However, a STATE will still be emitted if the Stream is using an incremental sync type.
|
|
848
849
|
isSilent = false;
|
|
849
850
|
childConcurrency;
|
|
851
|
+
// Optional total row count for percentage progress logging
|
|
852
|
+
totalRows;
|
|
850
853
|
// Metrics configuration
|
|
851
854
|
rowsSyncedMetricsConf;
|
|
852
855
|
executionTimeMetricsConf;
|
|
@@ -1124,6 +1127,10 @@ var Stream = class {
|
|
|
1124
1127
|
metric.increment();
|
|
1125
1128
|
});
|
|
1126
1129
|
rowsSent += 1;
|
|
1130
|
+
if (rowsSent % 1e3 === 0) {
|
|
1131
|
+
const percentStr = this.totalRows ? ` (${Math.round(rowsSent / this.totalRows * 100)}%)` : "";
|
|
1132
|
+
logger.info(`\u23F3 Stream: ${this.streamId} - ${rowsSent} records synced so far...${percentStr}`);
|
|
1133
|
+
}
|
|
1127
1134
|
}
|
|
1128
1135
|
const stateServiceInst = StateService.getInstance();
|
|
1129
1136
|
const state = stateServiceInst.getBookmark(this.streamId);
|
|
@@ -1796,7 +1803,8 @@ var flattenSchema = (streamId, schema) => {
|
|
|
1796
1803
|
};
|
|
1797
1804
|
|
|
1798
1805
|
// src/sdk/models/target/target.ts
|
|
1799
|
-
var
|
|
1806
|
+
var import_ajv = __toESM(require("ajv"));
|
|
1807
|
+
var import_ajv_formats = __toESM(require("ajv-formats"));
|
|
1800
1808
|
var import_dayjs4 = __toESM(require("dayjs"));
|
|
1801
1809
|
|
|
1802
1810
|
// src/sdk/models/target/streamDbState.ts
|
|
@@ -1874,6 +1882,8 @@ var StreamState = class {
|
|
|
1874
1882
|
// So we need to keep track of whether we are at the first load or not
|
|
1875
1883
|
_hasBeenLoadedYetDuringThisSync;
|
|
1876
1884
|
replicationMethod;
|
|
1885
|
+
// Pre-compiled ajv ValidateFunction — compiled once per schema, reused for every row
|
|
1886
|
+
compiledValidateFn;
|
|
1877
1887
|
constructor(streamId, dbSync, replicationMethod) {
|
|
1878
1888
|
this.streamId = streamId;
|
|
1879
1889
|
this.dbSync = dbSync;
|
|
@@ -1891,6 +1901,12 @@ var StreamState = class {
|
|
|
1891
1901
|
getSchema() {
|
|
1892
1902
|
return this.schema;
|
|
1893
1903
|
}
|
|
1904
|
+
setCompiledValidateFn(fn) {
|
|
1905
|
+
this.compiledValidateFn = fn;
|
|
1906
|
+
}
|
|
1907
|
+
getCompiledValidateFn() {
|
|
1908
|
+
return this.compiledValidateFn;
|
|
1909
|
+
}
|
|
1894
1910
|
getBatchDate() {
|
|
1895
1911
|
return this.batchDate.format(defaultDateTimeFormat);
|
|
1896
1912
|
}
|
|
@@ -1947,8 +1963,8 @@ var ITarget = class _ITarget {
|
|
|
1947
1963
|
flushedState;
|
|
1948
1964
|
renameColumnStore;
|
|
1949
1965
|
streams;
|
|
1950
|
-
// JSON Schema validator
|
|
1951
|
-
|
|
1966
|
+
// JSON Schema validator (ajv — pre-compiles schemas into optimized JS functions)
|
|
1967
|
+
ajv;
|
|
1952
1968
|
constructor(config, stateProvider) {
|
|
1953
1969
|
this.config = config;
|
|
1954
1970
|
this.stateProvider = stateProvider;
|
|
@@ -1956,7 +1972,7 @@ var ITarget = class _ITarget {
|
|
|
1956
1972
|
this.streams = {};
|
|
1957
1973
|
this.batchedState = {};
|
|
1958
1974
|
this.flushedState = {};
|
|
1959
|
-
this.
|
|
1975
|
+
this.ajv = (0, import_ajv_formats.default)(new import_ajv.default({ allErrors: true, strict: false }));
|
|
1960
1976
|
this.syncTime = (0, import_dayjs4.default)();
|
|
1961
1977
|
this.renameColumnStore = new RenameColumnStore();
|
|
1962
1978
|
}
|
|
@@ -2015,12 +2031,14 @@ var ITarget = class _ITarget {
|
|
|
2015
2031
|
if (!stream) {
|
|
2016
2032
|
throw new MissingSchemaError(`A record for stream:\`${streamId}\` was encountered before a corresponding schema.`, streamId);
|
|
2017
2033
|
}
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2034
|
+
if (stream.getBatchedRowCount() % 1e4 === 0) {
|
|
2035
|
+
await semaphore.runExclusive(async () => {
|
|
2036
|
+
const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
|
|
2037
|
+
if (shouldUploadIntermediateBatch) {
|
|
2038
|
+
await this.loadAllStreamsInWarehouse({ isFinalLoad: false });
|
|
2039
|
+
}
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2024
2042
|
const record = message.record;
|
|
2025
2043
|
const schema = stream.getSchema();
|
|
2026
2044
|
if (!schema) {
|
|
@@ -2029,13 +2047,13 @@ var ITarget = class _ITarget {
|
|
|
2029
2047
|
}
|
|
2030
2048
|
const recordWithoutParasiteProperties = removeParasiteProperties(record, schema);
|
|
2031
2049
|
const recordWithWhalyFields = addWhalyFields(recordWithoutParasiteProperties, stream.getBatchDate());
|
|
2032
|
-
const
|
|
2033
|
-
if (!
|
|
2050
|
+
const validateFn = stream.getCompiledValidateFn();
|
|
2051
|
+
if (validateFn && !validateFn(recordWithWhalyFields)) {
|
|
2034
2052
|
throw new SchemaValidationError(`Stream: ${streamId} - Record is not valid according to schema.
|
|
2035
|
-
Validation errors: ${JSON.stringify(
|
|
2053
|
+
Validation errors: ${JSON.stringify(validateFn.errors)}
|
|
2036
2054
|
|
|
2037
2055
|
Record: ${JSON.stringify(recordWithoutParasiteProperties)}
|
|
2038
|
-
Schema: ${JSON.stringify(stream.getSchema())}`, streamId,
|
|
2056
|
+
Schema: ${JSON.stringify(stream.getSchema())}`, streamId, validateFn.errors ?? []);
|
|
2039
2057
|
}
|
|
2040
2058
|
const recordWithValidDate = this.validateDateRange(recordWithWhalyFields, schema);
|
|
2041
2059
|
const recordWithDecimal = this.convertNumberIntoDecimal(recordWithValidDate, schema);
|
|
@@ -2124,6 +2142,7 @@ var ITarget = class _ITarget {
|
|
|
2124
2142
|
}
|
|
2125
2143
|
});
|
|
2126
2144
|
streamState.setSchema(schema);
|
|
2145
|
+
streamState.setCompiledValidateFn(this.ajv.compile({ type: "object", properties: schema }));
|
|
2127
2146
|
const streamReplicationMethod = this.streams[streamId]?.getReplicationMethod();
|
|
2128
2147
|
if (message.keyProperties.length === 0 && streamReplicationMethod !== "APPEND" /* APPEND */) {
|
|
2129
2148
|
throw new Error(`StreamId: ${message.stream} - \`key_properties\` field is required and can't be empty in SCHEMA message.
|
|
@@ -2406,10 +2425,10 @@ var extractSingleSheetRows = async (fileName, conf) => {
|
|
|
2406
2425
|
if (!variables) {
|
|
2407
2426
|
throw new Error(`No variables extracted from filename, cannot extract derived field ${key}`);
|
|
2408
2427
|
}
|
|
2409
|
-
acc[key] = variables[column.variableName] ||
|
|
2428
|
+
acc[key] = variables[column.variableName] || null;
|
|
2410
2429
|
} else {
|
|
2411
2430
|
const colIndex = excelColumnToIndex(column.column);
|
|
2412
|
-
acc[key] = row[colIndex]?.v ||
|
|
2431
|
+
acc[key] = row[colIndex]?.v || null;
|
|
2413
2432
|
}
|
|
2414
2433
|
return acc;
|
|
2415
2434
|
}, {});
|
|
@@ -2609,6 +2628,7 @@ Actual: "${actual}" (${actualInfo.length} chars) [${actualInfo.hex}]`;
|
|
|
2609
2628
|
};
|
|
2610
2629
|
async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
2611
2630
|
const isGeneratorConfigArray = Array.isArray(fileConfig.fields);
|
|
2631
|
+
const configKeys = isGeneratorConfigArray ? [] : Object.keys(fileConfig.fields);
|
|
2612
2632
|
const csvOptions = { separator: fileConfig.separator };
|
|
2613
2633
|
if (isGeneratorConfigArray) {
|
|
2614
2634
|
csvOptions.headers = false;
|
|
@@ -2629,28 +2649,23 @@ async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
|
2629
2649
|
try {
|
|
2630
2650
|
for await (const row of import_stream2.Readable.from(csvStream)) {
|
|
2631
2651
|
if (!isGeneratorConfigArray) {
|
|
2632
|
-
const rowData =
|
|
2652
|
+
const rowData = fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {};
|
|
2653
|
+
for (const key of configKeys) {
|
|
2633
2654
|
const keyGenerator = fileConfig.fields[key.trim()];
|
|
2634
|
-
if (!keyGenerator)
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
...acc,
|
|
2639
|
-
[keyGenerator.key]: keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[key]) : row[key]
|
|
2640
|
-
};
|
|
2641
|
-
}, fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {});
|
|
2655
|
+
if (!keyGenerator) continue;
|
|
2656
|
+
const rawValue = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[key]) : row[key];
|
|
2657
|
+
rowData[keyGenerator.key] = rawValue === "" ? null : rawValue;
|
|
2658
|
+
}
|
|
2642
2659
|
yield rowData;
|
|
2643
2660
|
} else {
|
|
2644
|
-
const
|
|
2661
|
+
const rowKeys = Object.keys(row);
|
|
2662
|
+
const rowData = fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {};
|
|
2663
|
+
for (let i = 0; i < rowKeys.length; i++) {
|
|
2645
2664
|
const keyGenerator = fileConfig.fields[i];
|
|
2646
|
-
if (!keyGenerator)
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
...acc,
|
|
2651
|
-
[keyGenerator.key]: keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i]
|
|
2652
|
-
};
|
|
2653
|
-
}, fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {});
|
|
2665
|
+
if (!keyGenerator) continue;
|
|
2666
|
+
const rawValue = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i];
|
|
2667
|
+
rowData[keyGenerator.key] = rawValue === "" ? null : rawValue;
|
|
2668
|
+
}
|
|
2654
2669
|
yield rowData;
|
|
2655
2670
|
}
|
|
2656
2671
|
}
|
|
@@ -2658,6 +2673,17 @@ async function* rowGeneratorFromCsv(path2, fileConfig) {
|
|
|
2658
2673
|
initialReadStream.destroy();
|
|
2659
2674
|
}
|
|
2660
2675
|
}
|
|
2676
|
+
async function countCsvLines(filePath) {
|
|
2677
|
+
return new Promise((resolve, reject) => {
|
|
2678
|
+
let newlines = 0;
|
|
2679
|
+
(0, import_fs2.createReadStream)(filePath).on("data", (chunk2) => {
|
|
2680
|
+
const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
|
|
2681
|
+
for (let i = 0; i < buf.length; i++) {
|
|
2682
|
+
if (buf[i] === 10) newlines++;
|
|
2683
|
+
}
|
|
2684
|
+
}).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
|
|
2685
|
+
});
|
|
2686
|
+
}
|
|
2661
2687
|
var checkCsvHeaderRow = async (path2, fileConfig) => {
|
|
2662
2688
|
return new Promise((resolve, reject) => {
|
|
2663
2689
|
const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
|
|
@@ -2851,8 +2877,19 @@ var FileStream = class extends Stream {
|
|
|
2851
2877
|
/**
|
|
2852
2878
|
* Yield records from all file(s).
|
|
2853
2879
|
* When multiple file paths are provided, records are yielded sequentially from each file.
|
|
2880
|
+
* Pre-counts total rows across all files to enable percentage progress logging.
|
|
2854
2881
|
*/
|
|
2855
2882
|
async *_getRecords() {
|
|
2883
|
+
let total = 0;
|
|
2884
|
+
for (const filePath of this.localFilePaths) {
|
|
2885
|
+
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2886
|
+
const data = await extractExcelRows(filePath, this.config.excel);
|
|
2887
|
+
total += data.length;
|
|
2888
|
+
} else if (this.config.format === "CSV" /* CSV */) {
|
|
2889
|
+
total += await countCsvLines(filePath);
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
this.totalRows = total;
|
|
2856
2893
|
for (const filePath of this.localFilePaths) {
|
|
2857
2894
|
if (this.config.format === "EXCEL" /* EXCEL */) {
|
|
2858
2895
|
const data = await extractExcelRows(filePath, this.config.excel);
|
|
@@ -3101,60 +3138,7 @@ var import_storage = require("@google-cloud/storage");
|
|
|
3101
3138
|
var import_util5 = require("util");
|
|
3102
3139
|
var pathModule = __toESM(require("path"));
|
|
3103
3140
|
var import_fs3 = require("fs");
|
|
3104
|
-
|
|
3105
|
-
// node_modules/uuid/dist/esm-node/rng.js
|
|
3106
|
-
var import_crypto = __toESM(require("crypto"));
|
|
3107
|
-
var rnds8Pool = new Uint8Array(256);
|
|
3108
|
-
var poolPtr = rnds8Pool.length;
|
|
3109
|
-
function rng() {
|
|
3110
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
3111
|
-
import_crypto.default.randomFillSync(rnds8Pool);
|
|
3112
|
-
poolPtr = 0;
|
|
3113
|
-
}
|
|
3114
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
3115
|
-
}
|
|
3116
|
-
|
|
3117
|
-
// node_modules/uuid/dist/esm-node/regex.js
|
|
3118
|
-
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
3119
|
-
|
|
3120
|
-
// node_modules/uuid/dist/esm-node/validate.js
|
|
3121
|
-
function validate(uuid) {
|
|
3122
|
-
return typeof uuid === "string" && regex_default.test(uuid);
|
|
3123
|
-
}
|
|
3124
|
-
var validate_default = validate;
|
|
3125
|
-
|
|
3126
|
-
// node_modules/uuid/dist/esm-node/stringify.js
|
|
3127
|
-
var byteToHex = [];
|
|
3128
|
-
for (let i = 0; i < 256; ++i) {
|
|
3129
|
-
byteToHex.push((i + 256).toString(16).substr(1));
|
|
3130
|
-
}
|
|
3131
|
-
function stringify3(arr, offset = 0) {
|
|
3132
|
-
const uuid = (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
|
3133
|
-
if (!validate_default(uuid)) {
|
|
3134
|
-
throw TypeError("Stringified UUID is invalid");
|
|
3135
|
-
}
|
|
3136
|
-
return uuid;
|
|
3137
|
-
}
|
|
3138
|
-
var stringify_default = stringify3;
|
|
3139
|
-
|
|
3140
|
-
// node_modules/uuid/dist/esm-node/v4.js
|
|
3141
|
-
function v4(options, buf, offset) {
|
|
3142
|
-
options = options || {};
|
|
3143
|
-
const rnds = options.random || (options.rng || rng)();
|
|
3144
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
3145
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
3146
|
-
if (buf) {
|
|
3147
|
-
offset = offset || 0;
|
|
3148
|
-
for (let i = 0; i < 16; ++i) {
|
|
3149
|
-
buf[offset + i] = rnds[i];
|
|
3150
|
-
}
|
|
3151
|
-
return buf;
|
|
3152
|
-
}
|
|
3153
|
-
return stringify_default(rnds);
|
|
3154
|
-
}
|
|
3155
|
-
var v4_default = v4;
|
|
3156
|
-
|
|
3157
|
-
// src/services/cloud-storage.ts
|
|
3141
|
+
var import_crypto = require("crypto");
|
|
3158
3142
|
var logPrefix3 = "[CloudStorageService]";
|
|
3159
3143
|
var tmpDir = "tmp";
|
|
3160
3144
|
var CloudStorageService = class {
|
|
@@ -3292,12 +3276,15 @@ var CloudStorageService = class {
|
|
|
3292
3276
|
}
|
|
3293
3277
|
/**
|
|
3294
3278
|
* Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
|
|
3279
|
+
* Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
|
|
3280
|
+
* or `<prefix>/default/` otherwise.
|
|
3295
3281
|
* Returns the GCS File reference.
|
|
3296
3282
|
*/
|
|
3297
3283
|
async uploadFileWithUniqueName(filePath, prefix, streamId) {
|
|
3298
3284
|
try {
|
|
3299
3285
|
await this.bucket.get({ autoCreate: false });
|
|
3300
|
-
const
|
|
3286
|
+
const runFolder = process.env.RUN_ID ?? "default";
|
|
3287
|
+
const destinationFileName = `${prefix}/${runFolder}/${streamId}-${(0, import_crypto.randomUUID)()}.jsonnl`;
|
|
3301
3288
|
await this.bucket.upload(filePath, {
|
|
3302
3289
|
destination: destinationFileName
|
|
3303
3290
|
});
|
|
@@ -3954,6 +3941,7 @@ var GCSStateProvider = class {
|
|
|
3954
3941
|
_greaterThan,
|
|
3955
3942
|
addWhalyFields,
|
|
3956
3943
|
checkCsvHeaderRow,
|
|
3944
|
+
countCsvLines,
|
|
3957
3945
|
createCellReference,
|
|
3958
3946
|
createCsvStreamConfig,
|
|
3959
3947
|
createExcelGenerator,
|