s3db.js 7.1.0 → 7.2.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/s3db.d.ts CHANGED
@@ -418,23 +418,22 @@ declare module 's3db.js' {
418
418
  export interface BigQueryReplicatorConfig {
419
419
  projectId: string;
420
420
  datasetId: string;
421
- keyFilename?: string;
422
421
  credentials?: Record<string, any>;
423
- tableMapping?: Record<string, string>;
424
- logOperations?: boolean;
422
+ location?: string;
423
+ logTable?: string;
425
424
  batchSize?: number;
426
425
  maxRetries?: number;
427
- retryDelay?: number;
428
- writeDisposition?: 'WRITE_TRUNCATE' | 'WRITE_APPEND' | 'WRITE_EMPTY';
429
- createDisposition?: 'CREATE_IF_NEEDED' | 'CREATE_NEVER';
430
- schema?: Record<string, any>[];
431
- location?: string;
432
- clustering?: string[];
433
- partitioning?: {
434
- type: 'DAY' | 'HOUR' | 'MONTH' | 'YEAR';
435
- field?: string;
436
- };
437
- labels?: Record<string, string>;
426
+ writeDisposition?: string;
427
+ createDisposition?: string;
428
+ tableMapping?: Record<string, string>;
429
+ logOperations?: boolean;
430
+ }
431
+
432
+ /** BigQuery Resource Configuration */
433
+ export interface BigQueryResourceConfig {
434
+ table: string;
435
+ actions?: ('insert' | 'update' | 'delete')[];
436
+ transform?: (data: any) => any;
438
437
  }
439
438
 
440
439
  /** Postgres Replicator config */
@@ -1041,7 +1040,7 @@ declare module 's3db.js' {
1041
1040
 
1042
1041
  /** BigQuery Replicator class */
1043
1042
  export class BigqueryReplicator extends BaseReplicator {
1044
- constructor(config: BigQueryReplicatorConfig);
1043
+ constructor(config: BigQueryReplicatorConfig, resources: Record<string, string | BigQueryResourceConfig | BigQueryResourceConfig[]>);
1045
1044
  }
1046
1045
 
1047
1046
  /** Postgres Replicator class */
package/dist/s3db.es.js CHANGED
@@ -8002,22 +8002,25 @@ class BigqueryReplicator extends base_replicator_class_default {
8002
8002
  if (typeof config === "string") {
8003
8003
  parsed[resourceName] = [{
8004
8004
  table: config,
8005
- actions: ["insert"]
8005
+ actions: ["insert"],
8006
+ transform: null
8006
8007
  }];
8007
8008
  } else if (Array.isArray(config)) {
8008
8009
  parsed[resourceName] = config.map((item) => {
8009
8010
  if (typeof item === "string") {
8010
- return { table: item, actions: ["insert"] };
8011
+ return { table: item, actions: ["insert"], transform: null };
8011
8012
  }
8012
8013
  return {
8013
8014
  table: item.table,
8014
- actions: item.actions || ["insert"]
8015
+ actions: item.actions || ["insert"],
8016
+ transform: item.transform || null
8015
8017
  };
8016
8018
  });
8017
8019
  } else if (typeof config === "object") {
8018
8020
  parsed[resourceName] = [{
8019
8021
  table: config.table,
8020
- actions: config.actions || ["insert"]
8022
+ actions: config.actions || ["insert"],
8023
+ transform: config.transform || null
8021
8024
  }];
8022
8025
  }
8023
8026
  }
@@ -8041,6 +8044,9 @@ class BigqueryReplicator extends base_replicator_class_default {
8041
8044
  if (invalidActions.length > 0) {
8042
8045
  errors.push(`Invalid actions for resource '${resourceName}': ${invalidActions.join(", ")}. Valid actions: ${validActions.join(", ")}`);
8043
8046
  }
8047
+ if (tableConfig.transform && typeof tableConfig.transform !== "function") {
8048
+ errors.push(`Transform must be a function for resource '${resourceName}'`);
8049
+ }
8044
8050
  }
8045
8051
  }
8046
8052
  return { isValid: errors.length === 0, errors };
@@ -8076,7 +8082,16 @@ class BigqueryReplicator extends base_replicator_class_default {
8076
8082
  }
8077
8083
  getTablesForResource(resourceName, operation) {
8078
8084
  if (!this.resources[resourceName]) return [];
8079
- return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => tableConfig.table);
8085
+ return this.resources[resourceName].filter((tableConfig) => tableConfig.actions.includes(operation)).map((tableConfig) => ({
8086
+ table: tableConfig.table,
8087
+ transform: tableConfig.transform
8088
+ }));
8089
+ }
8090
+ applyTransform(data, transformFn) {
8091
+ if (!transformFn) return data;
8092
+ let transformedData = JSON.parse(JSON.stringify(data));
8093
+ if (transformedData._length) delete transformedData._length;
8094
+ return transformFn(transformedData);
8080
8095
  }
8081
8096
  async replicate(resourceName, operation, data, id, beforeData = null) {
8082
8097
  if (!this.enabled || !this.shouldReplicateResource(resourceName)) {
@@ -8085,40 +8100,56 @@ class BigqueryReplicator extends base_replicator_class_default {
8085
8100
  if (!this.shouldReplicateAction(resourceName, operation)) {
8086
8101
  return { skipped: true, reason: "action_not_included" };
8087
8102
  }
8088
- const tables = this.getTablesForResource(resourceName, operation);
8089
- if (tables.length === 0) {
8103
+ const tableConfigs = this.getTablesForResource(resourceName, operation);
8104
+ if (tableConfigs.length === 0) {
8090
8105
  return { skipped: true, reason: "no_tables_for_action" };
8091
8106
  }
8092
8107
  const results = [];
8093
8108
  const errors = [];
8094
8109
  const [ok, err, result] = await try_fn_default(async () => {
8095
8110
  const dataset = this.bigqueryClient.dataset(this.datasetId);
8096
- for (const tableId of tables) {
8111
+ for (const tableConfig of tableConfigs) {
8097
8112
  const [okTable, errTable] = await try_fn_default(async () => {
8098
- const table = dataset.table(tableId);
8113
+ const table = dataset.table(tableConfig.table);
8099
8114
  let job;
8100
8115
  if (operation === "insert") {
8101
- const row = { ...data };
8102
- job = await table.insert([row]);
8116
+ const transformedData = this.applyTransform(data, tableConfig.transform);
8117
+ job = await table.insert([transformedData]);
8103
8118
  } else if (operation === "update") {
8104
- const keys = Object.keys(data).filter((k) => k !== "id");
8105
- const setClause = keys.map((k) => `${k}=@${k}`).join(", ");
8106
- const params = { id };
8107
- keys.forEach((k) => {
8108
- params[k] = data[k];
8109
- });
8110
- const query = `UPDATE \`${this.projectId}.${this.datasetId}.${tableId}\` SET ${setClause} WHERE id=@id`;
8111
- const [updateJob] = await this.bigqueryClient.createQueryJob({
8112
- query,
8113
- params
8114
- });
8115
- await updateJob.getQueryResults();
8116
- job = [updateJob];
8119
+ const transformedData = this.applyTransform(data, tableConfig.transform);
8120
+ const keys = Object.keys(transformedData).filter((k) => k !== "id");
8121
+ const setClause = keys.map((k) => `${k} = @${k}`).join(", ");
8122
+ const params = { id, ...transformedData };
8123
+ const query = `UPDATE \`${this.projectId}.${this.datasetId}.${tableConfig.table}\` SET ${setClause} WHERE id = @id`;
8124
+ const maxRetries = 2;
8125
+ let lastError = null;
8126
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
8127
+ try {
8128
+ const [updateJob] = await this.bigqueryClient.createQueryJob({
8129
+ query,
8130
+ params,
8131
+ location: this.location
8132
+ });
8133
+ await updateJob.getQueryResults();
8134
+ job = [updateJob];
8135
+ break;
8136
+ } catch (error) {
8137
+ lastError = error;
8138
+ if (error?.message?.includes("streaming buffer") && attempt < maxRetries) {
8139
+ const delaySeconds = 30;
8140
+ await new Promise((resolve) => setTimeout(resolve, delaySeconds * 1e3));
8141
+ continue;
8142
+ }
8143
+ throw error;
8144
+ }
8145
+ }
8146
+ if (!job) throw lastError;
8117
8147
  } else if (operation === "delete") {
8118
- const query = `DELETE FROM \`${this.projectId}.${this.datasetId}.${tableId}\` WHERE id=@id`;
8148
+ const query = `DELETE FROM \`${this.projectId}.${this.datasetId}.${tableConfig.table}\` WHERE id = @id`;
8119
8149
  const [deleteJob] = await this.bigqueryClient.createQueryJob({
8120
8150
  query,
8121
- params: { id }
8151
+ params: { id },
8152
+ location: this.location
8122
8153
  });
8123
8154
  await deleteJob.getQueryResults();
8124
8155
  job = [deleteJob];
@@ -8126,14 +8157,14 @@ class BigqueryReplicator extends base_replicator_class_default {
8126
8157
  throw new Error(`Unsupported operation: ${operation}`);
8127
8158
  }
8128
8159
  results.push({
8129
- table: tableId,
8160
+ table: tableConfig.table,
8130
8161
  success: true,
8131
8162
  jobId: job[0]?.id
8132
8163
  });
8133
8164
  });
8134
8165
  if (!okTable) {
8135
8166
  errors.push({
8136
- table: tableId,
8167
+ table: tableConfig.table,
8137
8168
  error: errTable.message
8138
8169
  });
8139
8170
  }
@@ -8159,7 +8190,7 @@ class BigqueryReplicator extends base_replicator_class_default {
8159
8190
  resourceName,
8160
8191
  operation,
8161
8192
  id,
8162
- tables,
8193
+ tables: tableConfigs.map((t) => t.table),
8163
8194
  results,
8164
8195
  errors,
8165
8196
  success
@@ -8168,7 +8199,7 @@ class BigqueryReplicator extends base_replicator_class_default {
8168
8199
  success,
8169
8200
  results,
8170
8201
  errors,
8171
- tables
8202
+ tables: tableConfigs.map((t) => t.table)
8172
8203
  };
8173
8204
  });
8174
8205
  if (ok) return result;
@@ -12207,7 +12238,7 @@ class Database extends EventEmitter {
12207
12238
  super();
12208
12239
  this.version = "1";
12209
12240
  this.s3dbVersion = (() => {
12210
- const [ok, err, version] = try_fn_default(() => true ? "7.1.0" : "latest");
12241
+ const [ok, err, version] = try_fn_default(() => true ? "7.2.0" : "latest");
12211
12242
  return ok ? version : "latest";
12212
12243
  })();
12213
12244
  this.resources = {};