@speckle/shared
Version:
Shared code between various Speckle JS packages
201 lines • 9.57 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.obfuscateConnectionString = exports.getConnectionSettings = exports.configureKnexClient = exports.createKnexConfig = exports.loadMultiRegionsConfig = exports.regionConfigSchema = void 0;
const zod_1 = require("zod");
const promises_1 = __importDefault(require("node:fs/promises"));
const Knex = __importStar(require("knex")); // knex has broken types, hence the * as import
const _lodash_1 = require("#lodash");
// cause of knex's ESM/CJS interop issues
const knex = (0, _lodash_1.get)(Knex, 'knex') || (0, _lodash_1.get)(Knex, 'default');
exports.regionConfigSchema = zod_1.z.object({
postgres: zod_1.z.object({
connectionUri: zod_1.z
.string()
.describe('Full Postgres connection URI (e.g. "postgres://user:password@host:port/dbname")'),
databaseName: zod_1.z
.string()
.describe('Name of the database to connect to. Used where the connection string is to a connection pool, and does not include the database name.')
.optional(),
privateConnectionUri: zod_1.z
.string()
.describe('Full Postgres connection URI in VPN or Docker networks (e.g. "postgres://user:password@host:port/dbname")')
.optional(),
publicTlsCertificate: zod_1.z
.string()
.describe('Public TLS ("CA") certificate for the Postgres server')
.optional(),
skipInitialization: zod_1.z
.boolean()
.optional()
.describe('Skip database initialization (migration run & replication setup). Only used in tests.')
.refine((val) => val !== true || process.env.NODE_ENV === 'test', {
message: 'skipInitialization can only be set when NODE_ENV is "test"'
})
}),
blobStorage: zod_1.z.object({
endpoint: zod_1.z
.string()
.url()
.describe('URL of the S3-compatible storage endpoint, accessible from the server'),
publicEndpoint: zod_1.z
.string()
.url()
.optional()
.describe('Public URL of the S3-compatible storage endpoint, accessible from clients via the public internet'),
accessKey: zod_1.z.string().describe('Access key for the S3-compatible storage endpoint'),
secretKey: zod_1.z.string().describe('Secret key for the S3-compatible storage endpoint'),
bucket: zod_1.z.string().describe('Name of the S3-compatible storage bucket'),
createBucketIfNotExists: zod_1.z
.boolean()
.describe('Whether to create the bucket if it does not exist'),
s3Region: zod_1.z.string().describe('Region of the S3-compatible storage endpoint')
})
});
const multiRegionConfigSchema = zod_1.z.object({
main: exports.regionConfigSchema,
regions: zod_1.z.record(zod_1.z.string(), exports.regionConfigSchema),
defaultProjectRegionKey: zod_1.z.string().min(3).nullish()
});
const loadMultiRegionsConfig = async ({ path }) => {
let file;
try {
file = await promises_1.default.readFile(path, 'utf-8');
}
catch (e) {
if (e instanceof Error && 'code' in e && e.code === 'ENOENT') {
throw new Error(`Multi-region config file not found at path: ${path}`);
}
throw e;
}
let parsedJson;
try {
parsedJson = JSON.parse(file); // This will throw if the file is not valid JSON
}
catch {
throw new Error(`Multi-region config file at path '${path}' is not valid JSON`);
}
const schema = multiRegionConfigSchema;
const multiRegionConfigFileResult = schema.safeParse(parsedJson); // This will throw if the config is invalid
if (!multiRegionConfigFileResult.success)
throw new Error(`Multi-region config file at path '${path}' does not fit the schema: ${multiRegionConfigFileResult.error}`);
return multiRegionConfigFileResult.data;
};
exports.loadMultiRegionsConfig = loadMultiRegionsConfig;
const createKnexConfig = ({ connectionString, migrationDirs, isTestEnv, isDevOrTestEnv, logger, maxConnections, caCertificate, connectionAcquireTimeoutMillis, connectionCreateTimeoutMillis, asyncStackTraces, migrationSource }) => {
const shouldEnableAsyncStackTraces = (0, _lodash_1.isUndefined)(asyncStackTraces)
? isDevOrTestEnv
: asyncStackTraces;
return {
client: 'pg',
migrations: {
extension: 'ts',
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
migrationSource,
// these warnings are annoying locally when switching branches:
disableMigrationsListValidation: !!isDevOrTestEnv,
...(migrationSource
? {}
: {
// Can only be set if migrationSource is not set
loadExtensions: isTestEnv ? ['.js', '.ts'] : ['.js'],
directory: migrationDirs
})
},
log: {
warn(message) {
logger.warn(message);
},
error(message) {
logger.error(message);
},
deprecate(message) {
logger.info(message);
},
debug(message) {
logger.debug(message);
}
},
connection: {
connectionString,
ssl: caCertificate ? { ca: caCertificate, rejectUnauthorized: true } : undefined,
// eslint-disable-next-line camelcase
application_name: 'speckle_server'
},
// we wish to avoid leaking sql queries in the logs: https://knexjs.org/guide/#compilesqlonerror
compileSqlOnError: isDevOrTestEnv,
asyncStackTraces: shouldEnableAsyncStackTraces,
pool: {
min: 0,
max: maxConnections,
acquireTimeoutMillis: connectionAcquireTimeoutMillis, // If the maximum number of connections is reached, it wait for 16 seconds trying to acquire an existing connection before throwing a timeout error.
createTimeoutMillis: connectionCreateTimeoutMillis // If no existing connection is available and the maximum number of connections is not yet reached, the pool will try to create a new connection for 5 seconds before throwing a timeout error.
// createRetryIntervalMillis: 200, // Irrelevant & ignored because propagateCreateError is true.
// propagateCreateError: true // The propagateCreateError is set to true by default in Knex and throws a TimeoutError if the first create connection to the database fails. Knex recommends that this value is NOT set to false, despite what 'helpful' people on Stackoverflow tell you: https://github.com/knex/knex/issues/3455#issuecomment-535554401
}
};
};
exports.createKnexConfig = createKnexConfig;
const configureKnexClient = (config, configArgs) => {
const knexConfig = (0, exports.createKnexConfig)({
connectionString: config.postgres.connectionUri,
caCertificate: config.postgres.publicTlsCertificate,
...configArgs
});
const privateConfig = config.postgres.privateConnectionUri
? knex((0, exports.createKnexConfig)({
connectionString: config.postgres.privateConnectionUri,
caCertificate: config.postgres.publicTlsCertificate,
...configArgs
}))
: undefined;
return { public: knex(knexConfig), private: privateConfig };
};
exports.configureKnexClient = configureKnexClient;
const getConnectionSettings = (knex) => {
return knex.client.connectionSettings;
};
exports.getConnectionSettings = getConnectionSettings;
const obfuscateConnectionString = (connectionString) => {
const url = new URL(connectionString);
const obfuscatedUrl = new URL(url);
obfuscatedUrl.password = '****';
return obfuscatedUrl.toString();
};
exports.obfuscateConnectionString = obfuscateConnectionString;
//# sourceMappingURL=db.js.map