@shushed/helpers 0.0.26 → 0.0.28

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.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { JSONSchemaType } from 'ajv';
2
2
  import { DeepRedact } from '@hackylabs/deep-redact';
3
- import { DocumentReference, Firestore } from '@google-cloud/firestore';
3
+ import { Firestore, DocumentReference } from '@google-cloud/firestore';
4
4
  import { PubSub, Topic, Subscription } from '@google-cloud/pubsub';
5
5
  import * as _google_cloud_scheduler_build_protos_protos from '@google-cloud/scheduler/build/protos/protos';
6
6
  import { CloudSchedulerClient } from '@google-cloud/scheduler';
7
7
  import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
8
+ import { BigQuery } from '@google-cloud/bigquery';
8
9
 
9
10
  declare const schema$l: {
10
11
  readonly $schema: "http://json-schema.org/draft-07/schema#";
@@ -28715,6 +28716,12 @@ declare class Runtime {
28715
28716
  constructor(opts: Opts);
28716
28717
  }
28717
28718
 
28719
+ declare const isPubSubRequest: (headers: Record<string, string>) => boolean;
28720
+ declare const isCronMessage: (headers: Record<string, string>) => boolean;
28721
+ declare const validateGoogleAuth: (opts: Opts & {
28722
+ serviceAccount?: string | null;
28723
+ }, headers: Record<string, string>, firestore: Firestore) => Promise<void>;
28724
+
28718
28725
  type Level = 'env' | 'workflow' | 'trigger';
28719
28726
  declare class EnvEngine extends Runtime {
28720
28727
  docRef: Record<Level, DocumentReference>;
@@ -28737,12 +28744,14 @@ declare class PubSubHelper extends Runtime {
28737
28744
  createOrUpdate(opts: {
28738
28745
  topicName: string;
28739
28746
  dlq: boolean;
28747
+ bigQueryTableId?: string | null;
28740
28748
  retryLimit: number;
28741
28749
  retryDelay: number;
28742
28750
  ackDeadline: number;
28743
28751
  }): Promise<{
28744
28752
  topic: Topic;
28745
28753
  dlqTopic: Topic | null;
28754
+ bqSubscription: Subscription | null;
28746
28755
  dlqSubscription: Subscription | null;
28747
28756
  subscription: Subscription;
28748
28757
  }>;
@@ -28793,7 +28802,7 @@ declare class SchedulerHelper extends Runtime {
28793
28802
  timezone: string;
28794
28803
  }): Promise<{
28795
28804
  status: _google_cloud_scheduler_build_protos_protos.google.rpc.IStatus | null | undefined;
28796
- state: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "STATE_UNSPECIFIED" | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28805
+ state: "STATE_UNSPECIFIED" | _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28797
28806
  name: string | null | undefined;
28798
28807
  httpTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IHttpTarget | null | undefined;
28799
28808
  pubsubTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IPubsubTarget | null | undefined;
@@ -28803,7 +28812,7 @@ declare class SchedulerHelper extends Runtime {
28803
28812
  timezone: string;
28804
28813
  }): Promise<{
28805
28814
  status: _google_cloud_scheduler_build_protos_protos.google.rpc.IStatus | null | undefined;
28806
- state: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "STATE_UNSPECIFIED" | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28815
+ state: "STATE_UNSPECIFIED" | _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.Job.State | "ENABLED" | "PAUSED" | "DISABLED" | "UPDATE_FAILED" | null | undefined;
28807
28816
  name: string | null | undefined;
28808
28817
  httpTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IHttpTarget | null | undefined;
28809
28818
  pubsubTarget: _google_cloud_scheduler_build_protos_protos.google.cloud.scheduler.v1.IPubsubTarget | null | undefined;
@@ -28827,6 +28836,21 @@ declare class JWKSHelper extends Runtime {
28827
28836
  private fetchJWKS;
28828
28837
  }
28829
28838
 
28839
+ interface ColumnSchema {
28840
+ name: string;
28841
+ type: string;
28842
+ mode?: string;
28843
+ }
28844
+ declare class BigQueryHelper extends Runtime {
28845
+ private bigQuery;
28846
+ constructor(opts: Opts, bigQuery: BigQuery);
28847
+ createOrUpdate(opts: {
28848
+ datasetId: string;
28849
+ tableName: string;
28850
+ incomingSchema: ColumnSchema[];
28851
+ }): Promise<string>;
28852
+ }
28853
+
28830
28854
  type TriggerOnExecuteOptions = {
28831
28855
  req: {
28832
28856
  status?: number;
@@ -28973,6 +28997,8 @@ type TriggerOnCreateOptions = {
28973
28997
  };
28974
28998
  };
28975
28999
 
29000
+ type index_BigQueryHelper = BigQueryHelper;
29001
+ declare const index_BigQueryHelper: typeof BigQueryHelper;
28976
29002
  type index_EnvEngine = EnvEngine;
28977
29003
  declare const index_EnvEngine: typeof EnvEngine;
28978
29004
  type index_JWKSHelper = JWKSHelper;
@@ -28987,11 +29013,14 @@ type index_Secrets = Secrets;
28987
29013
  declare const index_Secrets: typeof Secrets;
28988
29014
  type index_TriggerOnCreateOptions = TriggerOnCreateOptions;
28989
29015
  type index_TriggerOnExecuteOptions = TriggerOnExecuteOptions;
29016
+ declare const index_isCronMessage: typeof isCronMessage;
29017
+ declare const index_isPubSubRequest: typeof isPubSubRequest;
28990
29018
  declare const index_sanitize: typeof sanitize;
28991
29019
  declare const index_sanitizeToString: typeof sanitizeToString;
28992
29020
  declare const index_validate: typeof validate;
29021
+ declare const index_validateGoogleAuth: typeof validateGoogleAuth;
28993
29022
  declare namespace index {
28994
- export { index_EnvEngine as EnvEngine, index_JWKSHelper as JWKSHelper, index_PubSubHelper as PubSubHelper, index_Runtime as Runtime, index_SchedulerHelper as SchedulerHelper, index_Secrets as Secrets, type index_TriggerOnCreateOptions as TriggerOnCreateOptions, type index_TriggerOnExecuteOptions as TriggerOnExecuteOptions, index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_validate as validate };
29023
+ export { index_BigQueryHelper as BigQueryHelper, index_EnvEngine as EnvEngine, index_JWKSHelper as JWKSHelper, index_PubSubHelper as PubSubHelper, index_Runtime as Runtime, index_SchedulerHelper as SchedulerHelper, index_Secrets as Secrets, type index_TriggerOnCreateOptions as TriggerOnCreateOptions, type index_TriggerOnExecuteOptions as TriggerOnExecuteOptions, index_isCronMessage as isCronMessage, index_isPubSubRequest as isPubSubRequest, index_sanitize as sanitize, index_sanitizeToString as sanitizeToString, index_validate as validate, index_validateGoogleAuth as validateGoogleAuth };
28995
29024
  }
28996
29025
 
28997
29026
  export { index as lib, index$9 as schema, index$1 as types };
package/dist/index.js CHANGED
@@ -280,15 +280,19 @@ var shipped_exports2 = {};
280
280
  // src-public/index.ts
281
281
  var src_public_exports = {};
282
282
  __export(src_public_exports, {
283
+ BigQueryHelper: () => bigquery_default,
283
284
  EnvEngine: () => env_default,
284
285
  JWKSHelper: () => jwks_default,
285
286
  PubSubHelper: () => pubsub_default,
286
287
  Runtime: () => Runtime,
287
288
  SchedulerHelper: () => scheduler_default,
288
289
  Secrets: () => secret_default,
290
+ isCronMessage: () => isCronMessage,
291
+ isPubSubRequest: () => isPubSubRequest,
289
292
  sanitize: () => sanitize,
290
293
  sanitizeToString: () => sanitizeToString,
291
- validate: () => validate
294
+ validate: () => validate,
295
+ validateGoogleAuth: () => validateGoogleAuth
292
296
  });
293
297
 
294
298
  // src-public/validate.ts
@@ -343,8 +347,8 @@ var sanitize = new import_deep_redact.DeepRedact({
343
347
  replaceStringByLength: true
344
348
  });
345
349
 
346
- // src-public/env.ts
347
- var crypto = __toESM(require("crypto"));
350
+ // src-public/jwks.ts
351
+ var import_jose = require("jose");
348
352
 
349
353
  // src-public/runtime.ts
350
354
  var DEFAULT_BRANCH = "migratedprod";
@@ -370,7 +374,121 @@ var Runtime = class {
370
374
  }
371
375
  };
372
376
 
377
+ // src-public/jwks.ts
378
+ var CACHE_TABLE = "cache-global";
379
+ var JWKSHelper = class extends Runtime {
380
+ constructor(opts, firestore) {
381
+ super(opts);
382
+ this.firestore = firestore;
383
+ }
384
+ async validateAuthorizationHeader(authorizationHeader, expectedServiceAccount = null) {
385
+ const splitted = authorizationHeader.split(".");
386
+ const keyMeta = JSON.parse(Buffer.from(splitted[0], "base64").toString("utf-8")) || {};
387
+ const meta = JSON.parse(Buffer.from(splitted[1], "base64").toString("utf-8")) || {};
388
+ if (!meta.email_verified) {
389
+ throw new Error("Authorization header - Email not verified");
390
+ }
391
+ if (expectedServiceAccount === null) {
392
+ expectedServiceAccount = `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`;
393
+ }
394
+ if (meta.email !== expectedServiceAccount) {
395
+ throw new Error(`Authorization header - Wrong service account, got ${meta?.email}, expected ${expectedServiceAccount}`);
396
+ }
397
+ if (!keyMeta.kid || !keyMeta.alg) {
398
+ throw new Error("Malformed Authorization Header. KID is missing");
399
+ }
400
+ const key = await this.getKey(keyMeta.kid);
401
+ if (!key) {
402
+ throw new Error("Incorrect Authorization Header. The key has not been found in the Google JWKS set");
403
+ }
404
+ const importedKey = await (0, import_jose.importJWK)(key, keyMeta.alg);
405
+ let validationCorrect = true;
406
+ try {
407
+ await (0, import_jose.jwtVerify)(authorizationHeader, importedKey, {
408
+ issuer: ["https://accounts.google.com", "accounts.google.com"],
409
+ clockTolerance: 0,
410
+ audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
411
+ });
412
+ } catch (err) {
413
+ validationCorrect = false;
414
+ }
415
+ if (!validationCorrect) {
416
+ throw new Error("Incorrect Authorization Header. The JWT is not valid");
417
+ }
418
+ return validationCorrect;
419
+ }
420
+ async getKey(keyId) {
421
+ const jwks = typeof global.jwks !== "undefined" ? global.jwks : null;
422
+ let parsedJwks = null;
423
+ if (jwks) {
424
+ try {
425
+ parsedJwks = JSON.parse(jwks);
426
+ } catch (err) {
427
+ err.message.toString();
428
+ }
429
+ }
430
+ let keyData = parsedJwks?.keys?.find((x) => x.kid === keyId);
431
+ let cachedItem;
432
+ if (!keyData) {
433
+ cachedItem = await this.firestore.collection(CACHE_TABLE).doc("google-jwks").get();
434
+ if (cachedItem.exists && cachedItem.data()?.expires_at.toDate() > /* @__PURE__ */ new Date()) {
435
+ keyData = cachedItem.data()?.value;
436
+ }
437
+ }
438
+ keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
439
+ if (!keyData) {
440
+ const result = await this.fetchJWKS();
441
+ parsedJwks = result.jwks;
442
+ if (cachedItem) {
443
+ cachedItem.ref.set({
444
+ value: parsedJwks,
445
+ expires_at: result.expiresAt
446
+ }, { merge: true });
447
+ }
448
+ keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
449
+ }
450
+ if (parsedJwks && cachedItem) {
451
+ global.jwks = JSON.stringify(parsedJwks);
452
+ }
453
+ return keyData;
454
+ }
455
+ async fetchJWKS() {
456
+ const date = /* @__PURE__ */ new Date();
457
+ let expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
458
+ const response = await fetch("https://www.googleapis.com/oauth2/v3/certs");
459
+ const maxAgePair = response.headers.get("Cache-Control")?.split(",").map((x) => x.trim().split("=")).find((x) => x[0] === "max-age");
460
+ if (maxAgePair && maxAgePair.length === 2) {
461
+ expiresAt = new Date(date.getTime() + parseInt(maxAgePair[1], 10) * 1e3);
462
+ }
463
+ const jwks = await response.json();
464
+ return {
465
+ expiresAt,
466
+ jwks
467
+ };
468
+ }
469
+ };
470
+ var jwks_default = JWKSHelper;
471
+
472
+ // src-public/utils.ts
473
+ var isPubSubRequest = (headers) => (headers["User-Agent"] || headers["user-agent"]) === "CloudPubSub-Google" || (headers["User-Agent"] || headers["user-agent"] || "").indexOf("APIs-Google") !== -1;
474
+ var isCronMessage = (headers) => (headers["User-Agent"] || headers["user-agent"]) === "Google-Cloud-Scheduler";
475
+ var validateGoogleAuth = async (opts, headers, firestore) => {
476
+ const token = headers.authorization?.split(" ")[1];
477
+ if (!token) {
478
+ throw new Error(
479
+ "Missing authorization header or invalid format, needs to be in format: Bearer <IdToken>"
480
+ );
481
+ }
482
+ try {
483
+ const jwksHelper = new jwks_default(opts, firestore);
484
+ await jwksHelper.validateAuthorizationHeader(token, opts.serviceAccount || null);
485
+ } catch (err) {
486
+ throw new Error("Invalid authorization header " + err.message);
487
+ }
488
+ };
489
+
373
490
  // src-public/env.ts
491
+ var crypto = __toESM(require("crypto"));
374
492
  var EnvEngine = class extends Runtime {
375
493
  docRef;
376
494
  store;
@@ -514,6 +632,9 @@ var AES256GCM = class {
514
632
  var env_default = EnvEngine;
515
633
 
516
634
  // src-public/pubsub.ts
635
+ function isSubscriptionOptionsPush(opts) {
636
+ return "endpointUrl" in opts;
637
+ }
517
638
  var PubSubHelper = class extends Runtime {
518
639
  constructor(opts, pubSub) {
519
640
  super(opts);
@@ -531,6 +652,18 @@ var PubSubHelper = class extends Runtime {
531
652
  dlqSubscription = await this.ensureDLQSubscriptionExists(dlqSubscriptionName, {
532
653
  topic: dlqTopic
533
654
  });
655
+ } else {
656
+ await this.deleteSubscription(`${subscriptionName}-dlq`);
657
+ }
658
+ let bqSubscription = null;
659
+ if (opts.bigQueryTableId) {
660
+ const bigQuerySubscriptionName = `${subscriptionName}-bq`;
661
+ bqSubscription = await this.ensureSubscribtionExists(bigQuerySubscriptionName, {
662
+ topic,
663
+ tableId: opts.bigQueryTableId
664
+ });
665
+ } else {
666
+ await this.deleteSubscription(`${subscriptionName}-bq`);
534
667
  }
535
668
  const subscription = await this.ensureSubscribtionExists(subscriptionName, {
536
669
  topic,
@@ -544,6 +677,7 @@ var PubSubHelper = class extends Runtime {
544
677
  return {
545
678
  topic,
546
679
  dlqTopic,
680
+ bqSubscription,
547
681
  dlqSubscription,
548
682
  subscription
549
683
  };
@@ -572,40 +706,58 @@ var PubSubHelper = class extends Runtime {
572
706
  let subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
573
707
  let endpointHasChanged = false;
574
708
  let retryLimitHasChanged = false;
709
+ let ackDeadlineHasChanged = false;
575
710
  let retryDelayHasChanged = false;
576
711
  let shouldRecreate = false;
577
712
  let dlqHasChanged = false;
713
+ let tableIdHasChanged = false;
578
714
  if (subscription) {
579
715
  const [subscriptionMetaData] = await subscription.getMetadata();
580
- endpointHasChanged = subscriptionMetaData.pushConfig?.pushEndpoint != opts.endpointUrl;
581
- retryDelayHasChanged = subscriptionMetaData.retryPolicy?.minimumBackoff?.seconds != opts.retryMinDelay;
582
- retryLimitHasChanged = subscriptionMetaData.deadLetterPolicy?.maxDeliveryAttempts != opts.retryLimit;
583
- dlqHasChanged = !subscriptionMetaData.deadLetterPolicy && !!opts.dlqTopic || !!subscriptionMetaData.deadLetterPolicy?.deadLetterTopic && !opts.dlqTopic;
584
- shouldRecreate = endpointHasChanged || retryDelayHasChanged || retryLimitHasChanged || dlqHasChanged;
716
+ if (!isSubscriptionOptionsPush(opts)) {
717
+ tableIdHasChanged = (subscriptionMetaData.bigqueryConfig?.table || null) !== (opts.tableId || null);
718
+ } else {
719
+ endpointHasChanged = subscriptionMetaData.pushConfig?.pushEndpoint != opts.endpointUrl;
720
+ dlqHasChanged = !subscriptionMetaData.deadLetterPolicy && !!opts.dlqTopic || !!subscriptionMetaData.deadLetterPolicy?.deadLetterTopic && !opts.dlqTopic;
721
+ retryDelayHasChanged = subscriptionMetaData.retryPolicy?.minimumBackoff?.seconds != opts.retryMinDelay;
722
+ retryLimitHasChanged = subscriptionMetaData.deadLetterPolicy?.maxDeliveryAttempts != opts.retryLimit;
723
+ ackDeadlineHasChanged = subscriptionMetaData.ackDeadlineSeconds !== (opts.ackDeadline || 90);
724
+ }
725
+ shouldRecreate = endpointHasChanged || retryDelayHasChanged || retryLimitHasChanged || dlqHasChanged || tableIdHasChanged || ackDeadlineHasChanged;
585
726
  }
586
727
  if (subscription && shouldRecreate) {
587
728
  await subscription.delete();
588
729
  }
589
- if (!subscription || shouldRecreate) {
590
- await opts.topic.createSubscription(subscriptionName, Object.assign({
591
- ackDeadlineSeconds: opts.ackDeadline || 90,
592
- pushConfig: {
593
- pushEndpoint: opts.endpointUrl,
594
- oidcToken: {
595
- serviceAccountEmail: `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
596
- audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
597
- }
598
- },
599
- retryPolicy: {
600
- minimumBackoff: { seconds: opts.retryMinDelay },
601
- maximumBackoff: { seconds: opts.retryMaxDelay }
730
+ const payload = {};
731
+ if (!isSubscriptionOptionsPush(opts)) {
732
+ payload.ackDeadlineSeconds = 60;
733
+ payload.bigqueryConfig = {
734
+ table: opts.tableId,
735
+ writeMetadata: true,
736
+ dropUnknownFields: true,
737
+ useTableSchema: true
738
+ };
739
+ } else {
740
+ payload.ackDeadlineSeconds = opts.ackDeadline || 90;
741
+ payload.pushConfig = {
742
+ pushEndpoint: opts.endpointUrl,
743
+ oidcToken: {
744
+ serviceAccountEmail: `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`,
745
+ audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
602
746
  }
603
- }, opts.dlqTopic ? {
604
- deadLetterPolicy: {
747
+ };
748
+ payload.retryPolicy = {
749
+ minimumBackoff: { seconds: opts.retryMinDelay },
750
+ maximumBackoff: { seconds: opts.retryMaxDelay }
751
+ };
752
+ if (opts.dlqTopic) {
753
+ payload.deadLetterPolicy = {
605
754
  deadLetterTopic: opts.dlqTopic.name,
606
755
  maxDeliveryAttempts: opts.retryLimit
607
- }
608
- } : {}));
756
+ };
757
+ }
758
+ }
759
+ if (!subscription || shouldRecreate) {
760
+ await opts.topic.createSubscription(subscriptionName, payload);
609
761
  [subscriptions] = await opts.topic.getSubscriptions();
610
762
  subscription = subscriptions.find((subscription2) => this.getNameFromFullyQualifiedName(subscription2.name) === subscriptionName);
611
763
  }
@@ -826,101 +978,63 @@ var Secrets = class extends Runtime {
826
978
  };
827
979
  var secret_default = Secrets;
828
980
 
829
- // src-public/jwks.ts
830
- var import_jose = require("jose");
831
- var CACHE_TABLE = "cache-global";
832
- var JWKSHelper = class extends Runtime {
833
- constructor(opts, firestore) {
981
+ // src-public/bigquery.ts
982
+ var BigQueryHelper = class extends Runtime {
983
+ constructor(opts, bigQuery) {
834
984
  super(opts);
835
- this.firestore = firestore;
985
+ this.bigQuery = bigQuery;
836
986
  }
837
- async validateAuthorizationHeader(authorizationHeader, expectedServiceAccount = null) {
838
- const splitted = authorizationHeader.split(".");
839
- const keyMeta = JSON.parse(Buffer.from(splitted[0], "base64").toString("utf-8")) || {};
840
- const meta = JSON.parse(Buffer.from(splitted[1], "base64").toString("utf-8")) || {};
841
- if (!meta.email_verified) {
842
- throw new Error("Authorization header - Email not verified");
843
- }
844
- if (expectedServiceAccount === null) {
845
- expectedServiceAccount = `runtime@${process.env.GCLOUD_PROJECT}.iam.gserviceaccount.com`;
846
- }
847
- if (meta.email !== expectedServiceAccount) {
848
- throw new Error(`Authorization header - Wrong service account, got ${meta?.email}, expected ${expectedServiceAccount}`);
849
- }
850
- if (!keyMeta.kid || !keyMeta.alg) {
851
- throw new Error("Malformed Authorization Header. KID is missing");
852
- }
853
- const key = await this.getKey(keyMeta.kid);
854
- if (!key) {
855
- throw new Error("Incorrect Authorization Header. The key has not been found in the Google JWKS set");
856
- }
857
- const importedKey = await (0, import_jose.importJWK)(key, keyMeta.alg);
858
- let validationCorrect = true;
859
- try {
860
- await (0, import_jose.jwtVerify)(authorizationHeader, importedKey, {
861
- issuer: ["https://accounts.google.com", "accounts.google.com"],
862
- clockTolerance: 0,
863
- audience: process.env.GCLOUD_PROJECT + "/" + this.triggerId
864
- });
865
- } catch (err) {
866
- validationCorrect = false;
867
- }
868
- if (!validationCorrect) {
869
- throw new Error("Incorrect Authorization Header. The JWT is not valid");
870
- }
871
- return validationCorrect;
872
- }
873
- async getKey(keyId) {
874
- const jwks = typeof global.jwks !== "undefined" ? global.jwks : null;
875
- let parsedJwks = null;
876
- if (jwks) {
877
- try {
878
- parsedJwks = JSON.parse(jwks);
879
- } catch (err) {
880
- err.message.toString();
881
- }
987
+ async createOrUpdate(opts) {
988
+ const { datasetId, tableName, incomingSchema } = opts;
989
+ const dataset = this.bigQuery.dataset(datasetId);
990
+ const table = dataset.table(tableName);
991
+ const fullTableId = `${this.bigQuery.projectId}.${datasetId}.${tableName}`;
992
+ const [tableExists] = await table.exists();
993
+ if (!tableExists) {
994
+ throw new Error(`Table ${fullTableId} does not exist.`);
882
995
  }
883
- let keyData = parsedJwks?.keys?.find((x) => x.kid === keyId);
884
- let cachedItem;
885
- if (!keyData) {
886
- cachedItem = await this.firestore.collection(CACHE_TABLE).doc("google-jwks").get();
887
- if (cachedItem.exists && cachedItem.data()?.expires_at.toDate() > /* @__PURE__ */ new Date()) {
888
- keyData = cachedItem.data()?.value;
996
+ const [metadata] = await table.getMetadata();
997
+ const existingSchema = metadata.schema.fields;
998
+ const alterStatements = [];
999
+ const existingColumnsMap = new Map(
1000
+ existingSchema.map((col) => [col.name.toLowerCase(), col])
1001
+ );
1002
+ const incomingColumnsMap = new Map(
1003
+ incomingSchema.map((col) => [col.name.toLowerCase(), col])
1004
+ );
1005
+ const currentDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "");
1006
+ for (const incomingCol of incomingSchema) {
1007
+ const existingCol = existingColumnsMap.get(incomingCol.name.toLowerCase());
1008
+ if (!existingCol) {
1009
+ alterStatements.push(
1010
+ `ALTER TABLE \`${fullTableId}\` ADD COLUMN \`${incomingCol.name}\` ${incomingCol.type}${incomingCol.mode === "REPEATED" ? " REPEATED" : ""};`
1011
+ );
1012
+ } else if (existingCol.type !== incomingCol.type || incomingCol.mode && existingCol.mode !== incomingCol.mode) {
1013
+ const renamed = `${incomingCol.name}${currentDate}`;
1014
+ alterStatements.push(
1015
+ `ALTER TABLE \`${fullTableId}\` RENAME COLUMN \`${incomingCol.name}\` TO \`${renamed}\`;`
1016
+ );
1017
+ alterStatements.push(
1018
+ `ALTER TABLE \`${fullTableId}\` ADD COLUMN \`${incomingCol.name}\` ${incomingCol.type}${incomingCol.mode === "REPEATED" ? " REPEATED" : ""};`
1019
+ );
889
1020
  }
890
1021
  }
891
- keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
892
- if (!keyData) {
893
- const result = await this.fetchJWKS();
894
- parsedJwks = result.jwks;
895
- if (cachedItem) {
896
- cachedItem.ref.set({
897
- value: parsedJwks,
898
- expires_at: result.expiresAt
899
- }, { merge: true });
1022
+ for (const existingCol of existingSchema) {
1023
+ if (!incomingColumnsMap.has(existingCol.name.toLowerCase())) {
1024
+ const renamed = `${existingCol.name}${currentDate}`;
1025
+ alterStatements.push(
1026
+ `ALTER TABLE \`${fullTableId}\` RENAME COLUMN \`${existingCol.name}\` TO \`${renamed}\`;`
1027
+ );
900
1028
  }
901
- keyData = parsedJwks?.keys.find((x) => x.kid === keyId);
902
1029
  }
903
- if (parsedJwks && cachedItem) {
904
- global.jwks = JSON.stringify(parsedJwks);
1030
+ for (const sql of alterStatements) {
1031
+ console.log(`Executing SQL: ${sql}`);
1032
+ await this.bigQuery.query({ query: sql });
905
1033
  }
906
- return keyData;
907
- }
908
- async fetchJWKS() {
909
- const date = /* @__PURE__ */ new Date();
910
- let expiresAt = new Date(Date.now() + 1e3 * 60 * 60);
911
- const response = await fetch("https://www.googleapis.com/oauth2/v3/certs");
912
- const maxAgePair = response.headers.get("Cache-Control")?.split(",").map((x) => x.trim().split("=")).find((x) => x[0] === "max-age");
913
- if (maxAgePair && maxAgePair.length === 2) {
914
- expiresAt = new Date(date.getTime() + parseInt(maxAgePair[1], 10) * 1e3);
915
- }
916
- const jwks = await response.json();
917
- return {
918
- expiresAt,
919
- jwks
920
- };
1034
+ return fullTableId;
921
1035
  }
922
1036
  };
923
- var jwks_default = JWKSHelper;
1037
+ var bigquery_default = BigQueryHelper;
924
1038
  // Annotate the CommonJS export names for ESM import in node:
925
1039
  0 && (module.exports = {
926
1040
  lib,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shushed/helpers",
3
- "version": "0.0.26",
3
+ "version": "0.0.28",
4
4
  "author": "",
5
5
  "license": "UNLICENSED",
6
6
  "description": "",