UNPKG

11.9 kBJavaScriptView Raw
1/*
2 * Copyright DataStax, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16'use strict';
17
18const util = require('util');
19const policies = require('./policies');
20const types = require('./types');
21const utils = require('./utils');
22const tracker = require('./tracker');
23const metrics = require('./metrics');
24const auth = require('./auth');
25
26/** Core connections per host for protocol versions 1 and 2 */
27const coreConnectionsPerHostV2 = {
28 [types.distance.local]: 2,
29 [types.distance.remote]: 1,
30 [types.distance.ignored]: 0
31};
32
33/** Core connections per host for protocol version 3 and above */
34const coreConnectionsPerHostV3 = {
35 [types.distance.local]: 1,
36 [types.distance.remote]: 1,
37 [types.distance.ignored]: 0
38};
39
40/** Default maxRequestsPerConnection value for protocol v1 and v2 */
41const maxRequestsPerConnectionV2 = 128;
42
43/** Default maxRequestsPerConnection value for protocol v3+ */
44const maxRequestsPerConnectionV3 = 2048;
45
46const continuousPageUnitBytes = 'bytes';
47const continuousPageDefaultSize = 5000;
48const continuousPageDefaultHighWaterMark = 10000;
49
50/**
51 * @returns {ClientOptions}
52 */
53function defaultOptions () {
54 return ({
55 policies: {
56 addressResolution: policies.defaultAddressTranslator(),
57 loadBalancing: policies.defaultLoadBalancingPolicy(),
58 reconnection: policies.defaultReconnectionPolicy(),
59 retry: policies.defaultRetryPolicy(),
60 speculativeExecution: policies.defaultSpeculativeExecutionPolicy(),
61 timestampGeneration: policies.defaultTimestampGenerator()
62 },
63 queryOptions: {
64 fetchSize: 5000,
65 prepare: false,
66 captureStackTrace: false
67 },
68 protocolOptions: {
69 port: 9042,
70 maxSchemaAgreementWaitSeconds: 10,
71 maxVersion: 0,
72 noCompact: false
73 },
74 pooling: {
75 heartBeatInterval: 30000,
76 warmup: true
77 },
78 socketOptions: {
79 connectTimeout: 5000,
80 defunctReadTimeoutThreshold: 64,
81 keepAlive: true,
82 keepAliveDelay: 0,
83 readTimeout: 12000,
84 tcpNoDelay: true,
85 coalescingThreshold: 65536
86 },
87 authProvider: null,
88 requestTracker: null,
89 metrics: new metrics.DefaultMetrics(),
90 maxPrepared: 500,
91 refreshSchemaDelay: 1000,
92 isMetadataSyncEnabled: true,
93 prepareOnAllHosts: true,
94 rePrepareOnUp: true,
95 encoding: {
96 copyBuffer: true,
97 useUndefinedAsUnset: true
98 },
99 monitorReporting: {
100 enabled: true
101 }
102 });
103}
104
105/**
106 * Extends and validates the user options
107 * @param {Object} [baseOptions] The source object instance that will be overridden
108 * @param {Object} userOptions
109 * @returns {Object}
110 */
111function extend(baseOptions, userOptions) {
112 if (arguments.length === 1) {
113 userOptions = arguments[0];
114 baseOptions = {};
115 }
116 const options = utils.deepExtend(baseOptions, defaultOptions(), userOptions);
117
118 if (!options.cloud) {
119 if (!Array.isArray(options.contactPoints) || options.contactPoints.length === 0) {
120 throw new TypeError('Contacts points are not defined.');
121 }
122
123 for (let i = 0; i < options.contactPoints.length; i++) {
124 const hostName = options.contactPoints[i];
125 if (!hostName) {
126 throw new TypeError(util.format('Contact point %s (%s) is not a valid host name, ' +
127 'the following values are valid contact points: ipAddress, hostName or ipAddress:port', i, hostName));
128 }
129 }
130
131 options.sni = undefined;
132 } else {
133 validateCloudOptions(options);
134 }
135
136 if (!options.logEmitter) {
137 options.logEmitter = function () {};
138 }
139 if (!options.queryOptions) {
140 throw new TypeError('queryOptions not defined in options');
141 }
142
143 if (options.requestTracker !== null && !(options.requestTracker instanceof tracker.RequestTracker)) {
144 throw new TypeError('requestTracker must be an instance of RequestTracker');
145 }
146
147 if (!(options.metrics instanceof metrics.ClientMetrics)) {
148 throw new TypeError('metrics must be an instance of ClientMetrics');
149 }
150
151 validatePoliciesOptions(options.policies);
152
153 validateProtocolOptions(options.protocolOptions);
154
155 validateSocketOptions(options.socketOptions);
156
157 validateAuthenticationOptions(options);
158
159 options.encoding = options.encoding || {};
160
161 validateEncodingOptions(options.encoding);
162
163 if (options.profiles && !Array.isArray(options.profiles)) {
164 throw new TypeError('profiles must be an Array of ExecutionProfile instances');
165 }
166
167 validateApplicationInfo(options);
168
169 validateMonitorReporting(options);
170
171 return options;
172}
173
174/**
175 * Validates the options to connect to a cloud instance.
176 * @private
177 */
178function validateCloudOptions(options) {
179 const bundle = options.cloud.secureConnectBundle;
180
181 // eslint-disable-next-line no-undef
182 if (!(typeof bundle === 'string' || (typeof URL !== 'undefined' && bundle instanceof URL))) {
183 throw new TypeError('secureConnectBundle in cloud options must be of type string');
184 }
185
186 if (options.contactPoints) {
187 throw new TypeError('Contact points can not be defined when cloud settings are provided');
188 }
189
190 if (options.sslOptions) {
191 throw new TypeError('SSL options can not be defined when cloud settings are provided');
192 }
193}
194
195/**
196 * Validates the policies from the client options.
197 * @param {ClientOptions.policies} policiesOptions
198 * @private
199 */
200function validatePoliciesOptions(policiesOptions) {
201 if (!policiesOptions) {
202 throw new TypeError('policies not defined in options');
203 }
204 if (!(policiesOptions.loadBalancing instanceof policies.loadBalancing.LoadBalancingPolicy)) {
205 throw new TypeError('Load balancing policy must be an instance of LoadBalancingPolicy');
206 }
207 if (!(policiesOptions.reconnection instanceof policies.reconnection.ReconnectionPolicy)) {
208 throw new TypeError('Reconnection policy must be an instance of ReconnectionPolicy');
209 }
210 if (!(policiesOptions.retry instanceof policies.retry.RetryPolicy)) {
211 throw new TypeError('Retry policy must be an instance of RetryPolicy');
212 }
213 if (!(policiesOptions.addressResolution instanceof policies.addressResolution.AddressTranslator)) {
214 throw new TypeError('Address resolution policy must be an instance of AddressTranslator');
215 }
216 if (policiesOptions.timestampGeneration !== null &&
217 !(policiesOptions.timestampGeneration instanceof policies.timestampGeneration.TimestampGenerator)) {
218 throw new TypeError('Timestamp generation policy must be an instance of TimestampGenerator');
219 }
220}
221
222/**
223 * Validates the protocol options.
224 * @param {ClientOptions.protocolOptions} protocolOptions
225 * @private
226 */
227function validateProtocolOptions(protocolOptions) {
228 if (!protocolOptions) {
229 throw new TypeError('protocolOptions not defined in options');
230 }
231 const version = protocolOptions.maxVersion;
232 if (version && (typeof version !== 'number' || !types.protocolVersion.isSupported(version))) {
233 throw new TypeError(util.format('protocolOptions.maxVersion provided (%s) is invalid', version));
234 }
235}
236
237/**
238 * Validates the socket options.
239 * @param {ClientOptions.socketOptions} socketOptions
240 * @private
241 */
242function validateSocketOptions(socketOptions) {
243 if (!socketOptions) {
244 throw new TypeError('socketOptions not defined in options');
245 }
246 if (typeof socketOptions.readTimeout !== 'number') {
247 throw new TypeError('socketOptions.readTimeout must be a Number');
248 }
249 if (typeof socketOptions.coalescingThreshold !== 'number' || socketOptions.coalescingThreshold <= 0) {
250 throw new TypeError('socketOptions.coalescingThreshold must be a positive Number');
251 }
252}
253
254/**
255 * Validates authentication provider and credentials.
256 * @param {ClientOptions} options
257 * @private
258 */
259function validateAuthenticationOptions(options) {
260 if (!options.authProvider) {
261 const credentials = options.credentials;
262 if (credentials) {
263 if (typeof credentials.username !== 'string' || typeof credentials.password !== 'string') {
264 throw new TypeError('credentials username and password must be a string');
265 }
266
267 options.authProvider = new auth.PlainTextAuthProvider(credentials.username, credentials.password);
268 } else {
269 options.authProvider = new auth.NoAuthProvider();
270 }
271 } else if (!(options.authProvider instanceof auth.AuthProvider)) {
272 throw new TypeError('options.authProvider must be an instance of AuthProvider');
273 }
274}
275
276/**
277 * Validates the encoding options.
278 * @param {ClientOptions.encoding} encodingOptions
279 * @private
280 */
281function validateEncodingOptions(encodingOptions) {
282 if (encodingOptions.map) {
283 const mapConstructor = encodingOptions.map;
284 if (typeof mapConstructor !== 'function' ||
285 typeof mapConstructor.prototype.forEach !== 'function' ||
286 typeof mapConstructor.prototype.set !== 'function') {
287 throw new TypeError('Map constructor not valid');
288 }
289 }
290
291 if (encodingOptions.set) {
292 const setConstructor = encodingOptions.set;
293 if (typeof setConstructor !== 'function' ||
294 typeof setConstructor.prototype.forEach !== 'function' ||
295 typeof setConstructor.prototype.add !== 'function') {
296 throw new TypeError('Set constructor not valid');
297 }
298 }
299
300 if ((encodingOptions.useBigIntAsLong || encodingOptions.useBigIntAsVarint) && typeof BigInt === 'undefined') {
301 throw new TypeError('BigInt is not supported by the JavaScript engine');
302 }
303}
304
305function validateApplicationInfo(options) {
306 function validateString(key) {
307 const str = options[key];
308
309 if (str !== null && str !== undefined && typeof str !== 'string') {
310 throw new TypeError(`${key} should be a String`);
311 }
312 }
313
314 validateString('applicationName');
315 validateString('applicationVersion');
316
317 if (options.id !== null && options.id !== undefined && !(options.id instanceof types.Uuid)) {
318 throw new TypeError('Client id must be a Uuid');
319 }
320}
321
322function validateMonitorReporting(options) {
323 const o = options.monitorReporting;
324 if (o === null || typeof o !== 'object') {
325 throw new TypeError(`Monitor reporting must be an object, obtained: ${o}`);
326 }
327}
328
329/**
330 * Sets the default options that depend on the protocol version and other metadata.
331 * @param {Client} client
332 */
333function setMetadataDependent(client) {
334 const version = client.controlConnection.protocolVersion;
335 let coreConnectionsPerHost = coreConnectionsPerHostV3;
336 let maxRequestsPerConnection = maxRequestsPerConnectionV3;
337
338 if (!types.protocolVersion.uses2BytesStreamIds(version)) {
339 coreConnectionsPerHost = coreConnectionsPerHostV2;
340 maxRequestsPerConnection = maxRequestsPerConnectionV2;
341 }
342
343 if (client.options.queryOptions.consistency === undefined) {
344 client.options.queryOptions.consistency =
345 client.metadata.isDbaas() ? types.consistencies.localQuorum : types.consistencies.localOne;
346 }
347
348 client.options.pooling = utils.deepExtend(
349 {}, { coreConnectionsPerHost, maxRequestsPerConnection }, client.options.pooling);
350}
351
352exports.extend = extend;
353exports.defaultOptions = defaultOptions;
354exports.coreConnectionsPerHostV2 = coreConnectionsPerHostV2;
355exports.coreConnectionsPerHostV3 = coreConnectionsPerHostV3;
356exports.maxRequestsPerConnectionV2 = maxRequestsPerConnectionV2;
357exports.maxRequestsPerConnectionV3 = maxRequestsPerConnectionV3;
358exports.setMetadataDependent = setMetadataDependent;
359exports.continuousPageUnitBytes = continuousPageUnitBytes;
360exports.continuousPageDefaultSize = continuousPageDefaultSize;
361exports.continuousPageDefaultHighWaterMark = continuousPageDefaultHighWaterMark;