ottoman
Version:
Ottoman Couchbase ODM
562 lines • 24 kB
JavaScript
"use strict";
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 (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __asyncValues = (this && this.__asyncValues) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator], i;
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.searchQuery = exports.model = exports.getCollection = exports.getModel = exports.start = exports.close = exports.connect = exports.getOttomanInstances = exports.getDefaultInstance = exports.Ottoman = exports.__ottomanInstances = exports.__ottoman = void 0;
const couchbase = __importStar(require("couchbase"));
const extract_connection_string_1 = require("../utils/extract-connection-string");
const create_model_1 = require("../model/create-model");
const constants_1 = require("../utils/constants");
const __1 = require("..");
const is_debug_mode_1 = require("../utils/is-debug-mode");
const ensure_n1ql_indexes_1 = require("../model/index/n1ql/ensure-n1ql-indexes");
const build_map_view_index_fn_1 = require("../model/index/view/build-map-view-index-fn");
const ensure_view_indexes_1 = require("../model/index/view/ensure-view-indexes");
const ottoman_errors_1 = require("../exceptions/ottoman-errors");
const parse_errors_1 = require("../utils/parse-errors");
const couchbase_1 = require("couchbase");
const generate_uuid_1 = require("../utils/generate-uuid");
exports.__ottomanInstances = [];
class Ottoman {
/**
* Retrieve all register callbacks to "IndexOnline" event
*/
get indexReadyHooks() {
return this.events['IndexOnline'];
}
/**
* Register Ottoman events
* @param event the name of the event you want to listen to.
* @param fn callback function to be executed when the event trigger up.
*/
on(event, fn) {
switch (event) {
case 'IndexOnline':
this.events.IndexOnline.push(fn);
if (this.indexOnline) {
setTimeout(() => fn(this.indexOnline instanceof Error ? this.indexOnline : null, this));
}
break;
}
}
/**
* CollectionManager allows the management of collections within a Bucket.
*
* Check the
* [Collection Manager Couchbase SDK API](https://docs.couchbase.com/sdk-api/couchbase-node-client/classes/CollectionManager.html)
* documentation for more details.
*/
get collectionManager() {
return this.bucket.collections();
}
/**
* Gets a bucket manager for this cluster.
*
* Check the
* [Bucket Manager Couchbase SDK API](https://docs.couchbase.com/sdk-api/couchbase-node-client/classes/BucketManager.html)
* documentation for more details.
*/
get bucketManager() {
return this.cluster.buckets();
}
/**
* QueryIndexManager provides an interface for managing the query indexes on the cluster.
*
* Check the
* [Query Index Manager Couchbase SDK API](https://docs.couchbase.com/sdk-api/couchbase-node-client/classes/QueryIndexManager.html)
* documentation for more details.
*/
get queryIndexManager() {
return this.cluster.queryIndexes();
}
/**
* ViewIndexManager is an interface which enables the management of view indexes on the cluster.
*
* @deprecated
* Check the
* [View Index Manager Couchbase SDK API](https://docs.couchbase.com/sdk-api/couchbase-node-client/classes/ViewIndexManager.html)
* documentation for more details.
*/
get viewIndexManager() {
return this.bucket.viewIndexes();
}
/**
* Cluster represents an entire Couchbase Server cluster.
*/
get cluster() {
return this._cluster;
}
constructor(config = {}) {
this.n1qlIndexes = {};
this.viewIndexes = {};
this.refdocIndexes = {};
this.events = {
IndexOnline: [],
};
this.indexOnline = false;
/**
* @ignore
*/
this.getRefdocIndexByKey = (key) => this.refdocIndexes[key];
/**
* @ignore
*/
this.registerIndex = (indexName, fields, modelName) => {
this.n1qlIndexes[indexName] = { fields, modelName };
};
/**
* @ignore
*/
this.registerViewIndex = (designDocName, indexName, fields, metadata) => {
const map = (0, build_map_view_index_fn_1.buildMapViewIndexFn)(metadata, fields);
if (!this.viewIndexes[designDocName]) {
this.viewIndexes[designDocName] = { views: {} };
}
this.viewIndexes[designDocName].views[indexName] = { map };
};
/**
* @ignore
*/
this.registerRefdocIndex = (fields, prefix) => {
if (!this.refdocIndexes[prefix]) {
this.refdocIndexes[prefix] = [];
}
this.refdocIndexes[prefix].push({ fields });
};
/**
* Dictionary for all models registered on this connection.
*/
this.models = {};
/**
* Contains the name of the current bucket.
*/
this.bucketName = '';
/**
* Connect to Couchbase server.
* @example
* ```javascript
* import { connect } from "ottoman";
* const connection = connect("couchbase://localhost/travel-sample@admin:password");
* ```
*/
this.connect = (connectOptions) => __awaiter(this, void 0, void 0, function* () {
const options = typeof connectOptions === 'object' ? connectOptions : (0, extract_connection_string_1.extractConnectionString)(connectOptions);
const { connectionString, bucketName } = options, _options = __rest(options, ["connectionString", "bucketName"]);
this._cluster = yield couchbase_1.Cluster.connect(connectionString, _options);
this.bucketName = bucketName;
this.couchbase = couchbase;
this.bucket = this._cluster.bucket(bucketName);
return this;
});
if (config.keyGeneratorDelimiter) {
(0, constants_1.validateDelimiter)(config.keyGeneratorDelimiter);
}
this.id = (0, generate_uuid_1.generateUUID)();
this.config = config;
if (!exports.__ottoman) {
// eslint-disable-next-line @typescript-eslint/no-this-alias
exports.__ottoman = this;
}
exports.__ottomanInstances.push(this);
}
/**
* Creates a Model on this connection.
* @example
* ```javascript
* const User = connection.model('User', { name: String }, { collectionName: 'users' });
* ```
*/
model(name, schema, options = {}) {
if (this.models[name]) {
throw new ottoman_errors_1.OttomanError(`A model with name '${name}' has already been registered.`);
}
if (options.keyGeneratorDelimiter) {
(0, constants_1.validateDelimiter)(options.keyGeneratorDelimiter);
}
const modelOptions = options;
modelOptions.collectionName = options.collectionName || this.config.collectionName || name;
modelOptions.scopeName = options.scopeName || this.config.scopeName || constants_1.DEFAULT_SCOPE;
modelOptions.keyGenerator = options.keyGenerator || this.config.keyGenerator || constants_1.KEY_GENERATOR;
if (options.keyGeneratorDelimiter || options.keyGeneratorDelimiter === '') {
modelOptions.keyGeneratorDelimiter = options.keyGeneratorDelimiter;
}
else if (this.config.keyGeneratorDelimiter || this.config.keyGeneratorDelimiter === '') {
modelOptions.keyGeneratorDelimiter = this.config.keyGeneratorDelimiter;
}
else {
modelOptions.keyGeneratorDelimiter = constants_1.KEY_GENERATOR_DELIMITER;
}
modelOptions.modelKey = options.modelKey || this.config.modelKey || constants_1.MODEL_KEY;
modelOptions.idKey = options.idKey || this.config.idKey || constants_1.DEFAULT_ID_KEY;
modelOptions.maxExpiry = options.maxExpiry || this.config.maxExpiry || constants_1.DEFAULT_MAX_EXPIRY;
const ModelFactory = (0, create_model_1.createModel)({ name, schemaDraft: schema, options: modelOptions, ottoman: this });
this.models[name] = ModelFactory;
return ModelFactory;
}
/**
* dropCollection drops a collection from a scope in a bucket.
* @param collectionName
* @param scopeName
* @param options
*/
dropCollection(collectionName, scopeName, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.collectionManager.dropCollection(collectionName, scopeName, options);
}
catch (e) {
(0, parse_errors_1.parseError)(e, { collectionName, scopeName });
}
});
}
/**
* dropScope drops a scope from a bucket.
* @param scopeName
* @param options
*/
dropScope(scopeName, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.collectionManager.dropScope(scopeName, options);
}
catch (e) {
(0, parse_errors_1.parseError)(e, { scopeName });
}
});
}
/**
* dropBucket drops a bucket from the cluster.
* @param bucketName
* @param options
*/
dropBucket(bucketName, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.bucketManager.dropBucket(bucketName, options);
}
catch (e) {
(0, parse_errors_1.parseError)(e, { bucketName });
}
});
}
/**
* Returns a Model constructor from the given model name.
*
* @example
* ```javascript
* const User = connection.getModel('User');
* ```
*/
getModel(name) {
return this.models[name];
}
/**
* Return a collection from the given collectionName in this bucket
* or default collection if collectionName is missing.
*/
getCollection(collectionName = constants_1.DEFAULT_COLLECTION, scopeName = constants_1.DEFAULT_SCOPE) {
return this.bucket.scope(scopeName === '_default' ? '' : scopeName).collection(collectionName === '_default' ? '' : collectionName);
}
getIndexes() {
return this.cluster.searchIndexes().getAllIndexes();
}
searchQuery(indexName, query, options) {
return this.cluster.searchQuery(indexName, query, options);
}
/**
* Closes the current connection.
*
* @example
* ```javascript
* connection.close().then(() => console.log('connection closed'));
* ```
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
if (this.cluster) {
yield this.cluster.close();
exports.__ottomanInstances = exports.__ottomanInstances.filter((instance) => instance.id !== this.id);
if ((exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.id) === this.id) {
exports.__ottoman = undefined;
}
}
});
}
/**
* Executes N1QL Queries.
*
* Ottoman provides a powerful [Query Builder](docs/basic/query-builder) system to create valid N1QL queries using method chaining.
* See the example below:
*
* @example
* ```js
* const expr_where = {
* $or: [
* { address: { $like: '%57-59%' } },
* { free_breakfast: true }
* ]
* };
* const query = new Query({}, 'travel-sample');
* const n1qlQuery = query.select([{ $field: 'address' }])
* .where(expr_where)
* .build()
*
* connection.query(n1qlQuery).then(result => console.log( result ))
* ```
* The above example will run this query:
* ```sql
* SELECT address
* FROM `travel-sample`
* WHERE (address LIKE '%57-59%' OR free_breakfast = true)
* ```
*/
query(query, options = {}) {
return __awaiter(this, void 0, void 0, function* () {
if (options.transactionContext) {
return options.transactionContext.query(query, options);
}
return this.cluster.query(query, options);
});
}
/**
* `ensureCollections` will attempt to create scopes and collections to map your models to in Couchbase Server.
*/
ensureCollections() {
var _a, e_1, _b, _c, _d, e_2, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
const scopePromises = new Map();
const collectionPromises = new Map();
for (const key in this.models) {
if (this.models[key]) {
const metadata = (0, __1.getModelMetadata)(this.models[key]);
const { scopeName, collectionName, maxExpiry } = metadata;
if (scopeName !== '_default') {
scopePromises.set(scopeName, this.collectionManager
.createScope(scopeName)
.then(() => {
if ((0, is_debug_mode_1.isDebugMode)()) {
console.info('Scope created: ', scopeName);
}
})
.catch((e) => {
if (!(e instanceof couchbase.ScopeExistsError)) {
throw e;
}
}));
}
if (collectionName !== '_default') {
collectionPromises.set(`${scopeName}${collectionName}`, tryCreateCollection({ collectionName, scopeName, maxExpiry, collectionManager: this.collectionManager }, this.config.retryCount));
}
}
}
try {
// eslint-disable-next-line no-unused-vars
for (var _g = true, _h = __asyncValues(scopePromises.values()), _j; _j = yield _h.next(), _a = _j.done, !_a;) {
_c = _j.value;
_g = false;
try {
const scope = _c;
}
finally {
_g = true;
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (!_g && !_a && (_b = _h.return)) yield _b.call(_h);
}
finally { if (e_1) throw e_1.error; }
}
try {
// eslint-disable-next-line no-unused-vars
for (var _k = true, _l = __asyncValues(collectionPromises.values()), _m; _m = yield _l.next(), _d = _m.done, !_d;) {
_f = _m.value;
_k = false;
try {
const collection = _f;
}
finally {
_k = true;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (!_k && !_d && (_e = _l.return)) yield _e.call(_l);
}
finally { if (e_2) throw e_2.error; }
}
});
}
/**
* `ensureIndexes` will attempt to create indexes defined in your schema if they do not exist.
* * @param options
* - `ignoreWatchIndexes`: by default `ensureIndexes` function will wait for indexes, but you can disabled it setting ignoreWatchIndexes to true.
*/
ensureIndexes(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
yield (0, ensure_n1ql_indexes_1.ensureN1qlIndexes)(this, this.n1qlIndexes);
yield (0, ensure_view_indexes_1.ensureViewIndexes)(this, this.viewIndexes);
if (!options.ignoreWatchIndexes && this.indexOnlinePromise) {
return this.indexOnlinePromise;
}
});
}
/**
* `start` method is just a shortcut to run `ensureCollections` and `ensureIndexes`.
* @param options
* - `ignoreWatchIndexes`: by default `start` function will wait for indexes, but you can disabled it setting ignoreWatchIndexes to true.
*
* Notice: It's not required to execute the `start` method in order for Ottoman work.
*/
start(options = {}) {
return __awaiter(this, void 0, void 0, function* () {
yield this.ensureCollections();
yield this.ensureIndexes(options);
});
}
/**
* The `$transactions` function passes a `TransactionAttemptContext` object to the transaction function.
* @param transactionFn The function to be executed as a transaction.
*
* @param config
* - `durabilityLevel`: Specifies the level of synchronous durability level.
* - `timeout`: Specifies the timeout for the transaction.
*
* @example
* ```javascript
* await otttoman.$transactions(async (ctx: TransactionAttemptContext) => {
* const user = await User.create({ name: "John Doe" }, { transactionContext: ctx });
* });
* ```
*
* Notice: You **MUST pass the `transactionContext` option in the model methods** to execute the operation(s) as a transaction.
*/
$transactions(transactionFn, config) {
return __awaiter(this, void 0, void 0, function* () {
if (Object.keys(this.refdocIndexes).length > 0) {
console.warn('\x1b[4m\x1b[43mWARNING:\x1b[0m One or more RefDoc indexes have been detected in this project. RefDoc indexes are not currently supported in transactions. \nFor more information visit https://ottomanjs.com/docs/advanced/transactions#transactions-with-refdoc-indexes.');
}
yield this.cluster.transactions().run(transactionFn, config);
});
}
}
exports.Ottoman = Ottoman;
const delay = (timems) => new Promise((resolve) => {
setTimeout(() => {
resolve(true);
}, timems);
});
const createCollection = ({ collectionManager, collectionName, scopeName, maxExpiry }) => {
return collectionManager
.createCollection({
name: collectionName,
scopeName,
maxExpiry: maxExpiry ? +maxExpiry : constants_1.DEFAULT_MAX_EXPIRY,
})
.then(() => {
if ((0, is_debug_mode_1.isDebugMode)()) {
console.info(`collection created: ${scopeName}/${collectionName}`);
}
});
};
const tryCreateCollection = ({ collectionManager, collectionName, scopeName, maxExpiry }, retries = 3, delayMS = 5000) => __awaiter(void 0, void 0, void 0, function* () {
let collectionDoesNotExists = true;
let err = null;
let tryDelay = 0;
let tryCount = 0;
while (collectionDoesNotExists && tryCount < retries + 1) {
yield delay(tryDelay);
try {
yield createCollection({ collectionManager, collectionName, scopeName, maxExpiry });
}
catch (e) {
if (e instanceof couchbase.CollectionExistsError)
collectionDoesNotExists = false;
err = e;
}
tryDelay += Math.max(Math.min(tryDelay * 10, delayMS), 100);
tryCount++;
}
if (collectionDoesNotExists && err)
throw err;
});
const getDefaultInstance = () => exports.__ottoman;
exports.getDefaultInstance = getDefaultInstance;
const getOttomanInstances = () => exports.__ottomanInstances;
exports.getOttomanInstances = getOttomanInstances;
const connect = (connectOptions) => __awaiter(void 0, void 0, void 0, function* () {
if (!exports.__ottoman) {
new Ottoman();
}
exports.__ottoman && (yield exports.__ottoman.connect(connectOptions));
});
exports.connect = connect;
const close = () => __awaiter(void 0, void 0, void 0, function* () {
if (exports.__ottoman) {
yield exports.__ottoman.close();
}
});
exports.close = close;
const start = () => exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.start();
exports.start = start;
const getModel = (name) => exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.getModel(name);
exports.getModel = getModel;
const getCollection = (collectionName = constants_1.DEFAULT_COLLECTION, scopeName = constants_1.DEFAULT_SCOPE) => exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.getCollection(collectionName, scopeName);
exports.getCollection = getCollection;
const model = (name, schema, options) => exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.model(name, schema, options);
exports.model = model;
const searchQuery = (indexName, query, options) => exports.__ottoman === null || exports.__ottoman === void 0 ? void 0 : exports.__ottoman.searchQuery(indexName, query, options);
exports.searchQuery = searchQuery;
//# sourceMappingURL=ottoman.js.map