UNPKG

@nozbe/watermelondb

Version:

Build powerful React Native and React web apps that scale from hundreds to tens of thousands of records and remain fast

294 lines (279 loc) 9.43 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _rx = require("../utils/rx"); var _invariant = _interopRequireDefault(require("../utils/common/invariant")); var _fp = require("../utils/fp"); var _Result = require("../utils/fp/Result"); var _Query = _interopRequireDefault(require("../Query")); var _RecordCache = _interopRequireDefault(require("./RecordCache")); var Collection = exports.default = /*#__PURE__*/function () { /** * `Model` subclass associated with this Collection */ /** * An `Rx.Subject` that emits a signal on every change (record creation/update/deletion) in * this Collection. * * The emissions contain information about which record was changed and what the change was. * * Warning: You can easily introduce performance bugs in your application by using this method * inappropriately. You generally should just use the `Query` API. */ function Collection(database, ModelClass) { var _this = this; this.changes = new _rx.Subject(); this._subscribers = []; this.database = database; this.modelClass = ModelClass; this._cache = new _RecordCache.default(ModelClass.table, function (raw) { return new ModelClass(_this, raw); }, this); } /** * `Database` associated with this Collection. */ var _proto = Collection.prototype; /** * Fetches the record with the given ID. * * If the record is not found, the Promise will reject. */ _proto.find = function (id) { return new Promise(function ($return) { var _this2 = this; return $return((0, _Result.toPromise)(function (callback) { return _this2._fetchRecord(id, callback); })); }.bind(this)); } /** * Fetches the given record and then starts observing it. * * This is a convenience method that's equivalent to * `collection.find(id)`, followed by `record.observe()`. */; _proto.findAndObserve = function (id) { var _this3 = this; return _rx.Observable.create(function (observer) { var unsubscribe = null; var unsubscribed = false; _this3._fetchRecord(id, function (result) { if (result.value) { var record = result.value; observer.next(record); unsubscribe = record.experimentalSubscribe(function (isDeleted) { if (!unsubscribed) { isDeleted ? observer.complete() : observer.next(record); } }); } else { // $FlowFixMe observer.error(result.error); } }); return function () { unsubscribed = true; unsubscribe && unsubscribe(); }; }); } /*:: query: ArrayOrSpreadFn<Clause, Query<Record>> */ /** * Returns a `Query` with conditions given. * * You can pass conditions as multiple arguments or a single array. * * See docs for details about the Query API. */ // $FlowFixMe ; _proto.query = function (...args) { var clauses = (0, _fp.fromArrayOrSpread)(args, 'Collection.query', 'Clause'); return new _Query.default(this, clauses); } /** * Creates a new record. * Pass a function to set attributes of the new record. * * Note: This method must be called within a Writer {@link Database#write}. * * @example * ```js * db.get(Tables.tasks).create(task => { * task.name = 'Task name' * }) * ``` */; _proto.create = function (recordBuilder = _fp.noop) { return new Promise(function ($return, $error) { var record; this.database._ensureInWriter("Collection.create()"); record = this.prepareCreate(recordBuilder); return Promise.resolve(this.database.batch(record)).then(function () { try { return $return(record); } catch ($boundEx) { return $error($boundEx); } }, $error); }.bind(this)); } /** * Prepares a new record to be created * * Use this to batch-execute multiple changes at once. * @see {Collection#create} * @see {Database#batch} */; _proto.prepareCreate = function (recordBuilder = _fp.noop) { // $FlowFixMe return this.modelClass._prepareCreate(this, recordBuilder); } /** * Prepares a new record to be created, based on a raw object. * * Don't use this unless you know how RawRecords work in WatermelonDB. See docs for more details. * * This is useful as a performance optimization, when adding online-only features to an otherwise * offline-first app, or if you're implementing your own sync mechanism. */; _proto.prepareCreateFromDirtyRaw = function (dirtyRaw) { // $FlowFixMe return this.modelClass._prepareCreateFromDirtyRaw(this, dirtyRaw); } /** * Returns a disposable record, based on a raw object. * * A disposable record is a read-only record that **does not** exist in the actual database. It's * not cached and cannot be saved in the database, updated, deleted, queried, or found by ID. It * only exists for as long as you keep a reference to it. * * Don't use this unless you know how RawRecords work in WatermelonDB. See docs for more details. * * This is useful for adding online-only features to an otherwise offline-first app, or for * temporary objects that are not meant to be persisted (as you can reuse existing Model helpers * and compatible UI components to display a disposable record). */; _proto.disposableFromDirtyRaw = function (dirtyRaw) { // $FlowFixMe return this.modelClass._disposableFromDirtyRaw(this, dirtyRaw); } // *** Implementation details *** // See: Query.fetch ; _proto._fetchQuery = function (query, callback) { var _this4 = this; this.database.adapter.underlyingAdapter.query(query.serialize(), function (result) { return callback((0, _Result.mapValue)(function (rawRecords) { return _this4._cache.recordsFromQueryResult(rawRecords); }, result)); }); }; _proto._fetchIds = function (query, callback) { this.database.adapter.underlyingAdapter.queryIds(query.serialize(), callback); }; _proto._fetchCount = function (query, callback) { this.database.adapter.underlyingAdapter.count(query.serialize(), callback); }; _proto._unsafeFetchRaw = function (query, callback) { this.database.adapter.underlyingAdapter.unsafeQueryRaw(query.serialize(), callback); } // Fetches exactly one record (See: Collection.find) ; _proto._fetchRecord = function (id, callback) { var _this5 = this; if ('string' !== typeof id) { callback({ error: new Error("Invalid record ID ".concat(this.table, "#").concat(id)) }); return; } var cachedRecord = this._cache.get(id); if (cachedRecord) { callback({ value: cachedRecord }); return; } this.database.adapter.underlyingAdapter.find(this.table, id, function (result) { return callback((0, _Result.mapValue)(function (rawRecord) { (0, _invariant.default)(rawRecord, "Record ".concat(_this5.table, "#").concat(id, " not found")); return _this5._cache.recordFromQueryResult(rawRecord); }, result)); }); }; _proto._applyChangesToCache = function (operations) { var _this6 = this; operations.forEach(function ({ record: record, type: type }) { if ('created' === type) { record._preparedState = null; _this6._cache.add(record); } else if ('destroyed' === type) { _this6._cache.delete(record); } }); }; _proto._notify = function (operations) { this._subscribers.forEach(function ([subscriber]) { subscriber(operations); }); this.changes.next(operations); operations.forEach(function ({ record: record, type: type }) { if ('updated' === type) { record._notifyChanged(); } else if ('destroyed' === type) { record._notifyDestroyed(); } }); }; /** * Notifies `subscriber` on every change (record creation/update/deletion) in this Collection. * * Notifications contain information about which record was changed and what the change was. * (Currently, subscribers are called before `changes` emissions, but this behavior might change) * * Warning: You can easily introduce performance bugs in your application by using this method * inappropriately. You generally should just use the `Query` API. */ _proto.experimentalSubscribe = function (subscriber, debugInfo) { var _this7 = this; var entry = [subscriber, debugInfo]; this._subscribers.push(entry); return function () { var idx = _this7._subscribers.indexOf(entry); -1 !== idx && _this7._subscribers.splice(idx, 1); }; }; return (0, _createClass2.default)(Collection, [{ key: "db", get: function get() { return this.database; } /** * Table name associated with this Collection */ }, { key: "table", get: function get() { // $FlowFixMe return this.modelClass.table; } /** * Table schema associated with this Collection */ }, { key: "schema", get: function get() { return this.database.schema.tables[this.table]; } }]); }();