git-documentdb
Version:
Offline-first database that syncs with Git
1,221 lines (1,220 loc) • 44.2 kB
JavaScript
"use strict";
/**
* GitDocumentDB
* Copyright (c) Hidekazu Kubota
*
* This source code is licensed under the Mozilla Public License Version 2.0
* found in the LICENSE file in the root directory of this source tree.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitDocumentDB = exports.generateDatabaseId = void 0;
const path_1 = __importDefault(require("path"));
const isomorphic_git_1 = __importDefault(require("isomorphic-git"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const rimraf_1 = __importDefault(require("rimraf"));
const tslog_1 = require("tslog");
const ulid_1 = require("ulid");
const remote_engine_1 = require("./remote/remote_engine");
const error_1 = require("./error");
const collection_1 = require("./collection");
const validator_1 = require("./validator");
const put_1 = require("./crud/put");
const sync_1 = require("./remote/sync");
const task_queue_1 = require("./task_queue");
const const_1 = require("./const");
const utils_1 = require("./utils");
const blob_1 = require("./crud/blob");
const remote_isomorphic_git = __importStar(require("./plugin/remote-isomorphic-git"));
const serialize_format_1 = require("./serialize_format");
/**
* Get database ID
*
* @internal
*/
function generateDatabaseId() {
return (0, ulid_1.ulid)(Date.now());
}
exports.generateDatabaseId = generateDatabaseId;
const INITIAL_DATABASE_OPEN_RESULT = {
dbId: '',
creator: '',
version: '',
isNew: false,
isCreatedByGitDDB: true,
isValidVersion: true,
serialize: 'json',
};
/**
* Main class of GitDocumentDB
*
* @remarks
* Call open() before using DB.
*
* @public
*/
class GitDocumentDB {
/**
* Constructor
*
* @remarks
* - The Git working directory will be `${options.localDir}/${options.dbName}`.
*
* @throws {@link Err.InvalidWorkingDirectoryPathLengthError}
* @throws {@link Err.UndefinedDatabaseNameError}
*
* @public
*/
// eslint-disable-next-line complexity
constructor(options) {
var _a, _b, _c, _d, _e, _f, _g;
/***********************************************
* Private properties
***********************************************/
this._synchronizers = {};
this._dbOpenResult = {
...INITIAL_DATABASE_OPEN_RESULT,
};
/**
* Default Git branch
*
* @readonly
* @public
*/
this.defaultBranch = 'main';
// Use definite assignment assertion
this._logger = {
silly: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.silly(colorTag() `${mes}`);
}
else {
this._tsLogger.silly(mes);
}
},
debug: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.debug(colorTag() `${mes}`);
}
else {
this._tsLogger.debug(mes);
}
},
trace: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.trace(colorTag() `${mes}`);
}
else {
this._tsLogger.trace(mes);
}
},
info: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.info(colorTag() `${mes}`);
}
else {
this._tsLogger.info(mes);
}
},
warn: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.warn(colorTag() `${mes}`);
}
else {
this._tsLogger.warn(mes);
}
},
error: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.error(colorTag() `${mes}`);
}
else {
this._tsLogger.error(mes);
}
},
fatal: (mes, colorTag) => {
if (this._logColorEnabled && colorTag !== undefined) {
this._tsLogger.fatal(colorTag() `${mes}`);
}
else {
this._tsLogger.fatal(mes);
}
},
};
this._isClosing = false;
/**
* Author name and email for commit
*
* @public
*/
this.author = {
name: 'GitDocumentDB',
email: 'gitddb@localhost',
};
/**
* Committer name and email for commit
*
* @public
*/
this.committer = {
name: 'GitDocumentDB',
email: 'gitddb@localhost',
};
if (options.dbName === undefined || options.dbName === '') {
throw new error_1.Err.UndefinedDatabaseNameError();
}
this._dbName = options.dbName;
this._localDir = (_a = options.localDir) !== null && _a !== void 0 ? _a : const_1.DEFAULT_LOCAL_DIR;
this._schema = (_b = options.schema) !== null && _b !== void 0 ? _b : {
json: {
keyInArrayedObject: undefined,
plainTextProperties: undefined,
},
};
this._logToTransport = options.logToTransport;
const format = (_c = options.serialize) !== null && _c !== void 0 ? _c : 'json';
if (format === 'front-matter') {
this._serializeFormat = new serialize_format_1.SerializeFormatFrontMatter();
}
else {
this._serializeFormat = new serialize_format_1.SerializeFormatJSON();
}
// Get full-path
this._workingDir = path_1.default.resolve(this._localDir, this._dbName);
this._validator = new validator_1.Validator(this._workingDir);
this.validator.validateDbName(this._dbName);
this.validator.validateLocalDir(this._localDir);
if (this._workingDir.length === 0 ||
validator_1.Validator.byteLengthOf(this._workingDir) > validator_1.Validator.maxWorkingDirectoryLength()) {
throw new error_1.Err.InvalidWorkingDirectoryPathLengthError(this._workingDir, 0, validator_1.Validator.maxWorkingDirectoryLength());
}
this._taskQueue = new task_queue_1.TaskQueue(this.logger);
// Set logLevel after initializing taskQueue.
this.logLevel = (_d = options.logLevel) !== null && _d !== void 0 ? _d : const_1.DEFAULT_LOG_LEVEL;
this._logColorEnabled = (_e = options.logColorEnabled) !== null && _e !== void 0 ? _e : true;
const collectionOptions = {
namePrefix: (_f = options === null || options === void 0 ? void 0 : options.namePrefix) !== null && _f !== void 0 ? _f : '',
debounceTime: (_g = options === null || options === void 0 ? void 0 : options.debounceTime) !== null && _g !== void 0 ? _g : -1,
idGenerator: options === null || options === void 0 ? void 0 : options.idGenerator,
};
this._rootCollection = new collection_1.Collection(this, '', undefined, collectionOptions);
// @ts-ignore
remote_engine_1.RemoteEngine[remote_isomorphic_git.name] = {};
Object.keys(remote_isomorphic_git).forEach(function (id) {
// Set to Remote object
// @ts-ignore
// eslint-disable-next-line import/namespace
remote_engine_1.RemoteEngine[remote_isomorphic_git.name][id] = remote_isomorphic_git[id];
});
}
static plugin(obj) {
const type = obj.type;
if (type === 'remote') {
if (obj.name !== undefined) {
// @ts-ignore
remote_engine_1.RemoteEngine[obj.name] = {};
Object.keys(obj).forEach(function (id) {
// Set to Remote object
// @ts-ignore
remote_engine_1.RemoteEngine[obj.name][id] = obj[id];
});
}
}
else {
Object.keys(obj).forEach(function (id) {
// Set to Instance property
// @ts-ignore
GitDocumentDB.prototype[id] = obj[id];
});
}
}
get serializeFormat() {
return this._serializeFormat;
}
/**
* A local directory path that stores repositories of GitDocumentDB
*
* @readonly
* @public
*/
get localDir() {
return this._localDir;
}
/**
* A name of a Git repository
*
* @readonly
* @public
*/
get dbName() {
return this._dbName;
}
/**
* Get a full path of the current Git working directory
*
* @returns A full path whose trailing slash is omitted
*
* @readonly
* @public
*/
get workingDir() {
return this._workingDir;
}
/**
* Get dbId
*
* @readonly
* @public
*/
get dbId() {
return this._dbOpenResult.dbId;
}
/**
* Get logger
*
* @readonly
* @public
*/
get tsLogger() {
return this._tsLogger;
}
/**
* Get logger
*
* @readonly
* @public
*/
get logger() {
return this._logger;
}
/**
* Schema for specific document type
*
* @readonly
* @public
*/
get schema() {
return this._schema;
}
/**
* logToTransport function for all log levels. See https://tslog.js.org/#/?id=transports
*
* @readonly
* @public
*/
get logToTransport() {
return this._logToTransport;
}
/**
* Task queue
*
* @readonly
* @public
*/
get taskQueue() {
return this._taskQueue;
}
/**
* DB is going to close
*
* @readonly
* @public
*/
get isClosing() {
return this._isClosing;
}
/**
* Name validator
*
* @readonly
* @public
*/
get validator() {
return this._validator;
}
/**
* Default collection whose collectionPath is ''.
*
* @readonly
* @public
*/
get rootCollection() {
return this._rootCollection;
}
/**
* logLevel ('silly' | 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal')
*
* @public
*/
get logLevel() {
return this._logLevel;
}
set logLevel(level) {
this._logLevel = level;
this._tsLogger = new tslog_1.Logger({
name: this._dbName,
minLevel: level,
displayDateTime: false,
displayFunctionName: false,
displayFilePath: 'hidden',
});
if (this.logToTransport) {
this._tsLogger.attachTransport({
silly: this.logToTransport,
debug: this.logToTransport,
trace: this.logToTransport,
info: this.logToTransport,
warn: this.logToTransport,
error: this.logToTransport,
fatal: this.logToTransport,
}, level);
}
if (this.taskQueue)
this.taskQueue.setLogger(this._logger);
}
/***********************************************
* Private methods
***********************************************/
/**
* Create local repository
*
* @throws {@link Err.CannotCreateDirectoryError}
*
* @throws # from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.SameIdExistsError}
* @throws - {@link Err.DocumentNotFoundError}
* @throws - {@link Err.CannotWriteDataError}
* @internal
*/
async _createRepository() {
// First commit
const info = {
dbId: generateDatabaseId(),
creator: const_1.DATABASE_CREATOR,
version: const_1.DATABASE_VERSION,
serialize: this._serializeFormat.format,
};
// Retry three times.
// Creating system files sometimes fail just after installing from Squirrel installer of Electron.
const retry = 3;
for (let i = 0; i < retry + 1; i++) {
// eslint-disable-next-line no-await-in-loop
const resEnsure = await fs_extra_1.default.ensureDir(this._workingDir).catch((err) => {
if (i >= retry)
throw new error_1.Err.CannotCreateDirectoryError(err.message);
return 'cannot_create';
});
if (resEnsure === 'cannot_create') {
// eslint-disable-next-line no-await-in-loop
await (0, utils_1.sleep)(const_1.FILE_CREATE_TIMEOUT);
this.logger.debug('retrying ensureDir in createRepository');
continue;
}
// eslint-disable-next-line no-await-in-loop
const resInit = await isomorphic_git_1.default
.init({ fs: fs_extra_1.default, dir: this._workingDir, defaultBranch: this.defaultBranch })
.catch((err) => {
if (i >= retry)
throw err;
return 'cannot_init';
});
if (resInit === 'cannot_init') {
// eslint-disable-next-line no-await-in-loop
await (0, utils_1.sleep)(const_1.FILE_CREATE_TIMEOUT);
this.logger.debug('retrying git.init in createRepository');
continue;
}
// Do not use this.put() because it increments TaskQueue.statistics.put.
// eslint-disable-next-line no-await-in-loop
const resPut = await (0, put_1.putWorker)(this, '', const_1.GIT_DOCUMENTDB_INFO_ID + const_1.JSON_POSTFIX, (0, utils_1.toSortedJSONString)(info), const_1.FIRST_COMMIT_MESSAGE).catch(err => {
if (i >= retry)
throw err;
return 'cannot_put';
});
if (resPut === 'cannot_put') {
// eslint-disable-next-line no-await-in-loop
await (0, utils_1.sleep)(const_1.FILE_CREATE_TIMEOUT);
fs_extra_1.default.removeSync(this._workingDir);
this.logger.debug('retrying putWorker in createRepository');
continue;
}
break;
}
this._dbOpenResult.isNew = true;
this._dbOpenResult = { ...this._dbOpenResult, ...info };
}
/***********************************************
* Public methods
***********************************************/
/**
* Open or create a Git repository
*
* @remarks
* - Create a new Git repository if a dbName specified in the constructor does not exist.
*
* - GitDocumentDB creates a legitimate Git repository and unique metadata under '.gitddb/'.
*
* - '.gitddb/' keeps {@link git-documentdb#DatabaseInfo} for combining databases, checking schema and migration.
*
* - GitDocumentDB can also load a Git repository that is created by other apps. It almost works; however, correct behavior is not guaranteed if it does not have a valid '.gitddb/'.
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.RepositoryNotFoundError} may occurs when openOptions.createIfNotExists is false.
*
* @throws # Errors from _createRepository
* @throws - {@link Err.CannotCreateDirectoryError}
*
* @throws # Errors from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.SameIdExistsError}
* @throws - {@link Err.DocumentNotFoundError}
* @throws - {@link Err.CannotWriteDataError}
*
* @public
*/
async open(openOptions) {
var _a;
if (this.isClosing) {
throw new error_1.Err.DatabaseClosingError();
}
if (this.isOpened) {
this._dbOpenResult.isNew = false;
return this._dbOpenResult;
}
if (openOptions === undefined) {
openOptions = {
createIfNotExists: undefined,
};
}
(_a = openOptions.createIfNotExists) !== null && _a !== void 0 ? _a : (openOptions.createIfNotExists = true);
const gitDir = this._workingDir + '/.git/';
if (!fs_extra_1.default.existsSync(gitDir)) {
if (openOptions.createIfNotExists) {
await this._createRepository();
}
else {
throw new error_1.Err.RepositoryNotFoundError(gitDir);
}
}
await this.loadDbInfo();
// Start when no exception
this._taskQueue.start();
return this._dbOpenResult;
}
/**
* Close a database
*
* @remarks
* - New CRUD operations are not available while closing.
*
* - Queued operations are executed before the database is closed unless it times out.
*
* @param options - The options specify how to close database.
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.DatabaseCloseTimeoutError}
*
* @public
*/
async close(options) {
var _a, _b;
if (this.isClosing) {
return Promise.reject(new error_1.Err.DatabaseClosingError());
}
// Stop remote
Object.values(this._synchronizers).forEach(sync => sync.close());
options !== null && options !== void 0 ? options : (options = { force: undefined, timeout: undefined });
(_a = options.force) !== null && _a !== void 0 ? _a : (options.force = false);
(_b = options.timeout) !== null && _b !== void 0 ? _b : (options.timeout = 10000);
// Wait taskQueue
try {
this._isClosing = true;
if (!options.force) {
const isTimeout = await this.taskQueue.waitCompletion(options.timeout);
if (isTimeout) {
return Promise.reject(new error_1.Err.DatabaseCloseTimeoutError());
}
}
}
finally {
this.taskQueue.stop();
this._synchronizers = {};
this._dbOpenResult = {
...INITIAL_DATABASE_OPEN_RESULT,
};
this._isClosing = false;
}
}
/**
* Destroy a database
*
* @remarks
* - {@link GitDocumentDB.close} is called automatically before destroying.
*
* - Default value of options.force is true.
*
* - destroy() removes the Git repository and the working directory from the filesystem.
*
* - destroy() does not remove localDir (which is specified in constructor).
*
* @param options - The options specify how to close database.
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.DatabaseCloseTimeoutError}
* @throws {@link Err.FileRemoveTimeoutError}
*
* @public
*/
async destroy(options = {}) {
var _a;
if (this.isClosing) {
return Promise.reject(new error_1.Err.DatabaseClosingError());
}
let closeError;
// NOTICE: options.force is true by default.
options.force = (_a = options.force) !== null && _a !== void 0 ? _a : true;
await this.close(options).catch(err => {
closeError = err;
});
// If the path does not exist, remove() silently does nothing.
// https://github.com/jprichardson/node-fs-extra/blob/master/docs/remove.md
// await fs.remove(this._workingDir).catch(err => {
await new Promise((resolve, reject) => {
// Set timeout because rimraf sometimes does not catch EPERM error.
setTimeout(() => {
reject(new error_1.Err.FileRemoveTimeoutError());
}, const_1.FILE_REMOVE_TIMEOUT);
(0, rimraf_1.default)(this._workingDir, error => {
if (error) {
reject(error);
}
resolve();
});
});
if (closeError instanceof Error) {
throw closeError;
}
return {
ok: true,
};
}
/**
* Test if a database is opened
*
* @public
*/
get isOpened() {
return this._dbOpenResult.dbId !== '';
}
/**
* Get a collection
*
* @param collectionPath - relative path from localDir. Sub-directories are also permitted. e.g. 'pages', 'pages/works'.
*
* @remarks
* - Notice that this function just read an existing directory. It does not make a new sub-directory.
*
* @returns A child collection of {@link git-documentdb#GitDocumentDB.rootCollection}
*
* @public
*/
collection(collectionPath, options) {
return new collection_1.Collection(this, collectionPath, this.rootCollection, options);
}
/**
* Get collections
*
* @param dirPath - Get collections directly under the dirPath. dirPath is a relative path from localDir. Default is ''.
* @returns Promise\<Collection[]\>
*
* @public
*/
async getCollections(dirPath = '') {
return await this.rootCollection.getCollections(dirPath);
}
/**
* getRemoteURLs
*
* @public
*/
getRemoteURLs() {
return Object.keys(this._synchronizers);
}
/**
* Get synchronizer
*
* @public
*/
getSync(remoteURL) {
return this._synchronizers[remoteURL];
}
/**
* Stop and unregister remote synchronization
*
* @public
*/
removeSync(remoteURL) {
this._synchronizers[remoteURL].pause();
delete this._synchronizers[remoteURL];
}
async sync(options, getSyncResult) {
if (options.remoteUrl !== undefined &&
this._synchronizers[options === null || options === void 0 ? void 0 : options.remoteUrl] !== undefined) {
throw new error_1.Err.RemoteAlreadyRegisteredError(options.remoteUrl);
}
if (getSyncResult) {
const [sync, syncResult] = await sync_1.syncAndGetResultImpl.call(this, options);
this._synchronizers[sync.remoteURL] = sync;
return [sync, syncResult];
}
const sync = await sync_1.syncImpl.call(this, options);
this._synchronizers[sync.remoteURL] = sync;
return sync;
}
/**
* Get a commit object
*
* @public
*/
async getCommit(oid) {
const readCommitResult = await isomorphic_git_1.default.readCommit({ fs: fs_extra_1.default, dir: this._workingDir, oid });
return (0, utils_1.normalizeCommit)(readCommitResult);
}
/**
* Save current author to .git/config
*
* @remarks
* Save GitDocumentDB#author. to user.name and user.email in .git/config
*
* @public
*/
async saveAuthor() {
var _a, _b;
if (((_a = this.author) === null || _a === void 0 ? void 0 : _a.name) !== undefined) {
await isomorphic_git_1.default.setConfig({
fs: fs_extra_1.default,
dir: this._workingDir,
path: 'user.name',
value: this.author.name,
});
}
if (((_b = this.author) === null || _b === void 0 ? void 0 : _b.email) !== undefined) {
await isomorphic_git_1.default.setConfig({
fs: fs_extra_1.default,
dir: this._workingDir,
path: 'user.email',
value: this.author.email,
});
}
}
/**
* Load author from .git/config
*
* @remarks
* Load user.name and user.email to GitDocumentDB#author.
* If not defined in .git/config, do nothing.
*
* @public
*/
async loadAuthor() {
const name = await isomorphic_git_1.default
.getConfig({
fs: fs_extra_1.default,
dir: this._workingDir,
path: 'user.name',
})
.catch(() => undefined);
this.author.name = name !== null && name !== void 0 ? name : this.author.name;
const email = await isomorphic_git_1.default
.getConfig({
fs: fs_extra_1.default,
dir: this._workingDir,
path: 'user.email',
})
.catch(() => undefined);
this.author.email = email !== null && email !== void 0 ? email : this.author.email;
}
/**
* Load DatabaseInfo from .gitddb/info.json
*
* @throws # Errors from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.SameIdExistsError}
* @throws - {@link Err.DocumentNotFoundError}
* @throws - {@link Err.CannotWriteDataError}
*
* @internal
*/
// eslint-disable-next-line complexity
async loadDbInfo() {
var _a, _b, _c;
let info;
// Don't use get() because isOpened is false.
const readBlobResult = await (0, blob_1.readLatestBlob)(this.workingDir, const_1.GIT_DOCUMENTDB_INFO_ID + const_1.JSON_POSTFIX).catch(() => undefined);
if (readBlobResult !== undefined) {
try {
info = (0, blob_1.blobToJsonDoc)(const_1.GIT_DOCUMENTDB_INFO_ID, readBlobResult, false, new serialize_format_1.SerializeFormatJSON(), const_1.JSON_POSTFIX);
}
catch (e) { }
}
info !== null && info !== void 0 ? info : (info = {
dbId: '',
creator: '',
version: '',
serialize: this._serializeFormat.format,
});
(_a = info.creator) !== null && _a !== void 0 ? _a : (info.creator = '');
(_b = info.version) !== null && _b !== void 0 ? _b : (info.version = '');
(_c = info.serialize) !== null && _c !== void 0 ? _c : (info.serialize = this._serializeFormat.format);
if (info.serialize !== this._serializeFormat.format) {
// TODO: Change serialize format
}
// Set dbId if not exists.
if (!info.dbId) {
info.dbId = generateDatabaseId();
// Do not use this.put() because it increments TaskQueue.statistics.put.
await (0, put_1.putWorker)(this, '', const_1.GIT_DOCUMENTDB_INFO_ID + const_1.JSON_POSTFIX, (0, utils_1.toSortedJSONString)(info), const_1.SET_DATABASE_ID_MESSAGE);
}
this._dbOpenResult.dbId = info.dbId;
this._dbOpenResult.creator = info.creator;
this._dbOpenResult.version = info.version;
if (new RegExp('^' + const_1.DATABASE_CREATOR).test(info.creator)) {
this._dbOpenResult.isCreatedByGitDDB = true;
if (new RegExp('^' + const_1.DATABASE_VERSION).test(info.version)) {
this._dbOpenResult.isValidVersion = true;
}
else {
this._dbOpenResult.isValidVersion = false;
/**
* TODO: Need migration
*/
}
}
else {
this._dbOpenResult.isCreatedByGitDDB = false;
this._dbOpenResult.isValidVersion = false;
}
}
put(_idOrDoc, jsonDocOrOptions, options) {
return this.rootCollection.put(_idOrDoc, jsonDocOrOptions, options);
}
insert(_idOrDoc, jsonDocOrOptions, options) {
options !== null && options !== void 0 ? options : (options = {});
options.insertOrUpdate = 'insert';
return this.rootCollection.insert(_idOrDoc, jsonDocOrOptions, options);
}
update(_idOrDoc, jsonDocOrOptions, options) {
return this.rootCollection.update(_idOrDoc, jsonDocOrOptions, options);
}
/**
* Insert data if not exists. Otherwise, update it.
*
* @param name - name is a file path.
*
* @remarks
* - The saved file path is `${GitDocumentDB#workingDir}/${name}extension`.
*
* - If a name parameter is undefined, it is automatically generated.
*
* - _id property of a JsonDoc is automatically set or overwritten by name parameter whose extension is removed.
*
* - An update operation is not skipped even if no change occurred on a specified data.
*
* - This is an alias of GitDocumentDB#rootCollection.putFatDoc()
*
* @throws {@link Err.InvalidJsonFileExtensionError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @privateRemarks # Errors from putImpl
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.TaskCancelError}
*
* @throws # Errors from validateDocument, validateId
* @throws - {@link Err.InvalidIdCharacterError}
* @throws - {@link Err.InvalidIdLengthError}
*
* @throws # Errors from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.CannotWriteDataError}
*
* @public
*/
putFatDoc(name, doc, options) {
return this.rootCollection.putFatDoc(name, doc, options);
}
/**
* Insert a data
*
* @param name - name is a file path.
*
* @remarks
* - Throws SameIdExistsError when data that has the same _id exists. It might be better to use put() instead of insert().
*
* - The saved file path is `${GitDocumentDB#workingDir}/${name}extension`.
*
* - If a name parameter is undefined, it is automatically generated.
*
* - _id property of a JsonDoc is automatically set or overwritten by name parameter whose extension is omitted.
*
* - This is an alias of GitDocumentDB#rootCollection.insertFatDoc()
*
* @throws {@link Err.InvalidJsonObjectError}
*
* @privateRemarks # Errors from putImpl
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.TaskCancelError}
*
* @throws # Errors from validateDocument, validateId
* @throws - {@link Err.InvalidIdCharacterError}
* @throws - {@link Err.InvalidIdLengthError}
*
* @throws # Errors from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.CannotWriteDataError}
*
* @throws - {@link Err.SameIdExistsError}
*
* @public
*/
insertFatDoc(name, doc, options) {
return this.rootCollection.insertFatDoc(name, doc, options);
}
/**
* Update a data
*
* @param name - name is a file path.
*
* @remarks
* - Throws DocumentNotFoundError if a specified data does not exist. It might be better to use put() instead of update().
*
* - The saved file path is `${GitDocumentDB#workingDir}/${name}extension`.
*
* - _id property of a JsonDoc is automatically set or overwritten by name parameter whose extension is omitted.
*
* - An update operation is not skipped even if no change occurred on a specified data.
*
* - This is an alias of GitDocumentDB#rootCollection.updateFatDoc()
*
* @throws {@link Err.InvalidJsonObjectError}
*
* @privateRemarks # Errors from putImpl
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.TaskCancelError}
*
* @throws # Errors from validateDocument, validateId
* @throws - {@link Err.InvalidIdCharacterError}
* @throws - {@link Err.InvalidIdLengthError}
*
* @throws # Errors from putWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.CannotCreateDirectoryError}
* @throws - {@link Err.CannotWriteDataError}
*
* @throws - {@link Err.DocumentNotFoundError}
*
* @public
*/
updateFatDoc(name, doc, options) {
return this.rootCollection.updateFatDoc(name, doc, options);
}
/**
* Get a JSON document
*
* @param _id - _id is a file path whose extension is omitted.
*
* @returns
* - undefined if a specified document does not exist.
*
* - JsonDoc may not have _id property when an app other than GitDocumentDB creates it.
*
* - This is an alias of GitDocumentDB#rootCollection.get()
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
get(_id) {
return this.rootCollection.get(_id);
}
/**
* Get a FatDoc data
*
* @param name - name is a file path.
*
* @returns
* - undefined if a specified data does not exist.
*
* - FatJsonDoc if the file extension is SerializeFormat.extension. Be careful that JsonDoc may not have _id property when an app other than GitDocumentDB creates it.
*
* - FatBinaryDoc if described in .gitattribtues, otherwise FatTextDoc.
*
* - getOptions.forceDocType always overwrite return type.
*
* - This is an alias of GitDocumentDB#rootCollection.getFatDoc()
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getFatDoc(name, getOptions) {
return this.rootCollection.getFatDoc(name, getOptions);
}
/**
* Get a Doc which has specified oid
*
* @param fileOid - Object ID (SHA-1 hash) that represents a Git object. (See https://git-scm.com/docs/git-hash-object )
*
* @remarks
* - undefined if a specified oid does not exist.
*
* - This is an alias of GitDocumentDB#rootCollection.getDocByOid()
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getDocByOid(fileOid, docType = 'binary') {
return this.rootCollection.getDocByOid(fileOid, docType);
}
/**
* Get an old revision of a document
*
* @param _id - _id is a file path whose extension is omitted.
* @param revision - Specify a number to go back to old revision. Default is 0.
* See {@link git-documentdb#GitDocumentDB.getHistory} for the array of revisions.
* @param historyOptions - The array of revisions is filtered by HistoryOptions.filter.
*
* @remarks
* - undefined if a specified document does not exist or it is deleted.
*
* - This is an alias of GitDocumentDB#rootCollection.getOldRevision()
*
* @example
* ```
* db.getOldRevision(_id, 0); // returns the latest document.
* db.getOldRevision(_id, 2); // returns a document two revisions older than the latest.
* ```
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getOldRevision(_id, revision, historyOptions) {
return this.rootCollection.getOldRevision(_id, revision, historyOptions);
}
/**
* Get an old revision of a FatDoc data
*
* @param name - name is a file path.
* @param revision - Specify a number to go back to old revision. Default is 0.
* See {@link git-documentdb#GitDocumentDB.getHistory} for the array of revisions.
* @param historyOptions - The array of revisions is filtered by HistoryOptions.filter.
*
* @remarks
* - undefined if a specified data does not exist or it is deleted.
*
* - JsonDoc if the file extension is SerializeFormat.extension. Be careful that JsonDoc may not have _id property when an app other than GitDocumentDB creates it.
*
* - FatBinaryDoc if described in .gitattribtues, otherwise FatTextDoc.
*
* - getOptions.forceDocType always overwrite return type.
*
* - This is an alias of GitDocumentDB#rootCollection.getFatDocOldRevision()
*
* @example
* ```
* db.getFatDocOldRevision(name, 0); // returns the latest FatDoc.
* db.getFatDocOldRevision(name, 2); // returns a FatDoc two revisions older than the latest.
* ```
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getFatDocOldRevision(name, revision, historyOptions, getOptions) {
return this.rootCollection.getFatDocOldRevision(name, revision, historyOptions, getOptions);
}
/**
* Get revision history of a document
*
* @param _id - _id is a file path whose extension is omitted.
* @param historyOptions - The array of revisions is filtered by HistoryOptions.filter.
*
* @remarks
* - By default, revisions are sorted by reverse chronological order. However, keep in mind that Git dates may not be consistent across repositories.
*
* - This is an alias of GitDocumentDB.rootCollection.getHistory().
*
* @returns Array of FatDoc or undefined.
* - undefined if a specified document does not exist or it is deleted.
*
* - JsonDoc if isJsonDocCollection is true or the file extension is SerializeFormat.extension.
*
* - Uint8Array or string if isJsonDocCollection is false.
*
* - getOptions.forceDocType always overwrite return type.
*
* @example
* ```
* Commit-01 to 08 were committed in order. file_v1 and file_v2 are two revisions of a file.
*
* - Commit-08: Not exists
* - Commit-07: deleted
* - Commit-06: file_v2
* - Commit-05: deleted
* - Commit-04: file_v2
* - Commit-03: file_v1
* - Commit-02: file_v1
* - Commit-01: Not exists
*
* Commit-02 newly inserted a file (file_v1).
* Commit-03 did not change about the file.
* Commit-04 updated the file from file_v1 to file_v2.
* Commit-05 deleted the file.
* Commit-06 inserted the deleted file (file_v2) again.
* Commit-07 deleted the file again.
* Commit-08 did not change about the file.
*
* Here, getHistory() will return [undefined, file_v2, undefined, file_v2, file_v1] as a history.
*
* NOTE:
* - Consecutive same values (commit-02 and commit-03) are combined into one.
* - getHistory() ignores commit-01 because it was committed before the first insert.
* Thus, a history is not [undefined, undefined, file_v2, undefined, file_v2, file_v1, file_v1, undefined].
* ```
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getHistory(_id, historyOptions) {
return this.rootCollection.getHistory(_id, historyOptions);
}
/**
* Get revision history of a FatDoc data
*
* @param name - name is a file path.
*
* @remarks
* - This is an alias of GitDocumentDB#rootCollection.getFatDocHistory()
*
* - See {@link git-documentdb#GitDocumentDB.getHistory} for detailed examples.
*
* @returns Array of FatDoc or undefined.
* - undefined if a specified data does not exist or it is deleted.
*
* - Array of FatJsonDoc if isJsonDocCollection is true or the file extension is SerializeFormat.extension. Be careful that JsonDoc may not have _id property when an app other than GitDocumentDB creates it.
*
* - Array of FatBinaryDoc if described in .gitattribtues, otherwise array of FatTextDoc.
*
* - getOptions.forceDocType always overwrite return type.
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
getFatDocHistory(name, historyOptions, getOptions) {
return this.rootCollection.getFatDocHistory(name, historyOptions, getOptions);
}
delete(idOrDoc, options) {
return this.rootCollection.delete(idOrDoc, options);
}
/**
* Delete a data
*
* @param name - name is a file path
*
* @remarks
* - This is an alias of GitDocumentDB#rootCollection.deleteFatDoc()
*
* @throws {@link Err.UndefinedDocumentIdError}
*
* @privateRemarks # Errors from deleteImpl
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.TaskCancelError}
*
* @throws # Errors from deleteWorker
* @throws - {@link Err.UndefinedDBError}
* @throws - {@link Err.DocumentNotFoundError}
* @throws - {@link Err.CannotDeleteDataError}
*
* @public
*/
deleteFatDoc(name, options) {
return this.rootCollection.deleteFatDoc(name, options);
}
/**
* Get all the JSON documents
*
* @remarks
* - This is an alias of GitDocumentDB#rootCollection.find()
*
* @param options - The options specify how to get documents.
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
find(options) {
return this.rootCollection.find(options);
}
/**
* Get all the FatDoc data
*
* @param options - The options specify how to get documents.
*
* @remarks
* - This is an alias of GitDocumentDB#rootCollection.findFatDoc()
*
* @throws {@link Err.DatabaseClosingError}
* @throws {@link Err.InvalidJsonObjectError}
*
* @public
*/
findFatDoc(options) {
return this.rootCollection.findFatDoc(options);
}
onSyncEvent(remoteURLorSync, event, callback) {
return this.rootCollection.onSyncEvent(remoteURLorSync, event, callback);
}
offSyncEvent(remoteURLorSync, event, callback) {
this.rootCollection.offSyncEvent(remoteURLorSync, event, callback);
}
}
exports.GitDocumentDB = GitDocumentDB;
//# sourceMappingURL=git_documentdb.js.map