@whaly/connector-sdk 0.3.0 → 0.3.1

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AxiosInstance, AxiosRequestHeaders, AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';
2
- import { Validator, ValidationError } from 'jsonschema';
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
- validator: Validator;
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: ValidationError[];
599
- constructor(message: string, streamId: StreamId, errors: ValidationError[]);
602
+ validationErrors: ErrorObject[];
603
+ constructor(message: string, streamId: StreamId, errors: ErrorObject[]);
600
604
  }
601
605
  declare class MissingFieldInSchemaError extends Error {
602
606
  streamId: StreamId;
@@ -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 { Validator, ValidationError } from 'jsonschema';
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
- validator: Validator;
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: ValidationError[];
599
- constructor(message: string, streamId: StreamId, errors: ValidationError[]);
602
+ validationErrors: ErrorObject[];
603
+ constructor(message: string, streamId: StreamId, errors: ErrorObject[]);
600
604
  }
601
605
  declare class MissingFieldInSchemaError extends Error {
602
606
  streamId: StreamId;
@@ -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 import_jsonschema = require("jsonschema");
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
- validator;
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.validator = new import_jsonschema.Validator();
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
- await semaphore.runExclusive(async () => {
2019
- const shouldUploadIntermediateBatch = this.shouldUploadIntermediateBatch(streamId);
2020
- if (shouldUploadIntermediateBatch) {
2021
- await this.loadAllStreamsInWarehouse({ isFinalLoad: false });
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 validationResult = this.validator.validate(recordWithWhalyFields, schema);
2033
- if (!validationResult.valid) {
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(validationResult.errors)}
2053
+ Validation errors: ${JSON.stringify(validateFn.errors)}
2036
2054
 
2037
2055
  Record: ${JSON.stringify(recordWithoutParasiteProperties)}
2038
- Schema: ${JSON.stringify(stream.getSchema())}`, streamId, validationResult.errors);
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.
@@ -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,21 @@ 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 = Object.keys(fileConfig.fields).reduce((acc, key) => {
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
- return acc;
2636
- }
2637
- return {
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
+ rowData[keyGenerator.key] = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[key]) : row[key];
2657
+ }
2642
2658
  yield rowData;
2643
2659
  } else {
2644
- const rowData = Object.keys(row).reduce((acc, key, i) => {
2660
+ const rowKeys = Object.keys(row);
2661
+ const rowData = fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {};
2662
+ for (let i = 0; i < rowKeys.length; i++) {
2645
2663
  const keyGenerator = fileConfig.fields[i];
2646
- if (!keyGenerator) {
2647
- return acc;
2648
- }
2649
- return {
2650
- ...acc,
2651
- [keyGenerator.key]: keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i]
2652
- };
2653
- }, fileConfig.addSyncedAtColumn ? { _wly_synced_at: syncedAt } : {});
2664
+ if (!keyGenerator) continue;
2665
+ rowData[keyGenerator.key] = keyGenerator.valueTransformer ? keyGenerator.valueTransformer(row[i]) : row[i];
2666
+ }
2654
2667
  yield rowData;
2655
2668
  }
2656
2669
  }
@@ -2658,6 +2671,17 @@ async function* rowGeneratorFromCsv(path2, fileConfig) {
2658
2671
  initialReadStream.destroy();
2659
2672
  }
2660
2673
  }
2674
+ async function countCsvLines(filePath) {
2675
+ return new Promise((resolve, reject) => {
2676
+ let newlines = 0;
2677
+ (0, import_fs2.createReadStream)(filePath).on("data", (chunk2) => {
2678
+ const buf = Buffer.isBuffer(chunk2) ? chunk2 : Buffer.from(chunk2);
2679
+ for (let i = 0; i < buf.length; i++) {
2680
+ if (buf[i] === 10) newlines++;
2681
+ }
2682
+ }).on("end", () => resolve(Math.max(0, newlines - 1))).on("error", reject);
2683
+ });
2684
+ }
2661
2685
  var checkCsvHeaderRow = async (path2, fileConfig) => {
2662
2686
  return new Promise((resolve, reject) => {
2663
2687
  const colsWithParsingConfig = Object.keys(fileConfig.fields).map((key) => key.trim());
@@ -2851,8 +2875,19 @@ var FileStream = class extends Stream {
2851
2875
  /**
2852
2876
  * Yield records from all file(s).
2853
2877
  * When multiple file paths are provided, records are yielded sequentially from each file.
2878
+ * Pre-counts total rows across all files to enable percentage progress logging.
2854
2879
  */
2855
2880
  async *_getRecords() {
2881
+ let total = 0;
2882
+ for (const filePath of this.localFilePaths) {
2883
+ if (this.config.format === "EXCEL" /* EXCEL */) {
2884
+ const data = await extractExcelRows(filePath, this.config.excel);
2885
+ total += data.length;
2886
+ } else if (this.config.format === "CSV" /* CSV */) {
2887
+ total += await countCsvLines(filePath);
2888
+ }
2889
+ }
2890
+ this.totalRows = total;
2856
2891
  for (const filePath of this.localFilePaths) {
2857
2892
  if (this.config.format === "EXCEL" /* EXCEL */) {
2858
2893
  const data = await extractExcelRows(filePath, this.config.excel);
@@ -3101,60 +3136,7 @@ var import_storage = require("@google-cloud/storage");
3101
3136
  var import_util5 = require("util");
3102
3137
  var pathModule = __toESM(require("path"));
3103
3138
  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
3139
+ var import_crypto = require("crypto");
3158
3140
  var logPrefix3 = "[CloudStorageService]";
3159
3141
  var tmpDir = "tmp";
3160
3142
  var CloudStorageService = class {
@@ -3292,12 +3274,15 @@ var CloudStorageService = class {
3292
3274
  }
3293
3275
  /**
3294
3276
  * Uploads a local file to the bucket with a unique name based on prefix, streamId, and UUID.
3277
+ * Files are stored under `<prefix>/<run-id>/` when the RUN_ID env var is set,
3278
+ * or `<prefix>/default/` otherwise.
3295
3279
  * Returns the GCS File reference.
3296
3280
  */
3297
3281
  async uploadFileWithUniqueName(filePath, prefix, streamId) {
3298
3282
  try {
3299
3283
  await this.bucket.get({ autoCreate: false });
3300
- const destinationFileName = `${prefix}/${streamId}-${v4_default()}.jsonnl`;
3284
+ const runFolder = process.env.RUN_ID ?? "default";
3285
+ const destinationFileName = `${prefix}/${runFolder}/${streamId}-${(0, import_crypto.randomUUID)()}.jsonnl`;
3301
3286
  await this.bucket.upload(filePath, {
3302
3287
  destination: destinationFileName
3303
3288
  });
@@ -3954,6 +3939,7 @@ var GCSStateProvider = class {
3954
3939
  _greaterThan,
3955
3940
  addWhalyFields,
3956
3941
  checkCsvHeaderRow,
3942
+ countCsvLines,
3957
3943
  createCellReference,
3958
3944
  createCsvStreamConfig,
3959
3945
  createExcelGenerator,