UNPKG

@google-cloud/spanner

Version:
1,375 lines 56.5 kB
"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 helper_1 = require("./helper");
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 spanner_api_1 = require("@google-cloud/spanner-api");
Object.defineProperty(exports, "protos", { enumerable: true, get: function () { return spanner_api_1.protos; } });
var google = spanner_api_1.protos.google;
var IsolationLevel = google.spanner.v1.TransactionOptions.IsolationLevel;
var ReadLockMode = google.spanner.v1.TransactionOptions.ReadWrite.ReadLockMode;
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");
const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
const interceptor_1 = require("./metrics/interceptor");
const spanner_metrics_exporter_1 = require("./metrics/spanner-metrics-exporter");
const metrics_tracer_factory_1 = require("./metrics/metrics-tracer-factory");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const gcpApiConfig = require('./spanner_grpc_config.json');
/**
 * Retrieves the universe domain.
 *
 * This function checks for a universe domain in the following order:
 * 1. The `universeDomain` property within the provided spanner options.
 * 2. The `universe_domain` property within the provided spanner options.
 * 3. The `GOOGLE_CLOUD_UNIVERSE_DOMAIN` environment variable.
 * 4. If none of the above properties will be set, it will fallback to `googleapis.com`.
 *
 * For consistency with the Auth client, if the `universe_domain` option or the
 * `GOOGLE_CLOUD_UNIVERSE_DOMAIN` env variable is used, this function will also set the
 * `universeDomain` property within the provided `SpannerOptions` object. This ensures the
 * Spanner client's universe domain aligns with the universe configured for authentication.
 *
 * @param {SpannerOptions} options - The Spanner client options.
 * @returns {string} The universe domain.
 */
function getUniverseDomain(options) {
    const universeDomainEnvVar = typeof process === 'object' && typeof process.env === 'object'
        ? process.env['GOOGLE_CLOUD_UNIVERSE_DOMAIN']
        : undefined;
    const universeDomain = options?.universeDomain ??
        options?.universe_domain ??
        universeDomainEnvVar ??
        'googleapis.com';
    // if the options.universe_domain/GOOGLE_CLOUD_UNIVERSE_DOMAIN env variable is set,
    // set its value to the Spanner `universeDomain` options
    // to match it with the universe from Auth Client
    if (!options?.universeDomain &&
        (options?.universe_domain || process.env.GOOGLE_CLOUD_UNIVERSE_DOMAIN)) {
        options.universeDomain = universeDomain;
    }
    return universeDomain;
}
/**
 * [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_;
    projectId_;
    projectFormattedName_;
    commonHeaders_;
    routeToLeaderEnabled = true;
    directedReadOptions;
    defaultTransactionOptions;
    _observabilityOptions;
    sessionLabels;
    _universeDomain;
    _isInSecureCredentials;
    _metricsEnabled = false;
    static _isAFEServerTimingEnabled;
    _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 = google.spanner.admin.database.v1.DatabaseDialect.POSTGRESQL;
    static GOOGLE_STANDARD_SQL = google.spanner.admin.database.v1.DatabaseDialect.GOOGLE_STANDARD_SQL;
    /**
     * Returns whether AFE (Application Frontend Extension) server timing is enabled.
     *
     * This method checks the value of the environment variable
     * `SPANNER_DISABLE_AFE_SERVER_TIMING`. If the variable is explicitly set to the
     * string `'true'`, then AFE server timing is considered disabled, and this method
     * returns `false`. For all other values (including if the variable is unset),
     * the method returns `true`.
     *
     * @returns {boolean} `true` if AFE server timing is enabled; otherwise, `false`.
     */
    static isAFEServerTimingEnabled = () => {
        if (this._isAFEServerTimingEnabled === undefined) {
            this._isAFEServerTimingEnabled =
                process.env['SPANNER_DISABLE_AFE_SERVER_TIMING'] !== 'true';
        }
        return this._isAFEServerTimingEnabled;
    };
    /** Resets the cached value (use in tests if env changes). */
    static _resetAFEServerTimingForTest() {
        this._isAFEServerTimingEnabled = undefined;
    }
    /**
     * 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': 120000,
            // 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,
                readLockMode: ReadLockMode.READ_LOCK_MODE_UNSPECIFIED,
            };
        delete options.defaultTransactionOptions;
        if (options?.universe_domain &&
            options?.universeDomain &&
            options?.universe_domain !== options?.universeDomain) {
            throw new Error('Please set either universe_domain or universeDomain, but not both.');
        }
        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 universeEndpoint = getUniverseDomain(options);
        const spannerUniverseEndpoint = 'spanner.' + universeEndpoint;
        const config = {
            baseUrl: options.apiEndpoint || options.servicePath || spannerUniverseEndpoint,
            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._isInSecureCredentials = options.sslCreds?._isSecure() === 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;
        if ((0, instrument_1.isTracingEnabled)(this._observabilityOptions)) {
            (0, instrument_1.ensureInitialContextManagerSet)();
        }
        this.sessionLabels = options.sessionLabels || null;
        this.commonHeaders_ = (0, common_1.getCommonHeaders)(this.projectFormattedName_, this._observabilityOptions?.enableEndToEndTracing);
        this._nthClientId = (0, request_id_header_1.nextSpannerClientId)();
        this._universeDomain = universeEndpoint;
        this.projectId_ = options.projectId;
        this.configureMetrics_(options.disableBuiltInMetrics);
    }
    get universeDomain() {
        return this._universeDomain;
    }
    /**
     * 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);
    }
    close(callback) {
        const performTeardown = async () => {
            const promises = [];
            this.clients_.forEach(c => {
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                const client = c;
                // Promise.resolve().then() safely converts synchronous throws into Promise rejections
                if (client.operationsClient && client.operationsClient.close) {
                    promises.push(Promise.resolve().then(() => client.operationsClient.close()));
                }
                if (client.close) {
                    promises.push(Promise.resolve().then(() => client.close()));
                }
            });
            // Wait for all close attempts to settle.
            // Map success to undefined, and failure to the error.
            const results = await Promise.all(promises.map(p => p.then(() => undefined, err => err || new Error('Unknown error during close'))));
            // Always execute cleanup
            try {
                await cleanup();
            }
            catch (err) {
                console.error('Error occured during cleanup: ', err);
            }
            // If any client failed to close, throw the first error we captured
            const firstError = results.find(r => r !== undefined);
            if (firstError) {
                throw firstError;
            }
        };
        const res = performTeardown();
        if (callback) {
            // process.nextTick prevents Unhandled Promise Rejections if callback throws
            res.then(() => process.nextTick(() => callback(null)), err => process.nextTick(() => callback(err)));
        }
        else {
            return res;
        }
    }
    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: Object.assign({
                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);
        const reqOpts = Object.assign({}, 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) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        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
                ? Object.assign({}, 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);
        const reqOpts = Object.assign({}, 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) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.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: Object.assign({
                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);
        const reqOpts = Object.assign({}, 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) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        return this.request({
            client: 'InstanceAdminClient',
            method: 'listInstanceConfigs',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, (err, instanceConfigs, nextPageRequest, ...args) => {
            const nextQuery = nextPageRequest
                ? Object.assign({}, 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);
        const reqOpts = Object.assign({}, options, {
            parent: 'projects/' + this.projectId,
        });
        // Copy over pageSize and pageToken values from gaxOptions.
        // However values set on options take precedence.
        if (gaxOpts) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.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 = Object.assign({}, {
            name: 'projects/' + this.projectId + '/instanceConfigs/' + name,
        });
        const gaxOpts = Object.assign({}, 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);
        const reqOpts = Object.assign({}, options, {
            parent: this.projectFormattedName_,
        });
        delete reqOpts.gaxOptions;
        // Copy over pageSize and pageToken values from gaxOptions.
        // However, values set on options take precedence.
        if (gaxOpts) {
            const gax = gaxOpts;
            if (gax.pageSize !== undefined) {
                reqOpts.pageSize ??= gax.pageSize;
                delete gax.pageSize;
            }
            if (gax.pageToken !== undefined) {
                reqOpts.pageToken ??= gax.pageToken;
                delete gax.pageToken;
            }
        }
        this.request({
            client: 'InstanceAdminClient',
            method: 'listInstanceConfigOperations',
            reqOpts,
            gaxOpts,
            headers: this.commonHeaders_,
        }, (err, operations, nextPageRequest, ...args) => {
            const nextQuery = nextPageRequest
                ? Object.assign({}, 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);
    }
    /**
     * Setup the OpenTelemetry metrics capturing for service metrics to Google Cloud Monitoring.
     */
    configureMetrics_(disableBuiltInMetrics) {
        // Only enable metrics if not explicitly disabled and we are not using
        // insecure credentials.
        const metricsExplicitlyDisabled = process.env.SPANNER_DISABLE_BUILTIN_METRICS === 'true' ||
            !!disableBuiltInMetrics;
        this._metricsEnabled =
            !metricsExplicitlyDisabled && !this._isInSecureCredentials;
        metrics_tracer_factory_1.MetricsTracerFactory.enabled = this._metricsEnabled;
        if (this._metricsEnabled) {
            try {
                this.auth.getProjectId((err, projectId) => {
                    if (err || !projectId) {
                        console.error('Unable to get Project Id for client side metrics, will skip exporting client' +
                            ' side metrics' +
                            err);
                        return;
                    }
                    this.projectId_ = projectId;
                    const factory = metrics_tracer_factory_1.MetricsTracerFactory.getInstance(projectId);
                    const periodicReader = new sdk_metrics_1.PeriodicExportingMetricReader({
                        exporter: new spanner_metrics_exporter_1.CloudMonitoringMetricsExporter({ auth: this.auth }, projectId),
                        exportIntervalMillis: 60000,
                    });
                    // Retrieve the MeterProvider to trigger construction
                    factory.getMeterProvider([periodicReader]);
                });
            }
            catch (err) {
                console.error('Unable to configure client side metrics, will skip exporting client' +
                    ' side metrics' +
                    err);
            }
        }
    }
    /**
     * 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;
            try {
                if (!this.clients_.has(clientName)) {
                    this.clients_.set(clientName, new v1[clientName](this.options));
                }
            }
            catch (err) {
                callback(err, null);
            }
            const gaxClient = this.clients_.get(clientName);
            let reqOpts = extend(true, {}, config.reqOpts);
            reqOpts = (0, helper_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, helper_1.replaceProjectIdToken)(this.projectId, projectId);
                this.projectFormattedName_ = (0, helper_1.replaceProjectIdToken)(this.projectFormattedName_, projectId);
                this.instances_.forEach(instance => {
                    instance.formattedName_ = (0, helper_1.replaceProjectIdToken)(instance.formattedName_, projectId);
                    instance.databases_.forEach(database => {
                        database.formattedName_ = (0, helper_1.replaceProjectIdToken)(database.formattedName_, projectId);
                    });
                });
                this.projectIdReplaced_ = true;
            }
            config.headers[common_1.CLOUD_RESOURCE_HEADER] = (0, helper_1.replaceProjectIdToken)(config.headers[common_1.CLOUD_RESOURCE_HEADER], projectId);
            if ((0, instrument_1.isTracingEnabled)(this._observabilityOptions)) {
                // 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 interceptors = [];
            if (this._metricsEnabled) {
                interceptors.push(interceptor_1.MetricInterceptor);
            }
            const requestFn = gaxClient[config.method].bind(gaxClient, reqOpts, 
            // Add headers to `gaxOpts`
            extend(true, {}, config.gaxOpts, {
                otherArgs: {
                    headers: config.headers,
                    options: {
                        interceptors: interceptors,
                    },
                },
            }));
            // 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) {
        let metricsTracer = null;
        if (this._metricsEnabled &&
            config.client === 'SpannerClient' &&
            this.projectId_) {
            metricsTracer =
                metrics_tracer_factory_1.MetricsTracerFactory?.getInstance(this.projectId_)?.createMetricsTracer(config.method, config.reqOpts.database ?? config.reqOpts.session, config.headers['x-goog-spanner-request-id']) ?? null;
        }
        metricsTracer?.recordOperationStart();
        if (typeof callback === 'function') {
            this.prepareGapicRequest_(config, (err, requestFn) => {
                if (err) {
                    callback(err);
                    metricsTracer?.recordOperationCompletion();
                }
                else {
                    const wrappedCallback = (...args) => {
                        metricsTracer?.recordOperationCompletion();
                        callback(...args);
                    };
                    requestFn(wrappedCallback);
                }
            });
        }
        else {
            return new Promise((resolve, reject) => {
                this.prepareGapicRequest_(config, (err, requestFn) => {
                    if (err) {
                        metricsTracer?.recordOperationCompletion();
                        reject(err);
                    }
                    else {
                        const result = requestFn();
                        if (result && typeof result.then === 'function') {
                            result
                                .then(val => {
                                metricsTracer?.recordOperationCompletion();
                                resolve(val);
                            })
                                .catch(error => {
                                metricsTracer?.recordOperationCompletion();
                                reject(error);
                            });
                        }
                        else {
                            metricsTracer?.recordOperationCompletion();
                            resolve(result);
                        }
                    }
                });
            });
        }
    }
    /**
     * 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) {
        let metricsTracer = null;
        if (this._metricsEnabled &&
            config.client === 'SpannerClient' &&
            this.projectId_) {
            metricsTracer =
                metrics_tracer_factory_1.MetricsTracerFactory?.getInstance(this.projectId_)?.createMetricsTracer(config.method, config.reqOpts.session ?? config.reqOpts.database, config.headers['x-goog-spanner-request-id']) ?? null;
        }
        metricsTracer?.recordOperationStart();
        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);
            });
        });
        stream.on('finish', () => {
            stream.destroy();
        });
        stream.on('close', () => {
            metricsTracer?.recordOperationCompletion();
        });
        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;
let cleanupCalled = false;
const cleanup = async () => {
    if (cleanupCalled)
        return;
    cleanupCalled = true;
    await metrics_tracer_factory_1.MetricsTracerFactory.resetInstance();
};
// For signals (let process exit naturally)
process.on('SIGINT', async () => {
    await cleanup();
});
process.on('SIGTERM', async () => {
    await cleanup();
});
// For natural exit (Node will NOT wait for async, so we must block the event loop)
process.on('beforeExit', () => {
    const done = cleanup();
    if (done && typeof done.then === 'function') {
        // Handle promise rejection
        done.catch(err => {
            console.error('Cleanup error before exit:', err);
        });
    }
});
/*! 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',
    ],
});
exports.default = { Spanner };
//# sourceMappingURL=index.js.map