You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
262 lines
8.7 KiB
262 lines
8.7 KiB
import type { Document } from './bson'; |
|
import { MongoInvalidArgumentError } from './error'; |
|
import type { TagSet } from './sdam/server_description'; |
|
import type { ClientSession } from './sessions'; |
|
|
|
/** @public */ |
|
export type ReadPreferenceLike = ReadPreference | ReadPreferenceMode; |
|
|
|
/** @public */ |
|
export const ReadPreferenceMode = Object.freeze({ |
|
primary: 'primary', |
|
primaryPreferred: 'primaryPreferred', |
|
secondary: 'secondary', |
|
secondaryPreferred: 'secondaryPreferred', |
|
nearest: 'nearest' |
|
} as const); |
|
|
|
/** @public */ |
|
export type ReadPreferenceMode = (typeof ReadPreferenceMode)[keyof typeof ReadPreferenceMode]; |
|
|
|
/** @public */ |
|
export interface HedgeOptions { |
|
/** Explicitly enable or disable hedged reads. */ |
|
enabled?: boolean; |
|
} |
|
|
|
/** @public */ |
|
export interface ReadPreferenceOptions { |
|
/** Max secondary read staleness in seconds, Minimum value is 90 seconds.*/ |
|
maxStalenessSeconds?: number; |
|
/** Server mode in which the same query is dispatched in parallel to multiple replica set members. */ |
|
hedge?: HedgeOptions; |
|
} |
|
|
|
/** @public */ |
|
export interface ReadPreferenceLikeOptions extends ReadPreferenceOptions { |
|
readPreference?: |
|
| ReadPreferenceLike |
|
| { |
|
mode?: ReadPreferenceMode; |
|
preference?: ReadPreferenceMode; |
|
tags?: TagSet[]; |
|
maxStalenessSeconds?: number; |
|
}; |
|
} |
|
|
|
/** @public */ |
|
export interface ReadPreferenceFromOptions extends ReadPreferenceLikeOptions { |
|
session?: ClientSession; |
|
readPreferenceTags?: TagSet[]; |
|
hedge?: HedgeOptions; |
|
} |
|
|
|
/** |
|
* The **ReadPreference** class is a class that represents a MongoDB ReadPreference and is |
|
* used to construct connections. |
|
* @public |
|
* |
|
* @see https://www.mongodb.com/docs/manual/core/read-preference/ |
|
*/ |
|
export class ReadPreference { |
|
mode: ReadPreferenceMode; |
|
tags?: TagSet[]; |
|
hedge?: HedgeOptions; |
|
maxStalenessSeconds?: number; |
|
minWireVersion?: number; |
|
|
|
public static PRIMARY = ReadPreferenceMode.primary; |
|
public static PRIMARY_PREFERRED = ReadPreferenceMode.primaryPreferred; |
|
public static SECONDARY = ReadPreferenceMode.secondary; |
|
public static SECONDARY_PREFERRED = ReadPreferenceMode.secondaryPreferred; |
|
public static NEAREST = ReadPreferenceMode.nearest; |
|
|
|
public static primary = new ReadPreference(ReadPreferenceMode.primary); |
|
public static primaryPreferred = new ReadPreference(ReadPreferenceMode.primaryPreferred); |
|
public static secondary = new ReadPreference(ReadPreferenceMode.secondary); |
|
public static secondaryPreferred = new ReadPreference(ReadPreferenceMode.secondaryPreferred); |
|
public static nearest = new ReadPreference(ReadPreferenceMode.nearest); |
|
|
|
/** |
|
* @param mode - A string describing the read preference mode (primary|primaryPreferred|secondary|secondaryPreferred|nearest) |
|
* @param tags - A tag set used to target reads to members with the specified tag(s). tagSet is not available if using read preference mode primary. |
|
* @param options - Additional read preference options |
|
*/ |
|
constructor(mode: ReadPreferenceMode, tags?: TagSet[], options?: ReadPreferenceOptions) { |
|
if (!ReadPreference.isValid(mode)) { |
|
throw new MongoInvalidArgumentError(`Invalid read preference mode ${JSON.stringify(mode)}`); |
|
} |
|
if (options == null && typeof tags === 'object' && !Array.isArray(tags)) { |
|
options = tags; |
|
tags = undefined; |
|
} else if (tags && !Array.isArray(tags)) { |
|
throw new MongoInvalidArgumentError('ReadPreference tags must be an array'); |
|
} |
|
|
|
this.mode = mode; |
|
this.tags = tags; |
|
this.hedge = options?.hedge; |
|
this.maxStalenessSeconds = undefined; |
|
this.minWireVersion = undefined; |
|
|
|
options = options ?? {}; |
|
if (options.maxStalenessSeconds != null) { |
|
if (options.maxStalenessSeconds <= 0) { |
|
throw new MongoInvalidArgumentError('maxStalenessSeconds must be a positive integer'); |
|
} |
|
|
|
this.maxStalenessSeconds = options.maxStalenessSeconds; |
|
|
|
// NOTE: The minimum required wire version is 5 for this read preference. If the existing |
|
// topology has a lower value then a MongoError will be thrown during server selection. |
|
this.minWireVersion = 5; |
|
} |
|
|
|
if (this.mode === ReadPreference.PRIMARY) { |
|
if (this.tags && Array.isArray(this.tags) && this.tags.length > 0) { |
|
throw new MongoInvalidArgumentError('Primary read preference cannot be combined with tags'); |
|
} |
|
|
|
if (this.maxStalenessSeconds) { |
|
throw new MongoInvalidArgumentError( |
|
'Primary read preference cannot be combined with maxStalenessSeconds' |
|
); |
|
} |
|
|
|
if (this.hedge) { |
|
throw new MongoInvalidArgumentError( |
|
'Primary read preference cannot be combined with hedge' |
|
); |
|
} |
|
} |
|
} |
|
|
|
// Support the deprecated `preference` property introduced in the porcelain layer |
|
get preference(): ReadPreferenceMode { |
|
return this.mode; |
|
} |
|
|
|
static fromString(mode: string): ReadPreference { |
|
return new ReadPreference(mode as ReadPreferenceMode); |
|
} |
|
|
|
/** |
|
* Construct a ReadPreference given an options object. |
|
* |
|
* @param options - The options object from which to extract the read preference. |
|
*/ |
|
static fromOptions(options?: ReadPreferenceFromOptions): ReadPreference | undefined { |
|
if (!options) return; |
|
const readPreference = |
|
options.readPreference ?? options.session?.transaction.options.readPreference; |
|
const readPreferenceTags = options.readPreferenceTags; |
|
|
|
if (readPreference == null) { |
|
return; |
|
} |
|
|
|
if (typeof readPreference === 'string') { |
|
return new ReadPreference(readPreference, readPreferenceTags, { |
|
maxStalenessSeconds: options.maxStalenessSeconds, |
|
hedge: options.hedge |
|
}); |
|
} else if (!(readPreference instanceof ReadPreference) && typeof readPreference === 'object') { |
|
const mode = readPreference.mode || readPreference.preference; |
|
if (mode && typeof mode === 'string') { |
|
return new ReadPreference(mode, readPreference.tags ?? readPreferenceTags, { |
|
maxStalenessSeconds: readPreference.maxStalenessSeconds, |
|
hedge: options.hedge |
|
}); |
|
} |
|
} |
|
|
|
if (readPreferenceTags) { |
|
readPreference.tags = readPreferenceTags; |
|
} |
|
|
|
return readPreference as ReadPreference; |
|
} |
|
|
|
/** |
|
* Replaces options.readPreference with a ReadPreference instance |
|
*/ |
|
static translate(options: ReadPreferenceLikeOptions): ReadPreferenceLikeOptions { |
|
if (options.readPreference == null) return options; |
|
const r = options.readPreference; |
|
|
|
if (typeof r === 'string') { |
|
options.readPreference = new ReadPreference(r); |
|
} else if (r && !(r instanceof ReadPreference) && typeof r === 'object') { |
|
const mode = r.mode || r.preference; |
|
if (mode && typeof mode === 'string') { |
|
options.readPreference = new ReadPreference(mode, r.tags, { |
|
maxStalenessSeconds: r.maxStalenessSeconds |
|
}); |
|
} |
|
} else if (!(r instanceof ReadPreference)) { |
|
throw new MongoInvalidArgumentError(`Invalid read preference: ${r}`); |
|
} |
|
|
|
return options; |
|
} |
|
|
|
/** |
|
* Validate if a mode is legal |
|
* |
|
* @param mode - The string representing the read preference mode. |
|
*/ |
|
static isValid(mode: string): boolean { |
|
const VALID_MODES = new Set([ |
|
ReadPreference.PRIMARY, |
|
ReadPreference.PRIMARY_PREFERRED, |
|
ReadPreference.SECONDARY, |
|
ReadPreference.SECONDARY_PREFERRED, |
|
ReadPreference.NEAREST, |
|
null |
|
]); |
|
|
|
return VALID_MODES.has(mode as ReadPreferenceMode); |
|
} |
|
|
|
/** |
|
* Validate if a mode is legal |
|
* |
|
* @param mode - The string representing the read preference mode. |
|
*/ |
|
isValid(mode?: string): boolean { |
|
return ReadPreference.isValid(typeof mode === 'string' ? mode : this.mode); |
|
} |
|
|
|
/** |
|
* Indicates that this readPreference needs the "SecondaryOk" bit when sent over the wire |
|
* @see https://www.mongodb.com/docs/manual/reference/mongodb-wire-protocol/#op-query |
|
*/ |
|
secondaryOk(): boolean { |
|
const NEEDS_SECONDARYOK = new Set<string>([ |
|
ReadPreference.PRIMARY_PREFERRED, |
|
ReadPreference.SECONDARY, |
|
ReadPreference.SECONDARY_PREFERRED, |
|
ReadPreference.NEAREST |
|
]); |
|
|
|
return NEEDS_SECONDARYOK.has(this.mode); |
|
} |
|
|
|
/** |
|
* Check if the two ReadPreferences are equivalent |
|
* |
|
* @param readPreference - The read preference with which to check equality |
|
*/ |
|
equals(readPreference: ReadPreference): boolean { |
|
return readPreference.mode === this.mode; |
|
} |
|
|
|
/** Return JSON representation */ |
|
toJSON(): Document { |
|
const readPreference = { mode: this.mode } as Document; |
|
if (Array.isArray(this.tags)) readPreference.tags = this.tags; |
|
if (this.maxStalenessSeconds) readPreference.maxStalenessSeconds = this.maxStalenessSeconds; |
|
if (this.hedge) readPreference.hedge = this.hedge; |
|
return readPreference; |
|
} |
|
}
|
|
|