node-cosmos
Version:
A light weight azure cosmosdb client aiming at ease of use for creating REST API. Supports json filter, sort and offset/limit
206 lines (185 loc) • 6.51 kB
text/typescript
import { PostgresImpl, PostgresPoolFactory } from "./impl/postgresql/PostgresImpl";
import { Cosmos } from "./Cosmos";
import { CosmosImpl } from "./impl/cosmosdb/CosmosImpl";
import { MongoImpl } from "./impl/mongodb/MongoImpl";
import assert from "assert";
/**
* Builder class to build a cosmos instance (for azure cosmosdb or mongodb)
*/
export class CosmosBuilder {
/**
* Constant for dbType: cosmosdb
*/
public static readonly COSMOSDB = "cosmosdb";
/**
* Constant for dbType: mongodb
*/
public static readonly MONGODB = "mongodb";
/**
* Constant for dbType: postgres
*/
public static readonly POSTGRES = "postgres";
private dbType: string = CosmosBuilder.COSMOSDB;
private connectionString: string | undefined;
private postgresPoolFactory: PostgresPoolFactory | undefined;
/**
* Whether to auto generate _expireAtEnabled json field is ttl field is present. Used for compatibility for CosmosDB
*
*/
private expireAtEnabled = false;
/**
* Whether to auto generate _etag json field when created/updated. Used for compatibility for CosmosDB
*/
private etagEnabled = false;
/**
* Specify the dbType( "cosmosdb" or "mongodb" )
*
* @param dbType
* @return cosmosBuilder
*/
withDatabaseType(dbType: string | undefined): this {
this.dbType = dbType || CosmosBuilder.COSMOSDB;
return this;
}
/**
* Specify the connectionString for cosmosdb or mongodb
*
* @param connectionString
* @return cosmosBuilder
*/
withConnectionString(connectionString: string | undefined): this {
this.connectionString = connectionString;
return this;
}
/**
* Normalizes the host part of a connection string so Node resolves localhost consistently.
*/
public static normalizeConnectionString(
connectionString: string | undefined,
): string | undefined {
if (!connectionString) {
return connectionString;
}
const normalized = CosmosBuilder.normalizePostgresVariants(connectionString);
try {
const url = new URL(normalized);
CosmosBuilder.applySearchParamsToUserInfo(url);
if (url.hostname === "localhost") {
url.hostname = "127.0.0.1";
url.host = url.port ? `127.0.0.1:${url.port}` : "127.0.0.1";
}
return url.toString();
} catch {
// ignore and fallback to raw connection string
return normalized;
}
}
/**
* Transforms non-standard postgres connection strings (jdbc/postgresql schemes) into
* the canonical postgres:// format so downstream URL parsing behaves consistently.
*/
private static normalizePostgresVariants(connectionString: string): string {
let normalized = connectionString;
if (normalized.startsWith("jdbc:postgresql://")) {
normalized = normalized.substring("jdbc:".length);
}
if (normalized.startsWith("postgresql://")) {
normalized = `postgres://${normalized.substring("postgresql://".length)}`;
}
return normalized;
}
/**
* Moves credentials encoded as search parameters (user/password) into the URL's
* username/password fields, mirroring how postgres connection strings are typically formatted.
*/
private static applySearchParamsToUserInfo(url: URL): void {
const params = url.searchParams;
const user = params.get("user");
const password = params.get("password");
if (user && !url.username) {
url.username = user;
}
if (password && !url.password) {
url.password = password;
}
if (user) {
params.delete("user");
}
if (password) {
params.delete("password");
}
const serialized = params.toString();
url.search = serialized ? `?${serialized}` : "";
}
/**
* Specify whether to enable the expireAt feature for mongodb. No effect on cosmosdb.
*
* @param enabled
* @return cosmosBuilder
*/
withExpireAtEnabled(enabled: boolean): this {
this.expireAtEnabled = enabled;
return this;
}
/**
* Specify whether to enable the etag feature for mongodb. No effect on cosmosdb.
*
* @param enabled
* @return cosmosBuilder
*/
withEtagEnabled(enabled: boolean): this {
// the etag feature in mongo is not implemented yet
this.etagEnabled = enabled;
return this;
}
/**
* Specify a custom Pool factory for postgresql. Useful for tests.
*
* @param factory
* @returns cosmosBuilder
*/
withPostgresPoolFactory(factory: PostgresPoolFactory | undefined): this {
this.postgresPoolFactory = factory;
return this;
}
/**
* Build the instance representing a Cosmos instance.
*
* @return Cosmos instance
*/
build(): Cosmos {
const resolvedConnectionString = this.resolveConnectionString();
assert(this.dbType, `dbType should not be empty: ${this.dbType}`);
assert(
resolvedConnectionString,
`connectionString should not be empty ${resolvedConnectionString}`,
);
if (this.dbType === CosmosBuilder.COSMOSDB) {
return new CosmosImpl(resolvedConnectionString);
}
if (this.dbType === CosmosBuilder.MONGODB) {
return new MongoImpl(
resolvedConnectionString || "",
this.expireAtEnabled,
this.etagEnabled,
);
}
if (this.dbType === CosmosBuilder.POSTGRES) {
return new PostgresImpl(resolvedConnectionString, this.postgresPoolFactory);
}
throw new Error(`Not supported dbType: ${this.dbType}`);
}
/**
* Returns the connection string appropriate for the configured database type.
* Normalization (like postgres variant handling) is only applied for Postgres URLs.
*/
private resolveConnectionString(): string | undefined {
if (!this.connectionString) {
return this.connectionString;
}
if (this.dbType === CosmosBuilder.POSTGRES) {
return CosmosBuilder.normalizeConnectionString(this.connectionString);
}
return this.connectionString;
}
}