{"version":3,"file":"bundle.js","sources":["../../app/node_modules/svelte/internal/index.mjs","../../app/node_modules/style-inject/dist/style-inject.es.js","../../app/node_modules/svelte/easing/index.mjs","../../app/node_modules/svelte/transition/index.mjs","../../app/node_modules/lokijs/src/loki-indexed-adapter.js","../../app/node_modules/rollup-plugin-node-builtins/src/es6/empty.js","../../app/node_modules/lokijs/src/lokijs.js","../../app/src/components/shared/utils/Data.svelte","../../app/node_modules/ionicons/icons/imports/add-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/albums.mjs","../../app/node_modules/ionicons/icons/imports/analytics.mjs","../../app/node_modules/ionicons/icons/imports/apps.mjs","../../app/node_modules/ionicons/icons/imports/arrow-back.mjs","../../app/node_modules/ionicons/icons/imports/arrow-round-back.mjs","../../app/node_modules/ionicons/icons/imports/arrow-up.mjs","../../app/node_modules/ionicons/icons/imports/attach.mjs","../../app/node_modules/ionicons/icons/imports/book.mjs","../../app/node_modules/ionicons/icons/imports/bookmark.mjs","../../app/node_modules/ionicons/icons/imports/brush.mjs","../../app/node_modules/ionicons/icons/imports/checkmark-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/checkmark-circle.mjs","../../app/node_modules/ionicons/icons/imports/checkmark.mjs","../../app/node_modules/ionicons/icons/imports/close-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/close.mjs","../../app/node_modules/ionicons/icons/imports/cloud-circle.mjs","../../app/node_modules/ionicons/icons/imports/color-wand.mjs","../../app/node_modules/ionicons/icons/imports/compass.mjs","../../app/node_modules/ionicons/icons/imports/contact.mjs","../../app/node_modules/ionicons/icons/imports/create.mjs","../../app/node_modules/ionicons/icons/imports/cube.mjs","../../app/node_modules/ionicons/icons/imports/cut.mjs","../../app/node_modules/ionicons/icons/imports/document.mjs","../../app/node_modules/ionicons/icons/imports/download.mjs","../../app/node_modules/ionicons/icons/imports/expand.mjs","../../app/node_modules/ionicons/icons/imports/eye-off.mjs","../../app/node_modules/ionicons/icons/imports/eye.mjs","../../app/node_modules/ionicons/icons/imports/filing.mjs","../../app/node_modules/ionicons/icons/imports/funnel.mjs","../../app/node_modules/ionicons/icons/imports/grid.mjs","../../app/node_modules/ionicons/icons/imports/headset.mjs","../../app/node_modules/ionicons/icons/imports/help-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/image.mjs","../../app/node_modules/ionicons/icons/imports/information-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/link.mjs","../../app/node_modules/ionicons/icons/imports/list.mjs","../../app/node_modules/ionicons/icons/imports/locate.mjs","../../app/node_modules/ionicons/icons/imports/log-out.mjs","../../app/node_modules/ionicons/icons/imports/map.mjs","../../app/node_modules/ionicons/icons/imports/move.mjs","../../app/node_modules/ionicons/icons/imports/navigate.mjs","../../app/node_modules/ionicons/icons/imports/open.mjs","../../app/node_modules/ionicons/icons/imports/paper.mjs","../../app/node_modules/ionicons/icons/imports/person.mjs","../../app/node_modules/ionicons/icons/imports/pin.mjs","../../app/node_modules/ionicons/icons/imports/podium.mjs","../../app/node_modules/ionicons/icons/imports/print.mjs","../../app/node_modules/ionicons/icons/imports/radio-button-on.mjs","../../app/node_modules/ionicons/icons/imports/refresh-circle.mjs","../../app/node_modules/ionicons/icons/imports/refresh.mjs","../../app/node_modules/ionicons/icons/imports/remove-circle-outline.mjs","../../app/node_modules/ionicons/icons/imports/save.mjs","../../app/node_modules/ionicons/icons/imports/search.mjs","../../app/node_modules/ionicons/icons/imports/send.mjs","../../app/node_modules/ionicons/icons/imports/settings.mjs","../../app/node_modules/ionicons/icons/imports/share-alt.mjs","../../app/node_modules/ionicons/icons/imports/share.mjs","../../app/node_modules/ionicons/icons/imports/square-outline.mjs","../../app/node_modules/ionicons/icons/imports/stats.mjs","../../app/node_modules/ionicons/icons/imports/swap.mjs","../../app/node_modules/ionicons/icons/imports/sync.mjs","../../app/node_modules/ionicons/icons/imports/today.mjs","../../app/node_modules/ionicons/icons/imports/trash.mjs","../../app/node_modules/ionicons/icons/imports/videocam.mjs","../../app/node_modules/ionicons/icons/imports/warning.mjs","../../app/src/components/account/Nav.svelte","../../app/src/components/account/Login.svelte","../../app/src/components/account/Edit.svelte","../../app/src/pages/Account.svelte","../../app/node_modules/svelte-key/src/SvelteKey.svelte","../../app/src/components/shared/utils/Sort.svelte","../../app/src/components/shared/modal/Login.svelte","../../app/node_modules/iv-viewer/lib/util.js","../../app/node_modules/iv-viewer/lib/Slider.js","../../app/node_modules/iv-viewer/lib/ImageViewer.js","../../app/node_modules/iv-viewer/lib/FullScreen.js","../../app/node_modules/iv-viewer/lib/index.js","../../app/src/components/shared/modal/MediaView.svelte","../../app/src/components/shared/modal/MediaUpload.svelte","../../app/src/components/shared/modal/Forbidden.svelte","../../app/src/components/shared/form/Wrapper.svelte","../../app/node_modules/is-what/dist/index.esm.js","../../app/src/components/shared/form/helpers.js","../../app/src/components/shared/form/Form.svelte","../../app/src/components/shared/form/fields/BooleanField.svelte","../../app/src/components/shared/form/fields/NullField.svelte","../../app/src/components/shared/form/fields/NumberField.svelte","../../app/src/components/shared/form/fields/StringField.svelte","../../app/src/components/shared/form/fields/ObjectField.svelte","../../app/src/components/shared/form/fields/ArrayField.svelte","../../app/src/components/shared/form/fields/index.js","../../app/src/components/shared/form/index.js","../../app/src/components/form/Form.svelte","../../../node_modules/uuid/dist/esm-browser/rng.js","../../../node_modules/uuid/dist/esm-browser/regex.js","../../../node_modules/uuid/dist/esm-browser/validate.js","../../../node_modules/uuid/dist/esm-browser/stringify.js","../../../node_modules/uuid/dist/esm-browser/v1.js","../../../node_modules/uuid/dist/esm-browser/parse.js","../../../node_modules/uuid/dist/esm-browser/v35.js","../../../node_modules/uuid/dist/esm-browser/md5.js","../../../node_modules/uuid/dist/esm-browser/v3.js","../../../node_modules/uuid/dist/esm-browser/native.js","../../../node_modules/uuid/dist/esm-browser/v4.js","../../../node_modules/uuid/dist/esm-browser/sha1.js","../../../node_modules/uuid/dist/esm-browser/v5.js","../../../node_modules/uuid/dist/esm-browser/nil.js","../../../node_modules/uuid/dist/esm-browser/version.js","../../../node_modules/any-base/src/converter.js","../../../node_modules/any-base/index.js","../../../node_modules/short-uuid/index.js","../../app/node_modules/svelte-qrcode/src/lib/qrcode/index.js","../../app/node_modules/svelte-qrcode/src/lib/index.svelte","../../app/src/components/shared/modal/Info.svelte","../../app/src/components/shared/modal/Confirmation.svelte","../../app/node_modules/@turf/turf/turf.min.js","../../app/src/components/shared/modal/Form.svelte","../../app/src/pages/Form.svelte","../../app/node_modules/maplibre-gl/dist/maplibre-gl.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/mode_handler.js","../../app/node_modules/wgs84/index.js","../../app/node_modules/@mapbox/geojson-area/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/constants.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/sort_features.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/map_event_to_bounding_box.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/string_set.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/features_at.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/get_features_and_set_cursor.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/euclidean_distance.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/is_click.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/is_tap.js","../../app/node_modules/hat/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/feature_types/feature.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/feature_types/point.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/feature_types/line_string.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/feature_types/polygon.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/feature_types/multi_feature.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/mode_interface_accessors.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/mode_interface.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/object_to_mode.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/events.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/to_dense_array.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/render.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/store.js","../../app/node_modules/xtend/immutable.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/ui.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/setup.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/theme.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/common_selectors.js","../../app/node_modules/@mapbox/point-geometry/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/mouse_event_point.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/create_vertex.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/create_midpoint.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/create_supplementary_points.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/double_click_zoom.js","../../app/node_modules/@mapbox/geojson-normalize/index.js","../../app/node_modules/geojson-flatten/dist/index.js","../../app/node_modules/@mapbox/geojson-coords/flatten.js","../../app/node_modules/@mapbox/geojson-coords/index.js","../../app/node_modules/traverse/index.js","../../app/node_modules/@mapbox/extent/index.js","../../app/node_modules/@mapbox/geojson-extent/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/constrain_feature_movement.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/move_features.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/simple_select.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/direct_select.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/draw_point.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/is_event_at_coordinates.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/draw_polygon.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/draw_line_string.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/modes/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/options.js","../../app/node_modules/lodash.isequal/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/lib/string_sets_are_equal.js","../../app/node_modules/rollup-plugin-node-builtins/src/es6/path.js","../../app/node_modules/jsonlint-lines/lib/jsonlint.js","../../app/node_modules/@mapbox/geojsonhint/lib/rhr.js","../../app/node_modules/@mapbox/geojsonhint/lib/object.js","../../app/node_modules/@mapbox/geojsonhint/lib/index.js","../../app/node_modules/@mapbox/mapbox-gl-draw/src/api.js","../../app/node_modules/@mapbox/mapbox-gl-draw/index.js","../../app/node_modules/@turf/helpers/main.es.js","../../app/node_modules/@turf/meta/main.es.js","../../app/node_modules/@turf/bbox/main.es.js","../../app/src/components/shared/modal/Errors.svelte","../../app/src/components/shared/modal/Confirm.svelte","../../app/src/components/shared/modal/Option.svelte","../../app/src/components/shared/modal/History.svelte","../../app/src/components/shared/modal/Forms.svelte","../../app/src/components/shared/modal/Passphrase.svelte","../../app/src/components/shared/modal/Settings.svelte","../../app/node_modules/fuse.js/dist/fuse.esm.js","../../app/src/components/shared/grid/Paginate.svelte","../../app/src/components/shared/grid/debounce.js","../../app/node_modules/dot/doT.js","../../app/src/components/shared/grid/data-grid.js","../../app/src/components/shared/grid/local.js","../../app/node_modules/svelte/store/index.mjs","../../app/node_modules/immer/dist/immer.esm.js","../../app/src/components/shared/grid/store.js","../../app/src/components/shared/grid/Source.svelte","../../app/node_modules/chart.js/dist/chunks/helpers.segment.js","../../app/node_modules/chart.js/dist/chart.esm.js","../../app/node_modules/chart.js/auto/auto.esm.js","../../app/src/components/shared/modal/Profile.svelte","../../app/src/components/shared/sidebar/Layers.svelte","../../app/src/components/shared/sidebar/Search.svelte","../../app/src/components/shared/sidebar/Location.svelte","../../app/src/components/shared/sidebar/Log.svelte","../../app/src/components/shared/sidebar/Node.svelte","../../app/src/components/shared/sidebar/Project.svelte","../../app/src/components/shared/sidebar/Draw.svelte","../../app/src/components/map/Sidebar.svelte","../../app/src/components/map/callout/Details.svelte","../../app/src/components/map/callout/Media.svelte","../../app/src/components/map/callout/History.svelte","../../app/src/components/map/callout/Callout.svelte","../../app/node_modules/codemirror/lib/codemirror.js","../../app/node_modules/codemirror/addon/edit/continuelist.js","../../app/node_modules/easymde/src/js/codemirror/tablist.js","../../app/node_modules/codemirror/addon/display/fullscreen.js","../../app/node_modules/codemirror/mode/xml/xml.js","../../app/node_modules/codemirror/mode/meta.js","../../app/node_modules/codemirror/mode/markdown/markdown.js","../../app/node_modules/codemirror/addon/mode/overlay.js","../../app/node_modules/codemirror/addon/display/placeholder.js","../../app/node_modules/codemirror/addon/display/autorefresh.js","../../app/node_modules/codemirror/addon/selection/mark-selection.js","../../app/node_modules/codemirror/addon/search/searchcursor.js","../../app/node_modules/codemirror/mode/gfm/gfm.js","../../app/node_modules/typo-js/typo.js","../../app/node_modules/codemirror-spell-checker/src/js/spell-checker.js","../../app/node_modules/easymde/node_modules/marked/lib/marked.umd.js","../../app/node_modules/easymde/src/js/easymde.js","../../app/src/components/shared/modal/Message.svelte","../../app/node_modules/snarkdown/dist/snarkdown.es.js","../../app/src/components/shared/modal/MessageView.svelte","../../app/node_modules/@turf/bbox-polygon/main.es.js","../../app/node_modules/@turf/invariant/main.es.js","../../app/node_modules/@turf/boolean-point-in-polygon/main.es.js","../../app/node_modules/geojson-rbush/quickselect.js","../../app/node_modules/geojson-rbush/rbush.js","../../app/node_modules/geojson-rbush/index.js","../../app/node_modules/@turf/line-segment/main.es.js","../../app/node_modules/@turf/line-intersect/main.es.js","../../app/node_modules/@turf/polygon-to-line/main.es.js","../../app/node_modules/@turf/boolean-disjoint/main.es.js","../../app/node_modules/@turf/distance/main.es.js","../../app/node_modules/@turf/bearing/main.es.js","../../app/node_modules/@turf/destination/main.es.js","../../app/node_modules/@turf/nearest-point-on-line/main.es.js","../../app/node_modules/@turf/midpoint/main.es.js","../../app/node_modules/mapbox-gl-draw-snap-mode/src/utils/index.js","../../app/node_modules/mapbox-gl-draw-snap-mode/src/modes/snap_point.js","../../app/node_modules/mapbox-gl-draw-snap-mode/src/modes/snap_line.js","../../app/node_modules/mapbox-gl-draw-snap-mode/src/modes/snap_polygon.js","../../app/node_modules/mapbox-gl-draw-snap-mode/src/utils/customDrawStyles.js","../../app/src/pages/Map.svelte","../../app/src/components/media/Media.svelte","../../app/src/components/media/List.svelte","../../app/src/components/media/Sidebar.svelte","../../app/src/pages/Media.svelte","../../app/src/components/project/Sidebar.svelte","../../app/src/components/project/Project.svelte","../../app/src/pages/Project.svelte","../../app/src/components/scene/Sidebar.svelte","../../app/src/components/scene/Scene.svelte","../../app/src/pages/Scene.svelte","../../app/src/components/shared/modal/Map.svelte","../../app/src/components/source/PowerTable.svelte","../../app/src/components/stores/source.js","../../app/src/components/source/Link.svelte","../../app/src/components/source/Source.svelte","../../app/src/components/source/Item.svelte","../../app/src/components/source/Sidebar.svelte","../../app/src/components/shared/modal/Summaries.svelte","../../app/src/components/shared/modal/Summary.svelte","../../app/src/components/shared/modal/Item.svelte","../../app/src/pages/Source.svelte","../../app/src/pages/Subscription.svelte","../../app/src/pages/Success.svelte","../../app/vendor/pannellum.js","../../app/node_modules/@firebase/util/dist/index.esm2017.js","../../app/node_modules/@firebase/component/dist/esm/index.esm2017.js","../../app/node_modules/@firebase/logger/dist/esm/index.esm2017.js","../../app/node_modules/idb/build/wrap-idb-value.js","../../app/node_modules/idb/build/index.js","../../app/node_modules/@firebase/app/dist/esm/index.esm2017.js","../../app/node_modules/firebase/app/dist/esm/index.esm.js","../../app/node_modules/@firebase/installations/dist/esm/index.esm2017.js","../../app/node_modules/@firebase/messaging/dist/esm/index.esm2017.js","../../app/src/App.svelte","../../app/src/main.js"],"sourcesContent":["function noop() { }\nconst identity = x => x;\nfunction assign(tar, src) {\n // @ts-ignore\n for (const k in src)\n tar[k] = src[k];\n return tar;\n}\nfunction is_promise(value) {\n return value && typeof value === 'object' && typeof value.then === 'function';\n}\nfunction add_location(element, file, line, column, char) {\n element.__svelte_meta = {\n loc: { file, line, column, char }\n };\n}\nfunction run(fn) {\n return fn();\n}\nfunction blank_object() {\n return Object.create(null);\n}\nfunction run_all(fns) {\n fns.forEach(run);\n}\nfunction is_function(thing) {\n return typeof thing === 'function';\n}\nfunction safe_not_equal(a, b) {\n return a != a ? b == b : a !== b || ((a && typeof a === 'object') || typeof a === 'function');\n}\nfunction not_equal(a, b) {\n return a != a ? b == b : a !== b;\n}\nfunction is_empty(obj) {\n return Object.keys(obj).length === 0;\n}\nfunction validate_store(store, name) {\n if (store != null && typeof store.subscribe !== 'function') {\n throw new Error(`'${name}' is not a store with a 'subscribe' method`);\n }\n}\nfunction subscribe(store, ...callbacks) {\n if (store == null) {\n return noop;\n }\n const unsub = store.subscribe(...callbacks);\n return unsub.unsubscribe ? () => unsub.unsubscribe() : unsub;\n}\nfunction get_store_value(store) {\n let value;\n subscribe(store, _ => value = _)();\n return value;\n}\nfunction component_subscribe(component, store, callback) {\n component.$$.on_destroy.push(subscribe(store, callback));\n}\nfunction create_slot(definition, ctx, $$scope, fn) {\n if (definition) {\n const slot_ctx = get_slot_context(definition, ctx, $$scope, fn);\n return definition[0](slot_ctx);\n }\n}\nfunction get_slot_context(definition, ctx, $$scope, fn) {\n return definition[1] && fn\n ? assign($$scope.ctx.slice(), definition[1](fn(ctx)))\n : $$scope.ctx;\n}\nfunction get_slot_changes(definition, $$scope, dirty, fn) {\n if (definition[2] && fn) {\n const lets = definition[2](fn(dirty));\n if ($$scope.dirty === undefined) {\n return lets;\n }\n if (typeof lets === 'object') {\n const merged = [];\n const len = Math.max($$scope.dirty.length, lets.length);\n for (let i = 0; i < len; i += 1) {\n merged[i] = $$scope.dirty[i] | lets[i];\n }\n return merged;\n }\n return $$scope.dirty | lets;\n }\n return $$scope.dirty;\n}\nfunction update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_context_fn) {\n const slot_changes = get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn);\n if (slot_changes) {\n const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);\n slot.p(slot_context, slot_changes);\n }\n}\nfunction exclude_internal_props(props) {\n const result = {};\n for (const k in props)\n if (k[0] !== '$')\n result[k] = props[k];\n return result;\n}\nfunction compute_rest_props(props, keys) {\n const rest = {};\n keys = new Set(keys);\n for (const k in props)\n if (!keys.has(k) && k[0] !== '$')\n rest[k] = props[k];\n return rest;\n}\nfunction compute_slots(slots) {\n const result = {};\n for (const key in slots) {\n result[key] = true;\n }\n return result;\n}\nfunction once(fn) {\n let ran = false;\n return function (...args) {\n if (ran)\n return;\n ran = true;\n fn.call(this, ...args);\n };\n}\nfunction null_to_empty(value) {\n return value == null ? '' : value;\n}\nfunction set_store_value(store, ret, value = ret) {\n store.set(value);\n return ret;\n}\nconst has_prop = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);\nfunction action_destroyer(action_result) {\n return action_result && is_function(action_result.destroy) ? action_result.destroy : noop;\n}\n\nconst is_client = typeof window !== 'undefined';\nlet now = is_client\n ? () => window.performance.now()\n : () => Date.now();\nlet raf = is_client ? cb => requestAnimationFrame(cb) : noop;\n// used internally for testing\nfunction set_now(fn) {\n now = fn;\n}\nfunction set_raf(fn) {\n raf = fn;\n}\n\nconst tasks = new Set();\nfunction run_tasks(now) {\n tasks.forEach(task => {\n if (!task.c(now)) {\n tasks.delete(task);\n task.f();\n }\n });\n if (tasks.size !== 0)\n raf(run_tasks);\n}\n/**\n * For testing purposes only!\n */\nfunction clear_loops() {\n tasks.clear();\n}\n/**\n * Creates a new task that runs on each raf frame\n * until it returns a falsy value or is aborted\n */\nfunction loop(callback) {\n let task;\n if (tasks.size === 0)\n raf(run_tasks);\n return {\n promise: new Promise(fulfill => {\n tasks.add(task = { c: callback, f: fulfill });\n }),\n abort() {\n tasks.delete(task);\n }\n };\n}\n\nfunction append(target, node) {\n target.appendChild(node);\n}\nfunction insert(target, node, anchor) {\n target.insertBefore(node, anchor || null);\n}\nfunction detach(node) {\n node.parentNode.removeChild(node);\n}\nfunction destroy_each(iterations, detaching) {\n for (let i = 0; i < iterations.length; i += 1) {\n if (iterations[i])\n iterations[i].d(detaching);\n }\n}\nfunction element(name) {\n return document.createElement(name);\n}\nfunction element_is(name, is) {\n return document.createElement(name, { is });\n}\nfunction object_without_properties(obj, exclude) {\n const target = {};\n for (const k in obj) {\n if (has_prop(obj, k)\n // @ts-ignore\n && exclude.indexOf(k) === -1) {\n // @ts-ignore\n target[k] = obj[k];\n }\n }\n return target;\n}\nfunction svg_element(name) {\n return document.createElementNS('http://www.w3.org/2000/svg', name);\n}\nfunction text(data) {\n return document.createTextNode(data);\n}\nfunction space() {\n return text(' ');\n}\nfunction empty() {\n return text('');\n}\nfunction listen(node, event, handler, options) {\n node.addEventListener(event, handler, options);\n return () => node.removeEventListener(event, handler, options);\n}\nfunction prevent_default(fn) {\n return function (event) {\n event.preventDefault();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction stop_propagation(fn) {\n return function (event) {\n event.stopPropagation();\n // @ts-ignore\n return fn.call(this, event);\n };\n}\nfunction self(fn) {\n return function (event) {\n // @ts-ignore\n if (event.target === this)\n fn.call(this, event);\n };\n}\nfunction attr(node, attribute, value) {\n if (value == null)\n node.removeAttribute(attribute);\n else if (node.getAttribute(attribute) !== value)\n node.setAttribute(attribute, value);\n}\nfunction set_attributes(node, attributes) {\n // @ts-ignore\n const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);\n for (const key in attributes) {\n if (attributes[key] == null) {\n node.removeAttribute(key);\n }\n else if (key === 'style') {\n node.style.cssText = attributes[key];\n }\n else if (key === '__value') {\n node.value = node[key] = attributes[key];\n }\n else if (descriptors[key] && descriptors[key].set) {\n node[key] = attributes[key];\n }\n else {\n attr(node, key, attributes[key]);\n }\n }\n}\nfunction set_svg_attributes(node, attributes) {\n for (const key in attributes) {\n attr(node, key, attributes[key]);\n }\n}\nfunction set_custom_element_data(node, prop, value) {\n if (prop in node) {\n node[prop] = value;\n }\n else {\n attr(node, prop, value);\n }\n}\nfunction xlink_attr(node, attribute, value) {\n node.setAttributeNS('http://www.w3.org/1999/xlink', attribute, value);\n}\nfunction get_binding_group_value(group, __value, checked) {\n const value = new Set();\n for (let i = 0; i < group.length; i += 1) {\n if (group[i].checked)\n value.add(group[i].__value);\n }\n if (!checked) {\n value.delete(__value);\n }\n return Array.from(value);\n}\nfunction to_number(value) {\n return value === '' ? null : +value;\n}\nfunction time_ranges_to_array(ranges) {\n const array = [];\n for (let i = 0; i < ranges.length; i += 1) {\n array.push({ start: ranges.start(i), end: ranges.end(i) });\n }\n return array;\n}\nfunction children(element) {\n return Array.from(element.childNodes);\n}\nfunction claim_element(nodes, name, attributes, svg) {\n for (let i = 0; i < nodes.length; i += 1) {\n const node = nodes[i];\n if (node.nodeName === name) {\n let j = 0;\n const remove = [];\n while (j < node.attributes.length) {\n const attribute = node.attributes[j++];\n if (!attributes[attribute.name]) {\n remove.push(attribute.name);\n }\n }\n for (let k = 0; k < remove.length; k++) {\n node.removeAttribute(remove[k]);\n }\n return nodes.splice(i, 1)[0];\n }\n }\n return svg ? svg_element(name) : element(name);\n}\nfunction claim_text(nodes, data) {\n for (let i = 0; i < nodes.length; i += 1) {\n const node = nodes[i];\n if (node.nodeType === 3) {\n node.data = '' + data;\n return nodes.splice(i, 1)[0];\n }\n }\n return text(data);\n}\nfunction claim_space(nodes) {\n return claim_text(nodes, ' ');\n}\nfunction set_data(text, data) {\n data = '' + data;\n if (text.wholeText !== data)\n text.data = data;\n}\nfunction set_input_value(input, value) {\n input.value = value == null ? '' : value;\n}\nfunction set_input_type(input, type) {\n try {\n input.type = type;\n }\n catch (e) {\n // do nothing\n }\n}\nfunction set_style(node, key, value, important) {\n node.style.setProperty(key, value, important ? 'important' : '');\n}\nfunction select_option(select, value) {\n for (let i = 0; i < select.options.length; i += 1) {\n const option = select.options[i];\n if (option.__value === value) {\n option.selected = true;\n return;\n }\n }\n}\nfunction select_options(select, value) {\n for (let i = 0; i < select.options.length; i += 1) {\n const option = select.options[i];\n option.selected = ~value.indexOf(option.__value);\n }\n}\nfunction select_value(select) {\n const selected_option = select.querySelector(':checked') || select.options[0];\n return selected_option && selected_option.__value;\n}\nfunction select_multiple_value(select) {\n return [].map.call(select.querySelectorAll(':checked'), option => option.__value);\n}\n// unfortunately this can't be a constant as that wouldn't be tree-shakeable\n// so we cache the result instead\nlet crossorigin;\nfunction is_crossorigin() {\n if (crossorigin === undefined) {\n crossorigin = false;\n try {\n if (typeof window !== 'undefined' && window.parent) {\n void window.parent.document;\n }\n }\n catch (error) {\n crossorigin = true;\n }\n }\n return crossorigin;\n}\nfunction add_resize_listener(node, fn) {\n const computed_style = getComputedStyle(node);\n const z_index = (parseInt(computed_style.zIndex) || 0) - 1;\n if (computed_style.position === 'static') {\n node.style.position = 'relative';\n }\n const iframe = element('iframe');\n iframe.setAttribute('style', 'display: block; position: absolute; top: 0; left: 0; width: 100%; height: 100%; ' +\n `overflow: hidden; border: 0; opacity: 0; pointer-events: none; z-index: ${z_index};`);\n iframe.setAttribute('aria-hidden', 'true');\n iframe.tabIndex = -1;\n const crossorigin = is_crossorigin();\n let unsubscribe;\n if (crossorigin) {\n iframe.src = \"data:text/html,\";\n unsubscribe = listen(window, 'message', (event) => {\n if (event.source === iframe.contentWindow)\n fn();\n });\n }\n else {\n iframe.src = 'about:blank';\n iframe.onload = () => {\n unsubscribe = listen(iframe.contentWindow, 'resize', fn);\n };\n }\n append(node, iframe);\n return () => {\n if (crossorigin) {\n unsubscribe();\n }\n else if (unsubscribe && iframe.contentWindow) {\n unsubscribe();\n }\n detach(iframe);\n };\n}\nfunction toggle_class(element, name, toggle) {\n element.classList[toggle ? 'add' : 'remove'](name);\n}\nfunction custom_event(type, detail) {\n const e = document.createEvent('CustomEvent');\n e.initCustomEvent(type, false, false, detail);\n return e;\n}\nfunction query_selector_all(selector, parent = document.body) {\n return Array.from(parent.querySelectorAll(selector));\n}\nclass HtmlTag {\n constructor(anchor = null) {\n this.a = anchor;\n this.e = this.n = null;\n }\n m(html, target, anchor = null) {\n if (!this.e) {\n this.e = element(target.nodeName);\n this.t = target;\n this.h(html);\n }\n this.i(anchor);\n }\n h(html) {\n this.e.innerHTML = html;\n this.n = Array.from(this.e.childNodes);\n }\n i(anchor) {\n for (let i = 0; i < this.n.length; i += 1) {\n insert(this.t, this.n[i], anchor);\n }\n }\n p(html) {\n this.d();\n this.h(html);\n this.i(this.a);\n }\n d() {\n this.n.forEach(detach);\n }\n}\nfunction attribute_to_object(attributes) {\n const result = {};\n for (const attribute of attributes) {\n result[attribute.name] = attribute.value;\n }\n return result;\n}\nfunction get_custom_elements_slots(element) {\n const result = {};\n element.childNodes.forEach((node) => {\n result[node.slot || 'default'] = true;\n });\n return result;\n}\n\nconst active_docs = new Set();\nlet active = 0;\n// https://github.com/darkskyapp/string-hash/blob/master/index.js\nfunction hash(str) {\n let hash = 5381;\n let i = str.length;\n while (i--)\n hash = ((hash << 5) - hash) ^ str.charCodeAt(i);\n return hash >>> 0;\n}\nfunction create_rule(node, a, b, duration, delay, ease, fn, uid = 0) {\n const step = 16.666 / duration;\n let keyframes = '{\\n';\n for (let p = 0; p <= 1; p += step) {\n const t = a + (b - a) * ease(p);\n keyframes += p * 100 + `%{${fn(t, 1 - t)}}\\n`;\n }\n const rule = keyframes + `100% {${fn(b, 1 - b)}}\\n}`;\n const name = `__svelte_${hash(rule)}_${uid}`;\n const doc = node.ownerDocument;\n active_docs.add(doc);\n const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style')).sheet);\n const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});\n if (!current_rules[name]) {\n current_rules[name] = true;\n stylesheet.insertRule(`@keyframes ${name} ${rule}`, stylesheet.cssRules.length);\n }\n const animation = node.style.animation || '';\n node.style.animation = `${animation ? `${animation}, ` : ''}${name} ${duration}ms linear ${delay}ms 1 both`;\n active += 1;\n return name;\n}\nfunction delete_rule(node, name) {\n const previous = (node.style.animation || '').split(', ');\n const next = previous.filter(name\n ? anim => anim.indexOf(name) < 0 // remove specific animation\n : anim => anim.indexOf('__svelte') === -1 // remove all Svelte animations\n );\n const deleted = previous.length - next.length;\n if (deleted) {\n node.style.animation = next.join(', ');\n active -= deleted;\n if (!active)\n clear_rules();\n }\n}\nfunction clear_rules() {\n raf(() => {\n if (active)\n return;\n active_docs.forEach(doc => {\n const stylesheet = doc.__svelte_stylesheet;\n let i = stylesheet.cssRules.length;\n while (i--)\n stylesheet.deleteRule(i);\n doc.__svelte_rules = {};\n });\n active_docs.clear();\n });\n}\n\nfunction create_animation(node, from, fn, params) {\n if (!from)\n return noop;\n const to = node.getBoundingClientRect();\n if (from.left === to.left && from.right === to.right && from.top === to.top && from.bottom === to.bottom)\n return noop;\n const { delay = 0, duration = 300, easing = identity, \n // @ts-ignore todo: should this be separated from destructuring? Or start/end added to public api and documentation?\n start: start_time = now() + delay, \n // @ts-ignore todo:\n end = start_time + duration, tick = noop, css } = fn(node, { from, to }, params);\n let running = true;\n let started = false;\n let name;\n function start() {\n if (css) {\n name = create_rule(node, 0, 1, duration, delay, easing, css);\n }\n if (!delay) {\n started = true;\n }\n }\n function stop() {\n if (css)\n delete_rule(node, name);\n running = false;\n }\n loop(now => {\n if (!started && now >= start_time) {\n started = true;\n }\n if (started && now >= end) {\n tick(1, 0);\n stop();\n }\n if (!running) {\n return false;\n }\n if (started) {\n const p = now - start_time;\n const t = 0 + 1 * easing(p / duration);\n tick(t, 1 - t);\n }\n return true;\n });\n start();\n tick(0, 1);\n return stop;\n}\nfunction fix_position(node) {\n const style = getComputedStyle(node);\n if (style.position !== 'absolute' && style.position !== 'fixed') {\n const { width, height } = style;\n const a = node.getBoundingClientRect();\n node.style.position = 'absolute';\n node.style.width = width;\n node.style.height = height;\n add_transform(node, a);\n }\n}\nfunction add_transform(node, a) {\n const b = node.getBoundingClientRect();\n if (a.left !== b.left || a.top !== b.top) {\n const style = getComputedStyle(node);\n const transform = style.transform === 'none' ? '' : style.transform;\n node.style.transform = `${transform} translate(${a.left - b.left}px, ${a.top - b.top}px)`;\n }\n}\n\nlet current_component;\nfunction set_current_component(component) {\n current_component = component;\n}\nfunction get_current_component() {\n if (!current_component)\n throw new Error('Function called outside component initialization');\n return current_component;\n}\nfunction beforeUpdate(fn) {\n get_current_component().$$.before_update.push(fn);\n}\nfunction onMount(fn) {\n get_current_component().$$.on_mount.push(fn);\n}\nfunction afterUpdate(fn) {\n get_current_component().$$.after_update.push(fn);\n}\nfunction onDestroy(fn) {\n get_current_component().$$.on_destroy.push(fn);\n}\nfunction createEventDispatcher() {\n const component = get_current_component();\n return (type, detail) => {\n const callbacks = component.$$.callbacks[type];\n if (callbacks) {\n // TODO are there situations where events could be dispatched\n // in a server (non-DOM) environment?\n const event = custom_event(type, detail);\n callbacks.slice().forEach(fn => {\n fn.call(component, event);\n });\n }\n };\n}\nfunction setContext(key, context) {\n get_current_component().$$.context.set(key, context);\n}\nfunction getContext(key) {\n return get_current_component().$$.context.get(key);\n}\n// TODO figure out if we still want to support\n// shorthand events, or if we want to implement\n// a real bubbling mechanism\nfunction bubble(component, event) {\n const callbacks = component.$$.callbacks[event.type];\n if (callbacks) {\n callbacks.slice().forEach(fn => fn(event));\n }\n}\n\nconst dirty_components = [];\nconst intros = { enabled: false };\nconst binding_callbacks = [];\nconst render_callbacks = [];\nconst flush_callbacks = [];\nconst resolved_promise = Promise.resolve();\nlet update_scheduled = false;\nfunction schedule_update() {\n if (!update_scheduled) {\n update_scheduled = true;\n resolved_promise.then(flush);\n }\n}\nfunction tick() {\n schedule_update();\n return resolved_promise;\n}\nfunction add_render_callback(fn) {\n render_callbacks.push(fn);\n}\nfunction add_flush_callback(fn) {\n flush_callbacks.push(fn);\n}\nlet flushing = false;\nconst seen_callbacks = new Set();\nfunction flush() {\n if (flushing)\n return;\n flushing = true;\n do {\n // first, call beforeUpdate functions\n // and update components\n for (let i = 0; i < dirty_components.length; i += 1) {\n const component = dirty_components[i];\n set_current_component(component);\n update(component.$$);\n }\n set_current_component(null);\n dirty_components.length = 0;\n while (binding_callbacks.length)\n binding_callbacks.pop()();\n // then, once components are updated, call\n // afterUpdate functions. This may cause\n // subsequent updates...\n for (let i = 0; i < render_callbacks.length; i += 1) {\n const callback = render_callbacks[i];\n if (!seen_callbacks.has(callback)) {\n // ...so guard against infinite loops\n seen_callbacks.add(callback);\n callback();\n }\n }\n render_callbacks.length = 0;\n } while (dirty_components.length);\n while (flush_callbacks.length) {\n flush_callbacks.pop()();\n }\n update_scheduled = false;\n flushing = false;\n seen_callbacks.clear();\n}\nfunction update($$) {\n if ($$.fragment !== null) {\n $$.update();\n run_all($$.before_update);\n const dirty = $$.dirty;\n $$.dirty = [-1];\n $$.fragment && $$.fragment.p($$.ctx, dirty);\n $$.after_update.forEach(add_render_callback);\n }\n}\n\nlet promise;\nfunction wait() {\n if (!promise) {\n promise = Promise.resolve();\n promise.then(() => {\n promise = null;\n });\n }\n return promise;\n}\nfunction dispatch(node, direction, kind) {\n node.dispatchEvent(custom_event(`${direction ? 'intro' : 'outro'}${kind}`));\n}\nconst outroing = new Set();\nlet outros;\nfunction group_outros() {\n outros = {\n r: 0,\n c: [],\n p: outros // parent group\n };\n}\nfunction check_outros() {\n if (!outros.r) {\n run_all(outros.c);\n }\n outros = outros.p;\n}\nfunction transition_in(block, local) {\n if (block && block.i) {\n outroing.delete(block);\n block.i(local);\n }\n}\nfunction transition_out(block, local, detach, callback) {\n if (block && block.o) {\n if (outroing.has(block))\n return;\n outroing.add(block);\n outros.c.push(() => {\n outroing.delete(block);\n if (callback) {\n if (detach)\n block.d(1);\n callback();\n }\n });\n block.o(local);\n }\n}\nconst null_transition = { duration: 0 };\nfunction create_in_transition(node, fn, params) {\n let config = fn(node, params);\n let running = false;\n let animation_name;\n let task;\n let uid = 0;\n function cleanup() {\n if (animation_name)\n delete_rule(node, animation_name);\n }\n function go() {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n if (css)\n animation_name = create_rule(node, 0, 1, duration, delay, easing, css, uid++);\n tick(0, 1);\n const start_time = now() + delay;\n const end_time = start_time + duration;\n if (task)\n task.abort();\n running = true;\n add_render_callback(() => dispatch(node, true, 'start'));\n task = loop(now => {\n if (running) {\n if (now >= end_time) {\n tick(1, 0);\n dispatch(node, true, 'end');\n cleanup();\n return running = false;\n }\n if (now >= start_time) {\n const t = easing((now - start_time) / duration);\n tick(t, 1 - t);\n }\n }\n return running;\n });\n }\n let started = false;\n return {\n start() {\n if (started)\n return;\n delete_rule(node);\n if (is_function(config)) {\n config = config();\n wait().then(go);\n }\n else {\n go();\n }\n },\n invalidate() {\n started = false;\n },\n end() {\n if (running) {\n cleanup();\n running = false;\n }\n }\n };\n}\nfunction create_out_transition(node, fn, params) {\n let config = fn(node, params);\n let running = true;\n let animation_name;\n const group = outros;\n group.r += 1;\n function go() {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n if (css)\n animation_name = create_rule(node, 1, 0, duration, delay, easing, css);\n const start_time = now() + delay;\n const end_time = start_time + duration;\n add_render_callback(() => dispatch(node, false, 'start'));\n loop(now => {\n if (running) {\n if (now >= end_time) {\n tick(0, 1);\n dispatch(node, false, 'end');\n if (!--group.r) {\n // this will result in `end()` being called,\n // so we don't need to clean up here\n run_all(group.c);\n }\n return false;\n }\n if (now >= start_time) {\n const t = easing((now - start_time) / duration);\n tick(1 - t, t);\n }\n }\n return running;\n });\n }\n if (is_function(config)) {\n wait().then(() => {\n // @ts-ignore\n config = config();\n go();\n });\n }\n else {\n go();\n }\n return {\n end(reset) {\n if (reset && config.tick) {\n config.tick(1, 0);\n }\n if (running) {\n if (animation_name)\n delete_rule(node, animation_name);\n running = false;\n }\n }\n };\n}\nfunction create_bidirectional_transition(node, fn, params, intro) {\n let config = fn(node, params);\n let t = intro ? 0 : 1;\n let running_program = null;\n let pending_program = null;\n let animation_name = null;\n function clear_animation() {\n if (animation_name)\n delete_rule(node, animation_name);\n }\n function init(program, duration) {\n const d = program.b - t;\n duration *= Math.abs(d);\n return {\n a: t,\n b: program.b,\n d,\n duration,\n start: program.start,\n end: program.start + duration,\n group: program.group\n };\n }\n function go(b) {\n const { delay = 0, duration = 300, easing = identity, tick = noop, css } = config || null_transition;\n const program = {\n start: now() + delay,\n b\n };\n if (!b) {\n // @ts-ignore todo: improve typings\n program.group = outros;\n outros.r += 1;\n }\n if (running_program || pending_program) {\n pending_program = program;\n }\n else {\n // if this is an intro, and there's a delay, we need to do\n // an initial tick and/or apply CSS animation immediately\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, b, duration, delay, easing, css);\n }\n if (b)\n tick(0, 1);\n running_program = init(program, duration);\n add_render_callback(() => dispatch(node, b, 'start'));\n loop(now => {\n if (pending_program && now > pending_program.start) {\n running_program = init(pending_program, duration);\n pending_program = null;\n dispatch(node, running_program.b, 'start');\n if (css) {\n clear_animation();\n animation_name = create_rule(node, t, running_program.b, running_program.duration, 0, easing, config.css);\n }\n }\n if (running_program) {\n if (now >= running_program.end) {\n tick(t = running_program.b, 1 - t);\n dispatch(node, running_program.b, 'end');\n if (!pending_program) {\n // we're done\n if (running_program.b) {\n // intro — we can tidy up immediately\n clear_animation();\n }\n else {\n // outro — needs to be coordinated\n if (!--running_program.group.r)\n run_all(running_program.group.c);\n }\n }\n running_program = null;\n }\n else if (now >= running_program.start) {\n const p = now - running_program.start;\n t = running_program.a + running_program.d * easing(p / running_program.duration);\n tick(t, 1 - t);\n }\n }\n return !!(running_program || pending_program);\n });\n }\n }\n return {\n run(b) {\n if (is_function(config)) {\n wait().then(() => {\n // @ts-ignore\n config = config();\n go(b);\n });\n }\n else {\n go(b);\n }\n },\n end() {\n clear_animation();\n running_program = pending_program = null;\n }\n };\n}\n\nfunction handle_promise(promise, info) {\n const token = info.token = {};\n function update(type, index, key, value) {\n if (info.token !== token)\n return;\n info.resolved = value;\n let child_ctx = info.ctx;\n if (key !== undefined) {\n child_ctx = child_ctx.slice();\n child_ctx[key] = value;\n }\n const block = type && (info.current = type)(child_ctx);\n let needs_flush = false;\n if (info.block) {\n if (info.blocks) {\n info.blocks.forEach((block, i) => {\n if (i !== index && block) {\n group_outros();\n transition_out(block, 1, 1, () => {\n info.blocks[i] = null;\n });\n check_outros();\n }\n });\n }\n else {\n info.block.d(1);\n }\n block.c();\n transition_in(block, 1);\n block.m(info.mount(), info.anchor);\n needs_flush = true;\n }\n info.block = block;\n if (info.blocks)\n info.blocks[index] = block;\n if (needs_flush) {\n flush();\n }\n }\n if (is_promise(promise)) {\n const current_component = get_current_component();\n promise.then(value => {\n set_current_component(current_component);\n update(info.then, 1, info.value, value);\n set_current_component(null);\n }, error => {\n set_current_component(current_component);\n update(info.catch, 2, info.error, error);\n set_current_component(null);\n if (!info.hasCatch) {\n throw error;\n }\n });\n // if we previously had a then/catch block, destroy it\n if (info.current !== info.pending) {\n update(info.pending, 0);\n return true;\n }\n }\n else {\n if (info.current !== info.then) {\n update(info.then, 1, info.value, promise);\n return true;\n }\n info.resolved = promise;\n }\n}\n\nconst globals = (typeof window !== 'undefined'\n ? window\n : typeof globalThis !== 'undefined'\n ? globalThis\n : global);\n\nfunction destroy_block(block, lookup) {\n block.d(1);\n lookup.delete(block.key);\n}\nfunction outro_and_destroy_block(block, lookup) {\n transition_out(block, 1, 1, () => {\n lookup.delete(block.key);\n });\n}\nfunction fix_and_destroy_block(block, lookup) {\n block.f();\n destroy_block(block, lookup);\n}\nfunction fix_and_outro_and_destroy_block(block, lookup) {\n block.f();\n outro_and_destroy_block(block, lookup);\n}\nfunction update_keyed_each(old_blocks, dirty, get_key, dynamic, ctx, list, lookup, node, destroy, create_each_block, next, get_context) {\n let o = old_blocks.length;\n let n = list.length;\n let i = o;\n const old_indexes = {};\n while (i--)\n old_indexes[old_blocks[i].key] = i;\n const new_blocks = [];\n const new_lookup = new Map();\n const deltas = new Map();\n i = n;\n while (i--) {\n const child_ctx = get_context(ctx, list, i);\n const key = get_key(child_ctx);\n let block = lookup.get(key);\n if (!block) {\n block = create_each_block(key, child_ctx);\n block.c();\n }\n else if (dynamic) {\n block.p(child_ctx, dirty);\n }\n new_lookup.set(key, new_blocks[i] = block);\n if (key in old_indexes)\n deltas.set(key, Math.abs(i - old_indexes[key]));\n }\n const will_move = new Set();\n const did_move = new Set();\n function insert(block) {\n transition_in(block, 1);\n block.m(node, next);\n lookup.set(block.key, block);\n next = block.first;\n n--;\n }\n while (o && n) {\n const new_block = new_blocks[n - 1];\n const old_block = old_blocks[o - 1];\n const new_key = new_block.key;\n const old_key = old_block.key;\n if (new_block === old_block) {\n // do nothing\n next = new_block.first;\n o--;\n n--;\n }\n else if (!new_lookup.has(old_key)) {\n // remove old block\n destroy(old_block, lookup);\n o--;\n }\n else if (!lookup.has(new_key) || will_move.has(new_key)) {\n insert(new_block);\n }\n else if (did_move.has(old_key)) {\n o--;\n }\n else if (deltas.get(new_key) > deltas.get(old_key)) {\n did_move.add(new_key);\n insert(new_block);\n }\n else {\n will_move.add(old_key);\n o--;\n }\n }\n while (o--) {\n const old_block = old_blocks[o];\n if (!new_lookup.has(old_block.key))\n destroy(old_block, lookup);\n }\n while (n)\n insert(new_blocks[n - 1]);\n return new_blocks;\n}\nfunction validate_each_keys(ctx, list, get_context, get_key) {\n const keys = new Set();\n for (let i = 0; i < list.length; i++) {\n const key = get_key(get_context(ctx, list, i));\n if (keys.has(key)) {\n throw new Error('Cannot have duplicate keys in a keyed each');\n }\n keys.add(key);\n }\n}\n\nfunction get_spread_update(levels, updates) {\n const update = {};\n const to_null_out = {};\n const accounted_for = { $$scope: 1 };\n let i = levels.length;\n while (i--) {\n const o = levels[i];\n const n = updates[i];\n if (n) {\n for (const key in o) {\n if (!(key in n))\n to_null_out[key] = 1;\n }\n for (const key in n) {\n if (!accounted_for[key]) {\n update[key] = n[key];\n accounted_for[key] = 1;\n }\n }\n levels[i] = n;\n }\n else {\n for (const key in o) {\n accounted_for[key] = 1;\n }\n }\n }\n for (const key in to_null_out) {\n if (!(key in update))\n update[key] = undefined;\n }\n return update;\n}\nfunction get_spread_object(spread_props) {\n return typeof spread_props === 'object' && spread_props !== null ? spread_props : {};\n}\n\n// source: https://html.spec.whatwg.org/multipage/indices.html\nconst boolean_attributes = new Set([\n 'allowfullscreen',\n 'allowpaymentrequest',\n 'async',\n 'autofocus',\n 'autoplay',\n 'checked',\n 'controls',\n 'default',\n 'defer',\n 'disabled',\n 'formnovalidate',\n 'hidden',\n 'ismap',\n 'loop',\n 'multiple',\n 'muted',\n 'nomodule',\n 'novalidate',\n 'open',\n 'playsinline',\n 'readonly',\n 'required',\n 'reversed',\n 'selected'\n]);\n\nconst invalid_attribute_name_character = /[\\s'\">/=\\u{FDD0}-\\u{FDEF}\\u{FFFE}\\u{FFFF}\\u{1FFFE}\\u{1FFFF}\\u{2FFFE}\\u{2FFFF}\\u{3FFFE}\\u{3FFFF}\\u{4FFFE}\\u{4FFFF}\\u{5FFFE}\\u{5FFFF}\\u{6FFFE}\\u{6FFFF}\\u{7FFFE}\\u{7FFFF}\\u{8FFFE}\\u{8FFFF}\\u{9FFFE}\\u{9FFFF}\\u{AFFFE}\\u{AFFFF}\\u{BFFFE}\\u{BFFFF}\\u{CFFFE}\\u{CFFFF}\\u{DFFFE}\\u{DFFFF}\\u{EFFFE}\\u{EFFFF}\\u{FFFFE}\\u{FFFFF}\\u{10FFFE}\\u{10FFFF}]/u;\n// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2\n// https://infra.spec.whatwg.org/#noncharacter\nfunction spread(args, classes_to_add) {\n const attributes = Object.assign({}, ...args);\n if (classes_to_add) {\n if (attributes.class == null) {\n attributes.class = classes_to_add;\n }\n else {\n attributes.class += ' ' + classes_to_add;\n }\n }\n let str = '';\n Object.keys(attributes).forEach(name => {\n if (invalid_attribute_name_character.test(name))\n return;\n const value = attributes[name];\n if (value === true)\n str += ' ' + name;\n else if (boolean_attributes.has(name.toLowerCase())) {\n if (value)\n str += ' ' + name;\n }\n else if (value != null) {\n str += ` ${name}=\"${String(value).replace(/\"/g, '"').replace(/'/g, ''')}\"`;\n }\n });\n return str;\n}\nconst escaped = {\n '\"': '"',\n \"'\": ''',\n '&': '&',\n '<': '<',\n '>': '>'\n};\nfunction escape(html) {\n return String(html).replace(/[\"'&<>]/g, match => escaped[match]);\n}\nfunction each(items, fn) {\n let str = '';\n for (let i = 0; i < items.length; i += 1) {\n str += fn(items[i], i);\n }\n return str;\n}\nconst missing_component = {\n $$render: () => ''\n};\nfunction validate_component(component, name) {\n if (!component || !component.$$render) {\n if (name === 'svelte:component')\n name += ' this={...}';\n throw new Error(`<${name}> is not a valid SSR component. You may need to review your build config to ensure that dependencies are compiled, rather than imported as pre-compiled modules`);\n }\n return component;\n}\nfunction debug(file, line, column, values) {\n console.log(`{@debug} ${file ? file + ' ' : ''}(${line}:${column})`); // eslint-disable-line no-console\n console.log(values); // eslint-disable-line no-console\n return '';\n}\nlet on_destroy;\nfunction create_ssr_component(fn) {\n function $$render(result, props, bindings, slots) {\n const parent_component = current_component;\n const $$ = {\n on_destroy,\n context: new Map(parent_component ? parent_component.$$.context : []),\n // these will be immediately discarded\n on_mount: [],\n before_update: [],\n after_update: [],\n callbacks: blank_object()\n };\n set_current_component({ $$ });\n const html = fn(result, props, bindings, slots);\n set_current_component(parent_component);\n return html;\n }\n return {\n render: (props = {}, options = {}) => {\n on_destroy = [];\n const result = { title: '', head: '', css: new Set() };\n const html = $$render(result, props, {}, options);\n run_all(on_destroy);\n return {\n html,\n css: {\n code: Array.from(result.css).map(css => css.code).join('\\n'),\n map: null // TODO\n },\n head: result.title + result.head\n };\n },\n $$render\n };\n}\nfunction add_attribute(name, value, boolean) {\n if (value == null || (boolean && !value))\n return '';\n return ` ${name}${value === true ? '' : `=${typeof value === 'string' ? JSON.stringify(escape(value)) : `\"${value}\"`}`}`;\n}\nfunction add_classes(classes) {\n return classes ? ` class=\"${classes}\"` : '';\n}\n\nfunction bind(component, name, callback) {\n const index = component.$$.props[name];\n if (index !== undefined) {\n component.$$.bound[index] = callback;\n callback(component.$$.ctx[index]);\n }\n}\nfunction create_component(block) {\n block && block.c();\n}\nfunction claim_component(block, parent_nodes) {\n block && block.l(parent_nodes);\n}\nfunction mount_component(component, target, anchor) {\n const { fragment, on_mount, on_destroy, after_update } = component.$$;\n fragment && fragment.m(target, anchor);\n // onMount happens before the initial afterUpdate\n add_render_callback(() => {\n const new_on_destroy = on_mount.map(run).filter(is_function);\n if (on_destroy) {\n on_destroy.push(...new_on_destroy);\n }\n else {\n // Edge case - component was destroyed immediately,\n // most likely as a result of a binding initialising\n run_all(new_on_destroy);\n }\n component.$$.on_mount = [];\n });\n after_update.forEach(add_render_callback);\n}\nfunction destroy_component(component, detaching) {\n const $$ = component.$$;\n if ($$.fragment !== null) {\n run_all($$.on_destroy);\n $$.fragment && $$.fragment.d(detaching);\n // TODO null out other refs, including component.$$ (but need to\n // preserve final state?)\n $$.on_destroy = $$.fragment = null;\n $$.ctx = [];\n }\n}\nfunction make_dirty(component, i) {\n if (component.$$.dirty[0] === -1) {\n dirty_components.push(component);\n schedule_update();\n component.$$.dirty.fill(0);\n }\n component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));\n}\nfunction init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {\n const parent_component = current_component;\n set_current_component(component);\n const prop_values = options.props || {};\n const $$ = component.$$ = {\n fragment: null,\n ctx: null,\n // state\n props,\n update: noop,\n not_equal,\n bound: blank_object(),\n // lifecycle\n on_mount: [],\n on_destroy: [],\n before_update: [],\n after_update: [],\n context: new Map(parent_component ? parent_component.$$.context : []),\n // everything else\n callbacks: blank_object(),\n dirty,\n skip_bound: false\n };\n let ready = false;\n $$.ctx = instance\n ? instance(component, prop_values, (i, ret, ...rest) => {\n const value = rest.length ? rest[0] : ret;\n if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {\n if (!$$.skip_bound && $$.bound[i])\n $$.bound[i](value);\n if (ready)\n make_dirty(component, i);\n }\n return ret;\n })\n : [];\n $$.update();\n ready = true;\n run_all($$.before_update);\n // `false` as a special case of no DOM component\n $$.fragment = create_fragment ? create_fragment($$.ctx) : false;\n if (options.target) {\n if (options.hydrate) {\n const nodes = children(options.target);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.l(nodes);\n nodes.forEach(detach);\n }\n else {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n $$.fragment && $$.fragment.c();\n }\n if (options.intro)\n transition_in(component.$$.fragment);\n mount_component(component, options.target, options.anchor);\n flush();\n }\n set_current_component(parent_component);\n}\nlet SvelteElement;\nif (typeof HTMLElement === 'function') {\n SvelteElement = class extends HTMLElement {\n constructor() {\n super();\n this.attachShadow({ mode: 'open' });\n }\n connectedCallback() {\n // @ts-ignore todo: improve typings\n for (const key in this.$$.slotted) {\n // @ts-ignore todo: improve typings\n this.appendChild(this.$$.slotted[key]);\n }\n }\n attributeChangedCallback(attr, _oldValue, newValue) {\n this[attr] = newValue;\n }\n $destroy() {\n destroy_component(this, 1);\n this.$destroy = noop;\n }\n $on(type, callback) {\n // TODO should this delegate to addEventListener?\n const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));\n callbacks.push(callback);\n return () => {\n const index = callbacks.indexOf(callback);\n if (index !== -1)\n callbacks.splice(index, 1);\n };\n }\n $set($$props) {\n if (this.$$set && !is_empty($$props)) {\n this.$$.skip_bound = true;\n this.$$set($$props);\n this.$$.skip_bound = false;\n }\n }\n };\n}\nclass SvelteComponent {\n $destroy() {\n destroy_component(this, 1);\n this.$destroy = noop;\n }\n $on(type, callback) {\n const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));\n callbacks.push(callback);\n return () => {\n const index = callbacks.indexOf(callback);\n if (index !== -1)\n callbacks.splice(index, 1);\n };\n }\n $set($$props) {\n if (this.$$set && !is_empty($$props)) {\n this.$$.skip_bound = true;\n this.$$set($$props);\n this.$$.skip_bound = false;\n }\n }\n}\n\nfunction dispatch_dev(type, detail) {\n document.dispatchEvent(custom_event(type, Object.assign({ version: '3.29.6' }, detail)));\n}\nfunction append_dev(target, node) {\n dispatch_dev('SvelteDOMInsert', { target, node });\n append(target, node);\n}\nfunction insert_dev(target, node, anchor) {\n dispatch_dev('SvelteDOMInsert', { target, node, anchor });\n insert(target, node, anchor);\n}\nfunction detach_dev(node) {\n dispatch_dev('SvelteDOMRemove', { node });\n detach(node);\n}\nfunction detach_between_dev(before, after) {\n while (before.nextSibling && before.nextSibling !== after) {\n detach_dev(before.nextSibling);\n }\n}\nfunction detach_before_dev(after) {\n while (after.previousSibling) {\n detach_dev(after.previousSibling);\n }\n}\nfunction detach_after_dev(before) {\n while (before.nextSibling) {\n detach_dev(before.nextSibling);\n }\n}\nfunction listen_dev(node, event, handler, options, has_prevent_default, has_stop_propagation) {\n const modifiers = options === true ? ['capture'] : options ? Array.from(Object.keys(options)) : [];\n if (has_prevent_default)\n modifiers.push('preventDefault');\n if (has_stop_propagation)\n modifiers.push('stopPropagation');\n dispatch_dev('SvelteDOMAddEventListener', { node, event, handler, modifiers });\n const dispose = listen(node, event, handler, options);\n return () => {\n dispatch_dev('SvelteDOMRemoveEventListener', { node, event, handler, modifiers });\n dispose();\n };\n}\nfunction attr_dev(node, attribute, value) {\n attr(node, attribute, value);\n if (value == null)\n dispatch_dev('SvelteDOMRemoveAttribute', { node, attribute });\n else\n dispatch_dev('SvelteDOMSetAttribute', { node, attribute, value });\n}\nfunction prop_dev(node, property, value) {\n node[property] = value;\n dispatch_dev('SvelteDOMSetProperty', { node, property, value });\n}\nfunction dataset_dev(node, property, value) {\n node.dataset[property] = value;\n dispatch_dev('SvelteDOMSetDataset', { node, property, value });\n}\nfunction set_data_dev(text, data) {\n data = '' + data;\n if (text.wholeText === data)\n return;\n dispatch_dev('SvelteDOMSetData', { node: text, data });\n text.data = data;\n}\nfunction validate_each_argument(arg) {\n if (typeof arg !== 'string' && !(arg && typeof arg === 'object' && 'length' in arg)) {\n let msg = '{#each} only iterates over array-like objects.';\n if (typeof Symbol === 'function' && arg && Symbol.iterator in arg) {\n msg += ' You can use a spread to convert this iterable into an array.';\n }\n throw new Error(msg);\n }\n}\nfunction validate_slots(name, slot, keys) {\n for (const slot_key of Object.keys(slot)) {\n if (!~keys.indexOf(slot_key)) {\n console.warn(`<${name}> received an unexpected slot \"${slot_key}\".`);\n }\n }\n}\nclass SvelteComponentDev extends SvelteComponent {\n constructor(options) {\n if (!options || (!options.target && !options.$$inline)) {\n throw new Error(\"'target' is a required option\");\n }\n super();\n }\n $destroy() {\n super.$destroy();\n this.$destroy = () => {\n console.warn('Component was already destroyed'); // eslint-disable-line no-console\n };\n }\n $capture_state() { }\n $inject_state() { }\n}\nfunction loop_guard(timeout) {\n const start = Date.now();\n return () => {\n if (Date.now() - start > timeout) {\n throw new Error('Infinite loop detected');\n }\n };\n}\n\nexport { HtmlTag, SvelteComponent, SvelteComponentDev, SvelteElement, action_destroyer, add_attribute, add_classes, add_flush_callback, add_location, add_render_callback, add_resize_listener, add_transform, afterUpdate, append, append_dev, assign, attr, attr_dev, attribute_to_object, beforeUpdate, bind, binding_callbacks, blank_object, bubble, check_outros, children, claim_component, claim_element, claim_space, claim_text, clear_loops, component_subscribe, compute_rest_props, compute_slots, createEventDispatcher, create_animation, create_bidirectional_transition, create_component, create_in_transition, create_out_transition, create_slot, create_ssr_component, current_component, custom_event, dataset_dev, debug, destroy_block, destroy_component, destroy_each, detach, detach_after_dev, detach_before_dev, detach_between_dev, detach_dev, dirty_components, dispatch_dev, each, element, element_is, empty, escape, escaped, exclude_internal_props, fix_and_destroy_block, fix_and_outro_and_destroy_block, fix_position, flush, getContext, get_binding_group_value, get_current_component, get_custom_elements_slots, get_slot_changes, get_slot_context, get_spread_object, get_spread_update, get_store_value, globals, group_outros, handle_promise, has_prop, identity, init, insert, insert_dev, intros, invalid_attribute_name_character, is_client, is_crossorigin, is_empty, is_function, is_promise, listen, listen_dev, loop, loop_guard, missing_component, mount_component, noop, not_equal, now, null_to_empty, object_without_properties, onDestroy, onMount, once, outro_and_destroy_block, prevent_default, prop_dev, query_selector_all, raf, run, run_all, safe_not_equal, schedule_update, select_multiple_value, select_option, select_options, select_value, self, setContext, set_attributes, set_current_component, set_custom_element_data, set_data, set_data_dev, set_input_type, set_input_value, set_now, set_raf, set_store_value, set_style, set_svg_attributes, space, spread, stop_propagation, subscribe, svg_element, text, tick, time_ranges_to_array, to_number, toggle_class, transition_in, transition_out, update_keyed_each, update_slot, validate_component, validate_each_argument, validate_each_keys, validate_slots, validate_store, xlink_attr };\n","function styleInject(css, ref) {\n if ( ref === void 0 ) ref = {};\n var insertAt = ref.insertAt;\n\n if (!css || typeof document === 'undefined') { return; }\n\n var head = document.head || document.getElementsByTagName('head')[0];\n var style = document.createElement('style');\n style.type = 'text/css';\n\n if (insertAt === 'top') {\n if (head.firstChild) {\n head.insertBefore(style, head.firstChild);\n } else {\n head.appendChild(style);\n }\n } else {\n head.appendChild(style);\n }\n\n if (style.styleSheet) {\n style.styleSheet.cssText = css;\n } else {\n style.appendChild(document.createTextNode(css));\n }\n}\n\nexport default styleInject;\n","export { identity as linear } from '../internal/index.mjs';\n\n/*\nAdapted from https://github.com/mattdesl\nDistributed under MIT License https://github.com/mattdesl/eases/blob/master/LICENSE.md\n*/\nfunction backInOut(t) {\n const s = 1.70158 * 1.525;\n if ((t *= 2) < 1)\n return 0.5 * (t * t * ((s + 1) * t - s));\n return 0.5 * ((t -= 2) * t * ((s + 1) * t + s) + 2);\n}\nfunction backIn(t) {\n const s = 1.70158;\n return t * t * ((s + 1) * t - s);\n}\nfunction backOut(t) {\n const s = 1.70158;\n return --t * t * ((s + 1) * t + s) + 1;\n}\nfunction bounceOut(t) {\n const a = 4.0 / 11.0;\n const b = 8.0 / 11.0;\n const c = 9.0 / 10.0;\n const ca = 4356.0 / 361.0;\n const cb = 35442.0 / 1805.0;\n const cc = 16061.0 / 1805.0;\n const t2 = t * t;\n return t < a\n ? 7.5625 * t2\n : t < b\n ? 9.075 * t2 - 9.9 * t + 3.4\n : t < c\n ? ca * t2 - cb * t + cc\n : 10.8 * t * t - 20.52 * t + 10.72;\n}\nfunction bounceInOut(t) {\n return t < 0.5\n ? 0.5 * (1.0 - bounceOut(1.0 - t * 2.0))\n : 0.5 * bounceOut(t * 2.0 - 1.0) + 0.5;\n}\nfunction bounceIn(t) {\n return 1.0 - bounceOut(1.0 - t);\n}\nfunction circInOut(t) {\n if ((t *= 2) < 1)\n return -0.5 * (Math.sqrt(1 - t * t) - 1);\n return 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);\n}\nfunction circIn(t) {\n return 1.0 - Math.sqrt(1.0 - t * t);\n}\nfunction circOut(t) {\n return Math.sqrt(1 - --t * t);\n}\nfunction cubicInOut(t) {\n return t < 0.5 ? 4.0 * t * t * t : 0.5 * Math.pow(2.0 * t - 2.0, 3.0) + 1.0;\n}\nfunction cubicIn(t) {\n return t * t * t;\n}\nfunction cubicOut(t) {\n const f = t - 1.0;\n return f * f * f + 1.0;\n}\nfunction elasticInOut(t) {\n return t < 0.5\n ? 0.5 *\n Math.sin(((+13.0 * Math.PI) / 2) * 2.0 * t) *\n Math.pow(2.0, 10.0 * (2.0 * t - 1.0))\n : 0.5 *\n Math.sin(((-13.0 * Math.PI) / 2) * (2.0 * t - 1.0 + 1.0)) *\n Math.pow(2.0, -10.0 * (2.0 * t - 1.0)) +\n 1.0;\n}\nfunction elasticIn(t) {\n return Math.sin((13.0 * t * Math.PI) / 2) * Math.pow(2.0, 10.0 * (t - 1.0));\n}\nfunction elasticOut(t) {\n return (Math.sin((-13.0 * (t + 1.0) * Math.PI) / 2) * Math.pow(2.0, -10.0 * t) + 1.0);\n}\nfunction expoInOut(t) {\n return t === 0.0 || t === 1.0\n ? t\n : t < 0.5\n ? +0.5 * Math.pow(2.0, 20.0 * t - 10.0)\n : -0.5 * Math.pow(2.0, 10.0 - t * 20.0) + 1.0;\n}\nfunction expoIn(t) {\n return t === 0.0 ? t : Math.pow(2.0, 10.0 * (t - 1.0));\n}\nfunction expoOut(t) {\n return t === 1.0 ? t : 1.0 - Math.pow(2.0, -10.0 * t);\n}\nfunction quadInOut(t) {\n t /= 0.5;\n if (t < 1)\n return 0.5 * t * t;\n t--;\n return -0.5 * (t * (t - 2) - 1);\n}\nfunction quadIn(t) {\n return t * t;\n}\nfunction quadOut(t) {\n return -t * (t - 2.0);\n}\nfunction quartInOut(t) {\n return t < 0.5\n ? +8.0 * Math.pow(t, 4.0)\n : -8.0 * Math.pow(t - 1.0, 4.0) + 1.0;\n}\nfunction quartIn(t) {\n return Math.pow(t, 4.0);\n}\nfunction quartOut(t) {\n return Math.pow(t - 1.0, 3.0) * (1.0 - t) + 1.0;\n}\nfunction quintInOut(t) {\n if ((t *= 2) < 1)\n return 0.5 * t * t * t * t * t;\n return 0.5 * ((t -= 2) * t * t * t * t + 2);\n}\nfunction quintIn(t) {\n return t * t * t * t * t;\n}\nfunction quintOut(t) {\n return --t * t * t * t * t + 1;\n}\nfunction sineInOut(t) {\n return -0.5 * (Math.cos(Math.PI * t) - 1);\n}\nfunction sineIn(t) {\n const v = Math.cos(t * Math.PI * 0.5);\n if (Math.abs(v) < 1e-14)\n return 1;\n else\n return 1 - v;\n}\nfunction sineOut(t) {\n return Math.sin((t * Math.PI) / 2);\n}\n\nexport { backIn, backInOut, backOut, bounceIn, bounceInOut, bounceOut, circIn, circInOut, circOut, cubicIn, cubicInOut, cubicOut, elasticIn, elasticInOut, elasticOut, expoIn, expoInOut, expoOut, quadIn, quadInOut, quadOut, quartIn, quartInOut, quartOut, quintIn, quintInOut, quintOut, sineIn, sineInOut, sineOut };\n","import { cubicInOut, linear, cubicOut } from '../easing/index.mjs';\nimport { is_function, assign } from '../internal/index.mjs';\n\n/*! *****************************************************************************\r\nCopyright (c) Microsoft Corporation. All rights reserved.\r\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not use\r\nthis file except in compliance with the License. You may obtain a copy of the\r\nLicense at http://www.apache.org/licenses/LICENSE-2.0\r\n\r\nTHIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\r\nKIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\r\nWARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\r\nMERCHANTABLITY OR NON-INFRINGEMENT.\r\n\r\nSee the Apache Version 2.0 License for specific language governing permissions\r\nand limitations under the License.\r\n***************************************************************************** */\r\n\r\nfunction __rest(s, e) {\r\n var t = {};\r\n for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)\r\n t[p] = s[p];\r\n if (s != null && typeof Object.getOwnPropertySymbols === \"function\")\r\n for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {\r\n if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))\r\n t[p[i]] = s[p[i]];\r\n }\r\n return t;\r\n}\n\nfunction blur(node, { delay = 0, duration = 400, easing = cubicInOut, amount = 5, opacity = 0 }) {\n const style = getComputedStyle(node);\n const target_opacity = +style.opacity;\n const f = style.filter === 'none' ? '' : style.filter;\n const od = target_opacity * (1 - opacity);\n return {\n delay,\n duration,\n easing,\n css: (_t, u) => `opacity: ${target_opacity - (od * u)}; filter: ${f} blur(${u * amount}px);`\n };\n}\nfunction fade(node, { delay = 0, duration = 400, easing = linear }) {\n const o = +getComputedStyle(node).opacity;\n return {\n delay,\n duration,\n easing,\n css: t => `opacity: ${t * o}`\n };\n}\nfunction fly(node, { delay = 0, duration = 400, easing = cubicOut, x = 0, y = 0, opacity = 0 }) {\n const style = getComputedStyle(node);\n const target_opacity = +style.opacity;\n const transform = style.transform === 'none' ? '' : style.transform;\n const od = target_opacity * (1 - opacity);\n return {\n delay,\n duration,\n easing,\n css: (t, u) => `\n\t\t\ttransform: ${transform} translate(${(1 - t) * x}px, ${(1 - t) * y}px);\n\t\t\topacity: ${target_opacity - (od * u)}`\n };\n}\nfunction slide(node, { delay = 0, duration = 400, easing = cubicOut }) {\n const style = getComputedStyle(node);\n const opacity = +style.opacity;\n const height = parseFloat(style.height);\n const padding_top = parseFloat(style.paddingTop);\n const padding_bottom = parseFloat(style.paddingBottom);\n const margin_top = parseFloat(style.marginTop);\n const margin_bottom = parseFloat(style.marginBottom);\n const border_top_width = parseFloat(style.borderTopWidth);\n const border_bottom_width = parseFloat(style.borderBottomWidth);\n return {\n delay,\n duration,\n easing,\n css: t => 'overflow: hidden;' +\n `opacity: ${Math.min(t * 20, 1) * opacity};` +\n `height: ${t * height}px;` +\n `padding-top: ${t * padding_top}px;` +\n `padding-bottom: ${t * padding_bottom}px;` +\n `margin-top: ${t * margin_top}px;` +\n `margin-bottom: ${t * margin_bottom}px;` +\n `border-top-width: ${t * border_top_width}px;` +\n `border-bottom-width: ${t * border_bottom_width}px;`\n };\n}\nfunction scale(node, { delay = 0, duration = 400, easing = cubicOut, start = 0, opacity = 0 }) {\n const style = getComputedStyle(node);\n const target_opacity = +style.opacity;\n const transform = style.transform === 'none' ? '' : style.transform;\n const sd = 1 - start;\n const od = target_opacity * (1 - opacity);\n return {\n delay,\n duration,\n easing,\n css: (_t, u) => `\n\t\t\ttransform: ${transform} scale(${1 - (sd * u)});\n\t\t\topacity: ${target_opacity - (od * u)}\n\t\t`\n };\n}\nfunction draw(node, { delay = 0, speed, duration, easing = cubicInOut }) {\n const len = node.getTotalLength();\n if (duration === undefined) {\n if (speed === undefined) {\n duration = 800;\n }\n else {\n duration = len / speed;\n }\n }\n else if (typeof duration === 'function') {\n duration = duration(len);\n }\n return {\n delay,\n duration,\n easing,\n css: (t, u) => `stroke-dasharray: ${t * len} ${u * len}`\n };\n}\nfunction crossfade(_a) {\n var { fallback } = _a, defaults = __rest(_a, [\"fallback\"]);\n const to_receive = new Map();\n const to_send = new Map();\n function crossfade(from, node, params) {\n const { delay = 0, duration = d => Math.sqrt(d) * 30, easing = cubicOut } = assign(assign({}, defaults), params);\n const to = node.getBoundingClientRect();\n const dx = from.left - to.left;\n const dy = from.top - to.top;\n const dw = from.width / to.width;\n const dh = from.height / to.height;\n const d = Math.sqrt(dx * dx + dy * dy);\n const style = getComputedStyle(node);\n const transform = style.transform === 'none' ? '' : style.transform;\n const opacity = +style.opacity;\n return {\n delay,\n duration: is_function(duration) ? duration(d) : duration,\n easing,\n css: (t, u) => `\n\t\t\t\topacity: ${t * opacity};\n\t\t\t\ttransform-origin: top left;\n\t\t\t\ttransform: ${transform} translate(${u * dx}px,${u * dy}px) scale(${t + (1 - t) * dw}, ${t + (1 - t) * dh});\n\t\t\t`\n };\n }\n function transition(items, counterparts, intro) {\n return (node, params) => {\n items.set(params.key, {\n rect: node.getBoundingClientRect()\n });\n return () => {\n if (counterparts.has(params.key)) {\n const { rect } = counterparts.get(params.key);\n counterparts.delete(params.key);\n return crossfade(rect, node, params);\n }\n // if the node is disappearing altogether\n // (i.e. wasn't claimed by the other list)\n // then we need to supply an outro\n items.delete(params.key);\n return fallback && fallback(node, params, intro);\n };\n };\n }\n return [\n transition(to_send, to_receive, false),\n transition(to_receive, to_send, true)\n ];\n}\n\nexport { blur, crossfade, draw, fade, fly, scale, slide };\n","/*\n Loki IndexedDb Adapter (need to include this script to use it)\n\n Console Usage can be used for management/diagnostic, here are a few examples :\n adapter.getDatabaseList(); // with no callback passed, this method will log results to console\n adapter.saveDatabase('UserDatabase', JSON.stringify(myDb));\n adapter.loadDatabase('UserDatabase'); // will log the serialized db to console\n adapter.deleteDatabase('UserDatabase');\n*/\n\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD\n define([], factory);\n } else if (typeof exports === 'object') {\n // Node, CommonJS-like\n module.exports = factory();\n } else {\n // Browser globals (root is window)\n root.LokiIndexedAdapter = factory();\n }\n}(this, function () {\n return (function() {\n\n /**\n * Loki persistence adapter class for indexedDb.\n * This class fulfills abstract adapter interface which can be applied to other storage methods.\n * Utilizes the included LokiCatalog app/key/value database for actual database persistence.\n * Indexeddb is highly async, but this adapter has been made 'console-friendly' as well.\n * Anywhere a callback is omitted, it should return results (if applicable) to console.\n * IndexedDb storage is provided per-domain, so we implement app/key/value database to\n * allow separate contexts for separate apps within a domain.\n *\n * @example\n * var idbAdapter = new LokiIndexedAdapter('finance');\n *\n * @constructor LokiIndexedAdapter\n *\n * @param {string} appname - (Optional) Application name context can be used to distinguish subdomains, 'loki' by default\n * @param {object=} options Configuration options for the adapter\n * @param {boolean} options.closeAfterSave Whether the indexedDB database should be closed after saving.\n */\n function LokiIndexedAdapter(appname, options)\n {\n this.app = 'loki';\n this.options = options || {};\n\n if (typeof (appname) !== 'undefined')\n {\n this.app = appname;\n }\n\n // keep reference to catalog class for base AKV operations\n this.catalog = null;\n\n if (!this.checkAvailability()) {\n throw new Error('indexedDB does not seem to be supported for your environment');\n }\n }\n\n /**\n * Used for closing the indexeddb database.\n */\n LokiIndexedAdapter.prototype.closeDatabase = function ()\n {\n if (this.catalog && this.catalog.db) {\n this.catalog.db.close();\n this.catalog.db = null;\n }\n };\n\n /**\n * Used to check if adapter is available\n *\n * @returns {boolean} true if indexeddb is available, false if not.\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.checkAvailability = function()\n {\n if (typeof indexedDB !== 'undefined' && indexedDB) return true;\n\n return false;\n };\n\n /**\n * Retrieves a serialized db string from the catalog.\n *\n * @example\n * // LOAD\n * var idbAdapter = new LokiIndexedAdapter('finance');\n * var db = new loki('test', { adapter: idbAdapter });\n * db.loadDatabase(function(result) {\n * console.log('done');\n * });\n *\n * @param {string} dbname - the name of the database to retrieve.\n * @param {function} callback - callback should accept string param containing serialized db string.\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.loadDatabase = function(dbname, callback)\n {\n var appName = this.app;\n var adapter = this;\n\n // lazy open/create db reference so dont -need- callback in constructor\n if (this.catalog === null || this.catalog.db === null) {\n this.catalog = new LokiCatalog(function(cat) {\n adapter.catalog = cat;\n\n adapter.loadDatabase(dbname, callback);\n });\n\n return;\n }\n\n // lookup up db string in AKV db\n this.catalog.getAppKey(appName, dbname, function(result) {\n if (typeof (callback) === 'function') {\n if (result.id === 0) {\n callback(null);\n return;\n }\n callback(result.val);\n }\n else {\n // support console use of api\n console.log(result.val);\n }\n });\n };\n\n // alias\n LokiIndexedAdapter.prototype.loadKey = LokiIndexedAdapter.prototype.loadDatabase;\n\n /**\n * Saves a serialized db to the catalog.\n *\n * @example\n * // SAVE : will save App/Key/Val as 'finance'/'test'/{serializedDb}\n * var idbAdapter = new LokiIndexedAdapter('finance');\n * var db = new loki('test', { adapter: idbAdapter });\n * var coll = db.addCollection('testColl');\n * coll.insert({test: 'val'});\n * db.saveDatabase(); // could pass callback if needed for async complete\n *\n * @param {string} dbname - the name to give the serialized database within the catalog.\n * @param {string} dbstring - the serialized db string to save.\n * @param {function} callback - (Optional) callback passed obj.success with true or false\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.saveDatabase = function(dbname, dbstring, callback)\n {\n var appName = this.app;\n var adapter = this;\n\n function saveCallback(result) {\n if (result && result.success === true) {\n callback(null);\n }\n else {\n callback(new Error(\"Error saving database\"));\n }\n\n if (adapter.options.closeAfterSave) {\n adapter.closeDatabase();\n }\n }\n\n // lazy open/create db reference so dont -need- callback in constructor\n if (this.catalog === null || this.catalog.db === null) {\n this.catalog = new LokiCatalog(function(cat) {\n adapter.saveDatabase(dbname, dbstring, saveCallback);\n });\n\n return;\n }\n\n // set (add/update) entry to AKV database\n this.catalog.setAppKey(appName, dbname, dbstring, saveCallback);\n };\n\n // alias\n LokiIndexedAdapter.prototype.saveKey = LokiIndexedAdapter.prototype.saveDatabase;\n\n /**\n * Deletes a serialized db from the catalog.\n *\n * @example\n * // DELETE DATABASE\n * // delete 'finance'/'test' value from catalog\n * idbAdapter.deleteDatabase('test', function {\n * // database deleted\n * });\n *\n * @param {string} dbname - the name of the database to delete from the catalog.\n * @param {function=} callback - (Optional) executed on database delete\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.deleteDatabase = function(dbname, callback)\n {\n var appName = this.app;\n var adapter = this;\n\n // lazy open/create db reference and pass callback ahead\n if (this.catalog === null || this.catalog.db === null) {\n this.catalog = new LokiCatalog(function(cat) {\n adapter.catalog = cat;\n\n adapter.deleteDatabase(dbname, callback);\n });\n\n return;\n }\n\n // catalog was already initialized, so just lookup object and delete by id\n this.catalog.getAppKey(appName, dbname, function(result) {\n var id = result.id;\n\n if (id !== 0) {\n adapter.catalog.deleteAppKey(id, callback);\n } else if (typeof (callback) === 'function') {\n callback({ success: true });\n }\n });\n };\n\n // alias\n LokiIndexedAdapter.prototype.deleteKey = LokiIndexedAdapter.prototype.deleteDatabase;\n\n /**\n * Removes all database partitions and pages with the base filename passed in.\n * This utility method does not (yet) guarantee async deletions will be completed before returning\n *\n * @param {string} dbname - the base filename which container, partitions, or pages are derived\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.deleteDatabasePartitions = function(dbname) {\n var self=this;\n this.getDatabaseList(function(result) {\n result.forEach(function(str) {\n if (str.startsWith(dbname)) {\n self.deleteDatabase(str);\n }\n });\n });\n };\n\n /**\n * Retrieves object array of catalog entries for current app.\n *\n * @example\n * idbAdapter.getDatabaseList(function(result) {\n * // result is array of string names for that appcontext ('finance')\n * result.forEach(function(str) {\n * console.log(str);\n * });\n * });\n *\n * @param {function} callback - should accept array of database names in the catalog for current app.\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.getDatabaseList = function(callback)\n {\n var appName = this.app;\n var adapter = this;\n\n // lazy open/create db reference so dont -need- callback in constructor\n if (this.catalog === null || this.catalog.db === null) {\n this.catalog = new LokiCatalog(function(cat) {\n adapter.catalog = cat;\n\n adapter.getDatabaseList(callback);\n });\n\n return;\n }\n\n // catalog already initialized\n // get all keys for current appName, and transpose results so just string array\n this.catalog.getAppKeys(appName, function(results) {\n var names = [];\n\n for(var idx = 0; idx < results.length; idx++) {\n names.push(results[idx].key);\n }\n\n if (typeof (callback) === 'function') {\n callback(names);\n }\n else {\n names.forEach(function(obj) {\n console.log(obj);\n });\n }\n });\n };\n\n // alias\n LokiIndexedAdapter.prototype.getKeyList = LokiIndexedAdapter.prototype.getDatabaseList;\n\n /**\n * Allows retrieval of list of all keys in catalog along with size\n *\n * @param {function} callback - (Optional) callback to accept result array.\n * @memberof LokiIndexedAdapter\n */\n LokiIndexedAdapter.prototype.getCatalogSummary = function(callback)\n {\n var appName = this.app;\n var adapter = this;\n\n // lazy open/create db reference\n if (this.catalog === null || this.catalog.db === null) {\n this.catalog = new LokiCatalog(function(cat) {\n adapter.catalog = cat;\n\n adapter.getCatalogSummary(callback);\n });\n\n return;\n }\n\n // catalog already initialized\n // get all keys for current appName, and transpose results so just string array\n this.catalog.getAllKeys(function(results) {\n var entries = [];\n var obj,\n size,\n oapp,\n okey,\n oval;\n\n for(var idx = 0; idx < results.length; idx++) {\n obj = results[idx];\n oapp = obj.app || '';\n okey = obj.key || '';\n oval = obj.val || '';\n\n // app and key are composited into an appkey column so we will mult by 2\n size = oapp.length * 2 + okey.length * 2 + oval.length + 1;\n\n entries.push({ \"app\": obj.app, \"key\": obj.key, \"size\": size });\n }\n\n if (typeof (callback) === 'function') {\n callback(entries);\n }\n else {\n entries.forEach(function(obj) {\n console.log(obj);\n });\n }\n });\n };\n\n /**\n * LokiCatalog - underlying App/Key/Value catalog persistence\n * This non-interface class implements the actual persistence.\n * Used by the IndexedAdapter class.\n */\n function LokiCatalog(callback)\n {\n this.db = null;\n this.initializeLokiCatalog(callback);\n }\n\n LokiCatalog.prototype.initializeLokiCatalog = function(callback) {\n var openRequest = indexedDB.open('LokiCatalog', 1);\n var cat = this;\n\n // If database doesn't exist yet or its version is lower than our version specified above (2nd param in line above)\n openRequest.onupgradeneeded = function(e) {\n var thisDB = e.target.result;\n if (thisDB.objectStoreNames.contains('LokiAKV')) {\n thisDB.deleteObjectStore('LokiAKV');\n }\n\n if(!thisDB.objectStoreNames.contains('LokiAKV')) {\n var objectStore = thisDB.createObjectStore('LokiAKV', { keyPath: 'id', autoIncrement:true });\n objectStore.createIndex('app', 'app', {unique:false});\n objectStore.createIndex('key', 'key', {unique:false});\n // hack to simulate composite key since overhead is low (main size should be in val field)\n // user (me) required to duplicate the app and key into comma delimited appkey field off object\n // This will allow retrieving single record with that composite key as well as\n // still supporting opening cursors on app or key alone\n objectStore.createIndex('appkey', 'appkey', {unique:true});\n }\n };\n\n openRequest.onsuccess = function(e) {\n cat.db = e.target.result;\n\n if (typeof (callback) === 'function') callback(cat);\n };\n\n openRequest.onerror = function(e) {\n throw e;\n };\n };\n\n LokiCatalog.prototype.getAppKey = function(app, key, callback) {\n var transaction = this.db.transaction(['LokiAKV'], 'readonly');\n var store = transaction.objectStore('LokiAKV');\n var index = store.index('appkey');\n var appkey = app + \",\" + key;\n var request = index.get(appkey);\n\n request.onsuccess = (function(usercallback) {\n return function(e) {\n var lres = e.target.result;\n\n if (lres === null || typeof(lres) === 'undefined') {\n lres = {\n id: 0,\n success: false\n };\n }\n\n if (typeof(usercallback) === 'function') {\n usercallback(lres);\n }\n else {\n console.log(lres);\n }\n };\n })(callback);\n\n request.onerror = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback({ id: 0, success: false });\n }\n else {\n throw e;\n }\n };\n })(callback);\n };\n\n LokiCatalog.prototype.getAppKeyById = function (id, callback, data) {\n var transaction = this.db.transaction(['LokiAKV'], 'readonly');\n var store = transaction.objectStore('LokiAKV');\n var request = store.get(id);\n\n request.onsuccess = (function(data, usercallback){\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback(e.target.result, data);\n }\n else {\n console.log(e.target.result);\n }\n };\n })(data, callback);\n };\n\n LokiCatalog.prototype.setAppKey = function (app, key, val, callback) {\n var transaction = this.db.transaction(['LokiAKV'], 'readwrite');\n var store = transaction.objectStore('LokiAKV');\n var index = store.index('appkey');\n var appkey = app + \",\" + key;\n var request = index.get(appkey);\n\n // first try to retrieve an existing object by that key\n // need to do this because to update an object you need to have id in object, otherwise it will append id with new autocounter and clash the unique index appkey\n request.onsuccess = function(e) {\n var res = e.target.result;\n\n if (res === null || res === undefined) {\n res = {\n app:app,\n key:key,\n appkey: app + ',' + key,\n val:val\n };\n }\n else {\n res.val = val;\n }\n\n var requestPut = store.put(res);\n\n requestPut.onerror = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback({ success: false });\n }\n else {\n console.error('LokiCatalog.setAppKey (set) onerror');\n console.error(request.error);\n }\n };\n\n })(callback);\n\n requestPut.onsuccess = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback({ success: true });\n }\n };\n })(callback);\n };\n\n request.onerror = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback({ success: false });\n }\n else {\n console.error('LokiCatalog.setAppKey (get) onerror');\n console.error(request.error);\n }\n };\n })(callback);\n };\n\n LokiCatalog.prototype.deleteAppKey = function (id, callback) {\n var transaction = this.db.transaction(['LokiAKV'], 'readwrite');\n var store = transaction.objectStore('LokiAKV');\n var request = store.delete(id);\n\n request.onsuccess = (function(usercallback) {\n return function(evt) {\n if (typeof(usercallback) === 'function') usercallback({ success: true });\n };\n })(callback);\n\n request.onerror = (function(usercallback) {\n return function(evt) {\n if (typeof(usercallback) === 'function') {\n usercallback({ success: false });\n }\n else {\n console.error('LokiCatalog.deleteAppKey raised onerror');\n console.error(request.error);\n }\n };\n })(callback);\n };\n\n LokiCatalog.prototype.getAppKeys = function(app, callback) {\n var transaction = this.db.transaction(['LokiAKV'], 'readonly');\n var store = transaction.objectStore('LokiAKV');\n var index = store.index('app');\n\n // We want cursor to all values matching our (single) app param\n var singleKeyRange = IDBKeyRange.only(app);\n\n // To use one of the key ranges, pass it in as the first argument of openCursor()/openKeyCursor()\n var cursor = index.openCursor(singleKeyRange);\n\n // cursor internally, pushing results into this.data[] and return\n // this.data[] when done (similar to service)\n var localdata = [];\n\n cursor.onsuccess = (function(data, callback) {\n return function(e) {\n var cursor = e.target.result;\n if (cursor) {\n var currObject = cursor.value;\n\n data.push(currObject);\n\n cursor.continue();\n }\n else {\n if (typeof(callback) === 'function') {\n callback(data);\n }\n else {\n console.log(data);\n }\n }\n };\n })(localdata, callback);\n\n cursor.onerror = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') {\n usercallback(null);\n }\n else {\n console.error('LokiCatalog.getAppKeys raised onerror');\n console.error(e);\n }\n };\n })(callback);\n\n };\n\n // Hide 'cursoring' and return array of { id: id, key: key }\n LokiCatalog.prototype.getAllKeys = function (callback) {\n var transaction = this.db.transaction(['LokiAKV'], 'readonly');\n var store = transaction.objectStore('LokiAKV');\n var cursor = store.openCursor();\n\n var localdata = [];\n\n cursor.onsuccess = (function(data, callback) {\n return function(e) {\n var cursor = e.target.result;\n if (cursor) {\n var currObject = cursor.value;\n\n data.push(currObject);\n\n cursor.continue();\n }\n else {\n if (typeof(callback) === 'function') {\n callback(data);\n }\n else {\n console.log(data);\n }\n }\n };\n })(localdata, callback);\n\n cursor.onerror = (function(usercallback) {\n return function(e) {\n if (typeof(usercallback) === 'function') usercallback(null);\n };\n })(callback);\n\n };\n\n return LokiIndexedAdapter;\n\n }());\n}));\n","export default {};\n","/**\n * LokiJS\n * @author Joe Minichino \n *\n * A lightweight document oriented javascript database\n */\n(function (root, factory) {\n if (typeof define === 'function' && define.amd) {\n // AMD\n define([], factory);\n } else if (typeof exports === 'object') {\n // CommonJS\n module.exports = factory();\n } else {\n // Browser globals\n root.loki = factory();\n }\n}(this, function () {\n\n return (function () {\n 'use strict';\n\n var hasOwnProperty = Object.prototype.hasOwnProperty;\n\n function deepFreeze(obj) {\n var prop, i;\n if (Array.isArray(obj)) {\n for (i = 0; i < obj.length; i++) {\n deepFreeze(obj[i]);\n }\n freeze(obj);\n } else if (obj !== null && (typeof obj === 'object')) {\n for (prop in obj) {\n if (obj.hasOwnProperty(prop)) {\n deepFreeze(obj[prop]);\n }\n }\n freeze(obj);\n }\n }\n\n function freeze(obj) {\n if (!Object.isFrozen(obj)) {\n Object.freeze(obj);\n }\n }\n\n function unFreeze(obj) {\n if (!Object.isFrozen(obj)) {\n return obj;\n }\n return clone(obj, 'shallow');\n }\n\n var Utils = {\n copyProperties: function (src, dest) {\n var prop;\n for (prop in src) {\n dest[prop] = src[prop];\n }\n },\n // used to recursively scan hierarchical transform step object for param substitution\n resolveTransformObject: function (subObj, params, depth) {\n var prop,\n pname;\n\n if (typeof depth !== 'number') {\n depth = 0;\n }\n\n if (++depth >= 10) return subObj;\n\n for (prop in subObj) {\n if (typeof subObj[prop] === 'string' && subObj[prop].indexOf(\"[%lktxp]\") === 0) {\n pname = subObj[prop].substring(8);\n if (params.hasOwnProperty(pname)) {\n subObj[prop] = params[pname];\n }\n } else if (typeof subObj[prop] === \"object\") {\n subObj[prop] = Utils.resolveTransformObject(subObj[prop], params, depth);\n }\n }\n\n return subObj;\n },\n // top level utility to resolve an entire (single) transform (array of steps) for parameter substitution\n resolveTransformParams: function (transform, params) {\n var idx,\n clonedStep,\n resolvedTransform = [];\n\n if (typeof params === 'undefined') return transform;\n\n // iterate all steps in the transform array\n for (idx = 0; idx < transform.length; idx++) {\n // clone transform so our scan/replace can operate directly on cloned transform\n clonedStep = clone(transform[idx], \"shallow-recurse-objects\");\n resolvedTransform.push(Utils.resolveTransformObject(clonedStep, params));\n }\n\n return resolvedTransform;\n },\n\n // By default (if usingDotNotation is false), looks up path in\n // object via `object[path]`\n //\n // If `usingDotNotation` is true, then the path is assumed to\n // represent a nested path. It can be in the form of an array of\n // field names, or a period delimited string. The function will\n // look up the value of object[path[0]], and then call\n // result[path[1]] on the result, etc etc.\n //\n // If `usingDotNotation` is true, this function still supports\n // non nested fields.\n //\n // `usingDotNotation` is a performance optimization. The caller\n // may know that a path is *not* nested. In which case, this\n // function avoids a costly string.split('.')\n //\n // examples:\n // getIn({a: 1}, \"a\") => 1\n // getIn({a: 1}, \"a\", true) => 1\n // getIn({a: {b: 1}}, [\"a\", \"b\"], true) => 1\n // getIn({a: {b: 1}}, \"a.b\", true) => 1\n getIn: function (object, path, usingDotNotation) {\n if (object == null) {\n return undefined;\n }\n if (!usingDotNotation) {\n return object[path];\n }\n\n if (typeof (path) === \"string\") {\n path = path.split(\".\");\n }\n\n if (!Array.isArray(path)) {\n throw new Error(\"path must be a string or array. Found \" + typeof (path));\n }\n\n var index = 0,\n length = path.length;\n\n while (object != null && index < length) {\n object = object[path[index++]];\n }\n return (index && index == length) ? object : undefined;\n }\n };\n\n // wrapping in object to expose to default export for potential user override.\n // warning: overriding these methods will override behavior for all loki db instances in memory.\n // warning: if you use binary indices these comparators should be the same for all inserts/updates/removes.\n var Comparators = {\n aeq: aeqHelper,\n lt: ltHelper,\n gt: gtHelper\n };\n\n /** Helper function for determining 'loki' abstract equality which is a little more abstract than ==\n * aeqHelper(5, '5') === true\n * aeqHelper(5.0, '5') === true\n * aeqHelper(new Date(\"1/1/2011\"), new Date(\"1/1/2011\")) === true\n * aeqHelper({a:1}, {z:4}) === true (all objects sorted equally)\n * aeqHelper([1, 2, 3], [1, 3]) === false\n * aeqHelper([1, 2, 3], [1, 2, 3]) === true\n * aeqHelper(undefined, null) === true\n */\n function aeqHelper(prop1, prop2) {\n var cv1, cv2, t1, t2;\n\n if (prop1 === prop2) return true;\n\n // 'falsy' and Boolean handling\n if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {\n // dates and NaN conditions (typed dates before serialization)\n switch (prop1) {\n case undefined: t1 = 1; break;\n case null: t1 = 1; break;\n case false: t1 = 3; break;\n case true: t1 = 4; break;\n case \"\": t1 = 5; break;\n default: t1 = (prop1 === prop1) ? 9 : 0; break;\n }\n\n switch (prop2) {\n case undefined: t2 = 1; break;\n case null: t2 = 1; break;\n case false: t2 = 3; break;\n case true: t2 = 4; break;\n case \"\": t2 = 5; break;\n default: t2 = (prop2 === prop2) ? 9 : 0; break;\n }\n\n // one or both is edge case\n if (t1 !== 9 || t2 !== 9) {\n return (t1 === t2);\n }\n }\n\n // Handle 'Number-like' comparisons\n cv1 = Number(prop1);\n cv2 = Number(prop2);\n\n // if one or both are 'number-like'...\n if (cv1 === cv1 || cv2 === cv2) {\n return (cv1 === cv2);\n }\n\n // not strict equal nor less than nor gt so must be mixed types, convert to string and use that to compare\n cv1 = prop1.toString();\n cv2 = prop2.toString();\n\n return (cv1 == cv2);\n }\n\n /** Helper function for determining 'less-than' conditions for ops, sorting, and binary indices.\n * In the future we might want $lt and $gt ops to use their own functionality/helper.\n * Since binary indices on a property might need to index [12, NaN, new Date(), Infinity], we\n * need this function (as well as gtHelper) to always ensure one value is LT, GT, or EQ to another.\n */\n function ltHelper(prop1, prop2, equal) {\n var cv1, cv2, t1, t2;\n\n // if one of the params is falsy or strictly true or not equal to itself\n // 0, 0.0, \"\", NaN, null, undefined, not defined, false, true\n if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {\n switch (prop1) {\n case undefined: t1 = 1; break;\n case null: t1 = 1; break;\n case false: t1 = 3; break;\n case true: t1 = 4; break;\n case \"\": t1 = 5; break;\n // if strict equal probably 0 so sort higher, otherwise probably NaN so sort lower than even null\n default: t1 = (prop1 === prop1) ? 9 : 0; break;\n }\n\n switch (prop2) {\n case undefined: t2 = 1; break;\n case null: t2 = 1; break;\n case false: t2 = 3; break;\n case true: t2 = 4; break;\n case \"\": t2 = 5; break;\n default: t2 = (prop2 === prop2) ? 9 : 0; break;\n }\n\n // one or both is edge case\n if (t1 !== 9 || t2 !== 9) {\n return (t1 === t2) ? equal : (t1 < t2);\n }\n }\n\n // if both are numbers (string encoded or not), compare as numbers\n cv1 = Number(prop1);\n cv2 = Number(prop2);\n\n if (cv1 === cv1 && cv2 === cv2) {\n if (cv1 < cv2) return true;\n if (cv1 > cv2) return false;\n return equal;\n }\n\n if (cv1 === cv1 && cv2 !== cv2) {\n return true;\n }\n\n if (cv2 === cv2 && cv1 !== cv1) {\n return false;\n }\n\n if (prop1 < prop2) return true;\n if (prop1 > prop2) return false;\n if (prop1 == prop2) return equal;\n\n // not strict equal nor less than nor gt so must be mixed types, convert to string and use that to compare\n cv1 = prop1.toString();\n cv2 = prop2.toString();\n\n if (cv1 < cv2) {\n return true;\n }\n\n if (cv1 == cv2) {\n return equal;\n }\n\n return false;\n }\n\n function gtHelper(prop1, prop2, equal) {\n var cv1, cv2, t1, t2;\n\n // 'falsy' and Boolean handling\n if (!prop1 || !prop2 || prop1 === true || prop2 === true || prop1 !== prop1 || prop2 !== prop2) {\n switch (prop1) {\n case undefined: t1 = 1; break;\n case null: t1 = 1; break;\n case false: t1 = 3; break;\n case true: t1 = 4; break;\n case \"\": t1 = 5; break;\n // NaN 0\n default: t1 = (prop1 === prop1) ? 9 : 0; break;\n }\n\n switch (prop2) {\n case undefined: t2 = 1; break;\n case null: t2 = 1; break;\n case false: t2 = 3; break;\n case true: t2 = 4; break;\n case \"\": t2 = 5; break;\n default: t2 = (prop2 === prop2) ? 9 : 0; break;\n }\n\n // one or both is edge case\n if (t1 !== 9 || t2 !== 9) {\n return (t1 === t2) ? equal : (t1 > t2);\n }\n }\n\n // if both are numbers (string encoded or not), compare as numbers\n cv1 = Number(prop1);\n cv2 = Number(prop2);\n if (cv1 === cv1 && cv2 === cv2) {\n if (cv1 > cv2) return true;\n if (cv1 < cv2) return false;\n return equal;\n }\n\n if (cv1 === cv1 && cv2 !== cv2) {\n return false;\n }\n\n if (cv2 === cv2 && cv1 !== cv1) {\n return true;\n }\n\n if (prop1 > prop2) return true;\n if (prop1 < prop2) return false;\n if (prop1 == prop2) return equal;\n\n // not strict equal nor less than nor gt so must be dates or mixed types\n // convert to string and use that to compare\n cv1 = prop1.toString();\n cv2 = prop2.toString();\n\n if (cv1 > cv2) {\n return true;\n }\n\n if (cv1 == cv2) {\n return equal;\n }\n\n return false;\n }\n\n function sortHelper(prop1, prop2, desc) {\n if (Comparators.aeq(prop1, prop2)) return 0;\n\n if (Comparators.lt(prop1, prop2, false)) {\n return (desc) ? (1) : (-1);\n }\n\n if (Comparators.gt(prop1, prop2, false)) {\n return (desc) ? (-1) : (1);\n }\n\n // not lt, not gt so implied equality-- date compatible\n return 0;\n }\n\n /**\n * compoundeval() - helper function for compoundsort(), performing individual object comparisons\n *\n * @param {array} properties - array of property names, in order, by which to evaluate sort order\n * @param {object} obj1 - first object to compare\n * @param {object} obj2 - second object to compare\n * @returns {integer} 0, -1, or 1 to designate if identical (sortwise) or which should be first\n */\n function compoundeval(properties, obj1, obj2) {\n var res = 0;\n var prop, field, val1, val2, arr, path;\n for (var i = 0, len = properties.length; i < len; i++) {\n prop = properties[i];\n field = prop[0];\n if (~field.indexOf('.')) {\n arr = field.split('.');\n val1 = Utils.getIn(obj1, arr, true);\n val2 = Utils.getIn(obj2, arr, true);\n } else {\n val1 = obj1[field];\n val2 = obj2[field];\n }\n res = sortHelper(val1, val2, prop[1]);\n if (res !== 0) {\n return res;\n }\n }\n return 0;\n }\n\n /**\n * dotSubScan - helper function used for dot notation queries.\n *\n * @param {object} root - object to traverse\n * @param {array} paths - array of properties to drill into\n * @param {function} fun - evaluation function to test with\n * @param {any} value - comparative value to also pass to (compare) fun\n * @param {any} extra - extra arg to also pass to compare fun\n * @param {number} poffset - index of the item in 'paths' to start the sub-scan from\n */\n function dotSubScan(root, paths, fun, value, extra, poffset) {\n var pathOffset = poffset || 0;\n var path = paths[pathOffset];\n\n var valueFound = false;\n var element;\n if (typeof root === 'object' && path in root) {\n element = root[path];\n }\n if (pathOffset + 1 >= paths.length) {\n // if we have already expanded out the dot notation,\n // then just evaluate the test function and value on the element\n valueFound = fun(element, value, extra);\n } else if (Array.isArray(element)) {\n for (var index = 0, len = element.length; index < len; index += 1) {\n valueFound = dotSubScan(element[index], paths, fun, value, extra, pathOffset + 1);\n if (valueFound === true) {\n break;\n }\n }\n } else {\n valueFound = dotSubScan(element, paths, fun, value, extra, pathOffset + 1);\n }\n\n return valueFound;\n }\n\n function containsCheckFn(a) {\n if (typeof a === 'string' || Array.isArray(a)) {\n return function (b) {\n return a.indexOf(b) !== -1;\n };\n } else if (typeof a === 'object' && a !== null) {\n return function (b) {\n return hasOwnProperty.call(a, b);\n };\n }\n return null;\n }\n\n function doQueryOp(val, op, record) {\n for (var p in op) {\n if (hasOwnProperty.call(op, p)) {\n return LokiOps[p](val, op[p], record);\n }\n }\n return false;\n }\n\n var LokiOps = {\n // comparison operators\n // a is the value in the collection\n // b is the query value\n $eq: function (a, b) {\n return a === b;\n },\n\n // abstract/loose equality\n $aeq: function (a, b) {\n return a == b;\n },\n\n $ne: function (a, b) {\n // ecma 5 safe test for NaN\n if (b !== b) {\n // ecma 5 test value is not NaN\n return (a === a);\n }\n\n return a !== b;\n },\n // date equality / loki abstract equality test\n $dteq: function (a, b) {\n return Comparators.aeq(a, b);\n },\n\n // loki comparisons: return identical unindexed results as indexed comparisons\n $gt: function (a, b) {\n return Comparators.gt(a, b, false);\n },\n\n $gte: function (a, b) {\n return Comparators.gt(a, b, true);\n },\n\n $lt: function (a, b) {\n return Comparators.lt(a, b, false);\n },\n\n $lte: function (a, b) {\n return Comparators.lt(a, b, true);\n },\n\n // lightweight javascript comparisons\n $jgt: function (a, b) {\n return a > b;\n },\n\n $jgte: function (a, b) {\n return a >= b;\n },\n\n $jlt: function (a, b) {\n return a < b;\n },\n\n $jlte: function (a, b) {\n return a <= b;\n },\n\n // ex : coll.find({'orderCount': {$between: [10, 50]}});\n $between: function (a, vals) {\n if (a === undefined || a === null) return false;\n return (Comparators.gt(a, vals[0], true) && Comparators.lt(a, vals[1], true));\n },\n\n $jbetween: function (a, vals) {\n if (a === undefined || a === null) return false;\n return (a >= vals[0] && a <= vals[1]);\n },\n\n $in: function (a, b) {\n return b.indexOf(a) !== -1;\n },\n\n $inSet: function(a, b) {\n return b.has(a);\n },\n\n $nin: function (a, b) {\n return b.indexOf(a) === -1;\n },\n\n $keyin: function (a, b) {\n return a in b;\n },\n\n $nkeyin: function (a, b) {\n return !(a in b);\n },\n\n $definedin: function (a, b) {\n return b[a] !== undefined;\n },\n\n $undefinedin: function (a, b) {\n return b[a] === undefined;\n },\n\n $regex: function (a, b) {\n return b.test(a);\n },\n\n $containsString: function (a, b) {\n return (typeof a === 'string') && (a.indexOf(b) !== -1);\n },\n\n $containsNone: function (a, b) {\n return !LokiOps.$containsAny(a, b);\n },\n\n $containsAny: function (a, b) {\n var checkFn = containsCheckFn(a);\n if (checkFn !== null) {\n return (Array.isArray(b)) ? (b.some(checkFn)) : (checkFn(b));\n }\n return false;\n },\n\n $contains: function (a, b) {\n var checkFn = containsCheckFn(a);\n if (checkFn !== null) {\n return (Array.isArray(b)) ? (b.every(checkFn)) : (checkFn(b));\n }\n return false;\n },\n\n $elemMatch: function (a, b) {\n if (Array.isArray(a)) {\n return a.some(function (item) {\n return Object.keys(b).every(function (property) {\n var filter = b[property];\n if (!(typeof filter === 'object' && filter)) {\n filter = { $eq: filter };\n }\n\n if (property.indexOf('.') !== -1) {\n return dotSubScan(item, property.split('.'), doQueryOp, b[property], item);\n }\n return doQueryOp(item[property], filter, item);\n });\n });\n }\n return false;\n },\n\n $type: function (a, b, record) {\n var type = typeof a;\n if (type === 'object') {\n if (Array.isArray(a)) {\n type = 'array';\n } else if (a instanceof Date) {\n type = 'date';\n }\n }\n return (typeof b !== 'object') ? (type === b) : doQueryOp(type, b, record);\n },\n\n $finite: function (a, b) {\n return (b === isFinite(a));\n },\n\n $size: function (a, b, record) {\n if (Array.isArray(a)) {\n return (typeof b !== 'object') ? (a.length === b) : doQueryOp(a.length, b, record);\n }\n return false;\n },\n\n $len: function (a, b, record) {\n if (typeof a === 'string') {\n return (typeof b !== 'object') ? (a.length === b) : doQueryOp(a.length, b, record);\n }\n return false;\n },\n\n $where: function (a, b) {\n return b(a) === true;\n },\n\n // field-level logical operators\n // a is the value in the collection\n // b is the nested query operation (for '$not')\n // or an array of nested query operations (for '$and' and '$or')\n $not: function (a, b, record) {\n return !doQueryOp(a, b, record);\n },\n\n $and: function (a, b, record) {\n for (var idx = 0, len = b.length; idx < len; idx += 1) {\n if (!doQueryOp(a, b[idx], record)) {\n return false;\n }\n }\n return true;\n },\n\n $or: function (a, b, record) {\n for (var idx = 0, len = b.length; idx < len; idx += 1) {\n if (doQueryOp(a, b[idx], record)) {\n return true;\n }\n }\n return false;\n },\n\n $exists: function (a, b) {\n if (b) {\n return a !== undefined;\n } else {\n return a === undefined;\n }\n }\n };\n\n // ops that can be used with { $$op: 'column-name' } syntax\n var valueLevelOps = ['$eq', '$aeq', '$ne', '$dteq', '$gt', '$gte', '$lt', '$lte', '$jgt', '$jgte', '$jlt', '$jlte', '$type'];\n valueLevelOps.forEach(function (op) {\n var fun = LokiOps[op];\n LokiOps['$' + op] = function (a, spec, record) {\n if (typeof spec === 'string') {\n return fun(a, record[spec]);\n } else if (typeof spec === 'function') {\n return fun(a, spec(record));\n } else {\n throw new Error('Invalid argument to $$ matcher');\n }\n };\n });\n\n // if an op is registered in this object, our 'calculateRange' can use it with our binary indices.\n // if the op is registered to a function, we will run that function/op as a 2nd pass filter on results.\n // those 2nd pass filter functions should be similar to LokiOps functions, accepting 2 vals to compare.\n var indexedOps = {\n $eq: LokiOps.$eq,\n $aeq: true,\n $dteq: true,\n $gt: true,\n $gte: true,\n $lt: true,\n $lte: true,\n $in: true,\n $between: true\n };\n\n function clone(data, method) {\n if (data === null || data === undefined) {\n return null;\n }\n\n var cloneMethod = method || 'parse-stringify',\n cloned;\n\n switch (cloneMethod) {\n case \"parse-stringify\":\n cloned = JSON.parse(JSON.stringify(data));\n break;\n case \"jquery-extend-deep\":\n cloned = jQuery.extend(true, {}, data);\n break;\n case \"shallow\":\n // more compatible method for older browsers\n cloned = Object.create(data.constructor.prototype);\n Object.keys(data).map(function (i) {\n cloned[i] = data[i];\n });\n break;\n case \"shallow-assign\":\n // should be supported by newer environments/browsers\n cloned = Object.create(data.constructor.prototype);\n Object.assign(cloned, data);\n break;\n case \"shallow-recurse-objects\":\n // shallow clone top level properties\n cloned = clone(data, \"shallow\");\n var keys = Object.keys(data);\n // for each of the top level properties which are object literals, recursively shallow copy\n keys.forEach(function (key) {\n if (typeof data[key] === \"object\" && data[key].constructor.name === \"Object\") {\n cloned[key] = clone(data[key], \"shallow-recurse-objects\");\n } else if (Array.isArray(data[key])) {\n cloned[key] = cloneObjectArray(data[key], \"shallow-recurse-objects\");\n }\n });\n break;\n default:\n break;\n }\n\n return cloned;\n }\n\n function cloneObjectArray(objarray, method) {\n if (method == \"parse-stringify\") {\n return clone(objarray, method);\n }\n var result = [];\n for (var i = 0, len = objarray.length; i < len; i++) {\n result[i] = clone(objarray[i], method);\n }\n return result;\n }\n\n function localStorageAvailable() {\n try {\n return (window && window.localStorage !== undefined && window.localStorage !== null);\n } catch (e) {\n return false;\n }\n }\n\n\n /**\n * LokiEventEmitter is a minimalist version of EventEmitter. It enables any\n * constructor that inherits EventEmitter to emit events and trigger\n * listeners that have been added to the event through the on(event, callback) method\n *\n * @constructor LokiEventEmitter\n */\n function LokiEventEmitter() { }\n\n /**\n * @prop {hashmap} events - a hashmap, with each property being an array of callbacks\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.events = {};\n\n /**\n * @prop {boolean} asyncListeners - boolean determines whether or not the callbacks associated with each event\n * should happen in an async fashion or not\n * Default is false, which means events are synchronous\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.asyncListeners = false;\n\n /**\n * on(eventName, listener) - adds a listener to the queue of callbacks associated to an event\n * @param {string|string[]} eventName - the name(s) of the event(s) to listen to\n * @param {function} listener - callback function of listener to attach\n * @returns {int} the index of the callback in the array of listeners for a particular event\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.on = function (eventName, listener) {\n var event;\n var self = this;\n\n if (Array.isArray(eventName)) {\n eventName.forEach(function (currentEventName) {\n self.on(currentEventName, listener);\n });\n return listener;\n }\n\n event = this.events[eventName];\n if (!event) {\n event = this.events[eventName] = [];\n }\n event.push(listener);\n return listener;\n };\n\n /**\n * emit(eventName, data) - emits a particular event\n * with the option of passing optional parameters which are going to be processed by the callback\n * provided signatures match (i.e. if passing emit(event, arg0, arg1) the listener should take two parameters)\n * @param {string} eventName - the name of the event\n * @param {object=} data - optional object passed with the event\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.emit = function (eventName) {\n var self = this;\n var selfArgs;\n if (eventName && this.events[eventName]) {\n if (this.events[eventName].length) {\n selfArgs = Array.prototype.slice.call(arguments, 1);\n this.events[eventName].forEach(function (listener) {\n if (self.asyncListeners) {\n setTimeout(function () {\n listener.apply(self, selfArgs);\n }, 1);\n } else {\n listener.apply(self, selfArgs);\n }\n });\n }\n } else {\n throw new Error('No event ' + eventName + ' defined');\n }\n };\n\n /**\n * Alias of LokiEventEmitter.prototype.on\n * addListener(eventName, listener) - adds a listener to the queue of callbacks associated to an event\n * @param {string|string[]} eventName - the name(s) of the event(s) to listen to\n * @param {function} listener - callback function of listener to attach\n * @returns {int} the index of the callback in the array of listeners for a particular event\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.addListener = LokiEventEmitter.prototype.on;\n\n /**\n * removeListener() - removes the listener at position 'index' from the event 'eventName'\n * @param {string|string[]} eventName - the name(s) of the event(s) which the listener is attached to\n * @param {function} listener - the listener callback function to remove from emitter\n * @memberof LokiEventEmitter\n */\n LokiEventEmitter.prototype.removeListener = function (eventName, listener) {\n var self = this;\n\n if (Array.isArray(eventName)) {\n eventName.forEach(function (currentEventName) {\n self.removeListener(currentEventName, listener);\n });\n\n return;\n }\n\n if (this.events[eventName]) {\n var listeners = this.events[eventName];\n listeners.splice(listeners.indexOf(listener), 1);\n }\n };\n\n /**\n * Loki: The main database class\n * @constructor Loki\n * @implements LokiEventEmitter\n * @param {string} filename - name of the file to be saved to\n * @param {object=} options - (Optional) config options object\n * @param {string} options.env - override environment detection as 'NODEJS', 'BROWSER', 'CORDOVA'\n * @param {boolean} [options.verbose=false] - enable console output\n * @param {boolean} [options.autosave=false] - enables autosave\n * @param {int} [options.autosaveInterval=5000] - time interval (in milliseconds) between saves (if dirty)\n * @param {boolean} [options.autoload=false] - enables autoload on loki instantiation\n * @param {function} options.autoloadCallback - user callback called after database load\n * @param {adapter} options.adapter - an instance of a loki persistence adapter\n * @param {string} [options.serializationMethod='normal'] - ['normal', 'pretty', 'destructured']\n * @param {string} options.destructureDelimiter - string delimiter used for destructured serialization\n * @param {boolean} [options.throttledSaves=true] - debounces multiple calls to to saveDatabase reducing number of disk I/O operations\n and guaranteeing proper serialization of the calls.\n */\n function Loki(filename, options) {\n this.filename = filename || 'loki.db';\n this.collections = [];\n\n // persist version of code which created the database to the database.\n // could use for upgrade scenarios\n this.databaseVersion = 1.5;\n this.engineVersion = 1.5;\n\n // autosave support (disabled by default)\n // pass autosave: true, autosaveInterval: 6000 in options to set 6 second autosave\n this.autosave = false;\n this.autosaveInterval = 5000;\n this.autosaveHandle = null;\n this.throttledSaves = true;\n\n this.options = {};\n\n // currently keeping persistenceMethod and persistenceAdapter as loki level properties that\n // will not or cannot be deserialized. You are required to configure persistence every time\n // you instantiate a loki object (or use default environment detection) in order to load the database anyways.\n\n // persistenceMethod could be 'fs', 'localStorage', or 'adapter'\n // this is optional option param, otherwise environment detection will be used\n // if user passes their own adapter we will force this method to 'adapter' later, so no need to pass method option.\n this.persistenceMethod = null;\n\n // retain reference to optional (non-serializable) persistenceAdapter 'instance'\n this.persistenceAdapter = null;\n\n // flags used to throttle saves\n this.throttledSavePending = false;\n this.throttledCallbacks = [];\n\n // enable console output if verbose flag is set (disabled by default)\n this.verbose = options && options.hasOwnProperty('verbose') ? options.verbose : false;\n\n this.events = {\n 'init': [],\n 'loaded': [],\n 'flushChanges': [],\n 'close': [],\n 'changes': [],\n 'warning': []\n };\n\n var getENV = function () {\n if (typeof global !== 'undefined' && (global.android || global.NSObject)) {\n // If no adapter assume nativescript which needs adapter to be passed manually\n return 'NATIVESCRIPT'; //nativescript\n }\n\n if (typeof window === 'undefined') {\n return 'NODEJS';\n }\n\n if (typeof global !== 'undefined' && global.window && typeof process !== 'undefined') {\n return 'NODEJS'; //node-webkit\n }\n\n if (typeof document !== 'undefined') {\n if (document.URL.indexOf('http://') === -1 && document.URL.indexOf('https://') === -1) {\n return 'CORDOVA';\n }\n return 'BROWSER';\n }\n return 'CORDOVA';\n };\n\n // refactored environment detection due to invalid detection for browser environments.\n // if they do not specify an options.env we want to detect env rather than default to nodejs.\n // currently keeping two properties for similar thing (options.env and options.persistenceMethod)\n // might want to review whether we can consolidate.\n if (options && options.hasOwnProperty('env')) {\n this.ENV = options.env;\n } else {\n this.ENV = getENV();\n }\n\n // not sure if this is necessary now that i have refactored the line above\n if (this.ENV === 'undefined') {\n this.ENV = 'NODEJS';\n }\n\n this.configureOptions(options, true);\n\n this.on('init', this.clearChanges);\n\n }\n\n // db class is an EventEmitter\n Loki.prototype = new LokiEventEmitter();\n Loki.prototype.constructor = Loki;\n\n // experimental support for browserify's abstract syntax scan to pick up dependency of indexed adapter.\n // Hopefully, once this hits npm a browserify require of lokijs should scan the main file and detect this indexed adapter reference.\n Loki.prototype.getIndexedAdapter = function () {\n var adapter;\n\n if (typeof require === 'function') {\n adapter = require(\"./loki-indexed-adapter.js\");\n }\n\n return adapter;\n };\n\n\n /**\n * Allows reconfiguring database options\n *\n * @param {object} options - configuration options to apply to loki db object\n * @param {string} options.env - override environment detection as 'NODEJS', 'BROWSER', 'CORDOVA'\n * @param {boolean} options.verbose - enable console output (default is 'false')\n * @param {boolean} options.autosave - enables autosave\n * @param {int} options.autosaveInterval - time interval (in milliseconds) between saves (if dirty)\n * @param {boolean} options.autoload - enables autoload on loki instantiation\n * @param {function} options.autoloadCallback - user callback called after database load\n * @param {adapter} options.adapter - an instance of a loki persistence adapter\n * @param {string} options.serializationMethod - ['normal', 'pretty', 'destructured']\n * @param {string} options.destructureDelimiter - string delimiter used for destructured serialization\n * @param {boolean} initialConfig - (internal) true is passed when loki ctor is invoking\n * @memberof Loki\n */\n Loki.prototype.configureOptions = function (options, initialConfig) {\n var defaultPersistence = {\n 'NODEJS': 'fs',\n 'BROWSER': 'localStorage',\n 'CORDOVA': 'localStorage',\n 'MEMORY': 'memory'\n },\n persistenceMethods = {\n 'fs': LokiFsAdapter,\n 'localStorage': LokiLocalStorageAdapter,\n 'memory': LokiMemoryAdapter\n };\n\n this.options = {};\n\n this.persistenceMethod = null;\n // retain reference to optional persistence adapter 'instance'\n // currently keeping outside options because it can't be serialized\n this.persistenceAdapter = null;\n\n // process the options\n if (typeof (options) !== 'undefined') {\n this.options = options;\n\n if (this.options.hasOwnProperty('persistenceMethod')) {\n // check if the specified persistence method is known\n if (typeof (persistenceMethods[options.persistenceMethod]) == 'function') {\n this.persistenceMethod = options.persistenceMethod;\n this.persistenceAdapter = new persistenceMethods[options.persistenceMethod]();\n }\n // should be throw an error here, or just fall back to defaults ??\n }\n\n // if user passes adapter, set persistence mode to adapter and retain persistence adapter instance\n if (this.options.hasOwnProperty('adapter')) {\n this.persistenceMethod = 'adapter';\n this.persistenceAdapter = options.adapter;\n this.options.adapter = null;\n\n // if true, will keep track of dirty ids\n this.isIncremental = this.persistenceAdapter.mode === 'incremental';\n }\n\n\n // if they want to load database on loki instantiation, now is a good time to load... after adapter set and before possible autosave initiation\n if (options.autoload && initialConfig) {\n // for autoload, let the constructor complete before firing callback\n var self = this;\n setTimeout(function () {\n self.loadDatabase(options, options.autoloadCallback);\n }, 1);\n }\n\n if (this.options.hasOwnProperty('autosaveInterval')) {\n this.autosaveDisable();\n this.autosaveInterval = parseInt(this.options.autosaveInterval, 10);\n }\n\n if (this.options.hasOwnProperty('autosave') && this.options.autosave) {\n this.autosaveDisable();\n this.autosave = true;\n\n if (this.options.hasOwnProperty('autosaveCallback')) {\n this.autosaveEnable(options, options.autosaveCallback);\n } else {\n this.autosaveEnable();\n }\n }\n\n if (this.options.hasOwnProperty('throttledSaves')) {\n this.throttledSaves = this.options.throttledSaves;\n }\n } // end of options processing\n\n // ensure defaults exists for options which were not set\n if (!this.options.hasOwnProperty('serializationMethod')) {\n this.options.serializationMethod = 'normal';\n }\n\n // ensure passed or default option exists\n if (!this.options.hasOwnProperty('destructureDelimiter')) {\n this.options.destructureDelimiter = '$<\\n';\n }\n\n // if by now there is no adapter specified by user nor derived from persistenceMethod: use sensible defaults\n if (this.persistenceAdapter === null) {\n this.persistenceMethod = defaultPersistence[this.ENV];\n if (this.persistenceMethod) {\n this.persistenceAdapter = new persistenceMethods[this.persistenceMethod]();\n }\n }\n\n };\n\n /**\n * Copies 'this' database into a new Loki instance. Object references are shared to make lightweight.\n *\n * @param {object} options - apply or override collection level settings\n * @param {bool} options.removeNonSerializable - nulls properties not safe for serialization.\n * @memberof Loki\n */\n Loki.prototype.copy = function (options) {\n // in case running in an environment without accurate environment detection, pass 'NA'\n var databaseCopy = new Loki(this.filename, { env: \"NA\" });\n var clen, idx;\n\n options = options || {};\n\n // currently inverting and letting loadJSONObject do most of the work\n databaseCopy.loadJSONObject(this, { retainDirtyFlags: true });\n\n // since our JSON serializeReplacer is not invoked for reference database adapters, this will let us mimic\n if (options.hasOwnProperty(\"removeNonSerializable\") && options.removeNonSerializable === true) {\n databaseCopy.autosaveHandle = null;\n databaseCopy.persistenceAdapter = null;\n\n clen = databaseCopy.collections.length;\n for (idx = 0; idx < clen; idx++) {\n databaseCopy.collections[idx].constraints = null;\n databaseCopy.collections[idx].ttl = null;\n }\n }\n\n return databaseCopy;\n };\n\n /**\n * Adds a collection to the database.\n * @param {string} name - name of collection to add\n * @param {object=} options - (optional) options to configure collection with.\n * @param {array=} [options.unique=[]] - array of property names to define unique constraints for\n * @param {array=} [options.exact=[]] - array of property names to define exact constraints for\n * @param {array=} [options.indices=[]] - array property names to define binary indexes for\n * @param {boolean} [options.asyncListeners=false] - whether listeners are called asynchronously\n * @param {boolean} [options.disableMeta=false] - set to true to disable meta property on documents\n * @param {boolean} [options.disableChangesApi=true] - set to false to enable Changes Api\n * @param {boolean} [options.disableDeltaChangesApi=true] - set to false to enable Delta Changes API (requires Changes API, forces cloning)\n * @param {boolean} [options.autoupdate=false] - use Object.observe to update objects automatically\n * @param {boolean} [options.clone=false] - specify whether inserts and queries clone to/from user\n * @param {string} [options.cloneMethod='parse-stringify'] - 'parse-stringify', 'jquery-extend-deep', 'shallow, 'shallow-assign'\n * @param {int=} options.ttl - age of document (in ms.) before document is considered aged/stale.\n * @param {int=} options.ttlInterval - time interval for clearing out 'aged' documents; not set by default.\n * @returns {Collection} a reference to the collection which was just added\n * @memberof Loki\n */\n Loki.prototype.addCollection = function (name, options) {\n var i,\n len = this.collections.length;\n\n if (options && options.disableMeta === true) {\n if (options.disableChangesApi === false) {\n throw new Error(\"disableMeta option cannot be passed as true when disableChangesApi is passed as false\");\n }\n if (options.disableDeltaChangesApi === false) {\n throw new Error(\"disableMeta option cannot be passed as true when disableDeltaChangesApi is passed as false\");\n }\n if (typeof options.ttl === \"number\" && options.ttl > 0) {\n throw new Error(\"disableMeta option cannot be passed as true when ttl is enabled\");\n }\n }\n\n for (i = 0; i < len; i += 1) {\n if (this.collections[i].name === name) {\n return this.collections[i];\n }\n }\n\n var collection = new Collection(name, options);\n collection.isIncremental = this.isIncremental;\n this.collections.push(collection);\n\n if (this.verbose)\n collection.lokiConsoleWrapper = console;\n\n return collection;\n };\n\n Loki.prototype.loadCollection = function (collection) {\n if (!collection.name) {\n throw new Error('Collection must have a name property to be loaded');\n }\n this.collections.push(collection);\n };\n\n /**\n * Retrieves reference to a collection by name.\n * @param {string} collectionName - name of collection to look up\n * @returns {Collection} Reference to collection in database by that name, or null if not found\n * @memberof Loki\n */\n Loki.prototype.getCollection = function (collectionName) {\n var i,\n len = this.collections.length;\n\n for (i = 0; i < len; i += 1) {\n if (this.collections[i].name === collectionName) {\n return this.collections[i];\n }\n }\n\n // no such collection\n this.emit('warning', 'collection ' + collectionName + ' not found');\n return null;\n };\n\n /**\n * Renames an existing loki collection\n * @param {string} oldName - name of collection to rename\n * @param {string} newName - new name of collection\n * @returns {Collection} reference to the newly renamed collection\n * @memberof Loki\n */\n Loki.prototype.renameCollection = function (oldName, newName) {\n var c = this.getCollection(oldName);\n\n if (c) {\n c.name = newName;\n }\n\n return c;\n };\n\n /**\n * Returns a list of collections in the database.\n * @returns {object[]} array of objects containing 'name', 'type', and 'count' properties.\n * @memberof Loki\n */\n Loki.prototype.listCollections = function () {\n\n var i = this.collections.length,\n colls = [];\n\n while (i--) {\n colls.push({\n name: this.collections[i].name,\n type: this.collections[i].objType,\n count: this.collections[i].data.length\n });\n }\n return colls;\n };\n\n /**\n * Removes a collection from the database.\n * @param {string} collectionName - name of collection to remove\n * @memberof Loki\n */\n Loki.prototype.removeCollection = function (collectionName) {\n var i,\n len = this.collections.length;\n\n for (i = 0; i < len; i += 1) {\n if (this.collections[i].name === collectionName) {\n var tmpcol = new Collection(collectionName, {});\n var curcol = this.collections[i];\n for (var prop in curcol) {\n if (curcol.hasOwnProperty(prop) && tmpcol.hasOwnProperty(prop)) {\n curcol[prop] = tmpcol[prop];\n }\n }\n this.collections.splice(i, 1);\n return;\n }\n }\n };\n\n Loki.prototype.getName = function () {\n return this.name;\n };\n\n /**\n * serializeReplacer - used to prevent certain properties from being serialized\n *\n */\n Loki.prototype.serializeReplacer = function (key, value) {\n switch (key) {\n case 'autosaveHandle':\n case 'persistenceAdapter':\n case 'constraints':\n case 'ttl':\n return null;\n case 'throttledSavePending':\n case 'throttledCallbacks':\n return undefined;\n case 'lokiConsoleWrapper':\n return null;\n default:\n return value;\n }\n };\n\n /**\n * Serialize database to a string which can be loaded via {@link Loki#loadJSON}\n *\n * @returns {string} Stringified representation of the loki database.\n * @memberof Loki\n */\n Loki.prototype.serialize = function (options) {\n options = options || {};\n\n if (!options.hasOwnProperty(\"serializationMethod\")) {\n options.serializationMethod = this.options.serializationMethod;\n }\n\n switch (options.serializationMethod) {\n case \"normal\": return JSON.stringify(this, this.serializeReplacer);\n case \"pretty\": return JSON.stringify(this, this.serializeReplacer, 2);\n case \"destructured\": return this.serializeDestructured(); // use default options\n default: return JSON.stringify(this, this.serializeReplacer);\n }\n };\n\n // alias of serialize\n Loki.prototype.toJson = Loki.prototype.serialize;\n\n /**\n * Database level destructured JSON serialization routine to allow alternate serialization methods.\n * Internally, Loki supports destructuring via loki \"serializationMethod' option and\n * the optional LokiPartitioningAdapter class. It is also available if you wish to do\n * your own structured persistence or data exchange.\n *\n * @param {object=} options - output format options for use externally to loki\n * @param {bool=} options.partitioned - (default: false) whether db and each collection are separate\n * @param {int=} options.partition - can be used to only output an individual collection or db (-1)\n * @param {bool=} options.delimited - (default: true) whether subitems are delimited or subarrays\n * @param {string=} options.delimiter - override default delimiter\n *\n * @returns {string|array} A custom, restructured aggregation of independent serializations.\n * @memberof Loki\n */\n Loki.prototype.serializeDestructured = function (options) {\n var idx, sidx, result, resultlen;\n var reconstruct = [];\n var dbcopy;\n\n options = options || {};\n\n if (!options.hasOwnProperty(\"partitioned\")) {\n options.partitioned = false;\n }\n\n if (!options.hasOwnProperty(\"delimited\")) {\n options.delimited = true;\n }\n\n if (!options.hasOwnProperty(\"delimiter\")) {\n options.delimiter = this.options.destructureDelimiter;\n }\n\n // 'partitioned' along with 'partition' of 0 or greater is a request for single collection serialization\n if (options.partitioned === true && options.hasOwnProperty(\"partition\") && options.partition >= 0) {\n return this.serializeCollection({\n delimited: options.delimited,\n delimiter: options.delimiter,\n collectionIndex: options.partition\n });\n }\n\n // not just an individual collection, so we will need to serialize db container via shallow copy\n dbcopy = new Loki(this.filename);\n dbcopy.loadJSONObject(this);\n\n for (idx = 0; idx < dbcopy.collections.length; idx++) {\n dbcopy.collections[idx].data = [];\n }\n\n // if we -only- wanted the db container portion, return it now\n if (options.partitioned === true && options.partition === -1) {\n // since we are deconstructing, override serializationMethod to normal for here\n return dbcopy.serialize({\n serializationMethod: \"normal\"\n });\n }\n\n // at this point we must be deconstructing the entire database\n // start by pushing db serialization into first array element\n reconstruct.push(dbcopy.serialize({\n serializationMethod: \"normal\"\n }));\n\n dbcopy = null;\n\n // push collection data into subsequent elements\n for (idx = 0; idx < this.collections.length; idx++) {\n result = this.serializeCollection({\n delimited: options.delimited,\n delimiter: options.delimiter,\n collectionIndex: idx\n });\n\n // NDA : Non-Delimited Array : one iterable concatenated array with empty string collection partitions\n if (options.partitioned === false && options.delimited === false) {\n if (!Array.isArray(result)) {\n throw new Error(\"a nondelimited, non partitioned collection serialization did not return an expected array\");\n }\n\n // Array.concat would probably duplicate memory overhead for copying strings.\n // Instead copy each individually, and clear old value after each copy.\n // Hopefully this will allow g.c. to reduce memory pressure, if needed.\n resultlen = result.length;\n\n for (sidx = 0; sidx < resultlen; sidx++) {\n reconstruct.push(result[sidx]);\n result[sidx] = null;\n }\n\n reconstruct.push(\"\");\n }\n else {\n reconstruct.push(result);\n }\n }\n\n // Reconstruct / present results according to four combinations : D, DA, NDA, NDAA\n if (options.partitioned) {\n // DA : Delimited Array of strings [0] db [1] collection [n] collection { partitioned: true, delimited: true }\n // useful for simple future adaptations of existing persistence adapters to save collections separately\n if (options.delimited) {\n return reconstruct;\n }\n // NDAA : Non-Delimited Array with subArrays. db at [0] and collection subarrays at [n] { partitioned: true, delimited : false }\n // This format might be the most versatile for 'rolling your own' partitioned sync or save.\n // Memory overhead can be reduced by specifying a specific partition, but at this code path they did not, so its all.\n else {\n return reconstruct;\n }\n }\n else {\n // D : one big Delimited string { partitioned: false, delimited : true }\n // This is the method Loki will use internally if 'destructured'.\n // Little memory overhead improvements but does not require multiple asynchronous adapter call scheduling\n if (options.delimited) {\n // indicate no more collections\n reconstruct.push(\"\");\n\n return reconstruct.join(options.delimiter);\n }\n // NDA : Non-Delimited Array : one iterable array with empty string collection partitions { partitioned: false, delimited: false }\n // This format might be best candidate for custom synchronous syncs or saves\n else {\n // indicate no more collections\n reconstruct.push(\"\");\n\n return reconstruct;\n }\n }\n\n reconstruct.push(\"\");\n\n return reconstruct.join(delim);\n };\n\n /**\n * Collection level utility method to serialize a collection in a 'destructured' format\n *\n * @param {object=} options - used to determine output of method\n * @param {int} options.delimited - whether to return single delimited string or an array\n * @param {string} options.delimiter - (optional) if delimited, this is delimiter to use\n * @param {int} options.collectionIndex - specify which collection to serialize data for\n *\n * @returns {string|array} A custom, restructured aggregation of independent serializations for a single collection.\n * @memberof Loki\n */\n Loki.prototype.serializeCollection = function (options) {\n var doccount,\n docidx,\n resultlines = [];\n\n options = options || {};\n\n if (!options.hasOwnProperty(\"delimited\")) {\n options.delimited = true;\n }\n\n if (!options.hasOwnProperty(\"collectionIndex\")) {\n throw new Error(\"serializeCollection called without 'collectionIndex' option\");\n }\n\n doccount = this.collections[options.collectionIndex].data.length;\n\n resultlines = [];\n\n for (docidx = 0; docidx < doccount; docidx++) {\n resultlines.push(JSON.stringify(this.collections[options.collectionIndex].data[docidx]));\n }\n\n // D and DA\n if (options.delimited) {\n // indicate no more documents in collection (via empty delimited string)\n resultlines.push(\"\");\n\n return resultlines.join(options.delimiter);\n }\n else {\n // NDAA and NDA\n return resultlines;\n }\n };\n\n /**\n * Database level destructured JSON deserialization routine to minimize memory overhead.\n * Internally, Loki supports destructuring via loki \"serializationMethod' option and\n * the optional LokiPartitioningAdapter class. It is also available if you wish to do\n * your own structured persistence or data exchange.\n *\n * @param {string|array} destructuredSource - destructured json or array to deserialize from\n * @param {object=} options - source format options\n * @param {bool=} [options.partitioned=false] - whether db and each collection are separate\n * @param {int=} options.partition - can be used to deserialize only a single partition\n * @param {bool=} [options.delimited=true] - whether subitems are delimited or subarrays\n * @param {string=} options.delimiter - override default delimiter\n *\n * @returns {object|array} An object representation of the deserialized database, not yet applied to 'this' db or document array\n * @memberof Loki\n */\n Loki.prototype.deserializeDestructured = function (destructuredSource, options) {\n var workarray = [];\n var len, cdb;\n var idx, collIndex = 0, collCount, lineIndex = 1, done = false;\n var currLine, currObject;\n\n options = options || {};\n\n if (!options.hasOwnProperty(\"partitioned\")) {\n options.partitioned = false;\n }\n\n if (!options.hasOwnProperty(\"delimited\")) {\n options.delimited = true;\n }\n\n if (!options.hasOwnProperty(\"delimiter\")) {\n options.delimiter = this.options.destructureDelimiter;\n }\n\n // Partitioned\n // DA : Delimited Array of strings [0] db [1] collection [n] collection { partitioned: true, delimited: true }\n // NDAA : Non-Delimited Array with subArrays. db at [0] and collection subarrays at [n] { partitioned: true, delimited : false }\n // -or- single partition\n if (options.partitioned) {\n // handle single partition\n if (options.hasOwnProperty('partition')) {\n // db only\n if (options.partition === -1) {\n cdb = JSON.parse(destructuredSource[0]);\n\n return cdb;\n }\n\n // single collection, return doc array\n return this.deserializeCollection(destructuredSource[options.partition + 1], options);\n }\n\n // Otherwise we are restoring an entire partitioned db\n cdb = JSON.parse(destructuredSource[0]);\n collCount = cdb.collections.length;\n for (collIndex = 0; collIndex < collCount; collIndex++) {\n // attach each collection docarray to container collection data, add 1 to collection array index since db is at 0\n cdb.collections[collIndex].data = this.deserializeCollection(destructuredSource[collIndex + 1], options);\n }\n\n return cdb;\n }\n\n // Non-Partitioned\n // D : one big Delimited string { partitioned: false, delimited : true }\n // NDA : Non-Delimited Array : one iterable array with empty string collection partitions { partitioned: false, delimited: false }\n\n // D\n if (options.delimited) {\n workarray = destructuredSource.split(options.delimiter);\n destructuredSource = null; // lower memory pressure\n len = workarray.length;\n\n if (len === 0) {\n return null;\n }\n }\n // NDA\n else {\n workarray = destructuredSource;\n }\n\n // first line is database and collection shells\n cdb = JSON.parse(workarray[0]);\n collCount = cdb.collections.length;\n workarray[0] = null;\n\n while (!done) {\n currLine = workarray[lineIndex];\n\n // empty string indicates either end of collection or end of file\n if (workarray[lineIndex] === \"\") {\n // if no more collections to load into, we are done\n if (++collIndex > collCount) {\n done = true;\n }\n }\n else {\n currObject = JSON.parse(workarray[lineIndex]);\n cdb.collections[collIndex].data.push(currObject);\n }\n\n // lower memory pressure and advance iterator\n workarray[lineIndex++] = null;\n }\n\n return cdb;\n };\n\n /**\n * Collection level utility function to deserializes a destructured collection.\n *\n * @param {string|array} destructuredSource - destructured representation of collection to inflate\n * @param {object=} options - used to describe format of destructuredSource input\n * @param {int=} [options.delimited=false] - whether source is delimited string or an array\n * @param {string=} options.delimiter - if delimited, this is delimiter to use (if other than default)\n *\n * @returns {array} an array of documents to attach to collection.data.\n * @memberof Loki\n */\n Loki.prototype.deserializeCollection = function (destructuredSource, options) {\n var workarray = [];\n var idx, len;\n\n options = options || {};\n\n if (!options.hasOwnProperty(\"partitioned\")) {\n options.partitioned = false;\n }\n\n if (!options.hasOwnProperty(\"delimited\")) {\n options.delimited = true;\n }\n\n if (!options.hasOwnProperty(\"delimiter\")) {\n options.delimiter = this.options.destructureDelimiter;\n }\n\n if (options.delimited) {\n workarray = destructuredSource.split(options.delimiter);\n workarray.pop();\n }\n else {\n workarray = destructuredSource;\n }\n\n len = workarray.length;\n for (idx = 0; idx < len; idx++) {\n workarray[idx] = JSON.parse(workarray[idx]);\n }\n\n return workarray;\n };\n\n /**\n * Inflates a loki database from a serialized JSON string\n *\n * @param {string} serializedDb - a serialized loki database string\n * @param {object=} options - apply or override collection level settings\n * @param {bool} options.retainDirtyFlags - whether collection dirty flags will be preserved\n * @memberof Loki\n */\n Loki.prototype.loadJSON = function (serializedDb, options) {\n var dbObject;\n if (serializedDb.length === 0) {\n dbObject = {};\n } else {\n\n // using option defined in instantiated db not what was in serialized db\n switch (this.options.serializationMethod) {\n case \"normal\":\n case \"pretty\": dbObject = JSON.parse(serializedDb); break;\n case \"destructured\": dbObject = this.deserializeDestructured(serializedDb); break;\n default: dbObject = JSON.parse(serializedDb); break;\n }\n }\n\n this.loadJSONObject(dbObject, options);\n };\n\n /**\n * Inflates a loki database from a JS object\n *\n * @param {object} dbObject - a serialized loki database string\n * @param {object=} options - apply or override collection level settings\n * @param {bool} options.retainDirtyFlags - whether collection dirty flags will be preserved\n * @memberof Loki\n */\n Loki.prototype.loadJSONObject = function (dbObject, options) {\n var i = 0,\n len = dbObject.collections ? dbObject.collections.length : 0,\n coll,\n copyColl,\n clen,\n j,\n loader,\n collObj;\n\n this.name = dbObject.name;\n\n // restore save throttled boolean only if not defined in options\n if (dbObject.hasOwnProperty('throttledSaves') && options && !options.hasOwnProperty('throttledSaves')) {\n this.throttledSaves = dbObject.throttledSaves;\n }\n\n this.collections = [];\n\n function makeLoader(coll) {\n var collOptions = options[coll.name];\n var inflater;\n\n if (collOptions.proto) {\n inflater = collOptions.inflate || Utils.copyProperties;\n\n return function (data) {\n var collObj = new (collOptions.proto)();\n inflater(data, collObj);\n return collObj;\n };\n }\n\n return collOptions.inflate;\n }\n\n for (i; i < len; i += 1) {\n coll = dbObject.collections[i];\n\n copyColl = this.addCollection(coll.name, {\n disableChangesApi: coll.disableChangesApi,\n disableDeltaChangesApi: coll.disableDeltaChangesApi,\n disableMeta: coll.disableMeta,\n disableFreeze: coll.hasOwnProperty('disableFreeze') ? coll.disableFreeze : true\n });\n\n copyColl.adaptiveBinaryIndices = coll.hasOwnProperty('adaptiveBinaryIndices') ? (coll.adaptiveBinaryIndices === true) : false;\n copyColl.transactional = coll.transactional;\n copyColl.asyncListeners = coll.asyncListeners;\n copyColl.cloneObjects = coll.cloneObjects;\n copyColl.cloneMethod = coll.cloneMethod || \"parse-stringify\";\n copyColl.autoupdate = coll.autoupdate;\n copyColl.changes = coll.changes;\n copyColl.dirtyIds = coll.dirtyIds || [];\n\n if (options && options.retainDirtyFlags === true) {\n copyColl.dirty = coll.dirty;\n }\n else {\n copyColl.dirty = false;\n }\n\n // load each element individually\n clen = coll.data.length;\n j = 0;\n if (options && options.hasOwnProperty(coll.name)) {\n loader = makeLoader(coll);\n\n for (j; j < clen; j++) {\n collObj = loader(coll.data[j]);\n copyColl.data[j] = collObj;\n copyColl.addAutoUpdateObserver(collObj);\n if (!copyColl.disableFreeze) {\n deepFreeze(copyColl.data[j]);\n }\n }\n } else {\n\n for (j; j < clen; j++) {\n copyColl.data[j] = coll.data[j];\n copyColl.addAutoUpdateObserver(copyColl.data[j]);\n if (!copyColl.disableFreeze) {\n deepFreeze(copyColl.data[j]);\n }\n }\n }\n\n copyColl.maxId = (typeof coll.maxId === 'undefined') ? 0 : coll.maxId;\n if (typeof (coll.binaryIndices) !== 'undefined') {\n copyColl.binaryIndices = coll.binaryIndices;\n }\n if (typeof coll.transforms !== 'undefined') {\n copyColl.transforms = coll.transforms;\n }\n\n // regenerate unique indexes\n copyColl.uniqueNames = [];\n if (coll.hasOwnProperty(\"uniqueNames\")) {\n copyColl.uniqueNames = coll.uniqueNames;\n }\n\n // in case they are loading a database created before we added dynamic views, handle undefined\n if (typeof (coll.DynamicViews) === 'undefined') continue;\n\n // reinflate DynamicViews and attached Resultsets\n for (var idx = 0; idx < coll.DynamicViews.length; idx++) {\n var colldv = coll.DynamicViews[idx];\n\n var dv = copyColl.addDynamicView(colldv.name, colldv.options);\n dv.resultdata = colldv.resultdata;\n dv.resultsdirty = colldv.resultsdirty;\n dv.filterPipeline = colldv.filterPipeline;\n dv.sortCriteriaSimple = colldv.sortCriteriaSimple;\n dv.sortCriteria = colldv.sortCriteria;\n dv.sortFunction = null;\n dv.sortDirty = colldv.sortDirty;\n if (!copyColl.disableFreeze) {\n deepFreeze(dv.filterPipeline);\n if (dv.sortCriteriaSimple) {\n deepFreeze(dv.sortCriteriaSimple);\n } else if (dv.sortCriteria) {\n deepFreeze(dv.sortCriteria);\n }\n }\n dv.resultset.filteredrows = colldv.resultset.filteredrows;\n dv.resultset.filterInitialized = colldv.resultset.filterInitialized;\n\n dv.rematerialize({\n removeWhereFilters: true\n });\n }\n\n // Upgrade Logic for binary index refactoring at version 1.5\n if (dbObject.databaseVersion < 1.5) {\n // rebuild all indices\n copyColl.ensureAllIndexes(true);\n copyColl.dirty = true;\n }\n }\n };\n\n /**\n * Emits the close event. In autosave scenarios, if the database is dirty, this will save and disable timer.\n * Does not actually destroy the db.\n *\n * @param {function=} callback - (Optional) if supplied will be registered with close event before emitting.\n * @memberof Loki\n */\n Loki.prototype.close = function (callback) {\n // for autosave scenarios, we will let close perform final save (if dirty)\n // For web use, you might call from window.onbeforeunload to shutdown database, saving pending changes\n if (this.autosave) {\n this.autosaveDisable();\n if (this.autosaveDirty()) {\n this.saveDatabase(callback);\n callback = undefined;\n }\n }\n\n if (callback) {\n this.on('close', callback);\n }\n this.emit('close');\n };\n\n /**-------------------------+\n | Changes API |\n +--------------------------*/\n\n /**\n * The Changes API enables the tracking the changes occurred in the collections since the beginning of the session,\n * so it's possible to create a differential dataset for synchronization purposes (possibly to a remote db)\n */\n\n /**\n * (Changes API) : takes all the changes stored in each\n * collection and creates a single array for the entire database. If an array of names\n * of collections is passed then only the included collections will be tracked.\n *\n * @param {array=} optional array of collection names. No arg means all collections are processed.\n * @returns {array} array of changes\n * @see private method createChange() in Collection\n * @memberof Loki\n */\n Loki.prototype.generateChangesNotification = function (arrayOfCollectionNames) {\n function getCollName(coll) {\n return coll.name;\n }\n var changes = [],\n selectedCollections = arrayOfCollectionNames || this.collections.map(getCollName);\n\n this.collections.forEach(function (coll) {\n if (selectedCollections.indexOf(getCollName(coll)) !== -1) {\n changes = changes.concat(coll.getChanges());\n }\n });\n return changes;\n };\n\n /**\n * (Changes API) - stringify changes for network transmission\n * @returns {string} string representation of the changes\n * @memberof Loki\n */\n Loki.prototype.serializeChanges = function (collectionNamesArray) {\n return JSON.stringify(this.generateChangesNotification(collectionNamesArray));\n };\n\n /**\n * (Changes API) : clears all the changes in all collections.\n * @memberof Loki\n */\n Loki.prototype.clearChanges = function () {\n this.collections.forEach(function (coll) {\n if (coll.flushChanges) {\n coll.flushChanges();\n }\n });\n };\n\n /*------------------+\n | PERSISTENCE |\n -------------------*/\n\n /** there are two build in persistence adapters for internal use\n * fs for use in Nodejs type environments\n * localStorage for use in browser environment\n * defined as helper classes here so its easy and clean to use\n */\n\n /**\n * In in-memory persistence adapter for an in-memory database.\n * This simple 'key/value' adapter is intended for unit testing and diagnostics.\n *\n * @param {object=} options - memory adapter options\n * @param {boolean} [options.asyncResponses=false] - whether callbacks are invoked asynchronously\n * @param {int} [options.asyncTimeout=50] - timeout in ms to queue callbacks\n * @constructor LokiMemoryAdapter\n */\n function LokiMemoryAdapter(options) {\n this.hashStore = {};\n this.options = options || {};\n\n if (!this.options.hasOwnProperty('asyncResponses')) {\n this.options.asyncResponses = false;\n }\n\n if (!this.options.hasOwnProperty('asyncTimeout')) {\n this.options.asyncTimeout = 50; // 50 ms default\n }\n }\n\n /**\n * Loads a serialized database from its in-memory store.\n * (Loki persistence adapter interface function)\n *\n * @param {string} dbname - name of the database (filename/keyname)\n * @param {function} callback - adapter callback to return load result to caller\n * @memberof LokiMemoryAdapter\n */\n LokiMemoryAdapter.prototype.loadDatabase = function (dbname, callback) {\n var self = this;\n\n if (this.options.asyncResponses) {\n setTimeout(function () {\n if (self.hashStore.hasOwnProperty(dbname)) {\n callback(self.hashStore[dbname].value);\n }\n else {\n // database doesn't exist, return falsy\n callback(null);\n }\n }, this.options.asyncTimeout);\n }\n else {\n if (this.hashStore.hasOwnProperty(dbname)) {\n // database doesn't exist, return falsy\n callback(this.hashStore[dbname].value);\n }\n else {\n callback(null);\n }\n }\n };\n\n /**\n * Saves a serialized database to its in-memory store.\n * (Loki persistence adapter interface function)\n *\n * @param {string} dbname - name of the database (filename/keyname)\n * @param {function} callback - adapter callback to return load result to caller\n * @memberof LokiMemoryAdapter\n */\n LokiMemoryAdapter.prototype.saveDatabase = function (dbname, dbstring, callback) {\n var self = this;\n var saveCount;\n\n if (this.options.asyncResponses) {\n setTimeout(function () {\n saveCount = (self.hashStore.hasOwnProperty(dbname) ? self.hashStore[dbname].savecount : 0);\n\n self.hashStore[dbname] = {\n savecount: saveCount + 1,\n lastsave: new Date(),\n value: dbstring\n };\n\n callback();\n }, this.options.asyncTimeout);\n }\n else {\n saveCount = (this.hashStore.hasOwnProperty(dbname) ? this.hashStore[dbname].savecount : 0);\n\n this.hashStore[dbname] = {\n savecount: saveCount + 1,\n lastsave: new Date(),\n value: dbstring\n };\n\n callback();\n }\n };\n\n /**\n * Deletes a database from its in-memory store.\n *\n * @param {string} dbname - name of the database (filename/keyname)\n * @param {function} callback - function to call when done\n * @memberof LokiMemoryAdapter\n */\n LokiMemoryAdapter.prototype.deleteDatabase = function (dbname, callback) {\n if (this.hashStore.hasOwnProperty(dbname)) {\n delete this.hashStore[dbname];\n }\n\n if (typeof callback === \"function\") {\n callback();\n }\n };\n\n /**\n * An adapter for adapters. Converts a non reference mode adapter into a reference mode adapter\n * which can perform destructuring and partioning. Each collection will be stored in its own key/save and\n * only dirty collections will be saved. If you turn on paging with default page size of 25megs and save\n * a 75 meg collection it should use up roughly 3 save slots (key/value pairs sent to inner adapter).\n * A dirty collection that spans three pages will save all three pages again\n * Paging mode was added mainly because Chrome has issues saving 'too large' of a string within a\n * single indexeddb row. If a single document update causes the collection to be flagged as dirty, all\n * of that collection's pages will be written on next save.\n *\n * @param {object} adapter - reference to a 'non-reference' mode loki adapter instance.\n * @param {object=} options - configuration options for partitioning and paging\n * @param {bool} options.paging - (default: false) set to true to enable paging collection data.\n * @param {int} options.pageSize - (default : 25MB) you can use this to limit size of strings passed to inner adapter.\n * @param {string} options.delimiter - allows you to override the default delimeter\n * @constructor LokiPartitioningAdapter\n */\n function LokiPartitioningAdapter(adapter, options) {\n this.mode = \"reference\";\n this.adapter = null;\n this.options = options || {};\n this.dbref = null;\n this.dbname = \"\";\n this.pageIterator = {};\n\n // verify user passed an appropriate adapter\n if (adapter) {\n if (adapter.mode === \"reference\") {\n throw new Error(\"LokiPartitioningAdapter cannot be instantiated with a reference mode adapter\");\n }\n else {\n this.adapter = adapter;\n }\n }\n else {\n throw new Error(\"LokiPartitioningAdapter requires a (non-reference mode) adapter on construction\");\n }\n\n // set collection paging defaults\n if (!this.options.hasOwnProperty(\"paging\")) {\n this.options.paging = false;\n }\n\n // default to page size of 25 megs (can be up to your largest serialized object size larger than this)\n if (!this.options.hasOwnProperty(\"pageSize\")) {\n this.options.pageSize = 25 * 1024 * 1024;\n }\n\n if (!this.options.hasOwnProperty(\"delimiter\")) {\n this.options.delimiter = '$<\\n';\n }\n }\n\n /**\n * Loads a database which was partitioned into several key/value saves.\n * (Loki persistence adapter interface function)\n *\n * @param {string} dbname - name of the database (filename/keyname)\n * @param {function} callback - adapter callback to return load result to caller\n * @memberof LokiPartitioningAdapter\n */\n LokiPartitioningAdapter.prototype.loadDatabase = function (dbname, callback) {\n var self = this;\n this.dbname = dbname;\n this.dbref = new Loki(dbname);\n\n // load the db container (without data)\n this.adapter.loadDatabase(dbname, function (result) {\n // empty database condition is for inner adapter return null/undefined/falsy\n if (!result) {\n // partition 0 not found so new database, no need to try to load other partitions.\n // return same falsy result to loadDatabase to signify no database exists (yet)\n callback(result);\n return;\n }\n\n if (typeof result !== \"string\") {\n callback(new Error(\"LokiPartitioningAdapter received an unexpected response from inner adapter loadDatabase()\"));\n }\n\n // I will want to use loki destructuring helper methods so i will inflate into typed instance\n var db = JSON.parse(result);\n self.dbref.loadJSONObject(db);\n db = null;\n\n var clen = self.dbref.collections.length;\n\n if (self.dbref.collections.length === 0) {\n callback(self.dbref);\n return;\n }\n\n self.pageIterator = {\n collection: 0,\n pageIndex: 0\n };\n\n self.loadNextPartition(0, function () {\n callback(self.dbref);\n });\n });\n };\n\n /**\n * Used to sequentially load each collection partition, one at a time.\n *\n * @param {int} partition - ordinal collection position to load next\n * @param {function} callback - adapter callback to return load result to caller\n */\n LokiPartitioningAdapter.prototype.loadNextPartition = function (partition, callback) {\n var keyname = this.dbname + \".\" + partition;\n var self = this;\n\n if (this.options.paging === true) {\n this.pageIterator.pageIndex = 0;\n this.loadNextPage(callback);\n return;\n }\n\n this.adapter.loadDatabase(keyname, function (result) {\n var data = self.dbref.deserializeCollection(result, { delimited: true, collectionIndex: partition });\n self.dbref.collections[partition].data = data;\n\n if (++partition < self.dbref.collections.length) {\n self.loadNextPartition(partition, callback);\n }\n else {\n callback();\n }\n });\n };\n\n /**\n * Used to sequentially load the next page of collection partition, one at a time.\n *\n * @param {function} callback - adapter callback to return load result to caller\n */\n LokiPartitioningAdapter.prototype.loadNextPage = function (callback) {\n // calculate name for next saved page in sequence\n var keyname = this.dbname + \".\" + this.pageIterator.collection + \".\" + this.pageIterator.pageIndex;\n var self = this;\n\n // load whatever page is next in sequence\n this.adapter.loadDatabase(keyname, function (result) {\n var data = result.split(self.options.delimiter);\n result = \"\"; // free up memory now that we have split it into array\n var dlen = data.length;\n var idx;\n\n // detect if last page by presence of final empty string element and remove it if so\n var isLastPage = (data[dlen - 1] === \"\");\n if (isLastPage) {\n data.pop();\n dlen = data.length;\n // empty collections are just a delimiter meaning two blank items\n if (data[dlen - 1] === \"\" && dlen === 1) {\n data.pop();\n dlen = data.length;\n }\n }\n\n // convert stringified array elements to object instances and push to collection data\n for (idx = 0; idx < dlen; idx++) {\n self.dbref.collections[self.pageIterator.collection].data.push(JSON.parse(data[idx]));\n data[idx] = null;\n }\n data = [];\n\n // if last page, we are done with this partition\n if (isLastPage) {\n\n // if there are more partitions, kick off next partition load\n if (++self.pageIterator.collection < self.dbref.collections.length) {\n self.loadNextPartition(self.pageIterator.collection, callback);\n }\n else {\n callback();\n }\n }\n else {\n self.pageIterator.pageIndex++;\n self.loadNextPage(callback);\n }\n });\n };\n\n /**\n * Saves a database by partioning into separate key/value saves.\n * (Loki 'reference mode' persistence adapter interface function)\n *\n * @param {string} dbname - name of the database (filename/keyname)\n * @param {object} dbref - reference to database which we will partition and save.\n * @param {function} callback - adapter callback to return load result to caller\n *\n * @memberof LokiPartitioningAdapter\n */\n LokiPartitioningAdapter.prototype.exportDatabase = function (dbname, dbref, callback) {\n var self = this;\n var idx, clen = dbref.collections.length;\n\n this.dbref = dbref;\n this.dbname = dbname;\n\n // queue up dirty partitions to be saved\n this.dirtyPartitions = [-1];\n for (idx = 0; idx < clen; idx++) {\n if (dbref.collections[idx].dirty) {\n this.dirtyPartitions.push(idx);\n }\n }\n\n this.saveNextPartition(function (err) {\n callback(err);\n });\n };\n\n /**\n * Helper method used internally to save each dirty collection, one at a time.\n *\n * @param {function} callback - adapter callback to return load result to caller\n */\n LokiPartitioningAdapter.prototype.saveNextPartition = function (callback) {\n var self = this;\n var partition = this.dirtyPartitions.shift();\n var keyname = this.dbname + ((partition === -1) ? \"\" : (\".\" + partition));\n\n // if we are doing paging and this is collection partition\n if (this.options.paging && partition !== -1) {\n this.pageIterator = {\n collection: partition,\n docIndex: 0,\n pageIndex: 0\n };\n\n // since saveNextPage recursively calls itself until done, our callback means this whole paged partition is finished\n this.saveNextPage(function (err) {\n if (self.dirtyPartitions.length === 0) {\n callback(err);\n }\n else {\n self.saveNextPartition(callback);\n }\n });\n return;\n }\n\n // otherwise this is 'non-paged' partioning...\n var result = this.dbref.serializeDestructured({\n partitioned: true,\n delimited: true,\n partition: partition\n });\n\n this.adapter.saveDatabase(keyname, result, function (err) {\n if (err) {\n callback(err);\n return;\n }\n\n if (self.dirtyPartitions.length === 0) {\n callback(null);\n }\n else {\n self.saveNextPartition(callback);\n }\n });\n };\n\n /**\n * Helper method used internally to generate and save the next page of the current (dirty) partition.\n *\n * @param {function} callback - adapter callback to return load result to caller\n */\n LokiPartitioningAdapter.prototype.saveNextPage = function (callback) {\n var self = this;\n var coll = this.dbref.collections[this.pageIterator.collection];\n var keyname = this.dbname + \".\" + this.pageIterator.collection + \".\" + this.pageIterator.pageIndex;\n var pageLen = 0,\n cdlen = coll.data.length,\n delimlen = this.options.delimiter.length;\n var serializedObject = \"\",\n pageBuilder = \"\";\n var doneWithPartition = false,\n doneWithPage = false;\n\n var pageSaveCallback = function (err) {\n pageBuilder = \"\";\n\n if (err) {\n callback(err);\n }\n\n // update meta properties then continue process by invoking callback\n if (doneWithPartition) {\n callback(null);\n }\n else {\n self.pageIterator.pageIndex++;\n self.saveNextPage(callback);\n }\n };\n\n if (coll.data.length === 0) {\n doneWithPartition = true;\n }\n\n while (true) {\n if (!doneWithPartition) {\n // serialize object\n serializedObject = JSON.stringify(coll.data[this.pageIterator.docIndex]);\n pageBuilder += serializedObject;\n pageLen += serializedObject.length;\n\n // if no more documents in collection to add, we are done with partition\n if (++this.pageIterator.docIndex >= cdlen) doneWithPartition = true;\n }\n // if our current page is bigger than defined pageSize, we are done with page\n if (pageLen >= this.options.pageSize) doneWithPage = true;\n\n // if not done with current page, need delimiter before next item\n // if done with partition we also want a delmiter to indicate 'end of pages' final empty row\n if (!doneWithPage || doneWithPartition) {\n pageBuilder += this.options.delimiter;\n pageLen += delimlen;\n }\n\n // if we are done with page save it and pass off to next recursive call or callback\n if (doneWithPartition || doneWithPage) {\n this.adapter.saveDatabase(keyname, pageBuilder, pageSaveCallback);\n return;\n }\n }\n };\n\n /**\n * A loki persistence adapter which persists using node fs module\n * @constructor LokiFsAdapter\n */\n function LokiFsAdapter() {\n try {\n this.fs = require('fs');\n } catch (e) {\n this.fs = null;\n }\n }\n\n /**\n * loadDatabase() - Load data from file, will throw an error if the file does not exist\n * @param {string} dbname - the filename of the database to load\n * @param {function} callback - the callback to handle the result\n * @memberof LokiFsAdapter\n */\n LokiFsAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) {\n var self = this;\n\n this.fs.stat(dbname, function (err, stats) {\n if (!err && stats.isFile()) {\n self.fs.readFile(dbname, {\n encoding: 'utf8'\n }, function readFileCallback(err, data) {\n if (err) {\n callback(new Error(err));\n } else {\n callback(data);\n }\n });\n }\n else {\n callback(null);\n }\n });\n };\n\n /**\n * saveDatabase() - save data to file, will throw an error if the file can't be saved\n * might want to expand this to avoid dataloss on partial save\n * @param {string} dbname - the filename of the database to load\n * @param {function} callback - the callback to handle the result\n * @memberof LokiFsAdapter\n */\n LokiFsAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) {\n var self = this;\n var tmpdbname = dbname + '~';\n this.fs.writeFile(tmpdbname, dbstring, function writeFileCallback(err) {\n if (err) {\n callback(new Error(err));\n } else {\n self.fs.rename(tmpdbname, dbname, callback);\n }\n });\n };\n\n /**\n * deleteDatabase() - delete the database file, will throw an error if the\n * file can't be deleted\n * @param {string} dbname - the filename of the database to delete\n * @param {function} callback - the callback to handle the result\n * @memberof LokiFsAdapter\n */\n LokiFsAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) {\n this.fs.unlink(dbname, function deleteDatabaseCallback(err) {\n if (err) {\n callback(new Error(err));\n } else {\n callback();\n }\n });\n };\n\n\n /**\n * A loki persistence adapter which persists to web browser's local storage object\n * @constructor LokiLocalStorageAdapter\n */\n function LokiLocalStorageAdapter() { }\n\n /**\n * loadDatabase() - Load data from localstorage\n * @param {string} dbname - the name of the database to load\n * @param {function} callback - the callback to handle the result\n * @memberof LokiLocalStorageAdapter\n */\n LokiLocalStorageAdapter.prototype.loadDatabase = function loadDatabase(dbname, callback) {\n if (localStorageAvailable()) {\n callback(localStorage.getItem(dbname));\n } else {\n callback(new Error('localStorage is not available'));\n }\n };\n\n /**\n * saveDatabase() - save data to localstorage, will throw an error if the file can't be saved\n * might want to expand this to avoid dataloss on partial save\n * @param {string} dbname - the filename of the database to load\n * @param {function} callback - the callback to handle the result\n * @memberof LokiLocalStorageAdapter\n */\n LokiLocalStorageAdapter.prototype.saveDatabase = function saveDatabase(dbname, dbstring, callback) {\n if (localStorageAvailable()) {\n localStorage.setItem(dbname, dbstring);\n callback(null);\n } else {\n callback(new Error('localStorage is not available'));\n }\n };\n\n /**\n * deleteDatabase() - delete the database from localstorage, will throw an error if it\n * can't be deleted\n * @param {string} dbname - the filename of the database to delete\n * @param {function} callback - the callback to handle the result\n * @memberof LokiLocalStorageAdapter\n */\n LokiLocalStorageAdapter.prototype.deleteDatabase = function deleteDatabase(dbname, callback) {\n if (localStorageAvailable()) {\n localStorage.removeItem(dbname);\n callback(null);\n } else {\n callback(new Error('localStorage is not available'));\n }\n };\n\n /**\n * Wait for throttledSaves to complete and invoke your callback when drained or duration is met.\n *\n * @param {function} callback - callback to fire when save queue is drained, it is passed a sucess parameter value\n * @param {object=} options - configuration options\n * @param {boolean} options.recursiveWait - (default: true) if after queue is drained, another save was kicked off, wait for it\n * @param {bool} options.recursiveWaitLimit - (default: false) limit our recursive waiting to a duration\n * @param {int} options.recursiveWaitLimitDelay - (default: 2000) cutoff in ms to stop recursively re-draining\n * @memberof Loki\n */\n Loki.prototype.throttledSaveDrain = function (callback, options) {\n var self = this;\n var now = (new Date()).getTime();\n\n if (!this.throttledSaves) {\n callback(true);\n }\n\n options = options || {};\n if (!options.hasOwnProperty('recursiveWait')) {\n options.recursiveWait = true;\n }\n if (!options.hasOwnProperty('recursiveWaitLimit')) {\n options.recursiveWaitLimit = false;\n }\n if (!options.hasOwnProperty('recursiveWaitLimitDuration')) {\n options.recursiveWaitLimitDuration = 2000;\n }\n if (!options.hasOwnProperty('started')) {\n options.started = (new Date()).getTime();\n }\n\n // if save is pending\n if (this.throttledSaves && this.throttledSavePending) {\n // if we want to wait until we are in a state where there are no pending saves at all\n if (options.recursiveWait) {\n // queue the following meta callback for when it completes\n this.throttledCallbacks.push(function () {\n // if there is now another save pending...\n if (self.throttledSavePending) {\n // if we wish to wait only so long and we have exceeded limit of our waiting, callback with false success value\n if (options.recursiveWaitLimit && (now - options.started > options.recursiveWaitLimitDuration)) {\n callback(false);\n return;\n }\n // it must be ok to wait on next queue drain\n self.throttledSaveDrain(callback, options);\n return;\n }\n // no pending saves so callback with true success\n else {\n callback(true);\n return;\n }\n });\n }\n // just notify when current queue is depleted\n else {\n this.throttledCallbacks.push(callback);\n return;\n }\n }\n // no save pending, just callback\n else {\n callback(true);\n }\n };\n\n /**\n * Internal load logic, decoupled from throttling/contention logic\n *\n * @param {object} options - not currently used (remove or allow overrides?)\n * @param {function=} callback - (Optional) user supplied async callback / error handler\n */\n Loki.prototype.loadDatabaseInternal = function (options, callback) {\n var cFun = callback || function (err, data) {\n if (err) {\n throw err;\n }\n },\n self = this;\n\n // the persistenceAdapter should be present if all is ok, but check to be sure.\n if (this.persistenceAdapter !== null) {\n\n this.persistenceAdapter.loadDatabase(this.filename, function loadDatabaseCallback(dbString) {\n if (typeof (dbString) === 'string') {\n var parseSuccess = false;\n try {\n self.loadJSON(dbString, options || {});\n parseSuccess = true;\n } catch (err) {\n cFun(err);\n }\n if (parseSuccess) {\n cFun(null);\n self.emit('loaded', 'database ' + self.filename + ' loaded');\n }\n } else {\n // falsy result means new database\n if (!dbString) {\n cFun(null);\n self.emit('loaded', 'empty database ' + self.filename + ' loaded');\n return;\n }\n\n // instanceof error means load faulted\n if (dbString instanceof Error) {\n cFun(dbString);\n return;\n }\n\n // if adapter has returned an js object (other than null or error) attempt to load from JSON object\n if (typeof (dbString) === \"object\") {\n self.loadJSONObject(dbString, options || {});\n cFun(null); // return null on success\n self.emit('loaded', 'database ' + self.filename + ' loaded');\n return;\n }\n\n cFun(\"unexpected adapter response : \" + dbString);\n }\n });\n\n } else {\n cFun(new Error('persistenceAdapter not configured'));\n }\n };\n\n /**\n * Handles manually loading from file system, local storage, or adapter (such as indexeddb)\n * This method utilizes loki configuration options (if provided) to determine which\n * persistence method to use, or environment detection (if configuration was not provided).\n * To avoid contention with any throttledSaves, we will drain the save queue first.\n *\n * If you are configured with autosave, you do not need to call this method yourself.\n *\n * @param {object} options - if throttling saves and loads, this controls how we drain save queue before loading\n * @param {boolean} options.recursiveWait - (default: true) wait recursively until no saves are queued\n * @param {bool} options.recursiveWaitLimit - (default: false) limit our recursive waiting to a duration\n * @param {int} options.recursiveWaitLimitDelay - (default: 2000) cutoff in ms to stop recursively re-draining\n * @param {function=} callback - (Optional) user supplied async callback / error handler\n * @memberof Loki\n * @example\n * db.loadDatabase({}, function(err) {\n * if (err) {\n * console.log(\"error : \" + err);\n * }\n * else {\n * console.log(\"database loaded.\");\n * }\n * });\n */\n Loki.prototype.loadDatabase = function (options, callback) {\n var self = this;\n\n // if throttling disabled, just call internal\n if (!this.throttledSaves) {\n this.loadDatabaseInternal(options, callback);\n return;\n }\n\n // try to drain any pending saves in the queue to lock it for loading\n this.throttledSaveDrain(function (success) {\n if (success) {\n // pause/throttle saving until loading is done\n self.throttledSavePending = true;\n\n self.loadDatabaseInternal(options, function (err) {\n // now that we are finished loading, if no saves were throttled, disable flag\n if (self.throttledCallbacks.length === 0) {\n self.throttledSavePending = false;\n }\n // if saves requests came in while loading, kick off new save to kick off resume saves\n else {\n self.saveDatabase();\n }\n\n if (typeof callback === 'function') {\n callback(err);\n }\n });\n return;\n }\n else {\n if (typeof callback === 'function') {\n callback(new Error(\"Unable to pause save throttling long enough to read database\"));\n }\n }\n }, options);\n };\n\n /**\n * Internal save logic, decoupled from save throttling logic\n */\n Loki.prototype.saveDatabaseInternal = function (callback) {\n var cFun = callback || function (err) {\n if (err) {\n throw err;\n }\n return;\n };\n var self = this;\n\n // the persistenceAdapter should be present if all is ok, but check to be sure.\n if (!this.persistenceAdapter) {\n cFun(new Error('persistenceAdapter not configured'));\n return;\n }\n\n // run incremental, reference, or normal mode adapters, depending on what's available\n if (this.persistenceAdapter.mode === \"incremental\") {\n var cachedDirty;\n // ignore autosave until we copy loki (only then we can clear dirty flags,\n // but if we don't do it now, autosave will be triggered a lot unnecessarily)\n this.ignoreAutosave = true;\n this.persistenceAdapter.saveDatabase(\n this.filename,\n function getLokiCopy() {\n self.ignoreAutosave = false;\n if (cachedDirty) {\n cFun(new Error('adapter error - getLokiCopy called more than once'));\n return;\n }\n var lokiCopy = self.copy({ removeNonSerializable: true });\n\n // remember and clear dirty ids -- we must do it before the save so that if\n // and update occurs between here and callback, it will get saved later\n cachedDirty = self.collections.map(function (collection) {\n return [collection.dirty, collection.dirtyIds];\n });\n self.collections.forEach(function (col) {\n col.dirty = false;\n col.dirtyIds = [];\n });\n return lokiCopy;\n },\n function exportDatabaseCallback(err) {\n self.ignoreAutosave = false;\n if (err && cachedDirty) {\n // roll back dirty IDs to be saved later\n self.collections.forEach(function (col, i) {\n var cached = cachedDirty[i];\n col.dirty = col.dirty || cached[0];\n col.dirtyIds = col.dirtyIds.concat(cached[1]);\n });\n }\n cFun(err);\n });\n } else if (this.persistenceAdapter.mode === \"reference\" && typeof this.persistenceAdapter.exportDatabase === \"function\") {\n // TODO: dirty should be cleared here\n // filename may seem redundant but loadDatabase will need to expect this same filename\n this.persistenceAdapter.exportDatabase(this.filename, this.copy({ removeNonSerializable: true }), function exportDatabaseCallback(err) {\n self.autosaveClearFlags();\n cFun(err);\n });\n }\n // otherwise just pass the serialized database to adapter\n else {\n // persistenceAdapter might be asynchronous, so we must clear `dirty` immediately\n // or autosave won't work if an update occurs between here and the callback\n // TODO: This should be stored and rolled back in case of DB save failure\n this.autosaveClearFlags();\n this.persistenceAdapter.saveDatabase(this.filename, this.serialize(), function saveDatabasecallback(err) {\n cFun(err);\n });\n }\n };\n\n /**\n * Handles manually saving to file system, local storage, or adapter (such as indexeddb)\n * This method utilizes loki configuration options (if provided) to determine which\n * persistence method to use, or environment detection (if configuration was not provided).\n *\n * If you are configured with autosave, you do not need to call this method yourself.\n *\n * @param {function=} callback - (Optional) user supplied async callback / error handler\n * @memberof Loki\n * @example\n * db.saveDatabase(function(err) {\n * if (err) {\n * console.log(\"error : \" + err);\n * }\n * else {\n * console.log(\"database saved.\");\n * }\n * });\n */\n Loki.prototype.saveDatabase = function (callback) {\n if (!this.throttledSaves) {\n this.saveDatabaseInternal(callback);\n return;\n }\n\n if (this.throttledSavePending) {\n this.throttledCallbacks.push(callback);\n return;\n }\n\n var localCallbacks = this.throttledCallbacks;\n this.throttledCallbacks = [];\n localCallbacks.unshift(callback);\n this.throttledSavePending = true;\n\n var self = this;\n this.saveDatabaseInternal(function (err) {\n self.throttledSavePending = false;\n localCallbacks.forEach(function (pcb) {\n if (typeof pcb === 'function') {\n // Queue the callbacks so we first finish this method execution\n setTimeout(function () {\n pcb(err);\n }, 1);\n }\n });\n\n // since this is called async, future requests may have come in, if so.. kick off next save\n if (self.throttledCallbacks.length > 0) {\n self.saveDatabase();\n }\n });\n };\n\n // alias\n Loki.prototype.save = Loki.prototype.saveDatabase;\n\n /**\n * Handles deleting a database from file system, local\n * storage, or adapter (indexeddb)\n * This method utilizes loki configuration options (if provided) to determine which\n * persistence method to use, or environment detection (if configuration was not provided).\n *\n * @param {function=} callback - (Optional) user supplied async callback / error handler\n * @memberof Loki\n */\n Loki.prototype.deleteDatabase = function (options, callback) {\n var cFun = callback || function (err, data) {\n if (err) {\n throw err;\n }\n };\n\n // we aren't even using options, so we will support syntax where\n // callback is passed as first and only argument\n if (typeof options === 'function' && !callback) {\n cFun = options;\n }\n\n // the persistenceAdapter should be present if all is ok, but check to be sure.\n if (this.persistenceAdapter !== null) {\n this.persistenceAdapter.deleteDatabase(this.filename, function deleteDatabaseCallback(err) {\n cFun(err);\n });\n } else {\n cFun(new Error('persistenceAdapter not configured'));\n }\n };\n\n /**\n * autosaveDirty - check whether any collections are 'dirty' meaning we need to save (entire) database\n *\n * @returns {boolean} - true if database has changed since last autosave, false if not.\n */\n Loki.prototype.autosaveDirty = function () {\n for (var idx = 0; idx < this.collections.length; idx++) {\n if (this.collections[idx].dirty) {\n return true;\n }\n }\n\n return false;\n };\n\n /**\n * autosaveClearFlags - resets dirty flags on all collections.\n * Called from saveDatabase() after db is saved.\n *\n */\n Loki.prototype.autosaveClearFlags = function () {\n for (var idx = 0; idx < this.collections.length; idx++) {\n this.collections[idx].dirty = false;\n }\n };\n\n /**\n * autosaveEnable - begin a javascript interval to periodically save the database.\n *\n * @param {object} options - not currently used (remove or allow overrides?)\n * @param {function=} callback - (Optional) user supplied async callback\n */\n Loki.prototype.autosaveEnable = function (options, callback) {\n this.autosave = true;\n\n var delay = 5000,\n self = this;\n\n if (typeof (this.autosaveInterval) !== 'undefined' && this.autosaveInterval !== null) {\n delay = this.autosaveInterval;\n }\n\n this.autosaveHandle = setInterval(function autosaveHandleInterval() {\n // use of dirty flag will need to be hierarchical since mods are done at collection level with no visibility of 'db'\n // so next step will be to implement collection level dirty flags set on insert/update/remove\n // along with loki level isdirty() function which iterates all collections to see if any are dirty\n\n if (self.autosaveDirty() && !self.ignoreAutosave) {\n self.saveDatabase(callback);\n }\n }, delay);\n };\n\n /**\n * autosaveDisable - stop the autosave interval timer.\n *\n */\n Loki.prototype.autosaveDisable = function () {\n if (typeof (this.autosaveHandle) !== 'undefined' && this.autosaveHandle !== null) {\n clearInterval(this.autosaveHandle);\n this.autosaveHandle = null;\n }\n };\n\n\n /**\n * Resultset class allowing chainable queries. Intended to be instanced internally.\n * Collection.find(), Collection.where(), and Collection.chain() instantiate this.\n *\n * @example\n * mycollection.chain()\n * .find({ 'doors' : 4 })\n * .where(function(obj) { return obj.name === 'Toyota' })\n * .data();\n *\n * @constructor Resultset\n * @param {Collection} collection - The collection which this Resultset will query against.\n */\n function Resultset(collection, options) {\n options = options || {};\n\n // retain reference to collection we are querying against\n this.collection = collection;\n this.filteredrows = [];\n this.filterInitialized = false;\n\n return this;\n }\n\n /**\n * reset() - Reset the resultset to its initial state.\n *\n * @returns {Resultset} Reference to this resultset, for future chain operations.\n */\n Resultset.prototype.reset = function () {\n if (this.filteredrows.length > 0) {\n this.filteredrows = [];\n }\n this.filterInitialized = false;\n return this;\n };\n\n /**\n * toJSON() - Override of toJSON to avoid circular references\n *\n */\n Resultset.prototype.toJSON = function () {\n var copy = this.copy();\n copy.collection = null;\n return copy;\n };\n\n /**\n * Allows you to limit the number of documents passed to next chain operation.\n * A resultset copy() is made to avoid altering original resultset.\n *\n * @param {int} qty - The number of documents to return.\n * @returns {Resultset} Returns a copy of the resultset, limited by qty, for subsequent chain ops.\n * @memberof Resultset\n * // find the two oldest users\n * var result = users.chain().simplesort(\"age\", true).limit(2).data();\n */\n Resultset.prototype.limit = function (qty) {\n // if this has no filters applied, we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n var rscopy = new Resultset(this.collection);\n rscopy.filteredrows = this.filteredrows.slice(0, qty);\n rscopy.filterInitialized = true;\n return rscopy;\n };\n\n /**\n * Used for skipping 'pos' number of documents in the resultset.\n *\n * @param {int} pos - Number of documents to skip; all preceding documents are filtered out.\n * @returns {Resultset} Returns a copy of the resultset, containing docs starting at 'pos' for subsequent chain ops.\n * @memberof Resultset\n * // find everyone but the two oldest users\n * var result = users.chain().simplesort(\"age\", true).offset(2).data();\n */\n Resultset.prototype.offset = function (pos) {\n // if this has no filters applied, we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n var rscopy = new Resultset(this.collection);\n rscopy.filteredrows = this.filteredrows.slice(pos);\n rscopy.filterInitialized = true;\n return rscopy;\n };\n\n /**\n * copy() - To support reuse of resultset in branched query situations.\n *\n * @returns {Resultset} Returns a copy of the resultset (set) but the underlying document references will be the same.\n * @memberof Resultset\n */\n Resultset.prototype.copy = function () {\n var result = new Resultset(this.collection);\n\n if (this.filteredrows.length > 0) {\n result.filteredrows = this.filteredrows.slice();\n }\n result.filterInitialized = this.filterInitialized;\n\n return result;\n };\n\n /**\n * Alias of copy()\n * @memberof Resultset\n */\n Resultset.prototype.branch = Resultset.prototype.copy;\n\n /**\n * transform() - executes a named collection transform or raw array of transform steps against the resultset.\n *\n * @param transform {(string|array)} - name of collection transform or raw transform array\n * @param parameters {object=} - (Optional) object property hash of parameters, if the transform requires them.\n * @returns {Resultset} either (this) resultset or a clone of of this resultset (depending on steps)\n * @memberof Resultset\n * @example\n * users.addTransform('CountryFilter', [\n * {\n * type: 'find',\n * value: {\n * 'country': { $eq: '[%lktxp]Country' }\n * }\n * },\n * {\n * type: 'simplesort',\n * property: 'age',\n * options: { desc: false}\n * }\n * ]);\n * var results = users.chain().transform(\"CountryFilter\", { Country: 'fr' }).data();\n */\n Resultset.prototype.transform = function (transform, parameters) {\n var idx,\n step,\n rs = this;\n\n // if transform is name, then do lookup first\n if (typeof transform === 'string') {\n if (this.collection.transforms.hasOwnProperty(transform)) {\n transform = this.collection.transforms[transform];\n }\n }\n\n // either they passed in raw transform array or we looked it up, so process\n if (typeof transform !== 'object' || !Array.isArray(transform)) {\n throw new Error(\"Invalid transform\");\n }\n\n if (typeof parameters !== 'undefined') {\n transform = Utils.resolveTransformParams(transform, parameters);\n }\n\n for (idx = 0; idx < transform.length; idx++) {\n step = transform[idx];\n\n switch (step.type) {\n case \"find\":\n rs.find(step.value);\n break;\n case \"where\":\n rs.where(step.value);\n break;\n case \"simplesort\":\n rs.simplesort(step.property, step.desc || step.options);\n break;\n case \"compoundsort\":\n rs.compoundsort(step.value);\n break;\n case \"sort\":\n rs.sort(step.value);\n break;\n case \"limit\":\n rs = rs.limit(step.value);\n break; // limit makes copy so update reference\n case \"offset\":\n rs = rs.offset(step.value);\n break; // offset makes copy so update reference\n case \"map\":\n rs = rs.map(step.value, step.dataOptions);\n break;\n case \"eqJoin\":\n rs = rs.eqJoin(step.joinData, step.leftJoinKey, step.rightJoinKey, step.mapFun, step.dataOptions);\n break;\n // following cases break chain by returning array data so make any of these last in transform steps\n case \"mapReduce\":\n rs = rs.mapReduce(step.mapFunction, step.reduceFunction);\n break;\n // following cases update documents in current filtered resultset (use carefully)\n case \"update\":\n rs.update(step.value);\n break;\n case \"remove\":\n rs.remove();\n break;\n default:\n break;\n }\n }\n\n return rs;\n };\n\n /**\n * User supplied compare function is provided two documents to compare. (chainable)\n * @example\n * rslt.sort(function(obj1, obj2) {\n * if (obj1.name === obj2.name) return 0;\n * if (obj1.name > obj2.name) return 1;\n * if (obj1.name < obj2.name) return -1;\n * });\n *\n * @param {function} comparefun - A javascript compare function used for sorting.\n * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.\n * @memberof Resultset\n */\n Resultset.prototype.sort = function (comparefun) {\n // if this has no filters applied, just we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n var wrappedComparer =\n (function (userComparer, data) {\n return function (a, b) {\n return userComparer(data[a], data[b]);\n };\n })(comparefun, this.collection.data);\n\n this.filteredrows.sort(wrappedComparer);\n\n return this;\n };\n\n /**\n * Simpler, loose evaluation for user to sort based on a property name. (chainable).\n * Sorting based on the same lt/gt helper functions used for binary indices.\n *\n * @param {string} propname - name of property to sort by.\n * @param {object|bool=} options - boolean to specify if isdescending, or options object\n * @param {boolean} [options.desc=false] - whether to sort descending\n * @param {boolean} [options.disableIndexIntersect=false] - whether we should explicity not use array intersection.\n * @param {boolean} [options.forceIndexIntersect=false] - force array intersection (if binary index exists).\n * @param {boolean} [options.useJavascriptSorting=false] - whether results are sorted via basic javascript sort.\n * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.\n * @memberof Resultset\n * @example\n * var results = users.chain().simplesort('age').data();\n */\n Resultset.prototype.simplesort = function (propname, options) {\n var eff,\n targetEff = 10,\n dc = this.collection.data.length,\n frl = this.filteredrows.length,\n hasBinaryIndex = this.collection.binaryIndices.hasOwnProperty(propname);\n\n if (typeof (options) === 'undefined' || options === false) {\n options = { desc: false };\n }\n if (options === true) {\n options = { desc: true };\n }\n\n // if nothing in filtered rows array...\n if (frl === 0) {\n // if the filter is initialized to be empty resultset, do nothing\n if (this.filterInitialized) {\n return this;\n }\n\n // otherwise no filters applied implies all documents, so we need to populate filteredrows first\n\n // if we have a binary index, we can just use that instead of sorting (again)\n if (this.collection.binaryIndices.hasOwnProperty(propname)) {\n // make sure index is up-to-date\n this.collection.ensureIndex(propname);\n // copy index values into filteredrows\n this.filteredrows = this.collection.binaryIndices[propname].values.slice(0);\n\n if (options.desc) {\n this.filteredrows.reverse();\n }\n\n // we are done, return this (resultset) for further chain ops\n return this;\n }\n // otherwise initialize array for sort below\n else {\n // build full document index (to be sorted subsequently)\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n }\n // otherwise we had results to begin with, see if we qualify for index intercept optimization\n else {\n\n // If already filtered, but we want to leverage binary index on sort.\n // This will use custom array intection algorithm.\n if (!options.disableIndexIntersect && hasBinaryIndex) {\n\n // calculate filter efficiency\n eff = dc / frl;\n\n // when javascript sort fallback is enabled, you generally need more than ~17% of total docs in resultset\n // before array intersect is determined to be the faster algorithm, otherwise leave at 10% for loki sort.\n if (options.useJavascriptSorting) {\n targetEff = 6;\n }\n\n // anything more than ratio of 10:1 (total documents/current results) should use old sort code path\n // So we will only use array intersection if you have more than 10% of total docs in your current resultset.\n if (eff <= targetEff || options.forceIndexIntersect) {\n var idx, fr = this.filteredrows;\n var io = {};\n // set up hashobject for simple 'inclusion test' with existing (filtered) results\n for (idx = 0; idx < frl; idx++) {\n io[fr[idx]] = true;\n }\n // grab full sorted binary index array\n var pv = this.collection.binaryIndices[propname].values;\n\n // filter by existing results\n this.filteredrows = pv.filter(function (n) { return io[n]; });\n\n if (options.desc) {\n this.filteredrows.reverse();\n }\n\n return this;\n }\n }\n }\n\n // at this point, we will not be able to leverage binary index so we will have to do an array sort\n\n // if we have opted to use simplified javascript comparison function...\n if (options.useJavascriptSorting) {\n return this.sort(function (obj1, obj2) {\n if (obj1[propname] === obj2[propname]) return 0;\n if (obj1[propname] > obj2[propname]) return 1;\n if (obj1[propname] < obj2[propname]) return -1;\n });\n }\n\n // otherwise use loki sort which will return same results if column is indexed or not\n var wrappedComparer =\n (function (prop, desc, data) {\n var val1, val2, arr;\n return function (a, b) {\n if (~prop.indexOf('.')) {\n arr = prop.split('.');\n val1 = Utils.getIn(data[a], arr, true);\n val2 = Utils.getIn(data[b], arr, true);\n } else {\n val1 = data[a][prop];\n val2 = data[b][prop];\n }\n return sortHelper(val1, val2, desc);\n };\n })(propname, options.desc, this.collection.data);\n\n this.filteredrows.sort(wrappedComparer);\n\n return this;\n };\n\n /**\n * Allows sorting a resultset based on multiple columns.\n * @example\n * // to sort by age and then name (both ascending)\n * rs.compoundsort(['age', 'name']);\n * // to sort by age (ascending) and then by name (descending)\n * rs.compoundsort(['age', ['name', true]]);\n *\n * @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order\n * @returns {Resultset} Reference to this resultset, sorted, for future chain operations.\n * @memberof Resultset\n */\n Resultset.prototype.compoundsort = function (properties) {\n if (properties.length === 0) {\n throw new Error(\"Invalid call to compoundsort, need at least one property\");\n }\n\n var prop;\n if (properties.length === 1) {\n prop = properties[0];\n if (Array.isArray(prop)) {\n return this.simplesort(prop[0], prop[1]);\n }\n return this.simplesort(prop, false);\n }\n\n // unify the structure of 'properties' to avoid checking it repeatedly while sorting\n for (var i = 0, len = properties.length; i < len; i += 1) {\n prop = properties[i];\n if (!Array.isArray(prop)) {\n properties[i] = [prop, false];\n }\n }\n\n // if this has no filters applied, just we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n var wrappedComparer =\n (function (props, data) {\n return function (a, b) {\n return compoundeval(props, data[a], data[b]);\n };\n })(properties, this.collection.data);\n\n this.filteredrows.sort(wrappedComparer);\n\n return this;\n };\n\n /**\n * findOr() - oversee the operation of OR'ed query expressions.\n * OR'ed expression evaluation runs each expression individually against the full collection,\n * and finally does a set OR on each expression's results.\n * Each evaluation can utilize a binary index to prevent multiple linear array scans.\n *\n * @param {array} expressionArray - array of expressions\n * @returns {Resultset} this resultset for further chain ops.\n */\n Resultset.prototype.findOr = function (expressionArray) {\n var fr = null,\n fri = 0,\n frlen = 0,\n docset = [],\n idxset = [],\n idx = 0,\n origCount = this.count();\n\n // If filter is already initialized, then we query against only those items already in filter.\n // This means no index utilization for fields, so hopefully its filtered to a smallish filteredrows.\n for (var ei = 0, elen = expressionArray.length; ei < elen; ei++) {\n // we need to branch existing query to run each filter separately and combine results\n fr = this.branch().find(expressionArray[ei]).filteredrows;\n frlen = fr.length;\n\n // add any document 'hits'\n for (fri = 0; fri < frlen; fri++) {\n idx = fr[fri];\n if (idxset[idx] === undefined) {\n idxset[idx] = true;\n docset.push(idx);\n }\n }\n }\n\n this.filteredrows = docset;\n this.filterInitialized = true;\n\n return this;\n };\n Resultset.prototype.$or = Resultset.prototype.findOr;\n\n // precompile recursively\n function precompileQuery(operator, value) {\n // for regex ops, precompile\n if (operator === '$regex') {\n if (Array.isArray(value)) {\n value = new RegExp(value[0], value[1]);\n } else if (!(value instanceof RegExp)) {\n value = new RegExp(value);\n }\n }\n else if (typeof value === 'object') {\n for (var key in value) {\n if (key === '$regex' || typeof value[key] === 'object') {\n value[key] = precompileQuery(key, value[key]);\n }\n }\n }\n\n return value;\n }\n\n /**\n * findAnd() - oversee the operation of AND'ed query expressions.\n * AND'ed expression evaluation runs each expression progressively against the full collection,\n * internally utilizing existing chained resultset functionality.\n * Only the first filter can utilize a binary index.\n *\n * @param {array} expressionArray - array of expressions\n * @returns {Resultset} this resultset for further chain ops.\n */\n Resultset.prototype.findAnd = function (expressionArray) {\n // we have already implementing method chaining in this (our Resultset class)\n // so lets just progressively apply user supplied and filters\n for (var i = 0, len = expressionArray.length; i < len; i++) {\n if (this.count() === 0) {\n return this;\n }\n this.find(expressionArray[i]);\n }\n return this;\n };\n Resultset.prototype.$and = Resultset.prototype.findAnd;\n\n /**\n * Used for querying via a mongo-style query object.\n *\n * @param {object} query - A mongo-style query object used for filtering current results.\n * @param {boolean=} firstOnly - (Optional) Used by collection.findOne()\n * @returns {Resultset} this resultset for further chain ops.\n * @memberof Resultset\n * @example\n * var over30 = users.chain().find({ age: { $gte: 30 } }).data();\n */\n Resultset.prototype.find = function (query, firstOnly) {\n if (this.collection.data.length === 0) {\n this.filteredrows = [];\n this.filterInitialized = true;\n return this;\n }\n\n var queryObject = query || 'getAll',\n p,\n property,\n queryObjectOp,\n obj,\n operator,\n value,\n key,\n searchByIndex = false,\n result = [],\n filters = [],\n index = null;\n\n // flag if this was invoked via findOne()\n firstOnly = firstOnly || false;\n\n if (typeof queryObject === 'object') {\n for (p in queryObject) {\n obj = {};\n obj[p] = queryObject[p];\n filters.push(obj);\n\n if (hasOwnProperty.call(queryObject, p)) {\n property = p;\n queryObjectOp = queryObject[p];\n }\n }\n // if more than one expression in single query object,\n // convert implicit $and to explicit $and\n if (filters.length > 1) {\n return this.find({ '$and': filters }, firstOnly);\n }\n }\n\n // apply no filters if they want all\n if (!property || queryObject === 'getAll') {\n if (firstOnly) {\n if (this.filterInitialized) {\n this.filteredrows = this.filteredrows.slice(0, 1);\n } else {\n this.filteredrows = (this.collection.data.length > 0) ? [0] : [];\n this.filterInitialized = true;\n }\n }\n\n return this;\n }\n\n // injecting $and and $or expression tree evaluation here.\n if (property === '$and' || property === '$or') {\n this[property](queryObjectOp);\n\n // for chained find with firstonly,\n if (firstOnly && this.filteredrows.length > 1) {\n this.filteredrows = this.filteredrows.slice(0, 1);\n }\n\n return this;\n }\n\n // see if query object is in shorthand mode (assuming eq operator)\n if (queryObjectOp === null || (typeof queryObjectOp !== 'object' || queryObjectOp instanceof Date)) {\n operator = '$eq';\n value = queryObjectOp;\n } else if (typeof queryObjectOp === 'object') {\n for (key in queryObjectOp) {\n if (hasOwnProperty.call(queryObjectOp, key)) {\n operator = key;\n value = queryObjectOp[key];\n break;\n }\n }\n } else {\n throw new Error('Do not know what you want to do.');\n }\n\n if (operator === '$regex' || typeof value === 'object') {\n value = precompileQuery(operator, value);\n }\n\n // if user is deep querying the object such as find('name.first': 'odin')\n var usingDotNotation = (property.indexOf('.') !== -1);\n\n // if an index exists for the property being queried against, use it\n // for now only enabling where it is the first filter applied and prop is indexed\n var doIndexCheck = !this.filterInitialized;\n\n if (doIndexCheck && this.collection.binaryIndices[property] && indexedOps[operator]) {\n // this is where our lazy index rebuilding will take place\n // basically we will leave all indexes dirty until we need them\n // so here we will rebuild only the index tied to this property\n // ensureIndex() will only rebuild if flagged as dirty since we are not passing force=true param\n if (this.collection.adaptiveBinaryIndices !== true) {\n this.collection.ensureIndex(property);\n }\n\n searchByIndex = true;\n index = this.collection.binaryIndices[property];\n }\n\n // opportunistically speed up $in searches from O(n*m) to O(n*log m)\n if (!searchByIndex && operator === '$in' && Array.isArray(value) && typeof Set !== 'undefined') {\n value = new Set(value);\n operator = '$inSet';\n }\n\n // the comparison function\n var fun = LokiOps[operator];\n\n // \"shortcut\" for collection data\n var t = this.collection.data;\n // filter data length\n var i = 0,\n len = 0;\n\n // Query executed differently depending on :\n // - whether the property being queried has an index defined\n // - if chained, we handle first pass differently for initial filteredrows[] population\n //\n // For performance reasons, each case has its own if block to minimize in-loop calculations\n\n var filter, rowIdx = 0, record;\n\n // If the filteredrows[] is already initialized, use it\n if (this.filterInitialized) {\n filter = this.filteredrows;\n len = filter.length;\n\n // currently supporting dot notation for non-indexed conditions only\n if (usingDotNotation) {\n property = property.split('.');\n for (i = 0; i < len; i++) {\n rowIdx = filter[i];\n record = t[rowIdx];\n if (dotSubScan(record, property, fun, value, record)) {\n result.push(rowIdx);\n if (firstOnly) {\n this.filteredrows = result;\n return this;\n }\n }\n }\n } else {\n for (i = 0; i < len; i++) {\n rowIdx = filter[i];\n record = t[rowIdx];\n if (fun(record[property], value, record)) {\n result.push(rowIdx);\n if (firstOnly) {\n this.filteredrows = result;\n return this;\n }\n }\n }\n }\n }\n // first chained query so work against data[] but put results in filteredrows\n else {\n // if not searching by index\n if (!searchByIndex) {\n len = t.length;\n\n if (usingDotNotation) {\n property = property.split('.');\n for (i = 0; i < len; i++) {\n record = t[i];\n if (dotSubScan(record, property, fun, value, record)) {\n result.push(i);\n if (firstOnly) {\n this.filteredrows = result;\n this.filterInitialized = true;\n return this;\n }\n }\n }\n } else {\n for (i = 0; i < len; i++) {\n record = t[i];\n if (fun(record[property], value, record)) {\n result.push(i);\n if (firstOnly) {\n this.filteredrows = result;\n this.filterInitialized = true;\n return this;\n }\n }\n }\n }\n } else {\n // search by index\n var segm = this.collection.calculateRange(operator, property, value);\n\n if (operator !== '$in') {\n for (i = segm[0]; i <= segm[1]; i++) {\n if (indexedOps[operator] !== true) {\n // must be a function, implying 2nd phase filtering of results from calculateRange\n if (indexedOps[operator](Utils.getIn(t[index.values[i]], property, usingDotNotation), value)) {\n result.push(index.values[i]);\n if (firstOnly) {\n this.filteredrows = result;\n this.filterInitialized = true;\n return this;\n }\n }\n }\n else {\n result.push(index.values[i]);\n if (firstOnly) {\n this.filteredrows = result;\n this.filterInitialized = true;\n return this;\n }\n }\n }\n } else {\n for (i = 0, len = segm.length; i < len; i++) {\n result.push(index.values[segm[i]]);\n if (firstOnly) {\n this.filteredrows = result;\n this.filterInitialized = true;\n return this;\n }\n }\n }\n }\n\n }\n\n this.filteredrows = result;\n this.filterInitialized = true; // next time work against filteredrows[]\n return this;\n };\n\n\n /**\n * where() - Used for filtering via a javascript filter function.\n *\n * @param {function} fun - A javascript function used for filtering current results by.\n * @returns {Resultset} this resultset for further chain ops.\n * @memberof Resultset\n * @example\n * var over30 = users.chain().where(function(obj) { return obj.age >= 30; }.data();\n */\n Resultset.prototype.where = function (fun) {\n var viewFunction,\n result = [];\n\n if ('function' === typeof fun) {\n viewFunction = fun;\n } else {\n throw new TypeError('Argument is not a stored view or a function');\n }\n try {\n // If the filteredrows[] is already initialized, use it\n if (this.filterInitialized) {\n var j = this.filteredrows.length;\n\n while (j--) {\n if (viewFunction(this.collection.data[this.filteredrows[j]]) === true) {\n result.push(this.filteredrows[j]);\n }\n }\n\n this.filteredrows = result;\n\n return this;\n }\n // otherwise this is initial chained op, work against data, push into filteredrows[]\n else {\n var k = this.collection.data.length;\n\n while (k--) {\n if (viewFunction(this.collection.data[k]) === true) {\n result.push(k);\n }\n }\n\n this.filteredrows = result;\n this.filterInitialized = true;\n\n return this;\n }\n } catch (err) {\n throw err;\n }\n };\n\n /**\n * count() - returns the number of documents in the resultset.\n *\n * @returns {number} The number of documents in the resultset.\n * @memberof Resultset\n * @example\n * var over30Count = users.chain().find({ age: { $gte: 30 } }).count();\n */\n Resultset.prototype.count = function () {\n if (this.filterInitialized) {\n return this.filteredrows.length;\n }\n return this.collection.count();\n };\n\n /**\n * Terminates the chain and returns array of filtered documents\n *\n * @param {object=} options - allows specifying 'forceClones' and 'forceCloneMethod' options.\n * @param {boolean} options.forceClones - Allows forcing the return of cloned objects even when\n * the collection is not configured for clone object.\n * @param {string} options.forceCloneMethod - Allows overriding the default or collection specified cloning method.\n * Possible values include 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'\n * @param {bool} options.removeMeta - Will force clones and strip $loki and meta properties from documents\n *\n * @returns {array} Array of documents in the resultset\n * @memberof Resultset\n * @example\n * var resutls = users.chain().find({ age: 34 }).data();\n */\n Resultset.prototype.data = function (options) {\n var result = [],\n data = this.collection.data,\n obj,\n len,\n i,\n method;\n\n options = options || {};\n\n // if user opts to strip meta, then force clones and use 'shallow' if 'force' options are not present\n if (options.removeMeta && !options.forceClones) {\n options.forceClones = true;\n options.forceCloneMethod = options.forceCloneMethod || 'shallow';\n }\n\n // if collection has delta changes active, then force clones and use 'parse-stringify' for effective change tracking of nested objects\n // if collection is immutable freeze and unFreeze takes care of cloning\n if (!this.collection.disableDeltaChangesApi && this.collection.disableFreeze) {\n options.forceClones = true;\n options.forceCloneMethod = 'parse-stringify';\n }\n\n // if this has no filters applied, just return collection.data\n if (!this.filterInitialized) {\n if (this.filteredrows.length === 0) {\n // determine whether we need to clone objects or not\n if (this.collection.cloneObjects || options.forceClones) {\n len = data.length;\n method = options.forceCloneMethod || this.collection.cloneMethod;\n for (i = 0; i < len; i++) {\n obj = clone(data[i], method);\n if (options.removeMeta) {\n delete obj.$loki;\n delete obj.meta;\n }\n result.push(obj);\n }\n return result;\n }\n // otherwise we are not cloning so return sliced array with same object references\n else {\n return data.slice();\n }\n } else {\n // filteredrows must have been set manually, so use it\n this.filterInitialized = true;\n }\n }\n\n var fr = this.filteredrows;\n len = fr.length;\n\n if (this.collection.cloneObjects || options.forceClones) {\n method = options.forceCloneMethod || this.collection.cloneMethod;\n for (i = 0; i < len; i++) {\n obj = clone(data[fr[i]], method);\n if (options.removeMeta) {\n delete obj.$loki;\n delete obj.meta;\n }\n result.push(obj);\n }\n } else {\n for (i = 0; i < len; i++) {\n result.push(data[fr[i]]);\n }\n }\n return result;\n };\n\n /**\n * Used to run an update operation on all documents currently in the resultset.\n *\n * @param {function} updateFunction - User supplied updateFunction(obj) will be executed for each document object.\n * @returns {Resultset} this resultset for further chain ops.\n * @memberof Resultset\n * @example\n * users.chain().find({ country: 'de' }).update(function(user) {\n * user.phoneFormat = \"+49 AAAA BBBBBB\";\n * });\n */\n Resultset.prototype.update = function (updateFunction) {\n\n if (typeof (updateFunction) !== \"function\") {\n throw new TypeError('Argument is not a function');\n }\n\n // if this has no filters applied, we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n var obj, len = this.filteredrows.length,\n rcd = this.collection.data;\n\n // pass in each document object currently in resultset to user supplied updateFunction\n for (var idx = 0; idx < len; idx++) {\n // if we have cloning option specified or are doing differential delta changes, clone object first\n if (!this.disableFreeze || this.collection.cloneObjects || !this.collection.disableDeltaChangesApi) {\n obj = clone(rcd[this.filteredrows[idx]], this.collection.cloneMethod);\n updateFunction(obj);\n this.collection.update(obj);\n }\n else {\n // no need to clone, so just perform update on collection data object instance\n updateFunction(rcd[this.filteredrows[idx]]);\n this.collection.update(rcd[this.filteredrows[idx]]);\n }\n }\n\n return this;\n };\n\n /**\n * Removes all document objects which are currently in resultset from collection (as well as resultset)\n *\n * @returns {Resultset} this (empty) resultset for further chain ops.\n * @memberof Resultset\n * @example\n * // remove users inactive since 1/1/2001\n * users.chain().find({ lastActive: { $lte: new Date(\"1/1/2001\").getTime() } }).remove();\n */\n Resultset.prototype.remove = function () {\n\n // if this has no filters applied, we need to populate filteredrows first\n if (!this.filterInitialized && this.filteredrows.length === 0) {\n this.filteredrows = this.collection.prepareFullDocIndex();\n }\n\n this.collection.removeBatchByPositions(this.filteredrows);\n\n this.filteredrows = [];\n\n return this;\n };\n\n /**\n * data transformation via user supplied functions\n *\n * @param {function} mapFunction - this function accepts a single document for you to transform and return\n * @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value\n * @returns {value} The output of your reduceFunction\n * @memberof Resultset\n * @example\n * var db = new loki(\"order.db\");\n * var orders = db.addCollection(\"orders\");\n * orders.insert([{ qty: 4, unitCost: 100.00 }, { qty: 10, unitCost: 999.99 }, { qty: 2, unitCost: 49.99 }]);\n *\n * function mapfun (obj) { return obj.qty*obj.unitCost };\n * function reducefun(array) {\n * var grandTotal=0;\n * array.forEach(function(orderTotal) { grandTotal += orderTotal; });\n * return grandTotal;\n * }\n * var grandOrderTotal = orders.chain().mapReduce(mapfun, reducefun);\n * console.log(grandOrderTotal);\n */\n Resultset.prototype.mapReduce = function (mapFunction, reduceFunction) {\n try {\n return reduceFunction(this.data().map(mapFunction));\n } catch (err) {\n throw err;\n }\n };\n\n /**\n * eqJoin() - Left joining two sets of data. Join keys can be defined or calculated properties\n * eqJoin expects the right join key values to be unique. Otherwise left data will be joined on the last joinData object with that key\n * @param {Array|Resultset|Collection} joinData - Data array to join to.\n * @param {(string|function)} leftJoinKey - Property name in this result set to join on or a function to produce a value to join on\n * @param {(string|function)} rightJoinKey - Property name in the joinData to join on or a function to produce a value to join on\n * @param {function=} mapFun - (Optional) A function that receives each matching pair and maps them into output objects - function(left,right){return joinedObject}\n * @param {object=} dataOptions - options to data() before input to your map function\n * @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun\n * @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object\n * @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.\n * @returns {Resultset} A resultset with data in the format [{left: leftObj, right: rightObj}]\n * @memberof Resultset\n * @example\n * var db = new loki('sandbox.db');\n *\n * var products = db.addCollection('products');\n * var orders = db.addCollection('orders');\n *\n * products.insert({ productId: \"100234\", name: \"flywheel energy storage\", unitCost: 19999.99 });\n * products.insert({ productId: \"140491\", name: \"300F super capacitor\", unitCost: 129.99 });\n * products.insert({ productId: \"271941\", name: \"fuel cell\", unitCost: 3999.99 });\n * products.insert({ productId: \"174592\", name: \"390V 3AH lithium bank\", unitCost: 4999.99 });\n *\n * orders.insert({ orderDate : new Date(\"12/1/2017\").getTime(), prodId: \"174592\", qty: 2, customerId: 2 });\n * orders.insert({ orderDate : new Date(\"4/15/2016\").getTime(), prodId: \"271941\", qty: 1, customerId: 1 });\n * orders.insert({ orderDate : new Date(\"3/12/2017\").getTime(), prodId: \"140491\", qty: 4, customerId: 4 });\n * orders.insert({ orderDate : new Date(\"7/31/2017\").getTime(), prodId: \"100234\", qty: 7, customerId: 3 });\n * orders.insert({ orderDate : new Date(\"8/3/2016\").getTime(), prodId: \"174592\", qty: 3, customerId: 5 });\n *\n * var mapfun = function(left, right) {\n * return {\n * orderId: left.$loki,\n * orderDate: new Date(left.orderDate) + '',\n * customerId: left.customerId,\n * qty: left.qty,\n * productId: left.prodId,\n * prodName: right.name,\n * prodCost: right.unitCost,\n * orderTotal: +((right.unitCost * left.qty).toFixed(2))\n * };\n * };\n *\n * // join orders with relevant product info via eqJoin\n * var orderSummary = orders.chain().eqJoin(products, \"prodId\", \"productId\", mapfun).data();\n *\n * console.log(orderSummary);\n */\n Resultset.prototype.eqJoin = function (joinData, leftJoinKey, rightJoinKey, mapFun, dataOptions) {\n\n var leftData = [],\n leftDataLength,\n rightData = [],\n rightDataLength,\n key,\n result = [],\n leftKeyisFunction = typeof leftJoinKey === 'function',\n rightKeyisFunction = typeof rightJoinKey === 'function',\n joinMap = {};\n\n //get the left data\n leftData = this.data(dataOptions);\n leftDataLength = leftData.length;\n\n //get the right data\n if (joinData instanceof Collection) {\n rightData = joinData.chain().data(dataOptions);\n } else if (joinData instanceof Resultset) {\n rightData = joinData.data(dataOptions);\n } else if (Array.isArray(joinData)) {\n rightData = joinData;\n } else {\n throw new TypeError('joinData needs to be an array or result set');\n }\n rightDataLength = rightData.length;\n\n //construct a lookup table\n\n for (var i = 0; i < rightDataLength; i++) {\n key = rightKeyisFunction ? rightJoinKey(rightData[i]) : rightData[i][rightJoinKey];\n joinMap[key] = rightData[i];\n }\n\n if (!mapFun) {\n mapFun = function (left, right) {\n return {\n left: left,\n right: right\n };\n };\n }\n\n //Run map function over each object in the resultset\n for (var j = 0; j < leftDataLength; j++) {\n key = leftKeyisFunction ? leftJoinKey(leftData[j]) : leftData[j][leftJoinKey];\n result.push(mapFun(leftData[j], joinMap[key] || {}));\n }\n\n //return return a new resultset with no filters\n this.collection = new Collection('joinData');\n this.collection.insert(result);\n this.filteredrows = [];\n this.filterInitialized = false;\n\n return this;\n };\n\n /**\n * Applies a map function into a new collection for further chaining.\n * @param {function} mapFun - javascript map function\n * @param {object=} dataOptions - options to data() before input to your map function\n * @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun\n * @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object\n * @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.\n * @memberof Resultset\n * @example\n * var orders.chain().find({ productId: 32 }).map(function(obj) {\n * return {\n * orderId: $loki,\n * productId: productId,\n * quantity: qty\n * };\n * });\n */\n Resultset.prototype.map = function (mapFun, dataOptions) {\n var data = this.data(dataOptions).map(mapFun);\n //return return a new resultset with no filters\n this.collection = new Collection('mappedData');\n this.collection.insert(data);\n this.filteredrows = [];\n this.filterInitialized = false;\n\n return this;\n };\n\n /**\n * DynamicView class is a versatile 'live' view class which can have filters and sorts applied.\n * Collection.addDynamicView(name) instantiates this DynamicView object and notifies it\n * whenever documents are add/updated/removed so it can remain up-to-date. (chainable)\n *\n * @example\n * var mydv = mycollection.addDynamicView('test'); // default is non-persistent\n * mydv.applyFind({ 'doors' : 4 });\n * mydv.applyWhere(function(obj) { return obj.name === 'Toyota'; });\n * var results = mydv.data();\n *\n * @constructor DynamicView\n * @implements LokiEventEmitter\n * @param {Collection} collection - A reference to the collection to work against\n * @param {string} name - The name of this dynamic view\n * @param {object=} options - (Optional) Pass in object with 'persistent' and/or 'sortPriority' options.\n * @param {boolean} [options.persistent=false] - indicates if view is to main internal results array in 'resultdata'\n * @param {string} [options.sortPriority='passive'] - 'passive' (sorts performed on call to data) or 'active' (after updates)\n * @param {number} options.minRebuildInterval - minimum rebuild interval (need clarification to docs here)\n * @see {@link Collection#addDynamicView} to construct instances of DynamicView\n */\n function DynamicView(collection, name, options) {\n this.collection = collection;\n this.name = name;\n this.rebuildPending = false;\n this.options = options || {};\n\n if (!this.options.hasOwnProperty('persistent')) {\n this.options.persistent = false;\n }\n\n // 'persistentSortPriority':\n // 'passive' will defer the sort phase until they call data(). (most efficient overall)\n // 'active' will sort async whenever next idle. (prioritizes read speeds)\n if (!this.options.hasOwnProperty('sortPriority')) {\n this.options.sortPriority = 'passive';\n }\n\n if (!this.options.hasOwnProperty('minRebuildInterval')) {\n this.options.minRebuildInterval = 1;\n }\n\n this.resultset = new Resultset(collection);\n this.resultdata = [];\n this.resultsdirty = false;\n\n this.cachedresultset = null;\n\n // keep ordered filter pipeline\n this.filterPipeline = [];\n if (!this.collection.disableFreeze) {\n Object.freeze(this.filterPipeline);\n }\n\n // sorting member variables\n // we only support one active search, applied using applySort() or applySimpleSort()\n this.sortFunction = null;\n this.sortCriteria = null;\n this.sortCriteriaSimple = null;\n this.sortDirty = false;\n\n // for now just have 1 event for when we finally rebuilt lazy view\n // once we refactor transactions, i will tie in certain transactional events\n\n this.events = {\n 'rebuild': [],\n 'filter': [],\n 'sort': []\n };\n }\n\n DynamicView.prototype = new LokiEventEmitter();\n DynamicView.prototype.constructor = DynamicView;\n\n /**\n * getSort() - used to get the current sort\n *\n * @returns function (sortFunction) or array (sortCriteria) or object (sortCriteriaSimple)\n */\n DynamicView.prototype.getSort = function () {\n return this.sortFunction || this.sortCriteria || this.sortCriteriaSimple;\n };\n\n /**\n * rematerialize() - internally used immediately after deserialization (loading)\n * This will clear out and reapply filterPipeline ops, recreating the view.\n * Since where filters do not persist correctly, this method allows\n * restoring the view to state where user can re-apply those where filters.\n *\n * @param {Object=} options - (Optional) allows specification of 'removeWhereFilters' option\n * @returns {DynamicView} This dynamic view for further chained ops.\n * @memberof DynamicView\n * @fires DynamicView.rebuild\n */\n DynamicView.prototype.rematerialize = function (options) {\n var fpl,\n fpi,\n idx;\n\n options = options || {};\n\n this.resultdata = [];\n this.resultsdirty = true;\n this.resultset = new Resultset(this.collection);\n\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.sortDirty = true;\n }\n\n var wasFrozen = Object.isFrozen(this.filterPipeline);\n if (options.hasOwnProperty('removeWhereFilters')) {\n // for each view see if it had any where filters applied... since they don't\n // serialize those functions lets remove those invalid filters\n if (wasFrozen) {\n this.filterPipeline = this.filterPipeline.slice();\n }\n fpl = this.filterPipeline.length;\n fpi = fpl;\n while (fpi--) {\n if (this.filterPipeline[fpi].type === 'where') {\n if (fpi !== this.filterPipeline.length - 1) {\n this.filterPipeline[fpi] = this.filterPipeline[this.filterPipeline.length - 1];\n }\n this.filterPipeline.length--;\n }\n }\n }\n\n // back up old filter pipeline, clear filter pipeline, and reapply pipeline ops\n var ofp = this.filterPipeline;\n this.filterPipeline = [];\n\n // now re-apply 'find' filterPipeline ops\n fpl = ofp.length;\n for (idx = 0; idx < fpl; idx++) {\n this.applyFind(ofp[idx].val, ofp[idx].uid);\n }\n if (wasFrozen) {\n Object.freeze(this.filterPipeline);\n }\n\n // during creation of unit tests, i will remove this forced refresh and leave lazy\n this.data();\n\n // emit rebuild event in case user wants to be notified\n this.emit('rebuild', this);\n\n return this;\n };\n\n /**\n * branchResultset() - Makes a copy of the internal resultset for branched queries.\n * Unlike this dynamic view, the branched resultset will not be 'live' updated,\n * so your branched query should be immediately resolved and not held for future evaluation.\n *\n * @param {(string|array=)} transform - Optional name of collection transform, or an array of transform steps\n * @param {object=} parameters - optional parameters (if optional transform requires them)\n * @returns {Resultset} A copy of the internal resultset for branched queries.\n * @memberof DynamicView\n * @example\n * var db = new loki('test');\n * var coll = db.addCollection('mydocs');\n * var dv = coll.addDynamicView('myview');\n * var tx = [\n * {\n * type: 'offset',\n * value: '[%lktxp]pageStart'\n * },\n * {\n * type: 'limit',\n * value: '[%lktxp]pageSize'\n * }\n * ];\n * coll.addTransform('viewPaging', tx);\n *\n * // add some records\n *\n * var results = dv.branchResultset('viewPaging', { pageStart: 10, pageSize: 10 }).data();\n */\n DynamicView.prototype.branchResultset = function (transform, parameters) {\n var rs = this.resultset.branch();\n\n if (typeof transform === 'undefined') {\n return rs;\n }\n\n return rs.transform(transform, parameters);\n };\n\n /**\n * toJSON() - Override of toJSON to avoid circular references\n *\n */\n DynamicView.prototype.toJSON = function () {\n var copy = new DynamicView(this.collection, this.name, this.options);\n copy.resultset = this.resultset;\n copy.resultdata = []; // let's not save data (copy) to minimize size\n copy.resultsdirty = true;\n copy.filterPipeline = this.filterPipeline;\n copy.sortFunction = this.sortFunction;\n copy.sortCriteria = this.sortCriteria;\n copy.sortCriteriaSimple = this.sortCriteriaSimple || null;\n copy.sortDirty = this.sortDirty;\n\n // avoid circular reference, reapply in db.loadJSON()\n copy.collection = null;\n\n return copy;\n };\n\n /**\n * removeFilters() - Used to clear pipeline and reset dynamic view to initial state.\n * Existing options should be retained.\n * @param {object=} options - configure removeFilter behavior\n * @param {boolean=} options.queueSortPhase - (default: false) if true we will async rebuild view (maybe set default to true in future?)\n * @memberof DynamicView\n */\n DynamicView.prototype.removeFilters = function (options) {\n options = options || {};\n\n this.rebuildPending = false;\n this.resultset.reset();\n this.resultdata = [];\n this.resultsdirty = true;\n\n this.cachedresultset = null;\n\n var wasFrozen = Object.isFrozen(this.filterPipeline);\n var filterChanged = this.filterPipeline.length > 0;\n // keep ordered filter pipeline\n this.filterPipeline = [];\n if (wasFrozen) {\n Object.freeze(this.filterPipeline);\n }\n\n // sorting member variables\n // we only support one active search, applied using applySort() or applySimpleSort()\n this.sortFunction = null;\n this.sortCriteria = null;\n this.sortCriteriaSimple = null;\n this.sortDirty = false;\n\n if (options.queueSortPhase === true) {\n this.queueSortPhase();\n }\n\n if (filterChanged) {\n this.emit('filter');\n }\n };\n\n /**\n * applySort() - Used to apply a sort to the dynamic view\n * @example\n * dv.applySort(function(obj1, obj2) {\n * if (obj1.name === obj2.name) return 0;\n * if (obj1.name > obj2.name) return 1;\n * if (obj1.name < obj2.name) return -1;\n * });\n *\n * @param {function} comparefun - a javascript compare function used for sorting\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.applySort = function (comparefun) {\n this.sortFunction = comparefun;\n this.sortCriteria = null;\n this.sortCriteriaSimple = null;\n\n this.queueSortPhase();\n this.emit('sort');\n\n return this;\n };\n\n /**\n * applySimpleSort() - Used to specify a property used for view translation.\n * @example\n * dv.applySimpleSort(\"name\");\n *\n * @param {string} propname - Name of property by which to sort.\n * @param {object|boolean=} options - boolean for sort descending or options object\n * @param {boolean} [options.desc=false] - whether we should sort descending.\n * @param {boolean} [options.disableIndexIntersect=false] - whether we should explicity not use array intersection.\n * @param {boolean} [options.forceIndexIntersect=false] - force array intersection (if binary index exists).\n * @param {boolean} [options.useJavascriptSorting=false] - whether results are sorted via basic javascript sort.\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.applySimpleSort = function (propname, options) {\n this.sortCriteriaSimple = { propname: propname, options: options || false };\n if (!this.collection.disableFreeze) {\n deepFreeze(this.sortCriteriaSimple);\n }\n this.sortCriteria = null;\n this.sortFunction = null;\n\n this.queueSortPhase();\n this.emit('sort');\n\n return this;\n };\n\n /**\n * applySortCriteria() - Allows sorting a resultset based on multiple columns.\n * @example\n * // to sort by age and then name (both ascending)\n * dv.applySortCriteria(['age', 'name']);\n * // to sort by age (ascending) and then by name (descending)\n * dv.applySortCriteria(['age', ['name', true]);\n * // to sort by age (descending) and then by name (descending)\n * dv.applySortCriteria(['age', true], ['name', true]);\n *\n * @param {array} properties - array of property names or subarray of [propertyname, isdesc] used evaluate sort order\n * @returns {DynamicView} Reference to this DynamicView, sorted, for future chain operations.\n * @memberof DynamicView\n */\n DynamicView.prototype.applySortCriteria = function (criteria) {\n this.sortCriteria = criteria;\n if (!this.collection.disableFreeze) {\n deepFreeze(this.sortCriteria);\n }\n this.sortCriteriaSimple = null;\n this.sortFunction = null;\n\n this.queueSortPhase();\n this.emit('sort');\n return this;\n };\n\n /**\n * startTransaction() - marks the beginning of a transaction.\n *\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n */\n DynamicView.prototype.startTransaction = function () {\n this.cachedresultset = this.resultset.copy();\n\n return this;\n };\n\n /**\n * commit() - commits a transaction.\n *\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n */\n DynamicView.prototype.commit = function () {\n this.cachedresultset = null;\n\n return this;\n };\n\n /**\n * rollback() - rolls back a transaction.\n *\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n */\n DynamicView.prototype.rollback = function () {\n this.resultset = this.cachedresultset;\n\n if (this.options.persistent) {\n // for now just rebuild the persistent dynamic view data in this worst case scenario\n // (a persistent view utilizing transactions which get rolled back), we already know the filter so not too bad.\n this.resultdata = this.resultset.data();\n\n this.emit('rebuild', this);\n }\n\n return this;\n };\n\n\n /**\n * Implementation detail.\n * _indexOfFilterWithId() - Find the index of a filter in the pipeline, by that filter's ID.\n *\n * @param {(string|number)} uid - The unique ID of the filter.\n * @returns {number}: index of the referenced filter in the pipeline; -1 if not found.\n */\n DynamicView.prototype._indexOfFilterWithId = function (uid) {\n if (typeof uid === 'string' || typeof uid === 'number') {\n for (var idx = 0, len = this.filterPipeline.length; idx < len; idx += 1) {\n if (uid === this.filterPipeline[idx].uid) {\n return idx;\n }\n }\n }\n return -1;\n };\n\n /**\n * Implementation detail.\n * _addFilter() - Add the filter object to the end of view's filter pipeline and apply the filter to the resultset.\n *\n * @param {object} filter - The filter object. Refer to applyFilter() for extra details.\n */\n DynamicView.prototype._addFilter = function (filter) {\n var wasFrozen = Object.isFrozen(this.filterPipeline);\n if (wasFrozen) {\n this.filterPipeline = this.filterPipeline.slice();\n }\n if (!this.collection.disableFreeze) {\n deepFreeze(filter);\n }\n this.filterPipeline.push(filter);\n if (wasFrozen) {\n Object.freeze(this.filterPipeline);\n }\n this.resultset[filter.type](filter.val);\n };\n\n /**\n * reapplyFilters() - Reapply all the filters in the current pipeline.\n *\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n */\n DynamicView.prototype.reapplyFilters = function () {\n this.resultset.reset();\n\n this.cachedresultset = null;\n if (this.options.persistent) {\n this.resultdata = [];\n this.resultsdirty = true;\n }\n\n var filters = this.filterPipeline;\n var wasFrozen = Object.isFrozen(filters);\n this.filterPipeline = [];\n\n for (var idx = 0, len = filters.length; idx < len; idx += 1) {\n this._addFilter(filters[idx]);\n }\n if (wasFrozen) {\n Object.freeze(this.filterPipeline);\n }\n\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n this.emit('filter');\n return this;\n };\n\n /**\n * applyFilter() - Adds or updates a filter in the DynamicView filter pipeline\n *\n * @param {object} filter - A filter object to add to the pipeline.\n * The object is in the format { 'type': filter_type, 'val', filter_param, 'uid', optional_filter_id }\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.applyFilter = function (filter) {\n var idx = this._indexOfFilterWithId(filter.uid);\n if (idx >= 0) {\n var wasFrozen = Object.isFrozen(this.filterPipeline);\n if (wasFrozen) {\n this.filterPipeline = this.filterPipeline.slice();\n }\n this.filterPipeline[idx] = filter;\n if (wasFrozen) {\n freeze(filter);\n Object.freeze(this.filterPipeline);\n }\n return this.reapplyFilters();\n }\n\n this.cachedresultset = null;\n if (this.options.persistent) {\n this.resultdata = [];\n this.resultsdirty = true;\n }\n\n this._addFilter(filter);\n\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n\n this.emit('filter');\n return this;\n };\n\n /**\n * applyFind() - Adds or updates a mongo-style query option in the DynamicView filter pipeline\n *\n * @param {object} query - A mongo-style query object to apply to pipeline\n * @param {(string|number)=} uid - Optional: The unique ID of this filter, to reference it in the future.\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.applyFind = function (query, uid) {\n this.applyFilter({\n type: 'find',\n val: query,\n uid: uid\n });\n return this;\n };\n\n /**\n * applyWhere() - Adds or updates a javascript filter function in the DynamicView filter pipeline\n *\n * @param {function} fun - A javascript filter function to apply to pipeline\n * @param {(string|number)=} uid - Optional: The unique ID of this filter, to reference it in the future.\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.applyWhere = function (fun, uid) {\n this.applyFilter({\n type: 'where',\n val: fun,\n uid: uid\n });\n return this;\n };\n\n /**\n * removeFilter() - Remove the specified filter from the DynamicView filter pipeline\n *\n * @param {(string|number)} uid - The unique ID of the filter to be removed.\n * @returns {DynamicView} this DynamicView object, for further chain ops.\n * @memberof DynamicView\n */\n DynamicView.prototype.removeFilter = function (uid) {\n var idx = this._indexOfFilterWithId(uid);\n if (idx < 0) {\n throw new Error(\"Dynamic view does not contain a filter with ID: \" + uid);\n }\n var wasFrozen = Object.isFrozen(this.filterPipeline);\n if (wasFrozen) {\n this.filterPipeline = this.filterPipeline.slice();\n }\n this.filterPipeline.splice(idx, 1);\n if (wasFrozen) {\n Object.freeze(this.filterPipeline);\n }\n this.reapplyFilters();\n return this;\n };\n\n /**\n * count() - returns the number of documents representing the current DynamicView contents.\n *\n * @returns {number} The number of documents representing the current DynamicView contents.\n * @memberof DynamicView\n */\n DynamicView.prototype.count = function () {\n // in order to be accurate we will pay the minimum cost (and not alter dv state management)\n // recurring resultset data resolutions should know internally its already up to date.\n // for persistent data this will not update resultdata nor fire rebuild event.\n if (this.resultsdirty) {\n this.resultdata = this.resultset.data();\n }\n\n return this.resultset.count();\n };\n\n /**\n * data() - resolves and pending filtering and sorting, then returns document array as result.\n *\n * @param {object=} options - optional parameters to pass to resultset.data() if non-persistent\n * @param {boolean} options.forceClones - Allows forcing the return of cloned objects even when\n * the collection is not configured for clone object.\n * @param {string} options.forceCloneMethod - Allows overriding the default or collection specified cloning method.\n * Possible values include 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'\n * @param {bool} options.removeMeta - Will force clones and strip $loki and meta properties from documents\n * @returns {array} An array of documents representing the current DynamicView contents.\n * @memberof DynamicView\n */\n DynamicView.prototype.data = function (options) {\n // using final sort phase as 'catch all' for a few use cases which require full rebuild\n if (this.sortDirty || this.resultsdirty) {\n this.performSortPhase({\n suppressRebuildEvent: true\n });\n }\n return (this.options.persistent) ? (this.resultdata) : (this.resultset.data(options));\n };\n\n /**\n * queueRebuildEvent() - When the view is not sorted we may still wish to be notified of rebuild events.\n * This event will throttle and queue a single rebuild event when batches of updates affect the view.\n */\n DynamicView.prototype.queueRebuildEvent = function () {\n if (this.rebuildPending) {\n return;\n }\n this.rebuildPending = true;\n\n var self = this;\n setTimeout(function () {\n if (self.rebuildPending) {\n self.rebuildPending = false;\n self.emit('rebuild', self);\n }\n }, this.options.minRebuildInterval);\n };\n\n /**\n * queueSortPhase : If the view is sorted we will throttle sorting to either :\n * (1) passive - when the user calls data(), or\n * (2) active - once they stop updating and yield js thread control\n */\n DynamicView.prototype.queueSortPhase = function () {\n // already queued? exit without queuing again\n if (this.sortDirty) {\n return;\n }\n this.sortDirty = true;\n\n var self = this;\n if (this.options.sortPriority === \"active\") {\n // active sorting... once they are done and yield js thread, run async performSortPhase()\n setTimeout(function () {\n self.performSortPhase();\n }, this.options.minRebuildInterval);\n } else {\n // must be passive sorting... since not calling performSortPhase (until data call), lets use queueRebuildEvent to\n // potentially notify user that data has changed.\n this.queueRebuildEvent();\n }\n };\n\n /**\n * performSortPhase() - invoked synchronously or asynchronously to perform final sort phase (if needed)\n *\n */\n DynamicView.prototype.performSortPhase = function (options) {\n // async call to this may have been pre-empted by synchronous call to data before async could fire\n if (!this.sortDirty && !this.resultsdirty) {\n return;\n }\n\n options = options || {};\n\n if (this.sortDirty) {\n if (this.sortFunction) {\n this.resultset.sort(this.sortFunction);\n } else if (this.sortCriteria) {\n this.resultset.compoundsort(this.sortCriteria);\n } else if (this.sortCriteriaSimple) {\n this.resultset.simplesort(this.sortCriteriaSimple.propname, this.sortCriteriaSimple.options);\n }\n\n this.sortDirty = false;\n }\n\n if (this.options.persistent) {\n // persistent view, rebuild local resultdata array\n this.resultdata = this.resultset.data();\n this.resultsdirty = false;\n }\n\n if (!options.suppressRebuildEvent) {\n this.emit('rebuild', this);\n }\n };\n\n /**\n * evaluateDocument() - internal method for (re)evaluating document inclusion.\n * Called by : collection.insert() and collection.update().\n *\n * @param {int} objIndex - index of document to (re)run through filter pipeline.\n * @param {bool} isNew - true if the document was just added to the collection.\n */\n DynamicView.prototype.evaluateDocument = function (objIndex, isNew) {\n // if no filter applied yet, the result 'set' should remain 'everything'\n if (!this.resultset.filterInitialized) {\n if (this.options.persistent) {\n this.resultdata = this.resultset.data();\n }\n // need to re-sort to sort new document\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n return;\n }\n\n var ofr = this.resultset.filteredrows;\n var oldPos = (isNew) ? (-1) : (ofr.indexOf(+objIndex));\n var oldlen = ofr.length;\n\n // creating a 1-element resultset to run filter chain ops on to see if that doc passes filters;\n // mostly efficient algorithm, slight stack overhead price (this function is called on inserts and updates)\n var evalResultset = new Resultset(this.collection);\n evalResultset.filteredrows = [objIndex];\n evalResultset.filterInitialized = true;\n var filter;\n for (var idx = 0, len = this.filterPipeline.length; idx < len; idx++) {\n filter = this.filterPipeline[idx];\n evalResultset[filter.type](filter.val);\n }\n\n // not a true position, but -1 if not pass our filter(s), 0 if passed filter(s)\n var newPos = (evalResultset.filteredrows.length === 0) ? -1 : 0;\n\n // wasn't in old, shouldn't be now... do nothing\n if (oldPos === -1 && newPos === -1) return;\n\n // wasn't in resultset, should be now... add\n if (oldPos === -1 && newPos !== -1) {\n ofr.push(objIndex);\n\n if (this.options.persistent) {\n this.resultdata.push(this.collection.data[objIndex]);\n }\n\n // need to re-sort to sort new document\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n\n return;\n }\n\n // was in resultset, shouldn't be now... delete\n if (oldPos !== -1 && newPos === -1) {\n if (oldPos < oldlen - 1) {\n ofr.splice(oldPos, 1);\n\n if (this.options.persistent) {\n this.resultdata.splice(oldPos, 1);\n }\n } else {\n ofr.length = oldlen - 1;\n\n if (this.options.persistent) {\n this.resultdata.length = oldlen - 1;\n }\n }\n\n // in case changes to data altered a sort column\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n\n return;\n }\n\n // was in resultset, should still be now... (update persistent only?)\n if (oldPos !== -1 && newPos !== -1) {\n if (this.options.persistent) {\n // in case document changed, replace persistent view data with the latest collection.data document\n this.resultdata[oldPos] = this.collection.data[objIndex];\n }\n\n // in case changes to data altered a sort column\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n\n return;\n }\n };\n\n /**\n * removeDocument() - internal function called on collection.delete()\n * @param {number|number[]} objIndex - index of document to (re)run through filter pipeline.\n */\n DynamicView.prototype.removeDocument = function (objIndex) {\n var idx, rmidx, rmlen, rxo = {}, fxo = {};\n var adjels = [];\n var drs = this.resultset;\n var fr = this.resultset.filteredrows;\n var frlen = fr.length;\n\n // if no filter applied yet, the result 'set' should remain 'everything'\n if (!this.resultset.filterInitialized) {\n if (this.options.persistent) {\n this.resultdata = this.resultset.data();\n }\n // in case changes to data altered a sort column\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n return;\n }\n\n // if passed single index, wrap in array\n if (!Array.isArray(objIndex)) {\n objIndex = [objIndex];\n }\n\n rmlen = objIndex.length;\n // create intersection object of data indices to remove\n for (rmidx = 0; rmidx < rmlen; rmidx++) {\n rxo[objIndex[rmidx]] = true;\n }\n\n // pivot remove data indices into remove filteredrows indices and dump in hashobject\n for (idx = 0; idx < frlen; idx++) {\n if (rxo[fr[idx]]) fxo[idx] = true;\n }\n\n // if any of the removed items were in our filteredrows...\n if (Object.keys(fxo).length > 0) {\n // remove them from filtered rows\n this.resultset.filteredrows = this.resultset.filteredrows.filter(function (di, idx) { return !fxo[idx]; });\n // if persistent...\n if (this.options.persistent) {\n // remove from resultdata\n this.resultdata = this.resultdata.filter(function (obj, idx) { return !fxo[idx]; });\n }\n\n // and queue sorts\n if (this.sortFunction || this.sortCriteria || this.sortCriteriaSimple) {\n this.queueSortPhase();\n } else {\n this.queueRebuildEvent();\n }\n }\n\n // to remove holes, we need to 'shift down' indices, this filter function finds number of positions to shift\n var filt = function (idx) { return function (di) { return di < drs.filteredrows[idx]; }; };\n\n frlen = drs.filteredrows.length;\n for (idx = 0; idx < frlen; idx++) {\n // grab subset of removed elements where data index is less than current filtered row data index;\n // use this to determine how many positions iterated remaining data index needs to be 'shifted down'\n adjels = objIndex.filter(filt(idx));\n drs.filteredrows[idx] -= adjels.length;\n }\n };\n\n /**\n * mapReduce() - data transformation via user supplied functions\n *\n * @param {function} mapFunction - this function accepts a single document for you to transform and return\n * @param {function} reduceFunction - this function accepts many (array of map outputs) and returns single value\n * @returns The output of your reduceFunction\n * @memberof DynamicView\n */\n DynamicView.prototype.mapReduce = function (mapFunction, reduceFunction) {\n try {\n return reduceFunction(this.data().map(mapFunction));\n } catch (err) {\n throw err;\n }\n };\n\n\n /**\n * Collection class that handles documents of same type\n * @constructor Collection\n * @implements LokiEventEmitter\n * @param {string} name - collection name\n * @param {(array|object)=} options - (optional) array of property names to be indicized OR a configuration object\n * @param {array=} [options.unique=[]] - array of property names to define unique constraints for\n * @param {array=} [options.exact=[]] - array of property names to define exact constraints for\n * @param {array=} [options.indices=[]] - array property names to define binary indexes for\n * @param {boolean} [options.adaptiveBinaryIndices=true] - collection indices will be actively rebuilt rather than lazily\n * @param {boolean} [options.asyncListeners=false] - whether listeners are invoked asynchronously\n * @param {boolean} [options.disableMeta=false] - set to true to disable meta property on documents\n * @param {boolean} [options.disableChangesApi=true] - set to false to enable Changes API\n * @param {boolean} [options.disableDeltaChangesApi=true] - set to false to enable Delta Changes API (requires Changes API, forces cloning)\n * @param {boolean} [options.autoupdate=false] - use Object.observe to update objects automatically\n * @param {boolean} [options.clone=false] - specify whether inserts and queries clone to/from user\n * @param {boolean} [options.serializableIndices=true[]] - converts date values on binary indexed properties to epoch time\n * @param {boolean} [options.disableFreeze=true] - when false all docs are frozen\n * @param {string} [options.cloneMethod='parse-stringify'] - 'parse-stringify', 'jquery-extend-deep', 'shallow', 'shallow-assign'\n * @param {int=} options.ttl - age of document (in ms.) before document is considered aged/stale.\n * @param {int=} options.ttlInterval - time interval for clearing out 'aged' documents; not set by default.\n * @see {@link Loki#addCollection} for normal creation of collections\n */\n function Collection(name, options) {\n // the name of the collection\n\n this.name = name;\n // the data held by the collection\n this.data = [];\n this.idIndex = null; // position->$loki index (built lazily)\n this.binaryIndices = {}; // user defined indexes\n this.constraints = {\n unique: {},\n exact: {}\n };\n\n // unique contraints contain duplicate object references, so they are not persisted.\n // we will keep track of properties which have unique contraint applied here, and regenerate lazily\n this.uniqueNames = [];\n\n // transforms will be used to store frequently used query chains as a series of steps\n // which itself can be stored along with the database.\n this.transforms = {};\n\n // the object type of the collection\n this.objType = name;\n\n // in autosave scenarios we will use collection level dirty flags to determine whether save is needed.\n // currently, if any collection is dirty we will autosave the whole database if autosave is configured.\n // defaulting to true since this is called from addCollection and adding a collection should trigger save\n this.dirty = true;\n\n // private holders for cached data\n this.cachedIndex = null;\n this.cachedBinaryIndex = null;\n this.cachedData = null;\n var self = this;\n\n /* OPTIONS */\n options = options || {};\n\n // exact match and unique constraints\n if (options.hasOwnProperty('unique')) {\n if (!Array.isArray(options.unique)) {\n options.unique = [options.unique];\n }\n // save names; actual index is built lazily\n options.unique.forEach(function (prop) {\n self.uniqueNames.push(prop);\n });\n }\n\n if (options.hasOwnProperty('exact')) {\n options.exact.forEach(function (prop) {\n self.constraints.exact[prop] = new ExactIndex(prop);\n });\n }\n\n // if set to true we will optimally keep indices 'fresh' during insert/update/remove ops (never dirty/never needs rebuild)\n // if you frequently intersperse insert/update/remove ops between find ops this will likely be significantly faster option.\n this.adaptiveBinaryIndices = options.hasOwnProperty('adaptiveBinaryIndices') ? options.adaptiveBinaryIndices : true;\n\n // is collection transactional\n this.transactional = options.hasOwnProperty('transactional') ? options.transactional : false;\n\n // options to clone objects when inserting them\n this.cloneObjects = options.hasOwnProperty('clone') ? options.clone : false;\n\n // default clone method (if enabled) is parse-stringify\n this.cloneMethod = options.hasOwnProperty('cloneMethod') ? options.cloneMethod : \"parse-stringify\";\n\n // option to make event listeners async, default is sync\n this.asyncListeners = options.hasOwnProperty('asyncListeners') ? options.asyncListeners : false;\n\n // if set to true we will not maintain a meta property for a document\n this.disableMeta = options.hasOwnProperty('disableMeta') ? options.disableMeta : false;\n\n // disable track changes\n this.disableChangesApi = options.hasOwnProperty('disableChangesApi') ? options.disableChangesApi : true;\n\n // disable delta update object style on changes\n this.disableDeltaChangesApi = options.hasOwnProperty('disableDeltaChangesApi') ? options.disableDeltaChangesApi : true;\n if (this.disableChangesApi) { this.disableDeltaChangesApi = true; }\n\n // option to observe objects and update them automatically, ignored if Object.observe is not supported\n this.autoupdate = options.hasOwnProperty('autoupdate') ? options.autoupdate : false;\n\n // by default, if you insert a document into a collection with binary indices, if those indexed properties contain\n // a DateTime we will convert to epoch time format so that (across serializations) its value position will be the\n // same 'after' serialization as it was 'before'.\n this.serializableIndices = options.hasOwnProperty('serializableIndices') ? options.serializableIndices : true;\n\n // option to deep freeze all documents\n this.disableFreeze = options.hasOwnProperty('disableFreeze') ? options.disableFreeze : true;\n\n //option to activate a cleaner daemon - clears \"aged\" documents at set intervals.\n this.ttl = {\n age: null,\n ttlInterval: null,\n daemon: null\n };\n this.setTTL(options.ttl || -1, options.ttlInterval);\n\n // currentMaxId - change manually at your own peril!\n this.maxId = 0;\n\n this.DynamicViews = [];\n\n // events\n this.events = {\n 'insert': [],\n 'update': [],\n 'pre-insert': [],\n 'pre-update': [],\n 'close': [],\n 'flushbuffer': [],\n 'error': [],\n 'delete': [],\n 'warning': []\n };\n\n // changes are tracked by collection and aggregated by the db\n this.changes = [];\n\n // lightweight changes tracking (loki IDs only) for optimized db saving\n this.dirtyIds = [];\n\n // initialize optional user-supplied indices array ['age', 'lname', 'zip']\n var indices = [];\n if (options && options.indices) {\n if (Object.prototype.toString.call(options.indices) === '[object Array]') {\n indices = options.indices;\n } else if (typeof options.indices === 'string') {\n indices = [options.indices];\n } else {\n throw new TypeError('Indices needs to be a string or an array of strings');\n }\n }\n\n for (var idx = 0; idx < indices.length; idx++) {\n this.ensureIndex(indices[idx]);\n }\n\n function observerCallback(changes) {\n\n var changedObjects = typeof Set === 'function' ? new Set() : [];\n\n if (!changedObjects.add)\n changedObjects.add = function (object) {\n if (this.indexOf(object) === -1)\n this.push(object);\n return this;\n };\n\n changes.forEach(function (change) {\n changedObjects.add(change.object);\n });\n\n changedObjects.forEach(function (object) {\n if (!hasOwnProperty.call(object, '$loki'))\n return self.removeAutoUpdateObserver(object);\n try {\n self.update(object);\n } catch (err) { }\n });\n }\n\n this.observerCallback = observerCallback;\n\n //Compare changed object (which is a forced clone) with existing object and return the delta\n function getChangeDelta(obj, old) {\n if (old) {\n return getObjectDelta(old, obj);\n }\n else {\n return JSON.parse(JSON.stringify(obj));\n }\n }\n\n this.getChangeDelta = getChangeDelta;\n\n function getObjectDelta(oldObject, newObject) {\n var propertyNames = newObject !== null && typeof newObject === 'object' ? Object.keys(newObject) : null;\n if (propertyNames && propertyNames.length && ['string', 'boolean', 'number'].indexOf(typeof (newObject)) < 0) {\n var delta = {};\n for (var i = 0; i < propertyNames.length; i++) {\n var propertyName = propertyNames[i];\n if (newObject.hasOwnProperty(propertyName)) {\n if (!oldObject.hasOwnProperty(propertyName) || self.uniqueNames.indexOf(propertyName) >= 0 || propertyName == '$loki' || propertyName == 'meta') {\n delta[propertyName] = newObject[propertyName];\n }\n else {\n var propertyDelta = getObjectDelta(oldObject[propertyName], newObject[propertyName]);\n if (typeof propertyDelta !== \"undefined\" && propertyDelta != {}) {\n delta[propertyName] = propertyDelta;\n }\n }\n }\n }\n return Object.keys(delta).length === 0 ? undefined : delta;\n }\n else {\n return oldObject === newObject ? undefined : newObject;\n }\n }\n\n this.getObjectDelta = getObjectDelta;\n\n // clear all the changes\n function flushChanges() {\n self.changes = [];\n }\n\n this.getChanges = function () {\n return self.changes;\n };\n\n this.flushChanges = flushChanges;\n\n this.setChangesApi = function (enabled) {\n self.disableChangesApi = !enabled;\n if (!enabled) { self.disableDeltaChangesApi = false; }\n };\n\n this.on('delete', function deleteCallback(obj) {\n if (!self.disableChangesApi) {\n self.createChange(self.name, 'R', obj);\n }\n });\n\n this.on('warning', function (warning) {\n self.lokiConsoleWrapper.warn(warning);\n });\n // for de-serialization purposes\n flushChanges();\n }\n\n Collection.prototype = new LokiEventEmitter();\n Collection.prototype.contructor = Collection;\n\n /*\n * For ChangeAPI default to clone entire object, for delta changes create object with only differences (+ $loki and meta)\n */\n Collection.prototype.createChange = function (name, op, obj, old) {\n this.changes.push({\n name: name,\n operation: op,\n obj: op == 'U' && !this.disableDeltaChangesApi ? this.getChangeDelta(obj, old) : JSON.parse(JSON.stringify(obj))\n });\n };\n\n Collection.prototype.insertMeta = function (obj) {\n var len, idx;\n\n if (this.disableMeta || !obj) {\n return;\n }\n\n // if batch insert\n if (Array.isArray(obj)) {\n len = obj.length;\n\n for (idx = 0; idx < len; idx++) {\n if (!obj[idx].hasOwnProperty('meta')) {\n obj[idx].meta = {};\n }\n\n obj[idx].meta.created = (new Date()).getTime();\n obj[idx].meta.revision = 0;\n }\n\n return;\n }\n\n // single object\n if (!obj.meta) {\n obj.meta = {};\n }\n\n obj.meta.created = (new Date()).getTime();\n obj.meta.revision = 0;\n };\n\n Collection.prototype.updateMeta = function (obj) {\n if (this.disableMeta || !obj) {\n return obj;\n }\n if (!this.disableFreeze) {\n obj = unFreeze(obj);\n obj.meta = unFreeze(obj.meta);\n }\n obj.meta.updated = (new Date()).getTime();\n obj.meta.revision += 1;\n return obj;\n };\n\n Collection.prototype.createInsertChange = function (obj) {\n this.createChange(this.name, 'I', obj);\n };\n\n Collection.prototype.createUpdateChange = function (obj, old) {\n this.createChange(this.name, 'U', obj, old);\n };\n\n Collection.prototype.insertMetaWithChange = function (obj) {\n this.insertMeta(obj);\n this.createInsertChange(obj);\n };\n\n Collection.prototype.updateMetaWithChange = function (obj, old, objFrozen) {\n obj = this.updateMeta(obj, objFrozen);\n this.createUpdateChange(obj, old);\n return obj;\n };\n\n Collection.prototype.lokiConsoleWrapper = {\n log: function () { },\n warn: function () { },\n error: function () { },\n };\n\n Collection.prototype.addAutoUpdateObserver = function (object) {\n if (!this.autoupdate || typeof Object.observe !== 'function')\n return;\n\n Object.observe(object, this.observerCallback, ['add', 'update', 'delete', 'reconfigure', 'setPrototype']);\n };\n\n Collection.prototype.removeAutoUpdateObserver = function (object) {\n if (!this.autoupdate || typeof Object.observe !== 'function')\n return;\n\n Object.unobserve(object, this.observerCallback);\n };\n\n /**\n * Adds a named collection transform to the collection\n * @param {string} name - name to associate with transform\n * @param {array} transform - an array of transformation 'step' objects to save into the collection\n * @memberof Collection\n * @example\n * users.addTransform('progeny', [\n * {\n * type: 'find',\n * value: {\n * 'age': {'$lte': 40}\n * }\n * }\n * ]);\n *\n * var results = users.chain('progeny').data();\n */\n Collection.prototype.addTransform = function (name, transform) {\n if (this.transforms.hasOwnProperty(name)) {\n throw new Error(\"a transform by that name already exists\");\n }\n\n this.transforms[name] = transform;\n };\n\n /**\n * Retrieves a named transform from the collection.\n * @param {string} name - name of the transform to lookup.\n * @memberof Collection\n */\n Collection.prototype.getTransform = function (name) {\n return this.transforms[name];\n };\n\n /**\n * Updates a named collection transform to the collection\n * @param {string} name - name to associate with transform\n * @param {object} transform - a transformation object to save into collection\n * @memberof Collection\n */\n Collection.prototype.setTransform = function (name, transform) {\n this.transforms[name] = transform;\n };\n\n /**\n * Removes a named collection transform from the collection\n * @param {string} name - name of collection transform to remove\n * @memberof Collection\n */\n Collection.prototype.removeTransform = function (name) {\n delete this.transforms[name];\n };\n\n Collection.prototype.byExample = function (template) {\n var k, obj, query;\n query = [];\n for (k in template) {\n if (!template.hasOwnProperty(k)) continue;\n query.push((\n obj = {},\n obj[k] = template[k],\n obj\n ));\n }\n return {\n '$and': query\n };\n };\n\n Collection.prototype.findObject = function (template) {\n return this.findOne(this.byExample(template));\n };\n\n Collection.prototype.findObjects = function (template) {\n return this.find(this.byExample(template));\n };\n\n /*----------------------------+\n | TTL daemon |\n +----------------------------*/\n Collection.prototype.ttlDaemonFuncGen = function () {\n var collection = this;\n var age = this.ttl.age;\n return function ttlDaemon() {\n var now = Date.now();\n var toRemove = collection.chain().where(function daemonFilter(member) {\n var timestamp = member.meta.updated || member.meta.created;\n var diff = now - timestamp;\n return age < diff;\n });\n toRemove.remove();\n };\n };\n\n /**\n * Updates or applies collection TTL settings.\n * @param {int} age - age (in ms) to expire document from collection\n * @param {int} interval - time (in ms) to clear collection of aged documents.\n * @memberof Collection\n */\n Collection.prototype.setTTL = function (age, interval) {\n if (age < 0) {\n clearInterval(this.ttl.daemon);\n } else {\n this.ttl.age = age;\n this.ttl.ttlInterval = interval;\n this.ttl.daemon = setInterval(this.ttlDaemonFuncGen(), interval);\n }\n };\n\n /*----------------------------+\n | INDEXING |\n +----------------------------*/\n\n /**\n * create a row filter that covers all documents in the collection\n */\n Collection.prototype.prepareFullDocIndex = function () {\n var len = this.data.length;\n var indexes = new Array(len);\n for (var i = 0; i < len; i += 1) {\n indexes[i] = i;\n }\n return indexes;\n };\n\n /**\n * Will allow reconfiguring certain collection options.\n * @param {boolean} options.adaptiveBinaryIndices - collection indices will be actively rebuilt rather than lazily\n * @memberof Collection\n */\n Collection.prototype.configureOptions = function (options) {\n options = options || {};\n\n if (options.hasOwnProperty('adaptiveBinaryIndices')) {\n this.adaptiveBinaryIndices = options.adaptiveBinaryIndices;\n\n // if switching to adaptive binary indices, make sure none are 'dirty'\n if (this.adaptiveBinaryIndices) {\n this.ensureAllIndexes();\n }\n }\n };\n\n /**\n * Ensure binary index on a certain field\n * @param {string} property - name of property to create binary index on\n * @param {boolean=} force - (Optional) flag indicating whether to construct index immediately\n * @memberof Collection\n */\n Collection.prototype.ensureIndex = function (property, force) {\n // optional parameter to force rebuild whether flagged as dirty or not\n if (typeof (force) === 'undefined') {\n force = false;\n }\n\n if (property === null || property === undefined) {\n throw new Error('Attempting to set index without an associated property');\n }\n\n if (this.binaryIndices[property] && !force) {\n if (!this.binaryIndices[property].dirty) return;\n }\n\n // if the index is already defined and we are using adaptiveBinaryIndices and we are not forcing a rebuild, return.\n if (this.adaptiveBinaryIndices === true && this.binaryIndices.hasOwnProperty(property) && !force) {\n return;\n }\n\n var index = {\n 'name': property,\n 'dirty': true,\n 'values': this.prepareFullDocIndex()\n };\n this.binaryIndices[property] = index;\n\n var wrappedComparer =\n (function (prop, data) {\n var val1, val2;\n var propPath = ~prop.indexOf('.') ? prop.split('.') : false;\n return function (a, b) {\n if (propPath) {\n val1 = Utils.getIn(data[a], propPath, true);\n val2 = Utils.getIn(data[b], propPath, true);\n } else {\n val1 = data[a][prop];\n val2 = data[b][prop];\n }\n\n if (val1 !== val2) {\n if (Comparators.lt(val1, val2, false)) return -1;\n if (Comparators.gt(val1, val2, false)) return 1;\n }\n return 0;\n };\n })(property, this.data);\n\n index.values.sort(wrappedComparer);\n index.dirty = false;\n\n this.dirty = true; // for autosave scenarios\n };\n\n /**\n * Perform checks to determine validity/consistency of all binary indices\n * @param {object=} options - optional configuration object\n * @param {boolean} [options.randomSampling=false] - whether (faster) random sampling should be used\n * @param {number} [options.randomSamplingFactor=0.10] - percentage of total rows to randomly sample\n * @param {boolean} [options.repair=false] - whether to fix problems if they are encountered\n * @returns {string[]} array of index names where problems were found.\n * @memberof Collection\n * @example\n * // check all indices on a collection, returns array of invalid index names\n * var result = coll.checkAllIndexes({ repair: true, randomSampling: true, randomSamplingFactor: 0.15 });\n * if (result.length > 0) {\n * results.forEach(function(name) {\n * console.log('problem encountered with index : ' + name);\n * });\n * }\n */\n Collection.prototype.checkAllIndexes = function (options) {\n var key, bIndices = this.binaryIndices;\n var results = [], result;\n\n for (key in bIndices) {\n if (hasOwnProperty.call(bIndices, key)) {\n result = this.checkIndex(key, options);\n if (!result) {\n results.push(key);\n }\n }\n }\n\n return results;\n };\n\n /**\n * Perform checks to determine validity/consistency of a binary index\n * @param {string} property - name of the binary-indexed property to check\n * @param {object=} options - optional configuration object\n * @param {boolean} [options.randomSampling=false] - whether (faster) random sampling should be used\n * @param {number} [options.randomSamplingFactor=0.10] - percentage of total rows to randomly sample\n * @param {boolean} [options.repair=false] - whether to fix problems if they are encountered\n * @returns {boolean} whether the index was found to be valid (before optional correcting).\n * @memberof Collection\n * @example\n * // full test\n * var valid = coll.checkIndex('name');\n * // full test with repair (if issues found)\n * valid = coll.checkIndex('name', { repair: true });\n * // random sampling (default is 10% of total document count)\n * valid = coll.checkIndex('name', { randomSampling: true });\n * // random sampling (sample 20% of total document count)\n * valid = coll.checkIndex('name', { randomSampling: true, randomSamplingFactor: 0.20 });\n * // random sampling (implied boolean)\n * valid = coll.checkIndex('name', { randomSamplingFactor: 0.20 });\n * // random sampling with repair (if issues found)\n * valid = coll.checkIndex('name', { repair: true, randomSampling: true });\n */\n Collection.prototype.checkIndex = function (property, options) {\n options = options || {};\n // if 'randomSamplingFactor' specified but not 'randomSampling', assume true\n if (options.randomSamplingFactor && options.randomSampling !== false) {\n options.randomSampling = true;\n }\n options.randomSamplingFactor = options.randomSamplingFactor || 0.1;\n if (options.randomSamplingFactor < 0 || options.randomSamplingFactor > 1) {\n options.randomSamplingFactor = 0.1;\n }\n\n var valid = true, idx, iter, pos, len, biv;\n\n // make sure we are passed a valid binary index name\n if (!this.binaryIndices.hasOwnProperty(property)) {\n throw new Error(\"called checkIndex on property without an index: \" + property);\n }\n\n // if lazy indexing, rebuild only if flagged as dirty\n if (!this.adaptiveBinaryIndices) {\n this.ensureIndex(property);\n }\n\n biv = this.binaryIndices[property].values;\n len = biv.length;\n\n // if the index has an incorrect number of values\n if (len !== this.data.length) {\n if (options.repair) {\n this.ensureIndex(property, true);\n }\n return false;\n }\n\n if (len === 0) {\n return true;\n }\n\n var usingDotNotation = (property.indexOf('.') !== -1);\n\n if (len === 1) {\n valid = (biv[0] === 0);\n }\n else {\n if (options.randomSampling) {\n // validate first and last\n if (!LokiOps.$lte(Utils.getIn(this.data[biv[0]], property, usingDotNotation),\n Utils.getIn(this.data[biv[1]], property, usingDotNotation))) {\n valid = false;\n }\n if (!LokiOps.$lte(Utils.getIn(this.data[biv[len - 2]], property, usingDotNotation),\n Utils.getIn(this.data[biv[len - 1]], property, usingDotNotation))) {\n valid = false;\n }\n\n // if first and last positions are sorted correctly with their nearest neighbor,\n // continue onto random sampling phase...\n if (valid) {\n // # random samplings = total count * sampling factor\n iter = Math.floor((len - 1) * options.randomSamplingFactor);\n\n // for each random sampling, validate that the binary index is sequenced properly\n // with next higher value.\n for (idx = 0; idx < iter - 1; idx++) {\n // calculate random position\n pos = Math.floor(Math.random() * (len - 1));\n if (!LokiOps.$lte(Utils.getIn(this.data[biv[pos]], property, usingDotNotation),\n Utils.getIn(this.data[biv[pos + 1]], property, usingDotNotation))) {\n valid = false;\n break;\n }\n }\n }\n }\n else {\n // validate that the binary index is sequenced properly\n for (idx = 0; idx < len - 1; idx++) {\n if (!LokiOps.$lte(Utils.getIn(this.data[biv[idx]], property, usingDotNotation),\n Utils.getIn(this.data[biv[idx + 1]], property, usingDotNotation))) {\n valid = false;\n break;\n }\n }\n }\n }\n\n // if incorrectly sequenced and we are to fix problems, rebuild index\n if (!valid && options.repair) {\n this.ensureIndex(property, true);\n }\n\n return valid;\n };\n\n Collection.prototype.getBinaryIndexValues = function (property) {\n var idx, idxvals = this.binaryIndices[property].values;\n var result = [];\n\n for (idx = 0; idx < idxvals.length; idx++) {\n result.push(Utils.getIn(this.data[idxvals[idx]], property, true));\n }\n\n return result;\n };\n\n /**\n * Returns a named unique index\n * @param {string} field - indexed field name\n * @param {boolean} force - if `true`, will rebuild index; otherwise, function may return null\n */\n Collection.prototype.getUniqueIndex = function (field, force) {\n var index = this.constraints.unique[field];\n if (!index && force) {\n return this.ensureUniqueIndex(field);\n }\n return index;\n };\n\n Collection.prototype.ensureUniqueIndex = function (field) {\n var index = this.constraints.unique[field];\n if (!index) {\n // keep track of new unique index for regenerate after database (re)load.\n if (this.uniqueNames.indexOf(field) == -1) {\n this.uniqueNames.push(field);\n }\n }\n\n // if index already existed, (re)loading it will likely cause collisions, rebuild always\n this.constraints.unique[field] = index = new UniqueIndex(field);\n this.data.forEach(function (obj) {\n index.set(obj);\n });\n return index;\n };\n\n /**\n * Ensure all binary indices\n * @param {boolean} force - whether to force rebuild of existing lazy binary indices\n * @memberof Collection\n */\n Collection.prototype.ensureAllIndexes = function (force) {\n var key, bIndices = this.binaryIndices;\n for (key in bIndices) {\n if (hasOwnProperty.call(bIndices, key)) {\n this.ensureIndex(key, force);\n }\n }\n };\n\n /**\n * Internal method used to flag all lazy index as dirty\n */\n Collection.prototype.flagBinaryIndexesDirty = function () {\n var key, bIndices = this.binaryIndices;\n for (key in bIndices) {\n if (hasOwnProperty.call(bIndices, key)) {\n bIndices[key].dirty = true;\n }\n }\n };\n\n /**\n * Internal method used to flag a lazy index as dirty\n */\n Collection.prototype.flagBinaryIndexDirty = function (index) {\n if (this.binaryIndices[index])\n this.binaryIndices[index].dirty = true;\n };\n\n /**\n * Quickly determine number of documents in collection (or query)\n * @param {object=} query - (optional) query object to count results of\n * @returns {number} number of documents in the collection\n * @memberof Collection\n */\n Collection.prototype.count = function (query) {\n if (!query) {\n return this.data.length;\n }\n\n return this.chain().find(query).filteredrows.length;\n };\n\n /**\n * Rebuild idIndex\n */\n Collection.prototype.ensureId = function () {\n if (this.idIndex) {\n return;\n }\n var data = this.data,\n i = 0;\n var len = data.length;\n var index = new Array(len);\n for (i; i < len; i++) {\n index[i] = data[i].$loki;\n }\n this.idIndex = index;\n };\n\n /**\n * Rebuild idIndex async with callback - useful for background syncing with a remote server\n */\n Collection.prototype.ensureIdAsync = function (callback) {\n this.async(function () {\n this.ensureId();\n }, callback);\n };\n\n /**\n * Add a dynamic view to the collection\n * @param {string} name - name of dynamic view to add\n * @param {object=} options - options to configure dynamic view with\n * @param {boolean} [options.persistent=false] - indicates if view is to main internal results array in 'resultdata'\n * @param {string} [options.sortPriority='passive'] - 'passive' (sorts performed on call to data) or 'active' (after updates)\n * @param {number} options.minRebuildInterval - minimum rebuild interval (need clarification to docs here)\n * @returns {DynamicView} reference to the dynamic view added\n * @memberof Collection\n * @example\n * var pview = users.addDynamicView('progeny');\n * pview.applyFind({'age': {'$lte': 40}});\n * pview.applySimpleSort('name');\n *\n * var results = pview.data();\n **/\n\n Collection.prototype.addDynamicView = function (name, options) {\n var dv = new DynamicView(this, name, options);\n this.DynamicViews.push(dv);\n\n return dv;\n };\n\n /**\n * Remove a dynamic view from the collection\n * @param {string} name - name of dynamic view to remove\n * @memberof Collection\n **/\n Collection.prototype.removeDynamicView = function (name) {\n this.DynamicViews =\n this.DynamicViews.filter(function (dv) { return dv.name !== name; });\n };\n\n /**\n * Look up dynamic view reference from within the collection\n * @param {string} name - name of dynamic view to retrieve reference of\n * @returns {DynamicView} A reference to the dynamic view with that name\n * @memberof Collection\n **/\n Collection.prototype.getDynamicView = function (name) {\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n if (this.DynamicViews[idx].name === name) {\n return this.DynamicViews[idx];\n }\n }\n\n return null;\n };\n\n /**\n * Applies a 'mongo-like' find query object and passes all results to an update function.\n * For filter function querying you should migrate to [updateWhere()]{@link Collection#updateWhere}.\n *\n * @param {object|function} filterObject - 'mongo-like' query object (or deprecated filterFunction mode)\n * @param {function} updateFunction - update function to run against filtered documents\n * @memberof Collection\n */\n Collection.prototype.findAndUpdate = function (filterObject, updateFunction) {\n if (typeof (filterObject) === \"function\") {\n this.updateWhere(filterObject, updateFunction);\n }\n else {\n this.chain().find(filterObject).update(updateFunction);\n }\n };\n\n /**\n * Applies a 'mongo-like' find query object removes all documents which match that filter.\n *\n * @param {object} filterObject - 'mongo-like' query object\n * @memberof Collection\n */\n Collection.prototype.findAndRemove = function (filterObject) {\n this.chain().find(filterObject).remove();\n };\n\n /**\n * Adds object(s) to collection, ensure object(s) have meta properties, clone it if necessary, etc.\n * @param {(object|array)} doc - the document (or array of documents) to be inserted\n * @param {boolean=} overrideAdaptiveIndices - (optional) if `true`, adaptive indicies will be\n * temporarily disabled and then fully rebuilt after batch. This will be faster for\n * large inserts, but slower for small/medium inserts in large collections\n * @returns {(object|array)} document or documents inserted\n * @memberof Collection\n * @example\n * users.insert({\n * name: 'Odin',\n * age: 50,\n * address: 'Asgard'\n * });\n *\n * // alternatively, insert array of documents\n * users.insert([{ name: 'Thor', age: 35}, { name: 'Loki', age: 30}]);\n */\n Collection.prototype.insert = function (doc, overrideAdaptiveIndices) {\n if (!Array.isArray(doc)) {\n return this.insertOne(doc);\n }\n\n // holder to the clone of the object inserted if collections is set to clone objects\n var obj;\n var results = [];\n\n // if not cloning, disable adaptive binary indices for the duration of the batch insert,\n // followed by lazy rebuild and re-enabling adaptive indices after batch insert.\n var adaptiveBatchOverride = overrideAdaptiveIndices && !this.cloneObjects &&\n this.adaptiveBinaryIndices && Object.keys(this.binaryIndices).length > 0;\n\n if (adaptiveBatchOverride) {\n this.adaptiveBinaryIndices = false;\n }\n\n try {\n this.emit('pre-insert', doc);\n for (var i = 0, len = doc.length; i < len; i++) {\n obj = this.insertOne(doc[i], true);\n if (!obj) {\n return undefined;\n }\n results.push(obj);\n }\n } finally {\n if (adaptiveBatchOverride) {\n this.ensureAllIndexes();\n this.adaptiveBinaryIndices = true;\n }\n }\n\n // at the 'batch' level, if clone option is true then emitted docs are clones\n this.emit('insert', results);\n\n // if clone option is set, clone return values\n results = this.cloneObjects ? clone(results, this.cloneMethod) : results;\n\n return results.length === 1 ? results[0] : results;\n };\n\n /**\n * Adds a single object, ensures it has meta properties, clone it if necessary, etc.\n * @param {object} doc - the document to be inserted\n * @param {boolean} bulkInsert - quiet pre-insert and insert event emits\n * @returns {object} document or 'undefined' if there was a problem inserting it\n */\n Collection.prototype.insertOne = function (doc, bulkInsert) {\n var err = null;\n var returnObj;\n\n if (typeof doc !== 'object') {\n err = new TypeError('Document needs to be an object');\n } else if (doc === null) {\n err = new TypeError('Object cannot be null');\n }\n\n if (err !== null) {\n this.emit('error', err);\n throw err;\n }\n\n // if configured to clone, do so now... otherwise just use same obj reference\n var obj = this.cloneObjects ? clone(doc, this.cloneMethod) : doc;\n if (!this.disableFreeze) {\n obj = unFreeze(obj);\n }\n\n if (!this.disableMeta) {\n if (typeof obj.meta === 'undefined') {\n obj.meta = {\n revision: 0,\n created: 0\n };\n } else if (!this.disableFreeze) {\n obj.meta = unFreeze(obj.meta);\n }\n }\n\n // both 'pre-insert' and 'insert' events are passed internal data reference even when cloning\n // insert needs internal reference because that is where loki itself listens to add meta\n if (!bulkInsert) {\n this.emit('pre-insert', obj);\n }\n if (!this.add(obj)) {\n return undefined;\n }\n\n // update meta and store changes if ChangesAPI is enabled\n // (moved from \"insert\" event listener to allow internal reference to be used)\n if (this.disableChangesApi) {\n this.insertMeta(obj);\n } else {\n this.insertMetaWithChange(obj);\n }\n\n if (!this.disableFreeze) {\n deepFreeze(obj);\n }\n\n // if cloning is enabled, emit insert event with clone of new object\n returnObj = this.cloneObjects ? clone(obj, this.cloneMethod) : obj;\n\n if (!bulkInsert) {\n this.emit('insert', returnObj);\n }\n\n this.addAutoUpdateObserver(returnObj);\n\n return returnObj;\n };\n\n /**\n * Empties the collection.\n * @param {object=} options - configure clear behavior\n * @param {bool=} [options.removeIndices=false] - whether to remove indices in addition to data\n * @memberof Collection\n */\n Collection.prototype.clear = function (options) {\n var self = this;\n\n options = options || {};\n\n this.data = [];\n this.idIndex = null;\n this.cachedIndex = null;\n this.cachedBinaryIndex = null;\n this.cachedData = null;\n this.maxId = 0;\n this.DynamicViews = [];\n this.dirty = true;\n this.constraints = {\n unique: {},\n exact: {}\n };\n\n // if removing indices entirely\n if (options.removeIndices === true) {\n this.binaryIndices = {};\n this.uniqueNames = [];\n }\n // clear indices but leave definitions in place\n else {\n // clear binary indices\n var keys = Object.keys(this.binaryIndices);\n keys.forEach(function (biname) {\n self.binaryIndices[biname].dirty = false;\n self.binaryIndices[biname].values = [];\n });\n }\n };\n\n /**\n * Updates an object and notifies collection that the document has changed.\n * @param {object} doc - document to update within the collection\n * @memberof Collection\n */\n Collection.prototype.update = function (doc) {\n var adaptiveBatchOverride, k, len;\n\n if (Array.isArray(doc)) {\n len = doc.length;\n\n // if not cloning, disable adaptive binary indices for the duration of the batch update,\n // followed by lazy rebuild and re-enabling adaptive indices after batch update.\n adaptiveBatchOverride = !this.cloneObjects &&\n this.adaptiveBinaryIndices && Object.keys(this.binaryIndices).length > 0;\n\n if (adaptiveBatchOverride) {\n this.adaptiveBinaryIndices = false;\n }\n\n try {\n for (k = 0; k < len; k += 1) {\n this.update(doc[k]);\n }\n }\n finally {\n if (adaptiveBatchOverride) {\n this.ensureAllIndexes();\n this.adaptiveBinaryIndices = true;\n }\n }\n\n return;\n }\n\n // verify object is a properly formed document\n if (!hasOwnProperty.call(doc, '$loki')) {\n throw new Error('Trying to update unsynced document. Please save the document first by using insert() or addMany()');\n }\n try {\n this.startTransaction();\n var arr = this.get(doc.$loki, true),\n oldInternal, // ref to existing obj\n newInternal, // ref to new internal obj\n position,\n self = this;\n\n if (!arr) {\n throw new Error('Trying to update a document not in collection.');\n }\n\n oldInternal = arr[0]; // -internal- obj ref\n position = arr[1]; // position in data array\n\n // if configured to clone, do so now... otherwise just use same obj reference\n newInternal = this.cloneObjects || (!this.disableDeltaChangesApi && this.disableFreeze) ? clone(doc, this.cloneMethod) : doc;\n\n this.emit('pre-update', doc);\n\n this.uniqueNames.forEach(function (key) {\n self.getUniqueIndex(key, true).update(oldInternal, newInternal);\n });\n\n // operate the update\n this.data[position] = newInternal;\n\n if (newInternal !== doc) {\n this.addAutoUpdateObserver(doc);\n }\n\n // now that we can efficiently determine the data[] position of newly added document,\n // submit it for all registered DynamicViews to evaluate for inclusion/exclusion\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n this.DynamicViews[idx].evaluateDocument(position, false);\n }\n\n var key;\n if (this.adaptiveBinaryIndices) {\n // for each binary index defined in collection, immediately update rather than flag for lazy rebuild\n var bIndices = this.binaryIndices;\n for (key in bIndices) {\n this.adaptiveBinaryIndexUpdate(position, key);\n }\n }\n else {\n this.flagBinaryIndexesDirty();\n }\n\n this.idIndex[position] = newInternal.$loki;\n //this.flagBinaryIndexesDirty();\n\n if (this.isIncremental) {\n this.dirtyIds.push(newInternal.$loki);\n }\n\n this.commit();\n this.dirty = true; // for autosave scenarios\n\n // update meta and store changes if ChangesAPI is enabled\n if (this.disableChangesApi) {\n newInternal = this.updateMeta(newInternal);\n } else {\n newInternal = this.updateMetaWithChange(newInternal, oldInternal);\n }\n\n if (!this.disableFreeze) {\n deepFreeze(newInternal);\n }\n\n var returnObj;\n\n // if cloning is enabled, emit 'update' event and return with clone of new object\n if (this.cloneObjects) {\n returnObj = clone(newInternal, this.cloneMethod);\n }\n else {\n returnObj = newInternal;\n }\n\n this.emit('update', returnObj, oldInternal);\n return returnObj;\n } catch (err) {\n this.rollback();\n this.lokiConsoleWrapper.error(err.message);\n this.emit('error', err);\n throw (err); // re-throw error so user does not think it succeeded\n }\n };\n\n /**\n * Add object to collection\n */\n Collection.prototype.add = function (obj) {\n // if parameter isn't object exit with throw\n if ('object' !== typeof obj) {\n throw new TypeError('Object being added needs to be an object');\n }\n // if object you are adding already has id column it is either already in the collection\n // or the object is carrying its own 'id' property. If it also has a meta property,\n // then this is already in collection so throw error, otherwise rename to originalId and continue adding.\n if (typeof (obj.$loki) !== 'undefined') {\n throw new Error('Document is already in collection, please use update()');\n }\n\n /*\n * try adding object to collection\n */\n try {\n this.startTransaction();\n this.maxId++;\n\n if (isNaN(this.maxId)) {\n this.maxId = (this.data[this.data.length - 1].$loki + 1);\n }\n\n var newId = this.maxId;\n obj.$loki = newId;\n\n if (!this.disableMeta) {\n obj.meta.version = 0;\n }\n\n for (var i = 0, len = this.uniqueNames.length; i < len; i ++) {\n this.getUniqueIndex(this.uniqueNames[i], true).set(obj);\n }\n\n if (this.idIndex) {\n this.idIndex.push(newId);\n }\n\n if (this.isIncremental) {\n this.dirtyIds.push(newId);\n }\n\n // add the object\n this.data.push(obj);\n\n var addedPos = this.data.length - 1;\n\n // now that we can efficiently determine the data[] position of newly added document,\n // submit it for all registered DynamicViews to evaluate for inclusion/exclusion\n var dvlen = this.DynamicViews.length;\n for (i = 0; i < dvlen; i++) {\n this.DynamicViews[i].evaluateDocument(addedPos, true);\n }\n\n if (this.adaptiveBinaryIndices) {\n // for each binary index defined in collection, immediately update rather than flag for lazy rebuild\n var bIndices = this.binaryIndices;\n for (var key in bIndices) {\n this.adaptiveBinaryIndexInsert(addedPos, key);\n }\n }\n else {\n this.flagBinaryIndexesDirty();\n }\n\n this.commit();\n this.dirty = true; // for autosave scenarios\n\n return (this.cloneObjects) ? (clone(obj, this.cloneMethod)) : (obj);\n } catch (err) {\n this.rollback();\n this.lokiConsoleWrapper.error(err.message);\n this.emit('error', err);\n throw (err); // re-throw error so user does not think it succeeded\n }\n };\n\n /**\n * Applies a filter function and passes all results to an update function.\n *\n * @param {function} filterFunction - filter function whose results will execute update\n * @param {function} updateFunction - update function to run against filtered documents\n * @memberof Collection\n */\n Collection.prototype.updateWhere = function (filterFunction, updateFunction) {\n var results = this.where(filterFunction),\n i = 0,\n obj;\n try {\n for (i; i < results.length; i++) {\n obj = updateFunction(results[i]);\n this.update(obj);\n }\n\n } catch (err) {\n this.rollback();\n this.lokiConsoleWrapper.error(err.message);\n }\n };\n\n /**\n * Remove all documents matching supplied filter function.\n * For 'mongo-like' querying you should migrate to [findAndRemove()]{@link Collection#findAndRemove}.\n * @param {function|object} query - query object to filter on\n * @memberof Collection\n */\n Collection.prototype.removeWhere = function (query) {\n var list;\n if (typeof query === 'function') {\n list = this.data.filter(query);\n this.remove(list);\n } else {\n this.chain().find(query).remove();\n }\n };\n\n Collection.prototype.removeDataOnly = function () {\n this.remove(this.data.slice());\n };\n\n /**\n * Internal method to remove a batch of documents from the collection.\n * @param {number[]} positions - data/idIndex positions to remove\n */\n Collection.prototype.removeBatchByPositions = function (positions) {\n var len = positions.length;\n var xo = {};\n var dlen, didx, idx;\n var bic = Object.keys(this.binaryIndices).length;\n var uic = Object.keys(this.constraints.unique).length;\n var adaptiveOverride = this.adaptiveBinaryIndices && Object.keys(this.binaryIndices).length > 0;\n var doc, self = this;\n\n try {\n this.startTransaction();\n\n // create hashobject for positional removal inclusion tests...\n // all keys defined in this hashobject represent $loki ids of the documents to remove.\n this.ensureId();\n for (idx = 0; idx < len; idx++) {\n xo[this.idIndex[positions[idx]]] = true;\n }\n\n // if we will need to notify dynamic views and/or binary indices to update themselves...\n dlen = this.DynamicViews.length;\n if ((dlen > 0) || (bic > 0) || (uic > 0)) {\n if (dlen > 0) {\n // notify dynamic views to remove relevant documents at data positions\n for (didx = 0; didx < dlen; didx++) {\n // notify dv of remove (passing batch/array of positions)\n this.DynamicViews[didx].removeDocument(positions);\n }\n }\n\n // notify binary indices to update\n if (this.adaptiveBinaryIndices && !adaptiveOverride) {\n // for each binary index defined in collection, immediately update rather than flag for lazy rebuild\n var key, bIndices = this.binaryIndices;\n\n for (key in bIndices) {\n this.adaptiveBinaryIndexRemove(positions, key);\n }\n }\n else {\n this.flagBinaryIndexesDirty();\n }\n\n if (uic) {\n this.uniqueNames.forEach(function (key) {\n var index = self.getUniqueIndex(key);\n if (index) {\n for (idx = 0; idx < len; idx++) {\n doc = self.data[positions[idx]];\n if (doc[key] !== null && doc[key] !== undefined) {\n index.remove(doc[key]);\n }\n }\n }\n });\n }\n }\n\n // emit 'delete' events only of listeners are attached.\n // since data not removed yet, in future we can emit single delete event with array...\n // for now that might be breaking change to put in potential 1.6 or LokiDB (lokijs2) version\n if (!this.disableChangesApi || this.events.delete.length > 1) {\n for (idx = 0; idx < len; idx++) {\n this.emit('delete', this.data[positions[idx]]);\n }\n }\n\n // remove from data[] :\n // filter collection data for items not in inclusion hashobject\n this.data = this.data.filter(function (obj) {\n return !xo[obj.$loki];\n });\n\n if (this.isIncremental) {\n for(idx=0; idx < len; idx++) {\n this.dirtyIds.push(this.idIndex[positions[idx]]);\n }\n }\n\n // remove from idIndex[] :\n // filter idIndex for items not in inclusion hashobject\n this.idIndex = this.idIndex.filter(function (id) {\n return !xo[id];\n });\n\n if (this.adaptiveBinaryIndices && adaptiveOverride) {\n this.adaptiveBinaryIndices = false;\n this.ensureAllIndexes(true);\n this.adaptiveBinaryIndices = true;\n }\n\n this.commit();\n\n // flag collection as dirty for autosave\n this.dirty = true;\n }\n catch (err) {\n this.rollback();\n if (adaptiveOverride) {\n this.adaptiveBinaryIndices = true;\n }\n this.lokiConsoleWrapper.error(err.message);\n this.emit('error', err);\n return null;\n }\n };\n\n /**\n * Internal method called by remove()\n * @param {object[]|number[]} batch - array of documents or $loki ids to remove\n */\n Collection.prototype.removeBatch = function (batch) {\n var len = batch.length,\n dlen = this.data.length,\n idx;\n var xlt = {};\n var posx = [];\n\n // create lookup hashobject to translate $loki id to position\n for (idx = 0; idx < dlen; idx++) {\n xlt[this.data[idx].$loki] = idx;\n }\n\n // iterate the batch\n for (idx = 0; idx < len; idx++) {\n if (typeof (batch[idx]) === 'object') {\n posx.push(xlt[batch[idx].$loki]);\n }\n else {\n posx.push(xlt[batch[idx]]);\n }\n }\n\n this.removeBatchByPositions(posx);\n };\n\n /**\n * Remove a document from the collection\n * @param {object} doc - document to remove from collection\n * @memberof Collection\n */\n Collection.prototype.remove = function (doc) {\n var frozen;\n\n if (typeof doc === 'number') {\n doc = this.get(doc);\n }\n\n if ('object' !== typeof doc) {\n throw new Error('Parameter is not an object');\n }\n if (Array.isArray(doc)) {\n this.removeBatch(doc);\n return;\n }\n\n if (!hasOwnProperty.call(doc, '$loki')) {\n throw new Error('Object is not a document stored in the collection');\n }\n\n try {\n this.startTransaction();\n var arr = this.get(doc.$loki, true),\n // obj = arr[0],\n position = arr[1];\n var self = this;\n this.uniqueNames.forEach(function (key) {\n if (doc[key] !== null && typeof doc[key] !== 'undefined') {\n var index = self.getUniqueIndex(key);\n if (index) {\n index.remove(doc[key]);\n }\n }\n });\n // now that we can efficiently determine the data[] position of newly added document,\n // submit it for all registered DynamicViews to remove\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n this.DynamicViews[idx].removeDocument(position);\n }\n\n if (this.adaptiveBinaryIndices) {\n // for each binary index defined in collection, immediately update rather than flag for lazy rebuild\n var key, bIndices = this.binaryIndices;\n for (key in bIndices) {\n this.adaptiveBinaryIndexRemove(position, key);\n }\n }\n else {\n this.flagBinaryIndexesDirty();\n }\n\n this.data.splice(position, 1);\n this.removeAutoUpdateObserver(doc);\n\n // remove id from idIndex\n this.idIndex.splice(position, 1);\n\n if (this.isIncremental) {\n this.dirtyIds.push(doc.$loki);\n }\n\n this.commit();\n this.dirty = true; // for autosave scenarios\n this.emit('delete', arr[0]);\n\n if (!this.disableFreeze) {\n doc = unFreeze(doc);\n }\n delete doc.$loki;\n delete doc.meta;\n if (!this.disableFreeze) {\n freeze(doc);\n }\n return doc;\n\n } catch (err) {\n this.rollback();\n this.lokiConsoleWrapper.error(err.message);\n this.emit('error', err);\n return null;\n }\n };\n\n /*---------------------+\n | Finding methods |\n +----------------------*/\n\n /**\n * Get by Id - faster than other methods because of the searching algorithm\n * @param {int} id - $loki id of document you want to retrieve\n * @param {boolean} returnPosition - if 'true' we will return [object, position]\n * @returns {(object|array|null)} Object reference if document was found, null if not,\n * or an array if 'returnPosition' was passed.\n * @memberof Collection\n */\n Collection.prototype.get = function (id, returnPosition) {\n if (!this.idIndex) {\n this.ensureId();\n }\n\n var retpos = returnPosition || false,\n data = this.idIndex,\n max = data.length - 1,\n min = 0,\n mid = (min + max) >> 1;\n\n id = typeof id === 'number' ? id : parseInt(id, 10);\n\n if (isNaN(id)) {\n throw new TypeError('Passed id is not an integer');\n }\n\n while (data[min] < data[max]) {\n mid = (min + max) >> 1;\n\n if (data[mid] < id) {\n min = mid + 1;\n } else {\n max = mid;\n }\n }\n\n if (max === min && data[min] === id) {\n if (retpos) {\n return [this.data[min], min];\n }\n return this.data[min];\n }\n return null;\n\n };\n\n /**\n * Perform binary range lookup for the data[dataPosition][binaryIndexName] property value\n * Since multiple documents may contain the same value (which the index is sorted on),\n * we hone in on range and then linear scan range to find exact index array position.\n * @param {int} dataPosition : coll.data array index/position\n * @param {string} binaryIndexName : index to search for dataPosition in\n */\n Collection.prototype.getBinaryIndexPosition = function (dataPosition, binaryIndexName) {\n var val = Utils.getIn(this.data[dataPosition], binaryIndexName, true);\n var index = this.binaryIndices[binaryIndexName].values;\n\n // i think calculateRange can probably be moved to collection\n // as it doesn't seem to need resultset. need to verify\n var range = this.calculateRange(\"$eq\", binaryIndexName, val);\n\n if (range[0] === 0 && range[1] === -1) {\n // uhoh didn't find range\n return null;\n }\n\n var min = range[0];\n var max = range[1];\n\n // narrow down the sub-segment of index values\n // where the indexed property value exactly matches our\n // value and then linear scan to find exact -index- position\n for (var idx = min; idx <= max; idx++) {\n if (index[idx] === dataPosition) return idx;\n }\n\n // uhoh\n return null;\n };\n\n /**\n * Adaptively insert a selected item to the index.\n * @param {int} dataPosition : coll.data array index/position\n * @param {string} binaryIndexName : index to search for dataPosition in\n */\n Collection.prototype.adaptiveBinaryIndexInsert = function (dataPosition, binaryIndexName) {\n var usingDotNotation = (binaryIndexName.indexOf('.') !== -1);\n var index = this.binaryIndices[binaryIndexName].values;\n var val = Utils.getIn(this.data[dataPosition], binaryIndexName, usingDotNotation);\n\n // If you are inserting a javascript Date value into a binary index, convert to epoch time\n if (this.serializableIndices === true && val instanceof Date) {\n this.data[dataPosition][binaryIndexName] = val.getTime();\n val = Utils.getIn(this.data[dataPosition], binaryIndexName);\n }\n\n var idxPos = (index.length === 0) ? 0 : this.calculateRangeStart(binaryIndexName, val, true, usingDotNotation);\n\n // insert new data index into our binary index at the proper sorted location for relevant property calculated by idxPos.\n // doing this after adjusting dataPositions so no clash with previous item at that position.\n this.binaryIndices[binaryIndexName].values.splice(idxPos, 0, dataPosition);\n };\n\n /**\n * Adaptively update a selected item within an index.\n * @param {int} dataPosition : coll.data array index/position\n * @param {string} binaryIndexName : index to search for dataPosition in\n */\n Collection.prototype.adaptiveBinaryIndexUpdate = function (dataPosition, binaryIndexName) {\n // linear scan needed to find old position within index unless we optimize for clone scenarios later\n // within (my) node 5.6.0, the following for() loop with strict compare is -much- faster than indexOf()\n var idxPos,\n index = this.binaryIndices[binaryIndexName].values,\n len = index.length;\n\n for (idxPos = 0; idxPos < len; idxPos++) {\n if (index[idxPos] === dataPosition) break;\n }\n\n //var idxPos = this.binaryIndices[binaryIndexName].values.indexOf(dataPosition);\n this.binaryIndices[binaryIndexName].values.splice(idxPos, 1);\n\n //this.adaptiveBinaryIndexRemove(dataPosition, binaryIndexName, true);\n this.adaptiveBinaryIndexInsert(dataPosition, binaryIndexName);\n };\n\n /**\n * Adaptively remove a selected item from the index.\n * @param {number|number[]} dataPosition : coll.data array index/position\n * @param {string} binaryIndexName : index to search for dataPosition in\n */\n Collection.prototype.adaptiveBinaryIndexRemove = function (dataPosition, binaryIndexName, removedFromIndexOnly) {\n var bi = this.binaryIndices[binaryIndexName];\n var len, idx, rmidx, rmlen, rxo = {};\n var curr, shift, idxPos;\n\n if (Array.isArray(dataPosition)) {\n // when called from chained remove, and only one document in array,\n // it will be faster to use old algorithm\n rmlen = dataPosition.length;\n if (rmlen === 1) {\n dataPosition = dataPosition[0];\n }\n // we were passed an array (batch) of documents so use this 'batch optimized' algorithm\n else {\n for (rmidx = 0; rmidx < rmlen; rmidx++) {\n rxo[dataPosition[rmidx]] = true;\n }\n\n // remove document from index (with filter function)\n bi.values = bi.values.filter(function (di) { return !rxo[di]; });\n\n // if we passed this optional flag parameter, we are calling from adaptiveBinaryIndexUpdate,\n // in which case data positions stay the same.\n if (removedFromIndexOnly === true) {\n return;\n }\n\n var sortedPositions = dataPosition.slice();\n sortedPositions.sort(function (a, b) { return a - b; });\n\n // to remove holes, we need to 'shift down' the index's data array positions\n // we need to adjust array positions -1 for each index data positions greater than removed positions\n len = bi.values.length;\n for (idx = 0; idx < len; idx++) {\n curr = bi.values[idx];\n shift = 0;\n for (rmidx = 0; rmidx < rmlen && curr > sortedPositions[rmidx]; rmidx++) {\n shift++;\n }\n bi.values[idx] -= shift;\n }\n\n // batch processed, bail out\n return;\n }\n\n // not a batch so continue...\n }\n\n idxPos = this.getBinaryIndexPosition(dataPosition, binaryIndexName);\n\n if (idxPos === null) {\n // throw new Error('unable to determine binary index position');\n return null;\n }\n\n // remove document from index (with splice)\n bi.values.splice(idxPos, 1);\n\n // if we passed this optional flag parameter, we are calling from adaptiveBinaryIndexUpdate,\n // in which case data positions stay the same.\n if (removedFromIndexOnly === true) {\n return;\n }\n\n // since index stores data array positions, if we remove a document\n // we need to adjust array positions -1 for all document positions greater than removed position\n len = bi.values.length;\n for (idx = 0; idx < len; idx++) {\n if (bi.values[idx] > dataPosition) {\n bi.values[idx]--;\n }\n }\n };\n\n /**\n * Internal method used for index maintenance and indexed searching.\n * Calculates the beginning of an index range for a given value.\n * For index maintainance (adaptive:true), we will return a valid index position to insert to.\n * For querying (adaptive:false/undefined), we will :\n * return lower bound/index of range of that value (if found)\n * return next lower index position if not found (hole)\n * If index is empty it is assumed to be handled at higher level, so\n * this method assumes there is at least 1 document in index.\n *\n * @param {string} prop - name of property which has binary index\n * @param {any} val - value to find within index\n * @param {bool?} adaptive - if true, we will return insert position\n */\n Collection.prototype.calculateRangeStart = function (prop, val, adaptive, usingDotNotation) {\n var rcd = this.data;\n var index = this.binaryIndices[prop].values;\n var min = 0;\n var max = index.length - 1;\n var mid = 0;\n\n if (index.length === 0) {\n return -1;\n }\n\n var minVal = Utils.getIn(rcd[index[min]], prop, usingDotNotation);\n var maxVal = Utils.getIn(rcd[index[max]], prop, usingDotNotation);\n\n // hone in on start position of value\n while (min < max) {\n mid = (min + max) >> 1;\n\n if (Comparators.lt(Utils.getIn(rcd[index[mid]], prop, usingDotNotation), val, false)) {\n min = mid + 1;\n } else {\n max = mid;\n }\n }\n\n var lbound = min;\n\n // found it... return it\n if (Comparators.aeq(val, Utils.getIn(rcd[index[lbound]], prop, usingDotNotation))) {\n return lbound;\n }\n\n // if not in index and our value is less than the found one\n if (Comparators.lt(val, Utils.getIn(rcd[index[lbound]], prop, usingDotNotation), false)) {\n return adaptive ? lbound : lbound - 1;\n }\n\n // not in index and our value is greater than the found one\n return adaptive ? lbound + 1 : lbound;\n };\n\n /**\n * Internal method used for indexed $between. Given a prop (index name), and a value\n * (which may or may not yet exist) this will find the final position of that upper range value.\n */\n Collection.prototype.calculateRangeEnd = function (prop, val, usingDotNotation) {\n var rcd = this.data;\n var index = this.binaryIndices[prop].values;\n var min = 0;\n var max = index.length - 1;\n var mid = 0;\n\n if (index.length === 0) {\n return -1;\n }\n\n var minVal = Utils.getIn(rcd[index[min]], prop, usingDotNotation);\n var maxVal = Utils.getIn(rcd[index[max]], prop, usingDotNotation);\n\n // hone in on start position of value\n while (min < max) {\n mid = (min + max) >> 1;\n\n if (Comparators.lt(val, Utils.getIn(rcd[index[mid]], prop, usingDotNotation), false)) {\n max = mid;\n } else {\n min = mid + 1;\n }\n }\n\n var ubound = max;\n\n // only eq if last element in array is our val\n if (Comparators.aeq(val, Utils.getIn(rcd[index[ubound]], prop, usingDotNotation))) {\n return ubound;\n }\n\n // if not in index and our value is less than the found one\n if (Comparators.gt(val, Utils.getIn(rcd[index[ubound]], prop, usingDotNotation), false)) {\n return ubound + 1;\n }\n\n // either hole or first nonmatch\n if (Comparators.aeq(val, Utils.getIn(rcd[index[ubound - 1]], prop, usingDotNotation))) {\n return ubound - 1;\n }\n\n // hole, so ubound if nearest gt than the val we were looking for\n return ubound;\n };\n\n /**\n * calculateRange() - Binary Search utility method to find range/segment of values matching criteria.\n * this is used for collection.find() and first find filter of resultset/dynview\n * slightly different than get() binary search in that get() hones in on 1 value,\n * but we have to hone in on many (range)\n * @param {string} op - operation, such as $eq\n * @param {string} prop - name of property to calculate range for\n * @param {object} val - value to use for range calculation.\n * @returns {array} [start, end] index array positions\n */\n Collection.prototype.calculateRange = function (op, prop, val) {\n var rcd = this.data;\n var index = this.binaryIndices[prop].values;\n var min = 0;\n var max = index.length - 1;\n var mid = 0;\n var lbound, lval;\n var ubound, uval;\n\n // when no documents are in collection, return empty range condition\n if (rcd.length === 0) {\n return [0, -1];\n }\n\n var usingDotNotation = (prop.indexOf('.') !== -1);\n\n var minVal = Utils.getIn(rcd[index[min]], prop, usingDotNotation);\n var maxVal = Utils.getIn(rcd[index[max]], prop, usingDotNotation);\n\n // if value falls outside of our range return [0, -1] to designate no results\n switch (op) {\n case '$eq':\n case '$aeq':\n if (Comparators.lt(val, minVal, false) || Comparators.gt(val, maxVal, false)) {\n return [0, -1];\n }\n break;\n case '$dteq':\n if (Comparators.lt(val, minVal, false) || Comparators.gt(val, maxVal, false)) {\n return [0, -1];\n }\n break;\n case '$gt':\n // none are within range\n if (Comparators.gt(val, maxVal, true)) {\n return [0, -1];\n }\n // all are within range\n if (Comparators.gt(minVal, val, false)) {\n return [min, max];\n }\n break;\n case '$gte':\n // none are within range\n if (Comparators.gt(val, maxVal, false)) {\n return [0, -1];\n }\n // all are within range\n if (Comparators.gt(minVal, val, true)) {\n return [min, max];\n }\n break;\n case '$lt':\n // none are within range\n if (Comparators.lt(val, minVal, true)) {\n return [0, -1];\n }\n // all are within range\n if (Comparators.lt(maxVal, val, false)) {\n return [min, max];\n }\n break;\n case '$lte':\n // none are within range\n if (Comparators.lt(val, minVal, false)) {\n return [0, -1];\n }\n // all are within range\n if (Comparators.lt(maxVal, val, true)) {\n return [min, max];\n }\n break;\n case '$between':\n // none are within range (low range is greater)\n if (Comparators.gt(val[0], maxVal, false)) {\n return [0, -1];\n }\n // none are within range (high range lower)\n if (Comparators.lt(val[1], minVal, false)) {\n return [0, -1];\n }\n\n lbound = this.calculateRangeStart(prop, val[0], false, usingDotNotation);\n ubound = this.calculateRangeEnd(prop, val[1], usingDotNotation);\n\n if (lbound < 0) lbound++;\n if (ubound > max) ubound--;\n\n if (!Comparators.gt(Utils.getIn(rcd[index[lbound]], prop, usingDotNotation), val[0], true)) lbound++;\n if (!Comparators.lt(Utils.getIn(rcd[index[ubound]], prop, usingDotNotation), val[1], true)) ubound--;\n\n if (ubound < lbound) return [0, -1];\n\n return ([lbound, ubound]);\n case '$in':\n var idxset = [],\n segResult = [];\n // query each value '$eq' operator and merge the seqment results.\n for (var j = 0, len = val.length; j < len; j++) {\n var seg = this.calculateRange('$eq', prop, val[j]);\n\n for (var i = seg[0]; i <= seg[1]; i++) {\n if (idxset[i] === undefined) {\n idxset[i] = true;\n segResult.push(i);\n }\n }\n }\n return segResult;\n }\n\n // determine lbound where needed\n switch (op) {\n case '$eq':\n case '$aeq':\n case '$dteq':\n case '$gte':\n case '$lt':\n lbound = this.calculateRangeStart(prop, val, false, usingDotNotation);\n lval = Utils.getIn(rcd[index[lbound]], prop, usingDotNotation);\n break;\n default: break;\n }\n\n // determine ubound where needed\n switch (op) {\n case '$eq':\n case '$aeq':\n case '$dteq':\n case '$lte':\n case '$gt':\n ubound = this.calculateRangeEnd(prop, val, usingDotNotation);\n uval = Utils.getIn(rcd[index[ubound]], prop, usingDotNotation);\n break;\n default: break;\n }\n\n\n switch (op) {\n case '$eq':\n case '$aeq':\n case '$dteq':\n // if hole (not found)\n if (!Comparators.aeq(lval, val)) {\n return [0, -1];\n }\n\n return [lbound, ubound];\n\n case '$gt':\n // if hole (not found) ub position is already greater\n if (!Comparators.aeq(Utils.getIn(rcd[index[ubound]], prop, usingDotNotation), val)) {\n return [ubound, max];\n }\n // otherwise (found) so ubound is still equal, get next\n return [ubound + 1, max];\n\n case '$gte':\n // if hole (not found) lb position marks left outside of range\n if (!Comparators.aeq(Utils.getIn(rcd[index[lbound]], prop, usingDotNotation), val)) {\n return [lbound + 1, max];\n }\n // otherwise (found) so lb is first position where its equal\n return [lbound, max];\n\n case '$lt':\n // if hole (not found) position already is less than\n if (!Comparators.aeq(Utils.getIn(rcd[index[lbound]], prop, usingDotNotation), val)) {\n return [min, lbound];\n }\n // otherwise (found) so lb marks left inside of eq range, get previous\n return [min, lbound - 1];\n\n case '$lte':\n // if hole (not found) ub position marks right outside so get previous\n if (!Comparators.aeq(Utils.getIn(rcd[index[ubound]], prop, usingDotNotation), val)) {\n return [min, ubound - 1];\n }\n // otherwise (found) so ub is last position where its still equal\n return [min, ubound];\n\n default:\n return [0, rcd.length - 1];\n }\n };\n\n /**\n * Retrieve doc by Unique index\n * @param {string} field - name of uniquely indexed property to use when doing lookup\n * @param {value} value - unique value to search for\n * @returns {object} document matching the value passed\n * @memberof Collection\n */\n Collection.prototype.by = function (field, value) {\n var self;\n if (value === undefined) {\n self = this;\n return function (value) {\n return self.by(field, value);\n };\n }\n\n var result = this.getUniqueIndex(field, true).get(value);\n if (!this.cloneObjects) {\n return result;\n } else {\n return clone(result, this.cloneMethod);\n }\n };\n\n /**\n * Find one object by index property, by property equal to value\n * @param {object} query - query object used to perform search with\n * @returns {(object|null)} First matching document, or null if none\n * @memberof Collection\n */\n Collection.prototype.findOne = function (query) {\n query = query || {};\n\n // Instantiate Resultset and exec find op passing firstOnly = true param\n var result = this.chain().find(query, true).data();\n\n if (Array.isArray(result) && result.length === 0) {\n return null;\n } else {\n if (!this.cloneObjects) {\n return result[0];\n } else {\n return clone(result[0], this.cloneMethod);\n }\n }\n };\n\n /**\n * Chain method, used for beginning a series of chained find() and/or view() operations\n * on a collection.\n *\n * @param {string|array=} transform - named transform or array of transform steps\n * @param {object=} parameters - Object containing properties representing parameters to substitute\n * @returns {Resultset} (this) resultset, or data array if any map or join functions where called\n * @memberof Collection\n */\n Collection.prototype.chain = function (transform, parameters) {\n var rs = new Resultset(this);\n\n if (typeof transform === 'undefined') {\n return rs;\n }\n\n return rs.transform(transform, parameters);\n };\n\n /**\n * Find method, api is similar to mongodb.\n * for more complex queries use [chain()]{@link Collection#chain} or [where()]{@link Collection#where}.\n * @example {@tutorial Query Examples}\n * @param {object} query - 'mongo-like' query object\n * @returns {array} Array of matching documents\n * @memberof Collection\n */\n Collection.prototype.find = function (query) {\n return this.chain().find(query).data();\n };\n\n /**\n * Find object by unindexed field by property equal to value,\n * simply iterates and returns the first element matching the query\n */\n Collection.prototype.findOneUnindexed = function (prop, value) {\n var i = this.data.length,\n doc;\n while (i--) {\n if (Utils.getIn(this.data[i], prop, true) === value) {\n doc = this.data[i];\n return doc;\n }\n }\n return null;\n };\n\n /**\n * Transaction methods\n */\n\n /** start the transation */\n Collection.prototype.startTransaction = function () {\n if (this.transactional) {\n this.cachedData = clone(this.data, this.cloneMethod);\n this.cachedIndex = this.idIndex;\n this.cachedBinaryIndex = this.binaryIndices;\n this.cachedDirtyIds = this.dirtyIds;\n\n // propagate startTransaction to dynamic views\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n this.DynamicViews[idx].startTransaction();\n }\n }\n };\n\n /** commit the transation */\n Collection.prototype.commit = function () {\n if (this.transactional) {\n this.cachedData = null;\n this.cachedIndex = null;\n this.cachedBinaryIndex = null;\n this.cachedDirtyIds = null;\n\n // propagate commit to dynamic views\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n this.DynamicViews[idx].commit();\n }\n }\n };\n\n /** roll back the transation */\n Collection.prototype.rollback = function () {\n if (this.transactional) {\n if (this.cachedData !== null && this.cachedIndex !== null) {\n this.data = this.cachedData;\n this.idIndex = this.cachedIndex;\n this.binaryIndices = this.cachedBinaryIndex;\n this.dirtyIds = this.cachedDirtyIds;\n }\n\n // propagate rollback to dynamic views\n for (var idx = 0; idx < this.DynamicViews.length; idx++) {\n this.DynamicViews[idx].rollback();\n }\n }\n };\n\n // async executor. This is only to enable callbacks at the end of the execution.\n Collection.prototype.async = function (fun, callback) {\n setTimeout(function () {\n if (typeof fun === 'function') {\n fun();\n callback();\n } else {\n throw new TypeError('Argument passed for async execution is not a function');\n }\n }, 0);\n };\n\n /**\n * Query the collection by supplying a javascript filter function.\n * @example\n * var results = coll.where(function(obj) {\n * return obj.legs === 8;\n * });\n *\n * @param {function} fun - filter function to run against all collection docs\n * @returns {array} all documents which pass your filter function\n * @memberof Collection\n */\n Collection.prototype.where = function (fun) {\n return this.chain().where(fun).data();\n };\n\n /**\n * Map Reduce operation\n *\n * @param {function} mapFunction - function to use as map function\n * @param {function} reduceFunction - function to use as reduce function\n * @returns {data} The result of your mapReduce operation\n * @memberof Collection\n */\n Collection.prototype.mapReduce = function (mapFunction, reduceFunction) {\n try {\n return reduceFunction(this.data.map(mapFunction));\n } catch (err) {\n throw err;\n }\n };\n\n /**\n * Join two collections on specified properties\n *\n * @param {array|Resultset|Collection} joinData - array of documents to 'join' to this collection\n * @param {string} leftJoinProp - property name in collection\n * @param {string} rightJoinProp - property name in joinData\n * @param {function=} mapFun - (Optional) map function to use\n * @param {object=} dataOptions - options to data() before input to your map function\n * @param {bool} dataOptions.removeMeta - allows removing meta before calling mapFun\n * @param {boolean} dataOptions.forceClones - forcing the return of cloned objects to your map object\n * @param {string} dataOptions.forceCloneMethod - Allows overriding the default or collection specified cloning method.\n * @returns {Resultset} Result of the mapping operation\n * @memberof Collection\n */\n Collection.prototype.eqJoin = function (joinData, leftJoinProp, rightJoinProp, mapFun, dataOptions) {\n // logic in Resultset class\n return new Resultset(this).eqJoin(joinData, leftJoinProp, rightJoinProp, mapFun, dataOptions);\n };\n\n /* ------ STAGING API -------- */\n /**\n * stages: a map of uniquely identified 'stages', which hold copies of objects to be\n * manipulated without affecting the data in the original collection\n */\n Collection.prototype.stages = {};\n\n /**\n * (Staging API) create a stage and/or retrieve it\n * @memberof Collection\n */\n Collection.prototype.getStage = function (name) {\n if (!this.stages[name]) {\n this.stages[name] = {};\n }\n return this.stages[name];\n };\n /**\n * a collection of objects recording the changes applied through a commmitStage\n */\n Collection.prototype.commitLog = [];\n\n /**\n * (Staging API) create a copy of an object and insert it into a stage\n * @memberof Collection\n */\n Collection.prototype.stage = function (stageName, obj) {\n var copy = JSON.parse(JSON.stringify(obj));\n this.getStage(stageName)[obj.$loki] = copy;\n return copy;\n };\n\n /**\n * (Staging API) re-attach all objects to the original collection, so indexes and views can be rebuilt\n * then create a message to be inserted in the commitlog\n * @param {string} stageName - name of stage\n * @param {string} message\n * @memberof Collection\n */\n Collection.prototype.commitStage = function (stageName, message) {\n var stage = this.getStage(stageName),\n prop,\n timestamp = new Date().getTime();\n\n for (prop in stage) {\n\n this.update(stage[prop]);\n this.commitLog.push({\n timestamp: timestamp,\n message: message,\n data: JSON.parse(JSON.stringify(stage[prop]))\n });\n }\n this.stages[stageName] = {};\n };\n\n Collection.prototype.no_op = function () {\n return;\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.extract = function (field) {\n var i = 0,\n len = this.data.length,\n isDotNotation = isDeepProperty(field),\n result = [];\n for (i; i < len; i += 1) {\n result.push(deepProperty(this.data[i], field, isDotNotation));\n }\n return result;\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.max = function (field) {\n return Math.max.apply(null, this.extract(field));\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.min = function (field) {\n return Math.min.apply(null, this.extract(field));\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.maxRecord = function (field) {\n var i = 0,\n len = this.data.length,\n deep = isDeepProperty(field),\n result = {\n index: 0,\n value: undefined\n },\n max;\n\n for (i; i < len; i += 1) {\n if (max !== undefined) {\n if (max < deepProperty(this.data[i], field, deep)) {\n max = deepProperty(this.data[i], field, deep);\n result.index = this.data[i].$loki;\n }\n } else {\n max = deepProperty(this.data[i], field, deep);\n result.index = this.data[i].$loki;\n }\n }\n result.value = max;\n return result;\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.minRecord = function (field) {\n var i = 0,\n len = this.data.length,\n deep = isDeepProperty(field),\n result = {\n index: 0,\n value: undefined\n },\n min;\n\n for (i; i < len; i += 1) {\n if (min !== undefined) {\n if (min > deepProperty(this.data[i], field, deep)) {\n min = deepProperty(this.data[i], field, deep);\n result.index = this.data[i].$loki;\n }\n } else {\n min = deepProperty(this.data[i], field, deep);\n result.index = this.data[i].$loki;\n }\n }\n result.value = min;\n return result;\n };\n\n /**\n * @memberof Collection\n */\n Collection.prototype.extractNumerical = function (field) {\n return this.extract(field).map(parseBase10).filter(Number).filter(function (n) {\n return !(isNaN(n));\n });\n };\n\n /**\n * Calculates the average numerical value of a property\n *\n * @param {string} field - name of property in docs to average\n * @returns {number} average of property in all docs in the collection\n * @memberof Collection\n */\n Collection.prototype.avg = function (field) {\n return average(this.extractNumerical(field));\n };\n\n /**\n * Calculate standard deviation of a field\n * @memberof Collection\n * @param {string} field\n */\n Collection.prototype.stdDev = function (field) {\n return standardDeviation(this.extractNumerical(field));\n };\n\n /**\n * @memberof Collection\n * @param {string} field\n */\n Collection.prototype.mode = function (field) {\n var dict = {},\n data = this.extract(field);\n data.forEach(function (obj) {\n if (dict[obj]) {\n dict[obj] += 1;\n } else {\n dict[obj] = 1;\n }\n });\n var max,\n prop, mode;\n for (prop in dict) {\n if (max) {\n if (max < dict[prop]) {\n mode = prop;\n }\n } else {\n mode = prop;\n max = dict[prop];\n }\n }\n return mode;\n };\n\n /**\n * @memberof Collection\n * @param {string} field - property name\n */\n Collection.prototype.median = function (field) {\n var values = this.extractNumerical(field);\n values.sort(sub);\n\n var half = Math.floor(values.length / 2);\n\n if (values.length % 2) {\n return values[half];\n } else {\n return (values[half - 1] + values[half]) / 2.0;\n }\n };\n\n /**\n * General utils, including statistical functions\n */\n function isDeepProperty(field) {\n return field.indexOf('.') !== -1;\n }\n\n function parseBase10(num) {\n return parseFloat(num, 10);\n }\n\n function isNotUndefined(obj) {\n return obj !== undefined;\n }\n\n function add(a, b) {\n return a + b;\n }\n\n function sub(a, b) {\n return a - b;\n }\n\n function median(values) {\n values.sort(sub);\n var half = Math.floor(values.length / 2);\n return (values.length % 2) ? values[half] : ((values[half - 1] + values[half]) / 2.0);\n }\n\n function average(array) {\n return (array.reduce(add, 0)) / array.length;\n }\n\n function standardDeviation(values) {\n var avg = average(values);\n var squareDiffs = values.map(function (value) {\n var diff = value - avg;\n var sqrDiff = diff * diff;\n return sqrDiff;\n });\n\n var avgSquareDiff = average(squareDiffs);\n\n var stdDev = Math.sqrt(avgSquareDiff);\n return stdDev;\n }\n\n function deepProperty(obj, property, isDeep) {\n if (isDeep === false) {\n // pass without processing\n return obj[property];\n }\n var pieces = property.split('.'),\n root = obj;\n while (pieces.length > 0) {\n root = root[pieces.shift()];\n }\n return root;\n }\n\n function binarySearch(array, item, fun) {\n var lo = 0,\n hi = array.length,\n compared,\n mid;\n while (lo < hi) {\n mid = (lo + hi) >> 1;\n compared = fun.apply(null, [item, array[mid]]);\n if (compared === 0) {\n return {\n found: true,\n index: mid\n };\n } else if (compared < 0) {\n hi = mid;\n } else {\n lo = mid + 1;\n }\n }\n return {\n found: false,\n index: hi\n };\n }\n\n function BSonSort(fun) {\n return function (array, item) {\n return binarySearch(array, item, fun);\n };\n }\n\n function KeyValueStore() { }\n\n KeyValueStore.prototype = {\n keys: [],\n values: [],\n sort: function (a, b) {\n return (a < b) ? -1 : ((a > b) ? 1 : 0);\n },\n setSort: function (fun) {\n this.bs = new BSonSort(fun);\n },\n bs: function () {\n return new BSonSort(this.sort);\n },\n set: function (key, value) {\n var pos = this.bs(this.keys, key);\n if (pos.found) {\n this.values[pos.index] = value;\n } else {\n this.keys.splice(pos.index, 0, key);\n this.values.splice(pos.index, 0, value);\n }\n },\n get: function (key) {\n return this.values[binarySearch(this.keys, key, this.sort).index];\n }\n };\n\n function UniqueIndex(uniqueField) {\n this.field = uniqueField;\n this.keyMap = Object.create(null);\n this.lokiMap = Object.create(null);\n }\n UniqueIndex.prototype.keyMap = {};\n UniqueIndex.prototype.lokiMap = {};\n UniqueIndex.prototype.set = function (obj) {\n var fieldValue = obj[this.field];\n if (fieldValue !== null && typeof (fieldValue) !== 'undefined') {\n if (this.keyMap[fieldValue]) {\n throw new Error('Duplicate key for property ' + this.field + ': ' + fieldValue);\n } else {\n this.keyMap[fieldValue] = obj;\n this.lokiMap[obj.$loki] = fieldValue;\n }\n }\n };\n UniqueIndex.prototype.get = function (key) {\n return this.keyMap[key];\n };\n\n UniqueIndex.prototype.byId = function (id) {\n return this.keyMap[this.lokiMap[id]];\n };\n /**\n * Updates a document's unique index given an updated object.\n * @param {Object} obj Original document object\n * @param {Object} doc New document object (likely the same as obj)\n */\n UniqueIndex.prototype.update = function (obj, doc) {\n if (this.lokiMap[obj.$loki] !== doc[this.field]) {\n var old = this.lokiMap[obj.$loki];\n this.set(doc);\n // make the old key fail bool test, while avoiding the use of delete (mem-leak prone)\n this.keyMap[old] = undefined;\n } else {\n this.keyMap[obj[this.field]] = doc;\n }\n };\n UniqueIndex.prototype.remove = function (key) {\n var obj = this.keyMap[key];\n if (obj !== null && typeof obj !== 'undefined') {\n // avoid using `delete`\n this.keyMap[key] = undefined;\n this.lokiMap[obj.$loki] = undefined;\n } else {\n throw new Error('Key is not in unique index: ' + this.field);\n }\n };\n UniqueIndex.prototype.clear = function () {\n this.keyMap = Object.create(null);\n this.lokiMap = Object.create(null);\n };\n\n function ExactIndex(exactField) {\n this.index = Object.create(null);\n this.field = exactField;\n }\n\n // add the value you want returned to the key in the index\n ExactIndex.prototype = {\n set: function add(key, val) {\n if (this.index[key]) {\n this.index[key].push(val);\n } else {\n this.index[key] = [val];\n }\n },\n\n // remove the value from the index, if the value was the last one, remove the key\n remove: function remove(key, val) {\n var idxSet = this.index[key];\n for (var i in idxSet) {\n if (idxSet[i] == val) {\n idxSet.splice(i, 1);\n }\n }\n if (idxSet.length < 1) {\n this.index[key] = undefined;\n }\n },\n\n // get the values related to the key, could be more than one\n get: function get(key) {\n return this.index[key];\n },\n\n // clear will zap the index\n clear: function clear(key) {\n this.index = {};\n }\n };\n\n function SortedIndex(sortedField) {\n this.field = sortedField;\n }\n\n SortedIndex.prototype = {\n keys: [],\n values: [],\n // set the default sort\n sort: function (a, b) {\n return (a < b) ? -1 : ((a > b) ? 1 : 0);\n },\n bs: function () {\n return new BSonSort(this.sort);\n },\n // and allow override of the default sort\n setSort: function (fun) {\n this.bs = new BSonSort(fun);\n },\n // add the value you want returned to the key in the index\n set: function (key, value) {\n var pos = binarySearch(this.keys, key, this.sort);\n if (pos.found) {\n this.values[pos.index].push(value);\n } else {\n this.keys.splice(pos.index, 0, key);\n this.values.splice(pos.index, 0, [value]);\n }\n },\n // get all values which have a key == the given key\n get: function (key) {\n var bsr = binarySearch(this.keys, key, this.sort);\n if (bsr.found) {\n return this.values[bsr.index];\n } else {\n return [];\n }\n },\n // get all values which have a key < the given key\n getLt: function (key) {\n var bsr = binarySearch(this.keys, key, this.sort);\n var pos = bsr.index;\n if (bsr.found) pos--;\n return this.getAll(key, 0, pos);\n },\n // get all values which have a key > the given key\n getGt: function (key) {\n var bsr = binarySearch(this.keys, key, this.sort);\n var pos = bsr.index;\n if (bsr.found) pos++;\n return this.getAll(key, pos, this.keys.length);\n },\n\n // get all vals from start to end\n getAll: function (key, start, end) {\n var results = [];\n for (var i = start; i < end; i++) {\n results = results.concat(this.values[i]);\n }\n return results;\n },\n // just in case someone wants to do something smart with ranges\n getPos: function (key) {\n return binarySearch(this.keys, key, this.sort);\n },\n // remove the value from the index, if the value was the last one, remove the key\n remove: function (key, value) {\n var pos = binarySearch(this.keys, key, this.sort).index;\n var idxSet = this.values[pos];\n for (var i in idxSet) {\n if (idxSet[i] == value) idxSet.splice(i, 1);\n }\n if (idxSet.length < 1) {\n this.keys.splice(pos, 1);\n this.values.splice(pos, 1);\n }\n },\n // clear will zap the index\n clear: function () {\n this.keys = [];\n this.values = [];\n }\n };\n\n Loki.deepFreeze = deepFreeze;\n Loki.freeze = freeze;\n Loki.unFreeze = unFreeze;\n Loki.LokiOps = LokiOps;\n Loki.Collection = Collection;\n Loki.DynamicView = DynamicView;\n Loki.Resultset = Resultset;\n Loki.KeyValueStore = KeyValueStore;\n Loki.LokiMemoryAdapter = LokiMemoryAdapter;\n Loki.LokiPartitioningAdapter = LokiPartitioningAdapter;\n Loki.LokiLocalStorageAdapter = LokiLocalStorageAdapter;\n Loki.LokiFsAdapter = LokiFsAdapter;\n Loki.persistenceAdapters = {\n fs: LokiFsAdapter,\n localStorage: LokiLocalStorageAdapter\n };\n Loki.aeq = aeqHelper;\n Loki.lt = ltHelper;\n Loki.gt = gtHelper;\n Loki.Comparators = Comparators;\n return Loki;\n }());\n\n}));\n","","import ios from '../../dist/ionicons/svg/ios-add-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-add-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-albums.svg';\nimport md from '../../dist/ionicons/svg/md-albums.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-analytics.svg';\nimport md from '../../dist/ionicons/svg/md-analytics.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-apps.svg';\nimport md from '../../dist/ionicons/svg/md-apps.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-arrow-back.svg';\nimport md from '../../dist/ionicons/svg/md-arrow-back.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-arrow-round-back.svg';\nimport md from '../../dist/ionicons/svg/md-arrow-round-back.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-arrow-up.svg';\nimport md from '../../dist/ionicons/svg/md-arrow-up.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-attach.svg';\nimport md from '../../dist/ionicons/svg/md-attach.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-book.svg';\nimport md from '../../dist/ionicons/svg/md-book.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-bookmark.svg';\nimport md from '../../dist/ionicons/svg/md-bookmark.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-brush.svg';\nimport md from '../../dist/ionicons/svg/md-brush.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-checkmark-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-checkmark-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-checkmark-circle.svg';\nimport md from '../../dist/ionicons/svg/md-checkmark-circle.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-checkmark.svg';\nimport md from '../../dist/ionicons/svg/md-checkmark.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-close-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-close-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-close.svg';\nimport md from '../../dist/ionicons/svg/md-close.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-cloud-circle.svg';\nimport md from '../../dist/ionicons/svg/md-cloud-circle.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-color-wand.svg';\nimport md from '../../dist/ionicons/svg/md-color-wand.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-compass.svg';\nimport md from '../../dist/ionicons/svg/md-compass.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-contact.svg';\nimport md from '../../dist/ionicons/svg/md-contact.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-create.svg';\nimport md from '../../dist/ionicons/svg/md-create.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-cube.svg';\nimport md from '../../dist/ionicons/svg/md-cube.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-cut.svg';\nimport md from '../../dist/ionicons/svg/md-cut.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-document.svg';\nimport md from '../../dist/ionicons/svg/md-document.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-download.svg';\nimport md from '../../dist/ionicons/svg/md-download.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-expand.svg';\nimport md from '../../dist/ionicons/svg/md-expand.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-eye-off.svg';\nimport md from '../../dist/ionicons/svg/md-eye-off.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-eye.svg';\nimport md from '../../dist/ionicons/svg/md-eye.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-filing.svg';\nimport md from '../../dist/ionicons/svg/md-filing.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-funnel.svg';\nimport md from '../../dist/ionicons/svg/md-funnel.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-grid.svg';\nimport md from '../../dist/ionicons/svg/md-grid.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-headset.svg';\nimport md from '../../dist/ionicons/svg/md-headset.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-help-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-help-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-image.svg';\nimport md from '../../dist/ionicons/svg/md-image.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-information-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-information-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-link.svg';\nimport md from '../../dist/ionicons/svg/md-link.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-list.svg';\nimport md from '../../dist/ionicons/svg/md-list.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-locate.svg';\nimport md from '../../dist/ionicons/svg/md-locate.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-log-out.svg';\nimport md from '../../dist/ionicons/svg/md-log-out.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-map.svg';\nimport md from '../../dist/ionicons/svg/md-map.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-move.svg';\nimport md from '../../dist/ionicons/svg/md-move.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-navigate.svg';\nimport md from '../../dist/ionicons/svg/md-navigate.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-open.svg';\nimport md from '../../dist/ionicons/svg/md-open.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-paper.svg';\nimport md from '../../dist/ionicons/svg/md-paper.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-person.svg';\nimport md from '../../dist/ionicons/svg/md-person.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-pin.svg';\nimport md from '../../dist/ionicons/svg/md-pin.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-podium.svg';\nimport md from '../../dist/ionicons/svg/md-podium.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-print.svg';\nimport md from '../../dist/ionicons/svg/md-print.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-radio-button-on.svg';\nimport md from '../../dist/ionicons/svg/md-radio-button-on.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-refresh-circle.svg';\nimport md from '../../dist/ionicons/svg/md-refresh-circle.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-refresh.svg';\nimport md from '../../dist/ionicons/svg/md-refresh.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-remove-circle-outline.svg';\nimport md from '../../dist/ionicons/svg/md-remove-circle-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-save.svg';\nimport md from '../../dist/ionicons/svg/md-save.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-search.svg';\nimport md from '../../dist/ionicons/svg/md-search.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-send.svg';\nimport md from '../../dist/ionicons/svg/md-send.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-settings.svg';\nimport md from '../../dist/ionicons/svg/md-settings.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-share-alt.svg';\nimport md from '../../dist/ionicons/svg/md-share-alt.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-share.svg';\nimport md from '../../dist/ionicons/svg/md-share.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-square-outline.svg';\nimport md from '../../dist/ionicons/svg/md-square-outline.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-stats.svg';\nimport md from '../../dist/ionicons/svg/md-stats.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-swap.svg';\nimport md from '../../dist/ionicons/svg/md-swap.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-sync.svg';\nimport md from '../../dist/ionicons/svg/md-sync.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-today.svg';\nimport md from '../../dist/ionicons/svg/md-today.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-trash.svg';\nimport md from '../../dist/ionicons/svg/md-trash.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-videocam.svg';\nimport md from '../../dist/ionicons/svg/md-videocam.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","import ios from '../../dist/ionicons/svg/ios-warning.svg';\nimport md from '../../dist/ionicons/svg/md-warning.svg';\n\nexport default /*#__PURE__*/ {\n ios: ios,\n md: md\n};","\r\n\r\n\r\n\r\n","\r\n\r\n\r\n
\r\n
\r\n
\r\n
\r\n
    \r\n
  • \r\n \r\n
  • \r\n
  • \r\n \r\n
  • \r\n
\r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n \r\n

If you don't receive an email, please try again.

\r\n
\r\n \r\n
\r\n {#if loading}\r\n \r\n {:else}\r\n \r\n {/if} \r\n
\r\n
\r\n
\r\n
\r\n
\r\n\r\n","\r\n\r\n\r\n
\r\n
\r\n
\r\n
\r\n
    \r\n
  • \r\n \r\n
  • \r\n
  • \r\n \r\n
  • \r\n
\r\n
\r\n
\r\n
\r\n \r\n \r\n
\r\n {#if loading}\r\n \r\n {:else}\r\n \r\n {/if}\r\n
\r\n
\r\n
\r\n
\r\n
\r\n\r\n","\r\n\r\n
\r\n \r\n \r\n
\r\n\r\n","\n\n{#each items as x (key)}\n\t\n{/each}\n","","\r\n\r\n
\r\n
\r\n
\r\n
\r\n
Sign In
\r\n
\r\n
\r\n {#if email}\r\n
\r\n \r\n
\r\n \r\n

If you don't receive an email, please try again.

\r\n
\r\n \r\n
\r\n {:else}\r\n
\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n {/if}\r\n
\r\n
\r\n
\r\n
\r\n
Login with email
\r\n
\r\n
\r\n
\r\n \r\n
\r\n
\r\n
\r\n
\r\n
\r\n
{error}
\r\n
\r\n
\r\n {#if loading}\r\n \r\n {:else}\r\n \r\n {/if}\r\n
\r\n
\r\n
\r\n
\r\n
\r\n\r\n","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.noop = noop;\nexports.easeOutQuart = easeOutQuart;\nexports.createElement = createElement;\nexports.addClass = addClass;\nexports.removeClass = removeClass;\nexports.imageLoaded = imageLoaded;\nexports.toArray = toArray;\nexports.assign = assign;\nexports.css = css;\nexports.removeCss = removeCss;\nexports.wrap = wrap;\nexports.unwrap = unwrap;\nexports.remove = remove;\nexports.clamp = clamp;\nexports.assignEvent = assignEvent;\nexports.getTouchPointsDistance = getTouchPointsDistance;\nexports.MOUSE_WHEEL_COUNT = exports.ZOOM_CONSTANT = void 0;\n// constants\nvar ZOOM_CONSTANT = 15; // increase or decrease value for zoom on mouse wheel\n\nexports.ZOOM_CONSTANT = ZOOM_CONSTANT;\nvar MOUSE_WHEEL_COUNT = 5; // A mouse delta after which it should stop preventing default behaviour of mouse wheel\n\nexports.MOUSE_WHEEL_COUNT = MOUSE_WHEEL_COUNT;\n\nfunction noop() {} // ease out method\n\n/*\n t : current time,\n b : intial value,\n c : changed value,\n d : duration\n*/\n\n\nfunction easeOutQuart(t, b, c, d) {\n t /= d;\n t -= 1;\n return -c * (t * t * t * t - 1) + b;\n}\n\nfunction createElement(options) {\n var elem = document.createElement(options.tagName);\n if (options.id) elem.id = options.id;\n if (options.html) elem.innerHTML = options.html;\n if (options.className) elem.className = options.className;\n if (options.src) elem.src = options.src;\n if (options.style) elem.style.cssText = options.style;\n if (options.child) elem.appendChild(options.child); // Insert before\n\n if (options.insertBefore) {\n options.parent.insertBefore(elem, options.insertBefore); // Standard append\n } else {\n options.parent.appendChild(elem);\n }\n\n return elem;\n} // method to add class\n\n\nfunction addClass(el, className) {\n var classNameAry = className.split(' ');\n\n if (classNameAry.length > 1) {\n classNameAry.forEach(function (classItem) {\n return addClass(el, classItem);\n });\n } else if (el.classList) {\n el.classList.add(className);\n } else {\n el.className += \" \".concat(className); // eslint-disable-line no-param-reassign\n }\n} // method to remove class\n\n\nfunction removeClass(el, className) {\n var classNameAry = className.split(' ');\n\n if (classNameAry.length > 1) {\n classNameAry.forEach(function (classItem) {\n return removeClass(el, classItem);\n });\n } else if (el.classList) {\n el.classList.remove(className);\n } else {\n el.className = el.className.replace(new RegExp(\"(^|\\\\b)\".concat(className.split(' ').join('|'), \"(\\\\b|$)\"), 'gi'), ' '); // eslint-disable-line no-param-reassign\n }\n} // function to check if image is loaded\n\n\nfunction imageLoaded(img) {\n return img.complete && (typeof img.naturalWidth === 'undefined' || img.naturalWidth !== 0);\n}\n\nfunction toArray(list) {\n if (!(list instanceof NodeList || list instanceof HTMLCollection)) return [list];\n return Array.prototype.slice.call(list);\n}\n\nfunction assign(target) {\n for (var _len = arguments.length, rest = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {\n rest[_key - 1] = arguments[_key];\n }\n\n rest.forEach(function (obj) {\n Object.keys(obj).forEach(function (key) {\n target[key] = obj[key];\n });\n });\n return target;\n}\n\nfunction css(elements, properties) {\n var elmArray = toArray(elements);\n\n if (typeof properties === 'string') {\n return window.getComputedStyle(elmArray[0])[properties];\n }\n\n elmArray.forEach(function (element) {\n Object.keys(properties).forEach(function (key) {\n var value = properties[key];\n element.style[key] = value; // eslint-disable-line no-param-reassign\n });\n });\n return undefined;\n}\n\nfunction removeCss(element, property) {\n element.style.removeProperty(property);\n}\n\nfunction wrap(element, _ref) {\n var _ref$tag = _ref.tag,\n tag = _ref$tag === void 0 ? 'div' : _ref$tag,\n className = _ref.className,\n id = _ref.id,\n style = _ref.style;\n var wrapper = document.createElement(tag);\n if (className) wrapper.className = className;\n if (id) wrapper.id = id;\n if (style) wrapper.style = style;\n element.parentNode.insertBefore(wrapper, element);\n element.parentNode.removeChild(element);\n wrapper.appendChild(element);\n return wrapper;\n}\n\nfunction unwrap(element) {\n var parent = element.parentNode;\n\n if (parent !== document.body) {\n parent.parentNode.insertBefore(element, parent);\n parent.parentNode.removeChild(parent);\n }\n}\n\nfunction remove(elements) {\n var elmArray = toArray(elements);\n elmArray.forEach(function (element) {\n element.parentNode.removeChild(element);\n });\n}\n\nfunction clamp(num, min, max) {\n return Math.min(Math.max(num, min), max);\n}\n\nfunction assignEvent(element, events, handler) {\n if (typeof events === 'string') events = [events];\n events.forEach(function (event) {\n element.addEventListener(event, handler);\n });\n return function () {\n events.forEach(function (event) {\n element.removeEventListener(event, handler);\n });\n };\n}\n\nfunction getTouchPointsDistance(touches) {\n var touch0 = touches[0];\n var touch1 = touches[1];\n return Math.sqrt(Math.pow(touch1.pageX - touch0.pageX, 2) + Math.pow(touch1.pageY - touch0.pageY, 2));\n}","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _util = require(\"./util\");\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar Slider =\n/*#__PURE__*/\nfunction () {\n function Slider(container, _ref) {\n var _this = this;\n\n var _onStart = _ref.onStart,\n _onMove = _ref.onMove,\n onEnd = _ref.onEnd,\n isSliderEnabled = _ref.isSliderEnabled;\n\n _classCallCheck(this, Slider);\n\n _defineProperty(this, \"startHandler\", function (eStart) {\n if (!_this.isSliderEnabled()) return;\n\n _this.removeListeners();\n\n eStart.preventDefault();\n var moveHandler = _this.moveHandler,\n endHandler = _this.endHandler,\n onStart = _this.onStart;\n var isTouchEvent = eStart.type === 'touchstart';\n _this.touchMoveEvent = isTouchEvent ? 'touchmove' : 'mousemove';\n _this.touchEndEvent = isTouchEvent ? 'touchend' : 'mouseup';\n _this.sx = isTouchEvent ? eStart.touches[0].clientX : eStart.clientX;\n _this.sy = isTouchEvent ? eStart.touches[0].clientY : eStart.clientY;\n onStart(eStart, {\n x: _this.sx,\n y: _this.sy\n }); // add listeners\n\n document.addEventListener(_this.touchMoveEvent, moveHandler);\n document.addEventListener(_this.touchEndEvent, endHandler);\n /*\n add end handler in context menu as well.\n As mouseup event is not trigger on context menu open\n https://bugs.chromium.org/p/chromium/issues/detail?id=506801\n */\n\n document.addEventListener('contextmenu', endHandler);\n });\n\n _defineProperty(this, \"moveHandler\", function (eMove) {\n if (!_this.isSliderEnabled()) return;\n eMove.preventDefault();\n var sx = _this.sx,\n sy = _this.sy,\n onMove = _this.onMove;\n var isTouchEvent = _this.touchMoveEvent === 'touchmove'; // get the coordinates\n\n var mx = isTouchEvent ? eMove.touches[0].clientX : eMove.clientX;\n var my = isTouchEvent ? eMove.touches[0].clientY : eMove.clientY;\n onMove(eMove, {\n dx: mx - sx,\n dy: my - sy,\n mx: mx,\n my: my\n });\n });\n\n _defineProperty(this, \"endHandler\", function () {\n if (!_this.isSliderEnabled()) return;\n\n _this.removeListeners();\n\n _this.onEnd();\n });\n\n this.container = container;\n this.isSliderEnabled = isSliderEnabled;\n this.onStart = _onStart || _util.noop;\n this.onMove = _onMove || _util.noop;\n this.onEnd = onEnd || _util.noop;\n }\n\n _createClass(Slider, [{\n key: \"removeListeners\",\n // remove previous events if its not removed\n // - Case when while sliding mouse moved out of document and released there\n value: function removeListeners() {\n if (!this.touchMoveEvent) return;\n document.removeEventListener(this.touchMoveEvent, this.moveHandler);\n document.removeEventListener(this.touchEndEvent, this.endHandler);\n document.removeEventListener('contextmenu', this.endHandler);\n }\n }, {\n key: \"init\",\n value: function init() {\n var _this2 = this;\n\n ['touchstart', 'mousedown'].forEach(function (evt) {\n _this2.container.addEventListener(evt, _this2.startHandler);\n });\n }\n }, {\n key: \"destroy\",\n value: function destroy() {\n var _this3 = this;\n\n ['touchstart', 'mousedown'].forEach(function (evt) {\n _this3.container.removeEventListener(evt, _this3.startHandler);\n });\n this.removeListeners();\n }\n }]);\n\n return Slider;\n}();\n\nvar _default = Slider;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _util = require(\"./util\");\n\nvar _Slider = _interopRequireDefault(require(\"./Slider\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }\n\nfunction _nonIterableRest() { throw new TypeError(\"Invalid attempt to destructure non-iterable instance\"); }\n\nfunction _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i[\"return\"] != null) _i[\"return\"](); } finally { if (_d) throw _e; } } return _arr; }\n\nfunction _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar imageViewHtml = \"\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\";\n\nvar ImageViewer =\n/*#__PURE__*/\nfunction () {\n function ImageViewer(element) {\n var _this = this;\n\n var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};\n\n _classCallCheck(this, ImageViewer);\n\n _defineProperty(this, \"zoom\", function (perc, point) {\n var _options = _this._options,\n _elements = _this._elements,\n _state = _this._state;\n var curPerc = _state.zoomValue,\n imageDim = _state.imageDim,\n containerDim = _state.containerDim,\n zoomSliderLength = _state.zoomSliderLength;\n var image = _elements.image,\n zoomHandle = _elements.zoomHandle;\n var maxZoom = _options.maxZoom;\n perc = Math.round(Math.max(100, perc));\n perc = Math.min(maxZoom, perc);\n point = point || {\n x: containerDim.w / 2,\n y: containerDim.h / 2\n };\n var curLeft = parseFloat((0, _util.css)(image, 'left'));\n var curTop = parseFloat((0, _util.css)(image, 'top')); // clear any panning frames\n\n _this._clearFrames();\n\n var step = 0;\n var baseLeft = (containerDim.w - imageDim.w) / 2;\n var baseTop = (containerDim.h - imageDim.h) / 2;\n var baseRight = containerDim.w - baseLeft;\n var baseBottom = containerDim.h - baseTop;\n\n var zoom = function zoom() {\n step++;\n\n if (step < 16) {\n _this._frames.zoomFrame = requestAnimationFrame(zoom);\n }\n\n var tickZoom = (0, _util.easeOutQuart)(step, curPerc, perc - curPerc, 16);\n var ratio = tickZoom / curPerc;\n var imgWidth = imageDim.w * tickZoom / 100;\n var imgHeight = imageDim.h * tickZoom / 100;\n var newLeft = -((point.x - curLeft) * ratio - point.x);\n var newTop = -((point.y - curTop) * ratio - point.y); // fix for left and top\n\n newLeft = Math.min(newLeft, baseLeft);\n newTop = Math.min(newTop, baseTop); // fix for right and bottom\n\n if (newLeft + imgWidth < baseRight) {\n newLeft = baseRight - imgWidth; // newLeft - (newLeft + imgWidth - baseRight)\n }\n\n if (newTop + imgHeight < baseBottom) {\n newTop = baseBottom - imgHeight; // newTop + (newTop + imgHeight - baseBottom)\n }\n\n (0, _util.css)(image, {\n height: \"\".concat(imgHeight, \"px\"),\n width: \"\".concat(imgWidth, \"px\"),\n left: \"\".concat(newLeft, \"px\"),\n top: \"\".concat(newTop, \"px\")\n });\n _this._state.zoomValue = tickZoom;\n\n _this._resizeSnapHandle(imgWidth, imgHeight, newLeft, newTop); // update zoom handle position\n\n\n (0, _util.css)(zoomHandle, {\n left: \"\".concat((tickZoom - 100) * zoomSliderLength / (maxZoom - 100), \"px\")\n });\n };\n\n zoom();\n });\n\n _defineProperty(this, \"_clearFrames\", function () {\n var _this$_frames = _this._frames,\n slideMomentumCheck = _this$_frames.slideMomentumCheck,\n sliderMomentumFrame = _this$_frames.sliderMomentumFrame,\n zoomFrame = _this$_frames.zoomFrame;\n clearInterval(slideMomentumCheck);\n cancelAnimationFrame(sliderMomentumFrame);\n cancelAnimationFrame(zoomFrame);\n });\n\n _defineProperty(this, \"_resizeSnapHandle\", function (imgWidth, imgHeight, imgLeft, imgTop) {\n var _elements = _this._elements,\n _state = _this._state;\n var snapHandle = _elements.snapHandle,\n image = _elements.image;\n var imageDim = _state.imageDim,\n containerDim = _state.containerDim,\n zoomValue = _state.zoomValue,\n snapImageDim = _state.snapImageDim;\n var imageWidth = imgWidth || imageDim.w * zoomValue / 100;\n var imageHeight = imgHeight || imageDim.h * zoomValue / 100;\n var imageLeft = imgLeft || parseFloat((0, _util.css)(image, 'left'));\n var imageTop = imgTop || parseFloat((0, _util.css)(image, 'top'));\n var left = -imageLeft * snapImageDim.w / imageWidth;\n var top = -imageTop * snapImageDim.h / imageHeight;\n var handleWidth = containerDim.w * snapImageDim.w / imageWidth;\n var handleHeight = containerDim.h * snapImageDim.h / imageHeight;\n (0, _util.css)(snapHandle, {\n top: \"\".concat(top, \"px\"),\n left: \"\".concat(left, \"px\"),\n width: \"\".concat(handleWidth, \"px\"),\n height: \"\".concat(handleHeight, \"px\")\n });\n _this._state.snapHandleDim = {\n w: handleWidth,\n h: handleHeight\n };\n });\n\n _defineProperty(this, \"showSnapView\", function (noTimeout) {\n var _this$_state = _this._state,\n snapViewVisible = _this$_state.snapViewVisible,\n zoomValue = _this$_state.zoomValue,\n loaded = _this$_state.loaded;\n var snapView = _this._elements.snapView;\n if (!_this._options.snapView) return;\n if (snapViewVisible || zoomValue <= 100 || !loaded) return;\n clearTimeout(_this._frames.snapViewTimeout);\n _this._state.snapViewVisible = true;\n (0, _util.css)(snapView, {\n opacity: 1,\n pointerEvents: 'inherit'\n });\n\n if (!noTimeout) {\n _this._frames.snapViewTimeout = setTimeout(_this.hideSnapView, 1500);\n }\n });\n\n _defineProperty(this, \"hideSnapView\", function () {\n var snapView = _this._elements.snapView;\n (0, _util.css)(snapView, {\n opacity: 0,\n pointerEvents: 'none'\n });\n _this._state.snapViewVisible = false;\n });\n\n _defineProperty(this, \"refresh\", function () {\n _this._calculateDimensions();\n\n _this.resetZoom();\n });\n\n var _this$_findContainerA = this._findContainerAndImageSrc(element, options),\n container = _this$_findContainerA.container,\n domElement = _this$_findContainerA.domElement,\n imageSrc = _this$_findContainerA.imageSrc,\n hiResImageSrc = _this$_findContainerA.hiResImageSrc; // containers for elements\n\n\n this._elements = {\n container: container,\n domElement: domElement\n };\n this._options = _objectSpread({}, ImageViewer.defaults, options); // container for all events\n\n this._events = {}; // container for all timeout and frames\n\n this._frames = {}; // container for all sliders\n\n this._sliders = {}; // maintain current state\n\n this._state = {\n zoomValue: this._options.zoomValue\n };\n this._images = {\n imageSrc: imageSrc,\n hiResImageSrc: hiResImageSrc\n };\n\n this._init();\n\n if (imageSrc) {\n this._loadImages();\n } // store reference of imageViewer in domElement\n\n\n domElement._imageViewer = this;\n }\n\n _createClass(ImageViewer, [{\n key: \"_findContainerAndImageSrc\",\n value: function _findContainerAndImageSrc(element) {\n var domElement = element;\n var imageSrc, hiResImageSrc;\n\n if (typeof element === 'string') {\n domElement = document.querySelector(element);\n } // throw error if imageViewer is already assigned\n\n\n if (domElement._imageViewer) {\n throw new Error('An image viewer is already being initiated on the element.');\n }\n\n var container = element;\n\n if (domElement.tagName === 'IMG') {\n imageSrc = domElement.src;\n hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src'); // wrap the image with iv-container div\n\n container = (0, _util.wrap)(domElement, {\n className: 'iv-container iv-image-mode',\n style: {\n display: 'inline-block',\n overflow: 'hidden'\n }\n }); // hide the image and add iv-original-img class\n\n (0, _util.css)(domElement, {\n opacity: 0,\n position: 'relative',\n zIndex: -1\n });\n } else {\n imageSrc = domElement.getAttribute('src') || domElement.getAttribute('data-src');\n hiResImageSrc = domElement.getAttribute('high-res-src') || domElement.getAttribute('data-high-res-src');\n }\n\n return {\n container: container,\n domElement: domElement,\n imageSrc: imageSrc,\n hiResImageSrc: hiResImageSrc\n };\n }\n }, {\n key: \"_init\",\n value: function _init() {\n // initialize the dom elements\n this._initDom(); // initialize slider\n\n\n this._initImageSlider();\n\n this._initSnapSlider();\n\n this._initZoomSlider(); // enable pinch and zoom feature for touch screens\n\n\n this._pinchAndZoom(); // enable scroll zoom interaction\n\n\n this._scrollZoom(); // enable double tap to zoom interaction\n\n\n this._doubleTapToZoom(); // initialize events\n\n\n this._initEvents();\n }\n }, {\n key: \"_initDom\",\n value: function _initDom() {\n var container = this._elements.container; // add image-viewer layout elements\n\n (0, _util.createElement)({\n tagName: 'div',\n className: 'iv-wrap',\n html: imageViewHtml,\n parent: container\n }); // add container class on the container\n\n (0, _util.addClass)(container, 'iv-container'); // if the element is static position, position it relatively\n\n if ((0, _util.css)(container, 'position') === 'static') {\n (0, _util.css)(container, {\n position: 'relative'\n });\n } // save references for later use\n\n\n this._elements = _objectSpread({}, this._elements, {\n snapView: container.querySelector('.iv-snap-view'),\n snapImageWrap: container.querySelector('.iv-snap-image-wrap'),\n imageWrap: container.querySelector('.iv-image-wrap'),\n snapHandle: container.querySelector('.iv-snap-handle'),\n zoomHandle: container.querySelector('.iv-zoom-handle')\n });\n }\n }, {\n key: \"_initImageSlider\",\n value: function _initImageSlider() {\n var _this2 = this;\n\n var _elements = this._elements;\n var imageWrap = _elements.imageWrap;\n var positions, currentPos;\n /* Add slide interaction to image */\n\n var imageSlider = new _Slider.default(imageWrap, {\n isSliderEnabled: function isSliderEnabled() {\n var _this2$_state = _this2._state,\n loaded = _this2$_state.loaded,\n zooming = _this2$_state.zooming,\n zoomValue = _this2$_state.zoomValue;\n return loaded && !zooming && zoomValue > 100;\n },\n onStart: function onStart(e, position) {\n var snapSlider = _this2._sliders.snapSlider; // clear all animation frame and interval\n\n _this2._clearFrames();\n\n snapSlider.onStart(); // reset positions\n\n positions = [position, position];\n currentPos = undefined;\n _this2._frames.slideMomentumCheck = setInterval(function () {\n if (!currentPos) return;\n positions.shift();\n positions.push({\n x: currentPos.mx,\n y: currentPos.my\n });\n }, 50);\n },\n onMove: function onMove(e, position) {\n var snapImageDim = _this2._state.snapImageDim;\n var snapSlider = _this2._sliders.snapSlider;\n\n var imageCurrentDim = _this2._getImageCurrentDim();\n\n currentPos = position;\n snapSlider.onMove(e, {\n dx: -position.dx * snapImageDim.w / imageCurrentDim.w,\n dy: -position.dy * snapImageDim.h / imageCurrentDim.h\n });\n },\n onEnd: function onEnd() {\n var snapImageDim = _this2._state.snapImageDim;\n var snapSlider = _this2._sliders.snapSlider;\n\n var imageCurrentDim = _this2._getImageCurrentDim(); // clear all animation frame and interval\n\n\n _this2._clearFrames();\n\n var step, positionX, positionY;\n var xDiff = positions[1].x - positions[0].x;\n var yDiff = positions[1].y - positions[0].y;\n\n var momentum = function momentum() {\n if (step <= 60) {\n _this2._frames.sliderMomentumFrame = requestAnimationFrame(momentum);\n }\n\n positionX += (0, _util.easeOutQuart)(step, xDiff / 3, -xDiff / 3, 60);\n positionY += (0, _util.easeOutQuart)(step, yDiff / 3, -yDiff / 3, 60);\n snapSlider.onMove(null, {\n dx: -(positionX * snapImageDim.w / imageCurrentDim.w),\n dy: -(positionY * snapImageDim.h / imageCurrentDim.h)\n });\n step++;\n };\n\n if (Math.abs(xDiff) > 30 || Math.abs(yDiff) > 30) {\n step = 1;\n positionX = currentPos.dx;\n positionY = currentPos.dy;\n momentum();\n }\n }\n });\n imageSlider.init();\n this._sliders.imageSlider = imageSlider;\n }\n }, {\n key: \"_initSnapSlider\",\n value: function _initSnapSlider() {\n var _this3 = this;\n\n var snapHandle = this._elements.snapHandle;\n var startHandleTop, startHandleLeft;\n var snapSlider = new _Slider.default(snapHandle, {\n isSliderEnabled: function isSliderEnabled() {\n return _this3._state.loaded;\n },\n onStart: function onStart() {\n var _this3$_frames = _this3._frames,\n slideMomentumCheck = _this3$_frames.slideMomentumCheck,\n sliderMomentumFrame = _this3$_frames.sliderMomentumFrame;\n startHandleTop = parseFloat((0, _util.css)(snapHandle, 'top'));\n startHandleLeft = parseFloat((0, _util.css)(snapHandle, 'left')); // stop momentum on image\n\n clearInterval(slideMomentumCheck);\n cancelAnimationFrame(sliderMomentumFrame);\n },\n onMove: function onMove(e, position) {\n var _this3$_state = _this3._state,\n snapHandleDim = _this3$_state.snapHandleDim,\n snapImageDim = _this3$_state.snapImageDim;\n var image = _this3._elements.image;\n\n var imageCurrentDim = _this3._getImageCurrentDim(); // find handle left and top and make sure they lay between the snap image\n\n\n var maxLeft = Math.max(snapImageDim.w - snapHandleDim.w, startHandleLeft);\n var maxTop = Math.max(snapImageDim.h - snapHandleDim.h, startHandleTop);\n var minLeft = Math.min(0, startHandleLeft);\n var minTop = Math.min(0, startHandleTop);\n var left = (0, _util.clamp)(startHandleLeft + position.dx, minLeft, maxLeft);\n var top = (0, _util.clamp)(startHandleTop + position.dy, minTop, maxTop);\n var imgLeft = -left * imageCurrentDim.w / snapImageDim.w;\n var imgTop = -top * imageCurrentDim.h / snapImageDim.h;\n (0, _util.css)(snapHandle, {\n left: \"\".concat(left, \"px\"),\n top: \"\".concat(top, \"px\")\n });\n (0, _util.css)(image, {\n left: \"\".concat(imgLeft, \"px\"),\n top: \"\".concat(imgTop, \"px\")\n });\n }\n });\n snapSlider.init();\n this._sliders.snapSlider = snapSlider;\n }\n }, {\n key: \"_initZoomSlider\",\n value: function _initZoomSlider() {\n var _this4 = this;\n\n var _this$_elements = this._elements,\n snapView = _this$_elements.snapView,\n zoomHandle = _this$_elements.zoomHandle; // zoom in zoom out using zoom handle\n\n var sliderElm = snapView.querySelector('.iv-zoom-slider');\n var leftOffset, handleWidth; // on zoom slider we have to follow the mouse and set the handle to its position.\n\n var zoomSlider = new _Slider.default(sliderElm, {\n isSliderEnabled: function isSliderEnabled() {\n return _this4._state.loaded;\n },\n onStart: function onStart(eStart) {\n var slider = _this4._sliders.zoomSlider;\n leftOffset = sliderElm.getBoundingClientRect().left + document.body.scrollLeft;\n handleWidth = parseInt((0, _util.css)(zoomHandle, 'width'), 10); // move the handle to current mouse position\n\n slider.onMove(eStart);\n },\n onMove: function onMove(e) {\n var maxZoom = _this4._options.maxZoom;\n var zoomSliderLength = _this4._state.zoomSliderLength;\n var pageX = e.pageX !== undefined ? e.pageX : e.touches[0].pageX;\n var newLeft = (0, _util.clamp)(pageX - leftOffset - handleWidth / 2, 0, zoomSliderLength);\n var zoomValue = 100 + (maxZoom - 100) * newLeft / zoomSliderLength;\n\n _this4.zoom(zoomValue);\n }\n });\n zoomSlider.init();\n this._sliders.zoomSlider = zoomSlider;\n }\n }, {\n key: \"_initEvents\",\n value: function _initEvents() {\n this._snapViewEvents(); // handle window resize\n\n\n if (this._options.refreshOnResize) {\n this._events.onWindowResize = (0, _util.assignEvent)(window, 'resize', this.refresh);\n }\n }\n }, {\n key: \"_snapViewEvents\",\n value: function _snapViewEvents() {\n var _this5 = this;\n\n var _this$_elements2 = this._elements,\n imageWrap = _this$_elements2.imageWrap,\n snapView = _this$_elements2.snapView; // show snapView on mouse move\n\n this._events.snapViewOnMouseMove = (0, _util.assignEvent)(imageWrap, ['touchmove', 'mousemove'], function () {\n _this5.showSnapView();\n }); // keep showing snapView if on hover over it without any timeout\n\n this._events.mouseEnterSnapView = (0, _util.assignEvent)(snapView, ['mouseenter', 'touchstart'], function () {\n _this5._state.snapViewVisible = false;\n\n _this5.showSnapView(true);\n }); // on mouse leave set timeout to hide snapView\n\n this._events.mouseLeaveSnapView = (0, _util.assignEvent)(snapView, ['mouseleave', 'touchend'], function () {\n _this5._state.snapViewVisible = false;\n\n _this5.showSnapView();\n });\n }\n }, {\n key: \"_pinchAndZoom\",\n value: function _pinchAndZoom() {\n var _this6 = this;\n\n var _this$_elements3 = this._elements,\n imageWrap = _this$_elements3.imageWrap,\n container = _this$_elements3.container; // apply pinch and zoom feature\n\n var onPinchStart = function onPinchStart(eStart) {\n var _this6$_state = _this6._state,\n loaded = _this6$_state.loaded,\n startZoomValue = _this6$_state.zoomValue;\n var events = _this6._events;\n if (!loaded) return;\n var touch0 = eStart.touches[0];\n var touch1 = eStart.touches[1];\n\n if (!(touch0 && touch1)) {\n return;\n }\n\n _this6._state.zooming = true;\n var contOffset = container.getBoundingClientRect(); // find distance between two touch points\n\n var startDist = (0, _util.getTouchPointsDistance)(eStart.touches); // find the center for the zoom\n\n var center = {\n x: (touch1.pageX + touch0.pageX) / 2 - (contOffset.left + document.body.scrollLeft),\n y: (touch1.pageY + touch0.pageY) / 2 - (contOffset.top + document.body.scrollTop)\n };\n\n var moveListener = function moveListener(eMove) {\n // eMove.preventDefault();\n var newDist = (0, _util.getTouchPointsDistance)(eMove.touches);\n var zoomValue = startZoomValue + (newDist - startDist) / 2;\n\n _this6.zoom(zoomValue, center);\n };\n\n var endListener = function endListener() {\n // unbind events\n events.pinchMove();\n events.pinchEnd();\n _this6._state.zooming = false;\n }; // remove events if already assigned\n\n\n if (events.pinchMove) events.pinchMove();\n if (events.pinchEnd) events.pinchEnd(); // assign events\n\n events.pinchMove = (0, _util.assignEvent)(document, 'touchmove', moveListener);\n events.pinchEnd = (0, _util.assignEvent)(document, 'touchend', endListener);\n };\n\n this._events.pinchStart = (0, _util.assignEvent)(imageWrap, 'touchstart', onPinchStart);\n }\n }, {\n key: \"_scrollZoom\",\n value: function _scrollZoom() {\n var _this7 = this;\n\n /* Add zoom interaction in mouse wheel */\n var _options = this._options;\n var _this$_elements4 = this._elements,\n container = _this$_elements4.container,\n imageWrap = _this$_elements4.imageWrap;\n var changedDelta = 0;\n\n var onMouseWheel = function onMouseWheel(e) {\n var _this7$_state = _this7._state,\n loaded = _this7$_state.loaded,\n zoomValue = _this7$_state.zoomValue;\n if (!_options.zoomOnMouseWheel || !loaded) return; // clear all animation frame and interval\n\n _this7._clearFrames(); // cross-browser wheel delta\n\n\n var delta = Math.max(-1, Math.min(1, e.wheelDelta || -e.detail || -e.deltaY));\n var newZoomValue = zoomValue * (100 + delta * _util.ZOOM_CONSTANT) / 100;\n\n if (!(newZoomValue >= 100 && newZoomValue <= _options.maxZoom)) {\n changedDelta += Math.abs(delta);\n } else {\n changedDelta = 0;\n }\n\n e.preventDefault();\n if (changedDelta > _util.MOUSE_WHEEL_COUNT) return;\n var contOffset = container.getBoundingClientRect();\n var x = (e.pageX || e.pageX) - (contOffset.left + document.body.scrollLeft);\n var y = (e.pageY || e.pageY) - (contOffset.top + document.body.scrollTop);\n\n _this7.zoom(newZoomValue, {\n x: x,\n y: y\n }); // show the snap viewer\n\n\n _this7.showSnapView();\n };\n\n this._ev = (0, _util.assignEvent)(imageWrap, 'wheel', onMouseWheel);\n }\n }, {\n key: \"_doubleTapToZoom\",\n value: function _doubleTapToZoom() {\n var _this8 = this;\n\n var imageWrap = this._elements.imageWrap; // handle double tap for zoom in and zoom out\n\n var touchTime = 0;\n var point;\n\n var onDoubleTap = function onDoubleTap(e) {\n if (touchTime === 0) {\n touchTime = Date.now();\n point = {\n x: e.pageX,\n y: e.pageY\n };\n } else if (Date.now() - touchTime < 500 && Math.abs(e.pageX - point.x) < 50 && Math.abs(e.pageY - point.y) < 50) {\n if (_this8._state.zoomValue === _this8._options.zoomValue) {\n _this8.zoom(200);\n } else {\n _this8.resetZoom();\n }\n\n touchTime = 0;\n } else {\n touchTime = 0;\n }\n };\n\n (0, _util.assignEvent)(imageWrap, 'click', onDoubleTap);\n }\n }, {\n key: \"_getImageCurrentDim\",\n value: function _getImageCurrentDim() {\n var _this$_state2 = this._state,\n zoomValue = _this$_state2.zoomValue,\n imageDim = _this$_state2.imageDim;\n return {\n w: imageDim.w * (zoomValue / 100),\n h: imageDim.h * (zoomValue / 100)\n };\n }\n }, {\n key: \"_loadImages\",\n value: function _loadImages() {\n var _this9 = this;\n\n var _images = this._images,\n _elements = this._elements;\n var imageSrc = _images.imageSrc,\n hiResImageSrc = _images.hiResImageSrc;\n var container = _elements.container,\n snapImageWrap = _elements.snapImageWrap,\n imageWrap = _elements.imageWrap;\n var ivLoader = container.querySelector('.iv-loader'); // remove old images\n\n (0, _util.remove)(container.querySelectorAll('.iv-snap-image, .iv-image')); // add snapView image\n\n var snapImage = (0, _util.createElement)({\n tagName: 'img',\n className: 'iv-snap-image',\n src: imageSrc,\n insertBefore: snapImageWrap.firstChild,\n parent: snapImageWrap\n }); // add image\n\n var image = (0, _util.createElement)({\n tagName: 'img',\n className: 'iv-image iv-small-image',\n src: imageSrc,\n parent: imageWrap\n });\n this._state.loaded = false; // store image reference in _elements\n\n this._elements.image = image;\n this._elements.snapImage = snapImage;\n (0, _util.css)(ivLoader, {\n display: 'block'\n }); // keep visibility hidden until image is loaded\n\n (0, _util.css)(image, {\n visibility: 'hidden'\n }); // hide snap view if open\n\n this.hideSnapView();\n\n var onImageLoad = function onImageLoad() {\n // hide the iv loader\n (0, _util.css)(ivLoader, {\n display: 'none'\n }); // show the image\n\n (0, _util.css)(image, {\n visibility: 'visible'\n }); // load high resolution image if provided\n\n if (hiResImageSrc) {\n _this9._loadHighResImage(hiResImageSrc);\n } // set loaded flag to true\n\n\n _this9._state.loaded = true; // calculate the dimension\n\n _this9._calculateDimensions(); // reset the zoom\n\n\n _this9.resetZoom();\n };\n\n if ((0, _util.imageLoaded)(image)) {\n onImageLoad();\n } else {\n this._events.imageLoad = (0, _util.assignEvent)(image, 'load', onImageLoad);\n }\n }\n }, {\n key: \"_loadHighResImage\",\n value: function _loadHighResImage(hiResImageSrc) {\n var _this10 = this;\n\n var _this$_elements5 = this._elements,\n imageWrap = _this$_elements5.imageWrap,\n container = _this$_elements5.container;\n var lowResImg = this._elements.image;\n var hiResImage = (0, _util.createElement)({\n tagName: 'img',\n className: 'iv-image iv-large-image',\n src: hiResImageSrc,\n parent: imageWrap,\n style: lowResImg.style.cssText\n }); // add all the style attributes from lowResImg to highResImg\n\n hiResImage.style.cssText = lowResImg.style.cssText;\n this._elements.image = container.querySelectorAll('.iv-image');\n\n var onHighResImageLoad = function onHighResImageLoad() {\n // remove the low size image and set this image as default image\n (0, _util.remove)(lowResImg);\n _this10._elements.image = hiResImage; // this._calculateDimensions();\n };\n\n if ((0, _util.imageLoaded)(hiResImage)) {\n onHighResImageLoad();\n } else {\n this._events.hiResImageLoad = (0, _util.assignEvent)(hiResImage, 'load', onHighResImageLoad);\n }\n }\n }, {\n key: \"_calculateDimensions\",\n value: function _calculateDimensions() {\n var _this$_elements6 = this._elements,\n image = _this$_elements6.image,\n container = _this$_elements6.container,\n snapView = _this$_elements6.snapView,\n snapImage = _this$_elements6.snapImage,\n zoomHandle = _this$_elements6.zoomHandle; // calculate content width of image and snap image\n\n var imageWidth = parseInt((0, _util.css)(image, 'width'), 10);\n var imageHeight = parseInt((0, _util.css)(image, 'height'), 10);\n var contWidth = parseInt((0, _util.css)(container, 'width'), 10);\n var contHeight = parseInt((0, _util.css)(container, 'height'), 10);\n var snapViewWidth = snapView.clientWidth;\n var snapViewHeight = snapView.clientHeight; // set the container dimension\n\n this._state.containerDim = {\n w: contWidth,\n h: contHeight\n }; // set the image dimension\n\n var imgWidth;\n var imgHeight;\n var ratio = imageWidth / imageHeight;\n imgWidth = imageWidth > imageHeight && contHeight >= contWidth || ratio * contHeight > contWidth ? contWidth : ratio * contHeight;\n imgHeight = imgWidth / ratio;\n this._state.imageDim = {\n w: imgWidth,\n h: imgHeight\n }; // reset image position and zoom\n\n (0, _util.css)(image, {\n width: \"\".concat(imgWidth, \"px\"),\n height: \"\".concat(imgHeight, \"px\"),\n left: \"\".concat((contWidth - imgWidth) / 2, \"px\"),\n top: \"\".concat((contHeight - imgHeight) / 2, \"px\"),\n maxWidth: 'none',\n maxHeight: 'none'\n }); // set the snap Image dimension\n\n var snapWidth = imgWidth > imgHeight ? snapViewWidth : imgWidth * snapViewHeight / imgHeight;\n var snapHeight = imgHeight > imgWidth ? snapViewHeight : imgHeight * snapViewWidth / imgWidth;\n this._state.snapImageDim = {\n w: snapWidth,\n h: snapHeight\n };\n (0, _util.css)(snapImage, {\n width: \"\".concat(snapWidth, \"px\"),\n height: \"\".concat(snapHeight, \"px\")\n }); // calculate zoom slider area\n\n this._state.zoomSliderLength = snapViewWidth - zoomHandle.offsetWidth;\n }\n }, {\n key: \"resetZoom\",\n value: function resetZoom() {\n var animate = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;\n var zoomValue = this._options.zoomValue;\n\n if (!animate) {\n this._state.zoomValue = zoomValue;\n }\n\n this.zoom(zoomValue);\n }\n }, {\n key: \"load\",\n value: function load(imageSrc, hiResImageSrc) {\n this._images = {\n imageSrc: imageSrc,\n hiResImageSrc: hiResImageSrc\n };\n\n this._loadImages();\n }\n }, {\n key: \"destroy\",\n value: function destroy() {\n var _this$_elements7 = this._elements,\n container = _this$_elements7.container,\n domElement = _this$_elements7.domElement; // destroy all the sliders\n\n Object.entries(this._sliders).forEach(function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n key = _ref2[0],\n slider = _ref2[1];\n\n slider.destroy();\n }); // unbind all events\n\n Object.entries(this._events).forEach(function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n key = _ref4[0],\n unbindEvent = _ref4[1];\n\n unbindEvent();\n }); // clear all the frames\n\n this._clearFrames(); // remove html from the container\n\n\n (0, _util.remove)(container.querySelector('.iv-wrap')); // remove iv-container class from container\n\n (0, _util.removeClass)(container, 'iv-container'); // remove added style from container\n\n (0, _util.removeCss)(document.querySelector('html'), 'relative'); // if container has original image, unwrap the image and remove the class\n // which will happen when domElement is not the container\n\n if (domElement !== container) {\n (0, _util.unwrap)(domElement);\n } // remove imageViewer reference from dom element\n\n\n domElement._imageViewer = null;\n }\n }]);\n\n return ImageViewer;\n}();\n\nImageViewer.defaults = {\n zoomValue: 100,\n snapView: true,\n maxZoom: 500,\n refreshOnResize: true,\n zoomOnMouseWheel: true\n};\nvar _default = ImageViewer;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.default = void 0;\n\nvar _util = require(\"./util\");\n\nvar _ImageViewer2 = _interopRequireDefault(require(\"./ImageViewer\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nfunction _typeof(obj) { if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === \"function\" && obj.constructor === Symbol && obj !== Symbol.prototype ? \"symbol\" : typeof obj; }; } return _typeof(obj); }\n\nfunction _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\nfunction _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === \"object\" || typeof call === \"function\")) { return call; } return _assertThisInitialized(self); }\n\nfunction _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError(\"this hasn't been initialised - super() hasn't been called\"); } return self; }\n\nfunction _get(target, property, receiver) { if (typeof Reflect !== \"undefined\" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); }\n\nfunction _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }\n\nfunction _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }\n\nfunction _inherits(subClass, superClass) { if (typeof superClass !== \"function\" && superClass !== null) { throw new TypeError(\"Super expression must either be null or a function\"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }\n\nfunction _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }\n\nfunction _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }\n\nvar fullScreenHtml = \"\\n
\\n
\\n\";\n\nvar FullScreenViewer =\n/*#__PURE__*/\nfunction (_ImageViewer) {\n _inherits(FullScreenViewer, _ImageViewer);\n\n function FullScreenViewer() {\n var _this;\n\n var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};\n\n _classCallCheck(this, FullScreenViewer);\n\n var fullScreenElem = (0, _util.createElement)({\n tagName: 'div',\n className: 'iv-fullscreen',\n html: fullScreenHtml,\n parent: document.body\n });\n var container = fullScreenElem.querySelector('.iv-fullscreen-container'); // call the ImageViewer constructor\n\n _this = _possibleConstructorReturn(this, _getPrototypeOf(FullScreenViewer).call(this, container, _objectSpread({}, options, {\n refreshOnResize: false\n }))); // add fullScreenElem on element list\n\n _defineProperty(_assertThisInitialized(_this), \"hide\", function () {\n // hide the fullscreen\n (0, _util.css)(_this._elements.fullScreen, {\n display: 'none'\n }); // enable scroll\n\n (0, _util.removeCss)(document.querySelector('html'), 'overflow'); // remove window event\n\n _this._events.onWindowResize();\n });\n\n _this._elements.fullScreen = fullScreenElem;\n\n _this._initFullScreenEvents();\n\n return _this;\n }\n\n _createClass(FullScreenViewer, [{\n key: \"_initFullScreenEvents\",\n value: function _initFullScreenEvents() {\n var fullScreen = this._elements.fullScreen;\n var closeBtn = fullScreen.querySelector('.iv-fullscreen-close'); // add close button event\n\n this._events.onCloseBtnClick = (0, _util.assignEvent)(closeBtn, 'click', this.hide);\n }\n }, {\n key: \"show\",\n value: function show(imageSrc, hiResImageSrc) {\n // show the element\n (0, _util.css)(this._elements.fullScreen, {\n display: 'block'\n }); // if image source is provide load image source\n\n if (imageSrc) {\n this.load(imageSrc, hiResImageSrc);\n } // handle window resize\n\n\n this._events.onWindowResize = (0, _util.assignEvent)(window, 'resize', this.refresh); // disable scroll on html\n\n (0, _util.css)(document.querySelector('html'), {\n overflow: 'hidden'\n });\n }\n }, {\n key: \"destroy\",\n value: function destroy() {\n var fullScreen = this._elements.fullScreen; // destroy image viewer\n\n _get(_getPrototypeOf(FullScreenViewer.prototype), \"destroy\", this).call(this); // remove the element\n\n\n (0, _util.remove)(fullScreen);\n }\n }]);\n\n return FullScreenViewer;\n}(_ImageViewer2.default);\n\nvar _default = FullScreenViewer;\nexports.default = _default;","\"use strict\";\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nObject.defineProperty(exports, \"ImageViewer\", {\n enumerable: true,\n get: function get() {\n return _ImageViewer.default;\n }\n});\nObject.defineProperty(exports, \"FullScreenViewer\", {\n enumerable: true,\n get: function get() {\n return _FullScreen.default;\n }\n});\nexports.default = void 0;\n\nvar _ImageViewer = _interopRequireDefault(require(\"./ImageViewer\"));\n\nvar _FullScreen = _interopRequireDefault(require(\"./FullScreen\"));\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\nvar _default = _ImageViewer.default;\nexports.default = _default;","\r\n\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
\r\n
Media
\r\n
\r\n
\r\n {@html shareAlt.md}\r\n {#if modal.back}\r\n \r\n {/if}\r\n \r\n
\r\n
\r\n
\r\n
\r\n {#if modal.media.type == \"image\"}\r\n
\r\n {:else if modal.media.type == \"360\"}\r\n
\r\n {:else if modal.media.type == \"pdf\" || modal.media.type == \"link\"}\r\n
\r\n