UNPKG

@deepstream/client

Version:
373 lines (372 loc) 13.6 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.List = void 0; var utils = require("../util/utils"); var constants_1 = require("../constants"); var emitter_1 = require("../util/emitter"); var List = /** @class */ (function (_super) { __extends(List, _super); function List(record) { var _this = _super.call(this) || this; _this.record = record; _this.debugId = _this.record.getDebugId(); _this.wrappedFunctions = new Map(); _this.hasAddListener = false; _this.hasRemoveListener = false; _this.hasMoveListener = false; _this.subscriptions = []; _this.originalApplyUpdate = _this.record.applyUpdate.bind(_this.record); _this.record.applyUpdate = _this.applyUpdate.bind(_this); _this.record.addReference(_this); _this.record.on('discard', function () { return _this.emit('discard', _this); }, _this); _this.record.on('delete', function () { return _this.emit('delete', _this); }, _this); _this.record.on('error', function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } return _this.emit.apply(_this, __spreadArray(['error'], __read(args), false)); }, _this); return _this; } Object.defineProperty(List.prototype, "name", { get: function () { return this.record.name; }, enumerable: false, configurable: true }); Object.defineProperty(List.prototype, "isReady", { get: function () { return this.record.isReady; }, enumerable: false, configurable: true }); Object.defineProperty(List.prototype, "version", { get: function () { return this.record.version; }, enumerable: false, configurable: true }); List.prototype.whenReady = function (callback) { if (callback) { this.record.whenReady(this, callback); } else { return this.record.whenReady(this); } }; List.prototype.discard = function () { this.destroy(); this.record.removeReference(this); }; List.prototype.delete = function (callback) { this.destroy(); return this.record.delete(callback); }; /** * Returns the array of list entries or an * empty array if the list hasn't been populated yet. */ List.prototype.getEntries = function () { var entries = this.record.get(); if (!(entries instanceof Array)) { return []; } return entries; }; /** * Returns true if the list is empty */ List.prototype.isEmpty = function () { return this.getEntries().length === 0; }; List.prototype.setEntriesWithAck = function (entries, callback) { var _this = this; if (!callback) { return new Promise(function (resolve, reject) { _this.setEntries(entries, function (error) { if (error) { reject(error); } else { resolve(); } }); }); } this.setEntries(entries, callback); }; /** * Updates the list with a new set of entries */ List.prototype.setEntries = function (entries, callback) { var errorMsg = 'entries must be an array of record names'; var i; if (!(entries instanceof Array)) { throw new Error(errorMsg); } for (i = 0; i < entries.length; i++) { if (typeof entries[i] !== 'string') { throw new Error(errorMsg); } } this.beforeChange(); this.record.set({ data: entries, callback: callback }); this.afterChange(); }; /** * Removes an entry from the list * * @param {String} entry * @param {Number} [index] */ List.prototype.removeEntry = function (entry, index, callback) { // @ts-ignore var currentEntries = this.record.get(); var hasIndex = this.hasIndex(index); var entries = []; var i; for (i = 0; i < currentEntries.length; i++) { if (currentEntries[i] !== entry || (hasIndex && index !== i)) { entries.push(currentEntries[i]); } } this.beforeChange(); this.record.set({ data: entries, callback: callback }); this.afterChange(); }; /** * Adds an entry to the list * * @param {String} entry * @param {Number} [index] */ List.prototype.addEntry = function (entry, index, callback) { if (typeof entry !== 'string') { throw new Error('Entry must be a recordName'); } var hasIndex = this.hasIndex(index); var entries = this.getEntries(); if (hasIndex) { entries.splice(index, 0, entry); } else { entries.push(entry); } this.beforeChange(); this.record.set({ data: entries, callback: callback }); this.afterChange(); }; /** * Proxies the underlying Record's subscribe method. Makes sure * that no path is provided */ List.prototype.subscribe = function (callback) { var parameters = utils.normalizeArguments(arguments); if (parameters.path) { throw new Error('path is not supported for List.subscribe'); } // Make sure the callback is invoked with an empty array for new records var listCallback = function (scope, cb) { cb(scope.getEntries()); }.bind(this, this, parameters.callback); /** * Adding a property onto a function directly is terrible practice, * and we will change this as soon as we have a more seperate approach * of creating lists that doesn't have records default state. * * The reason we are holding a referencing to wrapped array is so that * on unsubscribe it can provide a reference to the actual method the * record is subscribed too. **/ this.wrappedFunctions.set(parameters.callback, listCallback); parameters.callback = listCallback; this.subscriptions.push(parameters); this.record.subscribe(parameters, this); }; /** * Proxies the underlying Record's unsubscribe method. Makes sure * that no path is provided */ List.prototype.unsubscribe = function (callback) { var parameters = utils.normalizeArguments(arguments); if (parameters.path) { throw new Error('path is not supported for List.unsubscribe'); } var listenCallback = this.wrappedFunctions.get(parameters.callback); parameters.callback = listenCallback; this.wrappedFunctions.delete(parameters.callback); this.subscriptions = this.subscriptions.filter(function (subscription) { if (parameters.callback && parameters.callback !== subscription.callback) return true; return false; }); this.record.unsubscribe(parameters, this); }; /** * Proxies the underlying Record's _update method. Set's * data to an empty array if no data is provided. */ List.prototype.applyUpdate = function (message) { if (!(message.parsedData instanceof Array)) { message.parsedData = []; } this.beforeChange(); this.originalApplyUpdate(message); this.afterChange(); }; /** * Validates that the index provided is within the current set of entries. */ List.prototype.hasIndex = function (index) { var hasIndex = false; var entries = this.getEntries(); if (index !== undefined) { if (isNaN(index)) { throw new Error('Index must be a number'); } if (index !== entries.length && (index >= entries.length || index < 0)) { throw new Error('Index must be within current entries'); } hasIndex = true; } return hasIndex; }; /** * Establishes the current structure of the list, provided the client has attached any * add / move / remove listener * * This will be called before any change to the list, regardsless if the change was triggered * by an incoming message from the server or by the client */ List.prototype.beforeChange = function () { this.hasAddListener = this.hasListeners(constants_1.EVENT.ENTRY_ADDED_EVENT); this.hasRemoveListener = this.hasListeners(constants_1.EVENT.ENTRY_REMOVED_EVENT); this.hasMoveListener = this.hasListeners(constants_1.EVENT.ENTRY_MOVED_EVENT); if (this.hasAddListener || this.hasRemoveListener || this.hasMoveListener) { this.beforeStructure = this.getStructure(); } else { this.beforeStructure = null; } }; /** * Compares the structure of the list after a change to its previous structure and notifies * any add / move / remove listener. Won't do anything if no listeners are attached. */ List.prototype.afterChange = function () { if (this.beforeStructure === null) { return; } var after = this.getStructure(); var before = this.beforeStructure; var entry; var i; if (this.hasRemoveListener) { for (entry in before) { for (i = 0; i < before[entry].length; i++) { if (after[entry] === undefined || after[entry][i] === undefined) { this.emit(constants_1.EVENT.ENTRY_REMOVED_EVENT, entry, before[entry][i]); } } } } if (this.hasAddListener || this.hasMoveListener) { for (entry in after) { if (before[entry] === undefined) { for (i = 0; i < after[entry].length; i++) { this.emit(constants_1.EVENT.ENTRY_ADDED_EVENT, entry, after[entry][i]); } } else { for (i = 0; i < after[entry].length; i++) { if (before[entry][i] !== after[entry][i]) { if (before[entry][i] === undefined) { this.emit(constants_1.EVENT.ENTRY_ADDED_EVENT, entry, after[entry][i]); } else { this.emit(constants_1.EVENT.ENTRY_MOVED_EVENT, entry, after[entry][i]); } } } } } } }; /** * Iterates through the list and creates a map with the entry as a key * and an array of its position(s) within the list as a value, e.g. * * { * 'recordA': [ 0, 3 ], * 'recordB': [ 1 ], * 'recordC': [ 2 ] * } */ List.prototype.getStructure = function () { var structure = {}; var i; var entries = this.getEntries(); for (i = 0; i < entries.length; i++) { if (structure[entries[i]] === undefined) { structure[entries[i]] = [i]; } else { structure[entries[i]].push(i); } } return structure; }; List.prototype.destroy = function () { for (var i = 0; i < this.subscriptions.length; i++) { this.record.unsubscribe(this.subscriptions[i], this); } this.wrappedFunctions.clear(); this.record.removeContext(this); }; return List; }(emitter_1.Emitter)); exports.List = List;