Google Cloud Platform

  • npm i @pi-r/gcp

Tip

The alias gcloud can be used in place of “gcp” for the service property.

Storage

Note

npm i firebase && npm i firebase-admin (optional)

Interface

import type { StorageOptions } from "@google-cloud/storage";
import type { FirebaseOptions } from "@firebase/app";
import type { ServiceAccount } from "firebase-admin/app";

interface GCPStorage extends CloudStorage {
    service: "gcp" | "gcloud";
    credential: string | GCPStorageCredential;
    bucket: string;
}

interface GCPStorageCredential extends StorageOptions, FirebaseOptions {
    product?: "firebase";
    admin?: boolean | string | ServiceAccount;
}

API

type Document = Record<string, any>;
type Metadata = Record<string, any>;

Authentication

using process.env
GOOGLE_APPLICATION_CREDENTIALS = "";
{
  "dataSource": {
    "credential": "main", // squared.cloud.json
    /* OR */
    "credential": {
      "keyFilename": "./gcp.json", // Path to JSON credentials
      "projectId": "nodejs"
    },
    /* OR */
    "credential": {
      "projectId": "nodejs",
      "credentials": {
        "client_email": "**********",
        "private_key": "**********",
        "type": "service_account", // Optional
        "project_id": "nodejs"
      }
    },
    /* OR */
    "credential": {
      "projectId": "nodejs", // When using GOOGLE_APPLICATION_CREDENTIALS
      "scopes": "https://www.googleapis.com/auth/cloud-platform" // Optional
    },
    /* OR */
    "credential": {
      "projectId": "nodejs",
      "apiKey": "**********",
      "authDomain": "<project-id>.firebaseapp.com",
      /* OR */
      "product": "firebase" // When using GOOGLE_APPLICATION_CREDENTIALS
    }
  }
}

Example usage

{
  "selector": "html", // Any resource
  "cloudStorage": [{
    "service": "gcp",
    "bucket": "nodejs-001",
    "credential": {/* Authentication */},
    "admin": {
      "publicRead": true, // New buckets only
      /* OR */
      "acl": "private", // See "policy"

      "emptyBucket": { // gcp.(deleteFiles | getFiles){GetFilesOptions}
        "recursive": true // Optional
      },
      "configBucket": {
        "policy": { // MakeBucketPrivateOptions
          "acl": "private", // makePrivate + includeFiles + projectPrivate
          "acl": "projectPrivate", // makePrivate + allUsers (delete) + allAuthenticatedUsers (delete)
          "acl": "authenticatedRead", // projectPrivate + allAuthenticatedUsers:READER
          "acl": "publicRead", // makePublic + includeFiles
          "acl": "publicReadWrite", // publicRead + allUsers:WRITER
          "acl": [{ "entity": "allUsers", "role": "READER" } /* add */, { "entity": "allAuthenticatedUsers" } /* delete */], // Custom

          /* Unofficial aliases - gcp.setMetadata{iamConfiguration} */
          "acl": "bucketAccessUniform", // Enable uniform bucket-level access
          "acl": "bucketAccessACL" // Revert uniform bucket-level access (within 90 days)
        },
        "tags": { // gcp.setMetadata{labels}
          "key_1": "value",
          "key_2": "value"
        },
        "tags": {}, // gcp.setMetadata{labels=null}
        "website": { // gcp.setMetadata{website}
          "indexPage": "index.html", // mainPageSuffix
          "errorPage": "404.html" // notFoundPage
        },
        /* During call to "upload" */
        "create": { // gcp.createBucket{CreateBucketRequest}
          "location": "ASIA",
          "storageClass": "STANDARD" // "NEARLINE" | "COLDLINE" | "ARCHIVE"
        },
        "lifecycle": [/* LifecycleRule */], // gcp.addLifecycleRule
        "lifecycle": [/* LifecycleRule */, false], // options.append = false
        "lifecycle": [], // Delete all rules
        "cors": [/* Cors */], // gcp.setCorsConfiguration
        "cors": [], // Delete all rules
        "retentionPolicy": 0, // gcp.removeRetentionPeriod
        "retentionPolicy": 86400 // gcp.setRetentionPeriod (seconds)
      }
    },
    "upload": {
      "publicRead": true, // Will not clobber existing ACLs
      "publicRead": 0, // Remove ACL without affecting other ACLs
      /* OR */
      "acl": "authenticatedRead", // "bucketOwnerFullControl" | "bucketOwnerRead" | "private" | "projectPrivate" | "publicRead"

      /* gcp.save */
      "options": { // UploadOptions
        "contentType": "text/html",
        "predefinedAcl": "publicRead", // Supplementary are public
        "metadata": {/* Metadata */} // All objects except when "metadata" is defined
      },

      /* gcp.uploadFileInChunks{chunkSizeBytes} */
      "chunkSize": "8mb", // Aligned to 1mb
      "chunkLimit": 5, // Same as "concurrencyLimit"
      "options": {
        "contentType": "image/png",
        "metadata": {/* Metadata */},
        /* UploadFileInChunksOptions - shared */
        "headers": {
          "Authorization": "",
          "x-goog-user-project": ""
        },
        "maxQueueSize": 5,
        "concurrencyLimit": 5
      },

      /* firebase.uploadBytes */
      "options": { // UploadMetadata
        "contentType": "text/html",
        "customMetadata": {/* Metadata */} // All objects except when "metadata" is defined
      },

      /* Primary object only */
      "metadata": {
        "key": "value",
        "key_delete": null
      }
    },
    "download": {
      /* gcp.createReadStream */
      "chunkSize": "", // Empty
      "chunkLimit": 1, // Will only stream when value is 1

      /* gcp.downloadFileInChunks{chunkSizeBytes} */
      "chunkSize": "32mb", // Aligned to 1mb
      "chunkLimit": 5, // Same as "concurrencyLimit"
      "options": {
        "concurrencyLimit": 5
      }
      /* Same as interface - gcp.download + firebase.getDownloadURL */
    }
  }]
}

Attention

Firebase does not support any bucket operations except emptyBucket and metadata.

Database

Interface

import type { AggregateSpec, FieldPath } from "@google-cloud/firestore";
import type { PathType } from "@google-cloud/datastore";
import type { entity } from "@google-cloud/datastore/build/src/entity";
import type { GoogleAuthOptions } from "google-auth-library";
import type { FirebaseOptions } from "@firebase/app";

interface GCPDatabaseQuery extends CloudDatabase {
    source: "cloud";
    service: "gcp" | "gcloud";
    credential: string | GCPDatabaseCredential;
    product?: "firestore" | "bigquery" | "bigtable" | "datastore" | "spanner" | "firebase";
    id?: string | string[] | number | boolean;
    params?: string | unknown[] | Document;
    database?: string;
    updateType?: 0 | 1 | 2 | 3;
    columns?: string[];
    keys?: DatastoreKey | DatastoreKey[];
    kind?: string | string[];
    orderBy?: (string | FieldPath | unknown[])[] | string | FieldPath;
    aggregateSpec?: AggregateSpec;
    pipeline?: boolean;
    flags?: number;
}

interface GCPDatabaseCredential extends GoogleAuthOptions, FirebaseOptions {
    product?: "firebase";
    admin?: boolean | string | ServiceAccount;
}

type DatastoreKey = string | PathType[] | entity.KeyOptions;
type Document = Record<string, any>;

Authentication

{
  "dataSource": {
    "credential": "main", // squared.cloud.json
    /* OR */
    "credential": {/* Same as Storage */},
    /* OR */
    "credential": {
      "projectId": "nodejs",
      "apiKey": "**********",
      "authDomain": "<project-id>.firebaseapp.com",
      "databaseURL": "https://<database-name>.firebaseio.com" // Required
    }
  }
}

Example usage

Firestore

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "firestore",
    "credential": {/* Authentication */},
    "table": "demo",

    /* a = fs.collection(table) | b = fs.collectionGroup(table){flags & 1} */
    "id": "8Qnt83DSNW0eNykpuzcQ", // a.doc
    /* OR */
    "id": ["8Qnt83DSNW0eNykpuzcQ", "aahiEBE4qHM73JE7jom3"], // fs.getAll (table/id)
    "options": {/* ReadOptions */},
    /* OR */
    "params": ["column", "column.sub"], // a.findNearest
    "query": [1, 2],
    "options": {
      "limit": 1000, // Optional
      "distanceMeasure": "EUCLIDEAN"
    },
    /* OR */
    "options": { // a.findNearest{VectorQueryOptions} (not cached)
      "vectorField": "column",
      "queryVector": [1, 2],
      "limit": 1000, // Optional
      "distanceMeasure": "EUCLIDEAN"
    },
    /* OR */
    "aggregateSpec": {/* AggregateSpec */}, // (a | b).aggregate (not cached)

    "updateType": 0, // 0 - update{exists} | 1 - create | 2 - set | 3 - set{merge}
    "update": {/* Document */}, // fs.update
    "update": {
      "key1": "__delete__", // FieldValue.delete()
      "key2": "__increment__", // FieldValue.increment(1)
      "key2": "__increment<number>__", // FieldValue.increment(number)
      "key3": "__serverTimestamp__", // FieldValue.serverTimestamp()
      "key4": "__vector<1, 2, 3>__", // FieldValue.vector([1, 2, 3])
      "key4": "__arrayUnion<a, b, c>__", // FieldValue.arrayUnion("a", "b", "c")
      "key4": "__arrayRemove<1, [2\\, 3\\, 4], 5>__" // FieldValue.arrayRemove(1, [2, 3, 4], 5)
    },
    "id": "8Qnt83DSNW0eNykpuzcQ" // Same as item being retrieved
  }
}
Query
{
  "dataSource": {
    "query": [
      ["where", "group", "==", "Firestore"],
      ["where", "id", "==", "8Qnt83DSNW0eNykpuzcQ"],
      ["findNearest", "column", [1, 2], { "limit": 1000, "distanceMeasure": "EUCLIDEAN" }],
      ["limitToLast", 2],
      ["orderBy", "title", "asc"]
    ],
    "query": [
      ["whereAnd",
        ["group", "==", "Firestore"],
        ["id", "==", "8Qnt83DSNW0eNykpuzcQ"]
      ],
      ["limitToLast", 2]
    ],
    "query": [
      ["whereOr",
        ["id", "==", "8Qnt83DSNW0eNykpuzcQ"],
        ["id", "==", "aahiEBE4qHM73JE7jom3"]
      ],
      ["orderBy", "title", "asc"]
    ],
    "orderBy": [
      ["title", "asc"]
    ],
    "id": 1 // b.getPartitions(id){number} // Group only (optional)
  }
}
  • endAt

  • endBefore

  • findNearest

  • limit

  • limitToLast

  • offset

  • orderBy

  • select

  • startAfter

  • startAt

  • where

  • whereAnd (unofficial)

  • whereOr (unofficial)

  • withConverter

Pipeline

These methods query parameters are interpreted when every item is an Array:

  • aggregate

  • distinct

  • select

  • sort

  • unnest

  • where

  • whereAnd (unofficial)

  • whereOr (unofficial)

{
  "dataSource": {
    "query": [
      ["aggregate", ["rating", "average", "averageRating"]], // field("rating").average().as("averageRating")
      ["aggregate", ["countAll", "totalBooks"]], // countAll().as("totalBooks")
      ["aggregate", ["name", ["startsWith", "Mr."], "countIf"]] // countIf(field("name").startsWith("Mr."))
    ],
    "query": [
      ["select", ["firstName", ["field", "lastName"], ["address", "toUpper", ["as", "upperAddress"]]]] // "firstName", field("lastName"), field("address").toUpper().as("upperAddress")
      ["distinct", [["author", "authorName"], "publishedAt"]] // field("author").as("authorName"), "publishedAt"
    ],
    "query": [
      ["sort", ["rating", ["title", true]]] // field("rating").ascending(), field("title").descending()
    ],
    "query": [
      ["unnest", ["tagIndex", ["tags", "tag", /* indexField? */]]] // "tagIndex", field("tags").as("tag")
    ],
    "query": [
      ["where", [
        ["rating", "lessThanOrEqual", 3.15], // field("rating").lessThanOrEqual(3.15)
        ["genre", ["toLower", ["equal", "Fun Facts"]]] // field("genre").toLower().equal("fun facts")
      ]]
    ],
    "query": [
      ["whereAnd", [
        ["rating", "lessThanOrEqual", 3.15], // and(field("rating").lessThanOrEqual(3.15), field("genre").toLower().equal("fun facts"))
        ["genre", ["toLower", ["equal", "Fun Facts"]]]
      ]],
      ["whereOr", [
        ["rating", "lessThanOrEqual", 3.15], // or(field("rating").lessThanOrEqual(3.15), field("genre").toLower().equal("fun facts"))
        ["genre", ["toLower", ["equal", "Fun Facts"]]]
      ]]
    ],
    "options": {/* PipelineExecuteOptions */}
  }
}

These methods query parameters are passed in directly without modification:

  • findNearest

  • limit

  • offset

  • rawStage

  • removeFields

  • replaceWith

  • sample

  • union

{
  "dataSource": {
    "query": [
      ["limit", 50], // limit(50)
      ["limit", { "limit": 75 }] // limit({ limit: 75 })
    ]
  }
}

BigQuery

Note

npm i @google-cloud/bigquery

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "bigquery",
    "credential": {/* Authentication */},

    "database": "nodejs", // Dataset (optional)
    "table": "demo", // Destination table (optional)

    "query": "SELECT name, count FROM `demo.names_2014` WHERE gender = 'M' ORDER BY count DESC LIMIT 10", // bq.getQueryResults

    "params": { "name": "value" },
    "params": ["arg0" /* ? */, "arg1" /* ? */],
    "options": {/* IQuery */},

    "update": "SELECT name, state FROM `bigquery-public-data.usa_names.usa_1910_current` LIMIT 10" // "database" | "database" + "table" (bq.setMetadata)
  }
}

Datastore

Note

npm i @google-cloud/datastore

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "datastore",
    "credential": {/* Authentication */},

    "keys": "task", // ds.get
    "keys": ["task", "sampletask1"], // PathType[]
    "keys": { // KeyOptions
      "namespace": "nodejs",
      "path": ["task", "sampletask3"]
    },
    "keys": ["task", ["task", "sampletask2"]],
    /* OR */
    "name": "<namespace>", // With "kind" (optional)
    "kind": "Task", // ds.runQuery (at least one parameter)
    "kind": ["Task1", "Task2"],
    "query": [
      ["filter", "done", "=", false],
      ["filter", "priority", ">=", 4],
      ["order", "priority", { "descending": true }]
    ],
    "options": {/* RunQueryOptions */},

    "update": {/* Document */}, // ds.save
    "keys": "task", // Same as item being retrieved
    /* OR */
    "kind": "Task",
    "query": [/* Same */]
  }
}
  • end

  • filter

  • groupBy

  • hasAncestor

  • limit

  • offset

  • order

  • select

  • start

Bigtable

Note

npm i @google-cloud/bigtable

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "bigtable",
    "credential": {/* Authentication */},
    "name": "nodejs", // Instance
    "table": "demo",

    "id": "rowKey1", // bt.get
    "columns": ["column1", "column2"],
    /* OR */
    "id": "<empty>", // bt.createReadStream

    "query": {/* Filter */}, // Overrides "filter" in GetRowOptions
    "options": {/* GetRowOptions */},

    "update": {/* Document */}, // bt.save
    "id": "rowKey1" // Same as item being retrieved
  }
}

Spanner

Note

npm i @google-cloud/spanner

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "spanner",
    "credential": {/* Authentication */},
    "name": "nodejs", // Instance

    "database": "sample", // Required
    "options": {
      "databasePool": {/* session-pool.SessionPoolOptions */},
      "databaseQuery": {/* protos.IQueryOptions */}
    },

    "table": "demo", // sp.table.read
    "columns": ["col1", "col2"], // Overrides "columns" in ReadRequest
    "query": { // ReadRequest
      "columns": [],
      "keys": []
    },
    "options": {
      "tableRead": {/* TimestampBounds */}
    },
    /* OR */
    "table": "<empty>", // sp.run
    "flags": 2, // sp.runStream
    "query": "SELECT 1", // DML
    "query": { // ExecuteSqlRequest
      "sql": "SELECT 1",
      "params": { "p1": 0, "p2": 1 },
      "types": {
        "p1": "numeric" // date | float | float32 | int | protoEnum | protoMessage | struct | pgJsonb | pgNumeric | timestamp
      }
    },

    "table": "demo", // sp.table.update
    "update": {/* Document */},
    "updateType": 0, // 0 - update | 1 - insert | 2 - replace
    "options": {
      "tableUpdate": {/* UpdateRowsOptions */}
    },
    /* OR */
    "table": "<empty>", // sp.runUpdate
    "update": "SELECT 1",
    "update": { "sql": "SELECT 1", "params": { "p1": 0, "p2": 1 } }
  }
}

Realtime Database

Note

npm i firebase && npm i firebase-admin (optional)

{
  "selector": "h1",
  "type": "text",
  "dataSource": {
    "source": "cloud",
    "service": "gcp",
    "product": "firebase",
    "credential": {/* Authentication */},

    "query": "path/to/ref", // rd.child
    /* OR */
    "query": "path/to/ref", // rd.query
    "orderBy": [
      ["orderByChild", "path/to/child"],
      ["startAfter", 5, "name"],
      ["limitToFirst", 1]
    ],

    "update": {/* Document */}, // rd.update
    "query": "path/to/ref" // Same as item being retrieved (rd.child)
  }
}
  • endBefore

  • endAt

  • equalTo

  • limitToFirst

  • limitToLast

  • orderByChild

  • orderByKey

  • orderByPriority

  • orderByValue

  • startAt

  • startAfter

@pi-r/gcp

Added in version 0.12.0:

  • Firestore pipeline (beta) queries are supported.

Changed in version 0.12.0:

  • BREAKING There is no special handling when uploading the file extension “.map”. CloudStorageUpload property descendantsGroup as [“.map”] can be used to restore the old behavior.

Added in version 0.11.0:

  • GCPStorage properties upload | download extended CopyObjectAction as copyObject | copyObject[].

Added in version 0.10.1:

  • Spanner method runStream for SQL requests was implemented.

Changed in version 0.10.0:

  • GCPStorage property admin.configBucket.retentionPolicy as 0 calls the method removeRetentionPeriod.

  • Firebase (admin) authentication using a JSON file or an object with a service account key is supported.

  • Firestore aggregate and collection group partition queries are supported.

  • Firestore method findNearest for vector queries as VectorQueryOptions was implemented.

Added in version 0.9.0:

  • GCPStorage property emptyBucket for directory listing as GetListOptions was implemented.

Added in version 0.8.0:

  • GCPStorage action download using createReadStream with chunkLimit was implemented.

  • Firestore method findNearest as a VectorQuery and Query is supported.

  • Firestore property update supports using FieldValue<”vector” | “arrayUnion” | “arrayRemove”> methods.

Added in version 0.7.1:

  • Firestore methods where | orderBy | select supports using FieldPath params.

Added in version 0.7.0:

  • CLOUD_UPLOAD_STREAM attribute in ICloudServiceClient was enabled.

  • CLOUD_UPLOAD_CHUNK attribute in ICloudServiceClient was enabled.

  • CLOUD_DOWNLOAD_CHUNK attribute in ICloudServiceClient was enabled.

  • chunkSize | chunkLimit in CloudStorageUpload were implemented.

  • chunkSize | chunkLimit in CloudStorageDownload were implemented.

  • GCPStorage configBucket.tags using Metadata was implemented.

  • GCPStorage configBucket.cors using Cors was implemented.

  • GCPStorage configBucket.lifecycle using LifecycleRule was implemented.

  • Firestore property update supports using FieldValue<”delete” | “increment” | “serverTimestamp”> methods.

  • Firestore property update supports using property updateType enum values.

Removed in version 0.7.0:

  • GCPStorageCredential no longer extends CreateBucketRequest.

Added in version 0.6.3:

  • Firestore property id supports multiple document references.

  • Firestore property query supports using Filter<”and” | “or”> conditional groups.

Added in version 0.6.2:

  • GoogleAuthOptions properties authClient and credentials were not detected during credential validation.

  • Datastore method createQuery with namespace and kind parameter is supported.