@google-cloud/spanner
Version:
Cloud Spanner Client Library for Node.js
1,142 lines • 45.7 kB
JavaScript
"use strict";
/*!
* Copyright 2016 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Interval = exports.SpannerDate = exports.PGNumeric = exports.Numeric = exports.Struct = exports.Int = exports.Float = exports.Float32 = exports.protos = exports.v1 = exports.MutationSet = exports.MutationGroup = exports.Transaction = exports.Snapshot = exports.PartitionedDml = exports.Table = exports.SessionPool = exports.Session = exports.Backup = exports.Database = exports.InstanceConfig = exports.Instance = exports.Spanner = void 0;
const service_1 = require("./common-grpc/service");
const precise_date_1 = require("@google-cloud/precise-date");
const projectify_1 = require("@google-cloud/projectify");
const promisify_1 = require("@google-cloud/promisify");
const extend = require("extend");
const google_auth_library_1 = require("google-auth-library");
const path = require("path");
const streamEvents = require("stream-events");
const through = require("through2");
const codec_1 = require("./codec");
Object.defineProperty(exports, "Float32", { enumerable: true, get: function () { return codec_1.Float32; } });
Object.defineProperty(exports, "Float", { enumerable: true, get: function () { return codec_1.Float; } });
Object.defineProperty(exports, "Int", { enumerable: true, get: function () { return codec_1.Int; } });
Object.defineProperty(exports, "Numeric", { enumerable: true, get: function () { return codec_1.Numeric; } });
Object.defineProperty(exports, "PGNumeric", { enumerable: true, get: function () { return codec_1.PGNumeric; } });
Object.defineProperty(exports, "SpannerDate", { enumerable: true, get: function () { return codec_1.SpannerDate; } });
Object.defineProperty(exports, "Interval", { enumerable: true, get: function () { return codec_1.Interval; } });
Object.defineProperty(exports, "Struct", { enumerable: true, get: function () { return codec_1.Struct; } });
const api_1 = require("@opentelemetry/api");
const backup_1 = require("./backup");
Object.defineProperty(exports, "Backup", { enumerable: true, get: function () { return backup_1.Backup; } });
const database_1 = require("./database");
Object.defineProperty(exports, "Database", { enumerable: true, get: function () { return database_1.Database; } });
const instance_1 = require("./instance");
Object.defineProperty(exports, "Instance", { enumerable: true, get: function () { return instance_1.Instance; } });
const instance_config_1 = require("./instance-config");
Object.defineProperty(exports, "InstanceConfig", { enumerable: true, get: function () { return instance_config_1.InstanceConfig; } });
const google_gax_1 = require("google-gax");
const protos_1 = require("../protos/protos");
var IsolationLevel = protos_1.google.spanner.v1.TransactionOptions.IsolationLevel;
const common_1 = require("./common");
const session_1 = require("./session");
Object.defineProperty(exports, "Session", { enumerable: true, get: function () { return session_1.Session; } });
const session_pool_1 = require("./session-pool");
Object.defineProperty(exports, "SessionPool", { enumerable: true, get: function () { return session_pool_1.SessionPool; } });
const table_1 = require("./table");
Object.defineProperty(exports, "Table", { enumerable: true, get: function () { return table_1.Table; } });
const transaction_1 = require("./transaction");
Object.defineProperty(exports, "MutationGroup", { enumerable: true, get: function () { return transaction_1.MutationGroup; } });
Object.defineProperty(exports, "MutationSet", { enumerable: true, get: function () { return transaction_1.MutationSet; } });
Object.defineProperty(exports, "PartitionedDml", { enumerable: true, get: function () { return transaction_1.PartitionedDml; } });
Object.defineProperty(exports, "Snapshot", { enumerable: true, get: function () { return transaction_1.Snapshot; } });
Object.defineProperty(exports, "Transaction", { enumerable: true, get: function () { return transaction_1.Transaction; } });
const grpcGcpModule = require("grpc-gcp");
const grpcGcp = grpcGcpModule(google_gax_1.grpc);
const v1 = require("./v1");
exports.v1 = v1;
const instrument_1 = require("./instrument");
const request_id_header_1 = require("./request_id_header");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const gcpApiConfig = require('./spanner_grpc_config.json');
/**
* [Cloud Spanner](https://cloud.google.com/spanner) is a highly scalable,
* transactional, managed, NewSQL database service. Cloud Spanner solves the
* need for a horizontally-scaling database with consistent global transaction
* and SQL semantics. With Cloud Spanner you don't need to choose between
* consistency and horizontal scaling — you get both.
*
* @class
*
* @see [Cloud Spanner Documentation](https://cloud.google.com/spanner/docs)
* @see [Cloud Spanner Concepts](https://cloud.google.com/spanner/docs/concepts)
*
* @example Install the client library with <a
* href="https://www.npmjs.com/">npm</a>:
* ```
* npm install --save @google-cloud/spanner
* ```
*
* @example Create a client that uses <a
* href="https://cloud.google.com/docs/authentication/production#providing_credentials_to_your_application">Application
* Default Credentials (ADC)</a>:
* ```
* const client = new Spanner();
* ```
*
* @example Create a client with <a
* href="https://cloud.google.com/docs/authentication/production#obtaining_and_providing_service_account_credentials_manually">explicit
* credentials</a>:
* ```
* const client = new Spanner({ projectId:
* 'your-project-id', keyFilename: '/path/to/keyfile.json'
* });
* ```
*
* @example <caption>include:samples/quickstart.js</caption>
* region_tag:spanner_quickstart
* Full quickstart example:
*
* @param {ClientConfig} [options] Configuration options.
*/
class Spanner extends service_1.GrpcService {
options;
auth;
clients_;
instances_;
instanceConfigs_;
projectIdReplaced_;
projectFormattedName_;
commonHeaders_;
routeToLeaderEnabled = true;
directedReadOptions;
defaultTransactionOptions;
_observabilityOptions;
_nthClientId;
/**
* Placeholder used to auto populate a column with the commit timestamp.
* This can only be used for timestamp columns that have set the option
* "(allow_commit_timestamp=true)" in the schema.
*
* @type {string}
*/
static COMMIT_TIMESTAMP = 'spanner.commit_timestamp()';
static POSTGRESQL = protos_1.google.spanner.admin.database.v1.DatabaseDialect.POSTGRESQL;
static GOOGLE_STANDARD_SQL = protos_1.google.spanner.admin.database.v1.DatabaseDialect.GOOGLE_STANDARD_SQL;
/**
* Gets the configured Spanner emulator host from an environment variable.
*/
static getSpannerEmulatorHost() {
const endpointWithPort = process.env.SPANNER_EMULATOR_HOST;
if (endpointWithPort) {
if (endpointWithPort.startsWith('http:') ||
endpointWithPort.startsWith('https:')) {
throw new google_gax_1.GoogleError('SPANNER_EMULATOR_HOST must not start with a protocol specification (http/https)');
}
const index = endpointWithPort.indexOf(':');
if (index > -1) {
const portName = endpointWithPort.substring(index + 1);
const port = +portName;
if (!port || port < 1 || port > 65535) {
throw new google_gax_1.GoogleError(`Invalid port number: ${portName}`);
}
return {
endpoint: endpointWithPort.substring(0, index),
port: +endpointWithPort.substring(index + 1),
};
}
return { endpoint: endpointWithPort };
}
return undefined;
}
constructor(options) {
const scopes = [];
const clientClasses = [
v1.DatabaseAdminClient,
v1.InstanceAdminClient,
v1.SpannerClient,
];
for (const clientClass of clientClasses) {
for (const scope of clientClass.scopes) {
if (scopes.indexOf(scope) === -1) {
scopes.push(scope);
}
}
}
options = Object.assign({
libName: 'gccl',
libVersion: require('../../package.json').version,
scopes,
// Add grpc keep alive setting
'grpc.keepalive_time_ms': 30000,
'grpc.keepalive_timeout_ms': 10000,
// Enable grpc-gcp support
'grpc.callInvocationTransformer': grpcGcp.gcpCallInvocationTransformer,
'grpc.channelFactoryOverride': grpcGcp.gcpChannelFactoryOverride,
'grpc.gcpApiConfig': grpcGcp.createGcpApiConfig(gcpApiConfig),
grpc: google_gax_1.grpc,
}, options || {});
const directedReadOptions = options.directedReadOptions
? options.directedReadOptions
: null;
delete options.directedReadOptions;
const defaultTransactionOptions = options.defaultTransactionOptions
? options.defaultTransactionOptions
: {
isolationLevel: IsolationLevel.ISOLATION_LEVEL_UNSPECIFIED,
};
delete options.defaultTransactionOptions;
const emulatorHost = Spanner.getSpannerEmulatorHost();
if (emulatorHost &&
emulatorHost.endpoint &&
emulatorHost.endpoint.length > 0) {
options.servicePath = emulatorHost.endpoint;
options.port = emulatorHost.port;
options.sslCreds = google_gax_1.grpc.credentials.createInsecure();
}
const config = {
baseUrl: options.apiEndpoint ||
options.servicePath ||
// TODO: for TPC, this needs to support universeDomain
'spanner.googleapis.com',
protosDir: path.resolve(__dirname, '../protos'),
protoServices: {
Operations: {
path: 'google/longrunning/operations.proto',
service: 'longrunning',
},
},
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
packageJson: require('../../package.json'),
};
super(config, options);
if (options.routeToLeaderEnabled === false) {
this.routeToLeaderEnabled = false;
}
this.options = options;
this.auth = new google_auth_library_1.GoogleAuth(this.options);
this.clients_ = new Map();
this.instances_ = new Map();
this.instanceConfigs_ = new Map();
this.projectIdReplaced_ = false;
this.projectFormattedName_ = 'projects/' + this.projectId;
this.directedReadOptions = directedReadOptions;
this.defaultTransactionOptions = defaultTransactionOptions;
this._observabilityOptions = options.observabilityOptions;
this.commonHeaders_ = (0, common_1.getCommonHeaders)(this.projectFormattedName_, this._observabilityOptions?.enableEndToEndTracing);
(0, instrument_1.ensureInitialContextManagerSet)();
this._nthClientId = (0, request_id_header_1.nextSpannerClientId)();
}
/**
* Gets the InstanceAdminClient object.
* The returned InstanceAdminClient object is a shared, managed instance and should not be manually closed.
* @returns {v1.InstanceAdminClient} The InstanceAdminClient object
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner({
* projectId: projectId,
* });
* const instanceAdminClient = spanner.getInstanceAdminClient();
* ```
*/
getInstanceAdminClient() {
const clientName = 'InstanceAdminClient';
if (!this.clients_.has(clientName)) {
this.clients_.set(clientName, new v1[clientName](this.options));
}
return this.clients_.get(clientName);
}
/**
* Gets the DatabaseAdminClient object.
* The returned DatabaseAdminClient object is a managed, shared instance and should not be manually closed.
* @returns {v1.DatabaseAdminClient} The DatabaseAdminClient object.
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner({
* projectId: projectId,
* });
* const databaseAdminClient = spanner.getDatabaseAdminClient();
* ```
*/
getDatabaseAdminClient() {
const clientName = 'DatabaseAdminClient';
if (!this.clients_.has(clientName)) {
this.clients_.set(clientName, new v1[clientName](this.options));
}
return this.clients_.get(clientName);
}
/** Closes this Spanner client and cleans up all resources used by it. */
close() {
this.clients_.forEach(c => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const client = c;
if (client.operationsClient && client.operationsClient.close) {
client.operationsClient.close();
}
client.close();
});
}
createInstance(name, config, callback) {
if (!name) {
throw new google_gax_1.GoogleError('A name is required to create an instance.');
}
if (!config) {
throw new google_gax_1.GoogleError(['A configuration object is required to create an instance.'].join(''));
}
const formattedName = instance_1.Instance.formatName_(this.projectId, name);
const displayName = config.displayName || formattedName.split('/').pop();
const reqOpts = {
parent: this.projectFormattedName_,
instanceId: formattedName.split('/').pop(),
instance: extend({
name: formattedName,
displayName,
nodeCount: config.nodes,
processingUnits: config.processingUnits,
}, config),
};
if (reqOpts.instance.nodeCount && reqOpts.instance.processingUnits) {
throw new google_gax_1.GoogleError(['Only one of nodeCount or processingUnits can be specified.'].join(''));
}
if (!reqOpts.instance.nodeCount && !reqOpts.instance.processingUnits) {
// If neither nodes nor processingUnits are specified, default to a
// nodeCount of 1.
reqOpts.instance.nodeCount = 1;
}
delete reqOpts.instance.nodes;
delete reqOpts.instance.gaxOptions;
if (config.config.indexOf('/') === -1) {
reqOpts.instance.config = `projects/${this.projectId}/instanceConfigs/${config.config}`;
}
this.request({
client: 'InstanceAdminClient',
method: 'createInstance',
reqOpts,
gaxOpts: config.gaxOptions,
headers: this.commonHeaders_,
}, (err, operation, resp) => {
if (err) {
callback(err, null, null, resp);
return;
}
const instance = this.instance(formattedName);
instance._observabilityOptions = this._observabilityOptions;
callback(null, instance, operation, resp);
});
}
getInstances(optionsOrCallback, cb) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
const options = typeof optionsOrCallback === 'object'
? optionsOrCallback
: {};
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const gaxOpts = extend(true, {}, options.gaxOptions);
let reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId,
});
delete reqOpts.gaxOptions;
// Copy over pageSize and pageToken values from gaxOptions.
// However values set on options take precedence.
if (gaxOpts) {
reqOpts = extend({}, {
pageSize: gaxOpts.pageSize,
pageToken: gaxOpts.pageToken,
}, reqOpts);
delete gaxOpts.pageToken;
delete gaxOpts.pageSize;
}
this.request({
client: 'InstanceAdminClient',
method: 'listInstances',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
}, (err, instances, nextPageRequest, ...args) => {
let instanceInstances = null;
if (instances) {
instanceInstances = instances.map(instance => {
const instanceInstance = self.instance(instance.name);
instanceInstance.metadata = instance;
return instanceInstance;
});
}
const nextQuery = nextPageRequest
? extend({}, options, nextPageRequest)
: null;
callback(err, instanceInstances, nextQuery, ...args);
});
}
/**
* Get a list of {@link Instance} objects as a readable object stream.
*
* Wrapper around {@link v1.InstanceAdminClient#listInstances}.
*
* @see {@link v1.InstanceAdminClient#listInstances}
* @see [ListInstances API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1#google.spanner.admin.instance.v1.InstanceAdmin.ListInstances)
*
* @method Spanner#getInstancesStream
* @param {GetInstancesOptions} [options] Query object for listing instances.
* @returns {ReadableStream} A readable stream that emits {@link Instance}
* instances.
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner();
*
* spanner.getInstancesStream()
* .on('error', console.error)
* .on('data', function(instance) {
* // `instance` is an `Instance` object.
* })
* .on('end', function() {
* // All instances retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* spanner.getInstancesStream()
* .on('data', function(instance) {
* this.end();
* });
* ```
*/
getInstancesStream(options = {}) {
const gaxOpts = extend(true, {}, options.gaxOptions);
let reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId,
});
delete reqOpts.gaxOptions;
// Copy over pageSize and pageToken values from gaxOptions.
// However values set on options take precedence.
if (gaxOpts) {
reqOpts = extend({}, {
pageSize: gaxOpts.pageSize,
pageToken: gaxOpts.pageToken,
}, reqOpts);
delete gaxOpts.pageSize;
delete gaxOpts.pageToken;
}
return this.requestStream({
client: 'InstanceAdminClient',
method: 'listInstancesStream',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
});
}
createInstanceConfig(name, config, callback) {
if (!name) {
throw new google_gax_1.GoogleError('A name is required to create an instance config.');
}
if (!config) {
throw new google_gax_1.GoogleError([
'A configuration object is required to create an instance config.',
].join(''));
}
if (!config.baseConfig) {
throw new google_gax_1.GoogleError(['Base instance config is required to create an instance config.'].join(''));
}
const formattedName = instance_config_1.InstanceConfig.formatName_(this.projectId, name);
const displayName = config.displayName || formattedName.split('/').pop();
const reqOpts = {
parent: this.projectFormattedName_,
instanceConfigId: formattedName.split('/').pop(),
instanceConfig: extend({
name: formattedName,
displayName,
}, config),
validateOnly: config.validateOnly,
};
if (config.baseConfig.indexOf('/') === -1) {
reqOpts.instanceConfig.baseConfig = `projects/${this.projectId}/instanceConfigs/${config.baseConfig}`;
}
// validateOnly need not be passed in if it is null.
if (reqOpts.validateOnly === null || reqOpts.validateOnly === undefined)
delete reqOpts.validateOnly;
// validateOnly and gaxOptions are not fields in InstanceConfig.
delete reqOpts.instanceConfig.validateOnly;
delete reqOpts.instanceConfig.gaxOptions;
this.request({
client: 'InstanceAdminClient',
method: 'createInstanceConfig',
reqOpts,
gaxOpts: config.gaxOptions,
headers: this.commonHeaders_,
}, (err, operation, resp) => {
if (err) {
callback(err, null, null, resp);
return;
}
const instanceConfig = this.instanceConfig(formattedName);
callback(null, instanceConfig, operation, resp);
});
}
getInstanceConfigs(optionsOrCallback, cb) {
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const options = typeof optionsOrCallback === 'object'
? optionsOrCallback
: {};
const gaxOpts = extend(true, {}, options.gaxOptions);
let reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId,
});
delete reqOpts.gaxOptions;
// Copy over pageSize and pageToken values from gaxOptions.
// However values set on options take precedence.
if (gaxOpts) {
reqOpts = extend({}, {
pageSize: gaxOpts.pageSize,
pageToken: gaxOpts.pageToken,
}, reqOpts);
delete gaxOpts.pageSize;
delete gaxOpts.pageToken;
}
return this.request({
client: 'InstanceAdminClient',
method: 'listInstanceConfigs',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
}, (err, instanceConfigs, nextPageRequest, ...args) => {
const nextQuery = nextPageRequest
? extend({}, options, nextPageRequest)
: null;
callback(err, instanceConfigs, nextQuery, ...args);
});
}
/**
* Get a list of instance configs as a readable object stream.
*
* Wrapper around {@link v1.InstanceAdminClient#listInstanceConfigsStream}.
*
* @see {@link v1.InstanceAdminClient#listInstanceConfigsStream}
* @see [ListInstanceConfigs API Documentation](https://cloud.google.com/spanner/docs/reference/rpc/google.spanner.admin.instance.v1#google.spanner.admin.instance.v1.InstanceAdmin.ListInstanceConfigs)
*
* @method Spanner#getInstanceConfigsStream
* @param {GetInstanceConfigsOptions} [options] Query object for listing instance
* configs.
* @returns {ReadableStream} A readable stream that emits instance configs.
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner();
*
* spanner.getInstanceConfigsStream()
* .on('error', console.error)
* .on('data', function(instanceConfig) {})
* .on('end', function() {
* // All instances retrieved.
* });
*
* //-
* // If you anticipate many results, you can end a stream early to prevent
* // unnecessary processing and API requests.
* //-
* spanner.getInstanceConfigsStream()
* .on('data', function(instanceConfig) {
* this.end();
* });
* ```
*/
getInstanceConfigsStream(options = {}) {
const gaxOpts = extend(true, {}, options.gaxOptions);
let reqOpts = extend({}, options, {
parent: 'projects/' + this.projectId,
});
// Copy over pageSize and pageToken values from gaxOptions.
// However values set on options take precedence.
if (gaxOpts) {
reqOpts = extend({}, {
pageSize: gaxOpts.pageSize,
pageToken: gaxOpts.pageToken,
}, reqOpts);
delete gaxOpts.pageSize;
delete gaxOpts.pageToken;
}
delete reqOpts.gaxOptions;
return this.requestStream({
client: 'InstanceAdminClient',
method: 'listInstanceConfigsStream',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
});
}
getInstanceConfig(name, optionsOrCallback, cb) {
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const options = typeof optionsOrCallback === 'object'
? optionsOrCallback
: {};
const reqOpts = extend({}, {
name: 'projects/' + this.projectId + '/instanceConfigs/' + name,
});
const gaxOpts = extend({}, options.gaxOptions);
return this.request({
client: 'InstanceAdminClient',
method: 'getInstanceConfig',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
}, (err, instanceConfig) => {
callback(err, instanceConfig);
});
}
getInstanceConfigOperations(optionsOrCallback, cb) {
const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
const options = typeof optionsOrCallback === 'object'
? optionsOrCallback
: {};
const gaxOpts = extend(true, {}, options.gaxOptions);
let reqOpts = extend({}, options, {
parent: this.projectFormattedName_,
});
delete reqOpts.gaxOptions;
// Copy over pageSize and pageToken values from gaxOptions.
// However, values set on options take precedence.
if (gaxOpts) {
reqOpts = extend({}, {
pageSize: gaxOpts.pageSize,
pageToken: gaxOpts.pageToken,
}, reqOpts);
delete gaxOpts.pageSize;
delete gaxOpts.pageToken;
}
this.request({
client: 'InstanceAdminClient',
method: 'listInstanceConfigOperations',
reqOpts,
gaxOpts,
headers: this.commonHeaders_,
}, (err, operations, nextPageRequest, ...args) => {
const nextQuery = nextPageRequest
? extend({}, options, nextPageRequest)
: null;
callback(err, operations, nextQuery, ...args);
});
}
/**
* Get a reference to an Instance object.
*
* @throws {GoogleError} If a name is not provided.
*
* @param {string} name The name of the instance.
* @returns {Instance} An Instance object.
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner();
* const instance = spanner.instance('my-instance');
* ```
*/
instance(name) {
if (!name) {
throw new google_gax_1.GoogleError('A name is required to access an Instance object.');
}
const key = name.split('/').pop();
if (!this.instances_.has(key)) {
this.instances_.set(key, new instance_1.Instance(this, name));
}
return this.instances_.get(key);
}
/**
* Get a reference to an InstanceConfig object.
*
* @throws {GoogleError} If a name is not provided.
*
* @param {string} name The name of the instance config.
* @returns {InstanceConfig} An InstanceConfig object.
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const spanner = new Spanner();
* const instanceConfig = spanner.instanceConfig('my-instance-config');
* ```
*/
instanceConfig(name) {
if (!name) {
throw new google_gax_1.GoogleError('A name is required to access an InstanceConfig object.');
}
const key = name.split('/').pop();
if (!this.instanceConfigs_.has(key)) {
this.instanceConfigs_.set(key, new instance_config_1.InstanceConfig(this, name));
}
return this.instanceConfigs_.get(key);
}
/**
* Prepare a gapic request. This will cache the GAX client and replace
* {{projectId}} placeholders, if necessary.
*
* @private
*
* @param {object} config Request config
* @param {function} callback Callback function
*/
prepareGapicRequest_(config, callback) {
this.auth.getProjectId((err, projectId) => {
if (err) {
callback(err);
return;
}
const clientName = config.client;
if (!this.clients_.has(clientName)) {
this.clients_.set(clientName, new v1[clientName](this.options));
}
const gaxClient = this.clients_.get(clientName);
let reqOpts = extend(true, {}, config.reqOpts);
reqOpts = (0, projectify_1.replaceProjectIdToken)(reqOpts, projectId);
// It would have been preferable to replace the projectId already in the
// constructor of Spanner, but that is not possible as auth.getProjectId
// is an async method. This is therefore the first place where we have
// access to the value that should be used instead of the placeholder.
if (!this.projectIdReplaced_) {
this.projectId = (0, projectify_1.replaceProjectIdToken)(this.projectId, projectId);
this.projectFormattedName_ = (0, projectify_1.replaceProjectIdToken)(this.projectFormattedName_, projectId);
this.instances_.forEach(instance => {
instance.formattedName_ = (0, projectify_1.replaceProjectIdToken)(instance.formattedName_, projectId);
instance.databases_.forEach(database => {
database.formattedName_ = (0, projectify_1.replaceProjectIdToken)(database.formattedName_, projectId);
});
});
this.projectIdReplaced_ = true;
}
config.headers[common_1.CLOUD_RESOURCE_HEADER] = (0, projectify_1.replaceProjectIdToken)(config.headers[common_1.CLOUD_RESOURCE_HEADER], projectId);
// Do context propagation
api_1.propagation.inject(api_1.context.active(), config.headers, {
set: (carrier, key, value) => {
carrier[key] = value; // Set the span context (trace and span ID)
},
});
// Attach the x-goog-spanner-request-id to the currently active span.
(0, request_id_header_1.attributeXGoogSpannerRequestIdToActiveSpan)(config);
const requestFn = gaxClient[config.method].bind(gaxClient, reqOpts,
// Add headers to `gaxOpts`
extend(true, {}, config.gaxOpts, {
otherArgs: {
headers: config.headers,
},
}));
// Wrap requestFn to inject the spanner request id into every returned error.
const wrappedRequestFn = (...args) => {
const hasCallback = args &&
args.length > 0 &&
typeof args[args.length - 1] === 'function';
switch (hasCallback) {
case true: {
const cb = args[args.length - 1];
const priorArgs = args.slice(0, args.length - 1);
requestFn(...priorArgs, (...results) => {
if (results && results.length > 0) {
const err = results[0];
(0, request_id_header_1.injectRequestIDIntoError)(config, err);
}
cb(...results);
});
return;
}
case false: {
const res = requestFn(...args);
const stream = res;
if (stream) {
stream.on('error', err => {
(0, request_id_header_1.injectRequestIDIntoError)(config, err);
});
}
const originallyPromise = res instanceof Promise;
if (!originallyPromise) {
return res;
}
return new Promise((resolve, reject) => {
requestFn(...args)
.then(resolve)
.catch(err => {
(0, request_id_header_1.injectRequestIDIntoError)(config, err);
reject(err);
});
});
}
}
};
callback(null, wrappedRequestFn);
});
}
/**
* Funnel all API requests through this method to be sure we have a project
* ID.
*
* @param {object} config Configuration object.
* @param {object} config.gaxOpts GAX options.
* @param {function} config.method The gax method to call.
* @param {object} config.reqOpts Request options.
* @param {function} [callback] Callback function.
* @returns {Promise}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
request(config, callback) {
if (typeof callback === 'function') {
this.prepareGapicRequest_(config, (err, requestFn) => {
if (err) {
callback(err);
}
else {
requestFn(callback);
}
});
}
else {
return new Promise((resolve, reject) => {
this.prepareGapicRequest_(config, (err, requestFn) => {
if (err) {
reject(err);
}
else {
resolve(requestFn());
}
});
});
}
}
/**
* Funnel all streaming API requests through this method to be sure we have a
* project ID.
*
* @param {object} config Configuration object.
* @param {object} config.gaxOpts GAX options.
* @param {function} config.method The gax method to call.
* @param {object} config.reqOpts Request options.
* @param {function} [callback] Callback function.
* @returns {Stream}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
requestStream(config) {
const stream = streamEvents(through.obj());
stream.once('reading', () => {
this.prepareGapicRequest_(config, (err, requestFn) => {
if (err) {
stream.destroy(err);
return;
}
requestFn()
.on('error', err => {
stream.destroy(err);
})
.pipe(stream);
});
});
return stream;
}
/**
* Helper function to get a Cloud Spanner Date object.
*
* DATE types represent a logical calendar date, independent of time zone.
* DATE values do not represent a specific 24-hour period. Rather, a given
* DATE value represents a different 24-hour period when interpreted in a
* different time zone. Because of this, all values passed to
* {@link Spanner.date} will be interpreted as local time.
*
* To represent an absolute point in time, use {@link Spanner.timestamp}.
*
* @param {string|number} [date] String representing the date or number
* representing the year.
* @param {number} [month] Number representing the month.
* @param {number} [date] Number representing the date.
* @returns {SpannerDate}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const date = Spanner.date('08-20-1969');
* ```
*/
static date(dateStringOrYear, month, date) {
if (typeof dateStringOrYear === 'number') {
return new codec_1.codec.SpannerDate(dateStringOrYear, month, date);
}
return new codec_1.codec.SpannerDate(dateStringOrYear);
}
/**
* Date object with nanosecond precision. Supports all standard Date arguments
* in addition to several custom types.
* @external PreciseDate
* @see {@link https://github.com/googleapis/nodejs-precise-date|PreciseDate}
*/
/**
* Helper function to get a Cloud Spanner Timestamp object.
*
* String timestamps should have a canonical format of
* `YYYY-[M]M-[D]D[( |T)[H]H:[M]M:[S]S[.DDDDDDDDD]]Z`
*
* **Timestamp values must be expressed in Zulu time and cannot include a UTC
* offset.**
*
* @see https://cloud.google.com/spanner/docs/data-types#timestamp-type
*
* @param {string|number|google.protobuf.Timestamp|external:PreciseDate}
* [timestamp] Either a RFC 3339 timestamp formatted string or a
* {@link google.protobuf.Timestamp} object. If a PreciseDate is given, it
* will return that timestamp as is.
* @returns {external:PreciseDate}
*
* @example
* ```
* const timestamp = Spanner.timestamp('2019-02-08T10:34:29.481145231Z');
*
* ```
* @example With a `google.protobuf.Timestamp` object
* ```
* const [seconds, nanos] = process.hrtime();
* const timestamp = Spanner.timestamp({seconds, nanos});
* ```
*
* @example With a Date timestamp
* ```
* const timestamp = Spanner.timestamp(Date.now());
* ```
*/
static timestamp(value) {
value = value || Date.now();
if (value instanceof precise_date_1.PreciseDate) {
return value;
}
return new precise_date_1.PreciseDate(value);
}
/**
* Helper function to get a Cloud Spanner Float32 object.
*
* @param {string|number} value The float as a number or string.
* @returns {Float32}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const float = Spanner.float32(10);
* ```
*/
static float32(value) {
return new codec_1.codec.Float32(value);
}
/**
* Helper function to get a Cloud Spanner Float64 object.
*
* @param {string|number} value The float as a number or string.
* @returns {Float}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const float = Spanner.float(10);
* ```
*/
static float(value) {
return new codec_1.codec.Float(value);
}
/**
* Helper function to get a Cloud Spanner Int64 object.
*
* @param {string|number} value The int as a number or string.
* @returns {Int}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const int = Spanner.int(10);
* ```
*/
static int(value) {
return new codec_1.codec.Int(value);
}
/**
* Helper function to get a Cloud Spanner Numeric object.
*
* @param {string} value The numeric value as a string.
* @returns {Numeric}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const numeric = Spanner.numeric("3.141592653");
* ```
*/
static numeric(value) {
return new codec_1.codec.Numeric(value);
}
/**
* Helper function to get a Cloud Spanner pgNumeric object.
*
* @param {string} value The pgNumeric value as a string.
* @returns {PGNumeric}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const pgNumeric = Spanner.pgNumeric("3.141592653");
* ```
*/
static pgNumeric(value) {
return new codec_1.codec.PGNumeric(value);
}
/**
* Helper function to get a Cloud Spanner pgJsonb object.
*
* @param {object|string} value The pgJsonb value as a string or object.
* @returns {PGJsonb}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const pgJsonb1 = Spanner.pgJsonb({rating: 6});
* const pgJsonb2 = Spanner.pgJsonb(`[
* {
* "name": null,
* "open": true
* }]`)
* ```
*/
static pgJsonb(value) {
return new codec_1.codec.PGJsonb(value);
}
/**
* Helper function to get a Cloud Spanner Interval object.
*
* @param {number} months The months part of Interval as number.
* @param {number} days The days part of Interval as number.
* @param {bigint} nanoseconds The nanoseconds part of Interval as bigint.
* @returns {Interval}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const interval = Spanner.Interval(10, 20, BigInt(30));
* ```
*/
static interval(months, days, nanoseconds) {
return new codec_1.codec.Interval(months, days, nanoseconds);
}
/**
* @typedef IProtoMessageParams
* @property {object} value Proto Message value as serialized-buffer or message object.
* @property {string} fullName Fully-qualified path name of proto message.
* @property {Function} [messageFunction] Function generated by protobufs containing
* helper methods for deserializing and serializing messages.
*/
/**
* Helper function to get a Cloud Spanner proto Message object.
*
* @param {IProtoMessageParams} params The proto message value params
* @returns {ProtoMessage}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const protoMessage = Spanner.protoMessage({
* value: singerInfo,
* messageFunction: music.SingerInfo,
* fullName: "examples.spanner.music.SingerInfo"
* });
* ```
*/
static protoMessage(params) {
return new codec_1.codec.ProtoMessage(params);
}
/**
* @typedef IProtoEnumParams
* @property {string | number} value Proto Enum value as a string constant or
* an integer constant.
* @property {string} fullName Fully-qualified path name of proto enum.
* @property {object} [enumObject] An enum object generated by protobufjs-cli.
*/
/**
* Helper function to get a Cloud Spanner proto enum object.
*
* @param {IProtoEnumParams} params The proto enum value params in the format of
* @code{IProtoEnumParams}
* @returns {ProtoEnum}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const protoEnum = Spanner.protoEnum({
* value: 'ROCK',
* enumObject: music.Genre,
* fullName: "examples.spanner.music.Genre"
* });
* ```
*/
static protoEnum(params) {
return new codec_1.codec.ProtoEnum(params);
}
/**
* Helper function to get a Cloud Spanner Struct object.
*
* @param {object} value The struct as a JSON object.
* @returns {Struct}
*
* @example
* ```
* const {Spanner} = require('@google-cloud/spanner');
* const struct = Spanner.struct({
* user: 'bob',
* age: 32
* });
* ```
*/
static struct(value) {
if (Array.isArray(value)) {
return codec_1.codec.Struct.fromArray(value);
}
return codec_1.codec.Struct.fromJSON(value);
}
}
exports.Spanner = Spanner;
/*! Developer Documentation
*
* All async methods (except for streams) will return a Promise in the event
* that a callback is omitted.
*/
(0, promisify_1.promisifyAll)(Spanner, {
exclude: [
'date',
'float32',
'float',
'instance',
'instanceConfig',
'int',
'numeric',
'pgNumeric',
'pgJsonb',
'operation',
'timestamp',
'interval',
'getInstanceAdminClient',
'getDatabaseAdminClient',
],
});
/**
* @type {object}
* @property {constructor} DatabaseAdminClient
* Reference to {@link v1.DatabaseAdminClient}
* @property {constructor} InstanceAdminClient
* Reference to {@link v1.InstanceAdminClient}
* @property {constructor} SpannerClient
* Reference to {@link v1.SpannerClient}
*/
const protos = require("../protos/protos");
exports.protos = protos;
exports.default = { Spanner };
//# sourceMappingURL=index.js.map