UNPKG

git-documentdb

Version:

Offline-first database that syncs with Git

1,221 lines (1,220 loc) 44.2 kB
"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