UNPKG

digipin-reactjs

Version:

React hooks and components for integrating DIGIPIN (Indian Postal Digital PIN) geocoding into React apps. Includes hooks, prebuilt UI, and helpers for seamless integration.

1,625 lines (1,538 loc) 81.2 kB
'use strict'; var require$$0 = require('react'); var jsxRuntime = {exports: {}}; var reactJsxRuntime_production = {}; /** * @license React * react-jsx-runtime.production.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_production; function requireReactJsxRuntime_production () { if (hasRequiredReactJsxRuntime_production) return reactJsxRuntime_production; hasRequiredReactJsxRuntime_production = 1; var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"); function jsxProd(type, config, maybeKey) { var key = null; void 0 !== maybeKey && (key = "" + maybeKey); void 0 !== config.key && (key = "" + config.key); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; config = maybeKey.ref; return { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: void 0 !== config ? config : null, props: maybeKey }; } reactJsxRuntime_production.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_production.jsx = jsxProd; reactJsxRuntime_production.jsxs = jsxProd; return reactJsxRuntime_production; } var reactJsxRuntime_development = {}; /** * @license React * react-jsx-runtime.development.js * * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var hasRequiredReactJsxRuntime_development; function requireReactJsxRuntime_development () { if (hasRequiredReactJsxRuntime_development) return reactJsxRuntime_development; hasRequiredReactJsxRuntime_development = 1; "production" !== process.env.NODE_ENV && (function () { function getComponentNameFromType(type) { if (null == type) return null; if ("function" === typeof type) return type.$$typeof === REACT_CLIENT_REFERENCE ? null : type.displayName || type.name || null; if ("string" === typeof type) return type; switch (type) { case REACT_FRAGMENT_TYPE: return "Fragment"; case REACT_PROFILER_TYPE: return "Profiler"; case REACT_STRICT_MODE_TYPE: return "StrictMode"; case REACT_SUSPENSE_TYPE: return "Suspense"; case REACT_SUSPENSE_LIST_TYPE: return "SuspenseList"; case REACT_ACTIVITY_TYPE: return "Activity"; } if ("object" === typeof type) switch ( ("number" === typeof type.tag && console.error( "Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue." ), type.$$typeof) ) { case REACT_PORTAL_TYPE: return "Portal"; case REACT_CONTEXT_TYPE: return (type.displayName || "Context") + ".Provider"; case REACT_CONSUMER_TYPE: return (type._context.displayName || "Context") + ".Consumer"; case REACT_FORWARD_REF_TYPE: var innerType = type.render; type = type.displayName; type || ((type = innerType.displayName || innerType.name || ""), (type = "" !== type ? "ForwardRef(" + type + ")" : "ForwardRef")); return type; case REACT_MEMO_TYPE: return ( (innerType = type.displayName || null), null !== innerType ? innerType : getComponentNameFromType(type.type) || "Memo" ); case REACT_LAZY_TYPE: innerType = type._payload; type = type._init; try { return getComponentNameFromType(type(innerType)); } catch (x) {} } return null; } function testStringCoercion(value) { return "" + value; } function checkKeyStringCoercion(value) { try { testStringCoercion(value); var JSCompiler_inline_result = !1; } catch (e) { JSCompiler_inline_result = true; } if (JSCompiler_inline_result) { JSCompiler_inline_result = console; var JSCompiler_temp_const = JSCompiler_inline_result.error; var JSCompiler_inline_result$jscomp$0 = ("function" === typeof Symbol && Symbol.toStringTag && value[Symbol.toStringTag]) || value.constructor.name || "Object"; JSCompiler_temp_const.call( JSCompiler_inline_result, "The provided key is an unsupported type %s. This value must be coerced to a string before using it here.", JSCompiler_inline_result$jscomp$0 ); return testStringCoercion(value); } } function getTaskName(type) { if (type === REACT_FRAGMENT_TYPE) return "<>"; if ( "object" === typeof type && null !== type && type.$$typeof === REACT_LAZY_TYPE ) return "<...>"; try { var name = getComponentNameFromType(type); return name ? "<" + name + ">" : "<...>"; } catch (x) { return "<...>"; } } function getOwner() { var dispatcher = ReactSharedInternals.A; return null === dispatcher ? null : dispatcher.getOwner(); } function UnknownOwner() { return Error("react-stack-top-frame"); } function hasValidKey(config) { if (hasOwnProperty.call(config, "key")) { var getter = Object.getOwnPropertyDescriptor(config, "key").get; if (getter && getter.isReactWarning) return false; } return void 0 !== config.key; } function defineKeyPropWarningGetter(props, displayName) { function warnAboutAccessingKey() { specialPropKeyWarningShown || ((specialPropKeyWarningShown = true), console.error( "%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://react.dev/link/special-props)", displayName )); } warnAboutAccessingKey.isReactWarning = true; Object.defineProperty(props, "key", { get: warnAboutAccessingKey, configurable: true }); } function elementRefGetterWithDeprecationWarning() { var componentName = getComponentNameFromType(this.type); didWarnAboutElementRef[componentName] || ((didWarnAboutElementRef[componentName] = true), console.error( "Accessing element.ref was removed in React 19. ref is now a regular prop. It will be removed from the JSX Element type in a future release." )); componentName = this.props.ref; return void 0 !== componentName ? componentName : null; } function ReactElement( type, key, self, source, owner, props, debugStack, debugTask ) { self = props.ref; type = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, props: props, _owner: owner }; null !== (void 0 !== self ? self : null) ? Object.defineProperty(type, "ref", { enumerable: false, get: elementRefGetterWithDeprecationWarning }) : Object.defineProperty(type, "ref", { enumerable: false, value: null }); type._store = {}; Object.defineProperty(type._store, "validated", { configurable: false, enumerable: false, writable: true, value: 0 }); Object.defineProperty(type, "_debugInfo", { configurable: false, enumerable: false, writable: true, value: null }); Object.defineProperty(type, "_debugStack", { configurable: false, enumerable: false, writable: true, value: debugStack }); Object.defineProperty(type, "_debugTask", { configurable: false, enumerable: false, writable: true, value: debugTask }); Object.freeze && (Object.freeze(type.props), Object.freeze(type)); return type; } function jsxDEVImpl( type, config, maybeKey, isStaticChildren, source, self, debugStack, debugTask ) { var children = config.children; if (void 0 !== children) if (isStaticChildren) if (isArrayImpl(children)) { for ( isStaticChildren = 0; isStaticChildren < children.length; isStaticChildren++ ) validateChildKeys(children[isStaticChildren]); Object.freeze && Object.freeze(children); } else console.error( "React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead." ); else validateChildKeys(children); if (hasOwnProperty.call(config, "key")) { children = getComponentNameFromType(type); var keys = Object.keys(config).filter(function (k) { return "key" !== k; }); isStaticChildren = 0 < keys.length ? "{key: someKey, " + keys.join(": ..., ") + ": ...}" : "{key: someKey}"; didWarnAboutKeySpread[children + isStaticChildren] || ((keys = 0 < keys.length ? "{" + keys.join(": ..., ") + ": ...}" : "{}"), console.error( 'A props object containing a "key" prop is being spread into JSX:\n let props = %s;\n <%s {...props} />\nReact keys must be passed directly to JSX without using spread:\n let props = %s;\n <%s key={someKey} {...props} />', isStaticChildren, children, keys, children ), (didWarnAboutKeySpread[children + isStaticChildren] = true)); } children = null; void 0 !== maybeKey && (checkKeyStringCoercion(maybeKey), (children = "" + maybeKey)); hasValidKey(config) && (checkKeyStringCoercion(config.key), (children = "" + config.key)); if ("key" in config) { maybeKey = {}; for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]); } else maybeKey = config; children && defineKeyPropWarningGetter( maybeKey, "function" === typeof type ? type.displayName || type.name || "Unknown" : type ); return ReactElement( type, children, self, source, getOwner(), maybeKey, debugStack, debugTask ); } function validateChildKeys(node) { "object" === typeof node && null !== node && node.$$typeof === REACT_ELEMENT_TYPE && node._store && (node._store.validated = 1); } var React = require$$0, REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_PORTAL_TYPE = Symbol.for("react.portal"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment"), REACT_STRICT_MODE_TYPE = Symbol.for("react.strict_mode"), REACT_PROFILER_TYPE = Symbol.for("react.profiler"); var REACT_CONSUMER_TYPE = Symbol.for("react.consumer"), REACT_CONTEXT_TYPE = Symbol.for("react.context"), REACT_FORWARD_REF_TYPE = Symbol.for("react.forward_ref"), REACT_SUSPENSE_TYPE = Symbol.for("react.suspense"), REACT_SUSPENSE_LIST_TYPE = Symbol.for("react.suspense_list"), REACT_MEMO_TYPE = Symbol.for("react.memo"), REACT_LAZY_TYPE = Symbol.for("react.lazy"), REACT_ACTIVITY_TYPE = Symbol.for("react.activity"), REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference"), ReactSharedInternals = React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, hasOwnProperty = Object.prototype.hasOwnProperty, isArrayImpl = Array.isArray, createTask = console.createTask ? console.createTask : function () { return null; }; React = { "react-stack-bottom-frame": function (callStackForError) { return callStackForError(); } }; var specialPropKeyWarningShown; var didWarnAboutElementRef = {}; var unknownOwnerDebugStack = React["react-stack-bottom-frame"].bind( React, UnknownOwner )(); var unknownOwnerDebugTask = createTask(getTaskName(UnknownOwner)); var didWarnAboutKeySpread = {}; reactJsxRuntime_development.Fragment = REACT_FRAGMENT_TYPE; reactJsxRuntime_development.jsx = function (type, config, maybeKey, source, self) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, false, source, self, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; reactJsxRuntime_development.jsxs = function (type, config, maybeKey, source, self) { var trackActualOwner = 1e4 > ReactSharedInternals.recentlyCreatedOwnerStacks++; return jsxDEVImpl( type, config, maybeKey, true, source, self, trackActualOwner ? Error("react-stack-top-frame") : unknownOwnerDebugStack, trackActualOwner ? createTask(getTaskName(type)) : unknownOwnerDebugTask ); }; })(); return reactJsxRuntime_development; } var hasRequiredJsxRuntime; function requireJsxRuntime () { if (hasRequiredJsxRuntime) return jsxRuntime.exports; hasRequiredJsxRuntime = 1; if (process.env.NODE_ENV === 'production') { jsxRuntime.exports = requireReactJsxRuntime_production(); } else { jsxRuntime.exports = requireReactJsxRuntime_development(); } return jsxRuntime.exports; } var jsxRuntimeExports = requireJsxRuntime(); var dist = {}; var core = {}; var hasRequiredCore; function requireCore () { if (hasRequiredCore) return core; hasRequiredCore = 1; Object.defineProperty(core, "__esModule", { value: true }); core.getDigiPin = getDigiPin; core.getLatLngFromDigiPin = getLatLngFromDigiPin; const DIGIPIN_GRID = [ ['F', 'C', '9', '8'], ['J', '3', '2', '7'], ['K', '4', '5', '6'], ['L', 'M', 'P', 'T'] ]; const BOUNDS = { minLat: 2.5, maxLat: 38.5, minLon: 63.5, maxLon: 99.5 }; /** * Encode latitude & longitude into DIGIPIN */ function getDigiPin(lat, lon) { if (lat < BOUNDS.minLat || lat > BOUNDS.maxLat) throw new Error('Latitude out of range'); if (lon < BOUNDS.minLon || lon > BOUNDS.maxLon) throw new Error('Longitude out of range'); let [minLat, maxLat, minLon, maxLon] = [BOUNDS.minLat, BOUNDS.maxLat, BOUNDS.minLon, BOUNDS.maxLon]; let code = ''; for (let level = 1; level <= 10; level++) { const latStep = (maxLat - minLat) / 4; const lonStep = (maxLon - minLon) / 4; let row = 3 - Math.floor((lat - minLat) / latStep); let col = Math.floor((lon - minLon) / lonStep); row = Math.min(3, Math.max(0, row)); col = Math.min(3, Math.max(0, col)); code += DIGIPIN_GRID[row][col]; if (level === 3 || level === 6) code += '-'; maxLat = minLat + latStep * (4 - row); minLat = minLat + latStep * (3 - row); minLon = minLon + lonStep * col; maxLon = minLon + lonStep; } return code; } /** * Decode DIGIPIN back to lat/lon center */ function getLatLngFromDigiPin(pin) { const clean = pin.replace(/-/g, ''); if (clean.length !== 10) throw new Error('Invalid DIGIPIN'); let [minLat, maxLat, minLon, maxLon] = [BOUNDS.minLat, BOUNDS.maxLat, BOUNDS.minLon, BOUNDS.maxLon]; for (const char of clean) { let found = false; let ri = 0, ci = 0; for (let r = 0; r < 4; r++) { for (let c = 0; c < 4; c++) { if (DIGIPIN_GRID[r][c] === char) { ri = r; ci = c; found = true; break; } } if (found) break; } if (!found) throw new Error('Invalid character'); const latStep = (maxLat - minLat) / 4; const lonStep = (maxLon - minLon) / 4; const newMaxLat = minLat + latStep * (4 - ri); const newMinLat = minLat + latStep * (3 - ri); const newMinLon = minLon + lonStep * ci; const newMaxLon = newMinLon + lonStep; [minLat, maxLat, minLon, maxLon] = [newMinLat, newMaxLat, newMinLon, newMaxLon]; } return { latitude: (minLat + maxLat) / 2, longitude: (minLon + maxLon) / 2 }; } return core; } var batch = {}; var hasRequiredBatch; function requireBatch () { if (hasRequiredBatch) return batch; hasRequiredBatch = 1; Object.defineProperty(batch, "__esModule", { value: true }); batch.batchEncode = batchEncode; batch.batchDecode = batchDecode; const core_1 = requireCore(); function batchEncode(coords) { return coords.map(c => (0, core_1.getDigiPin)(c.lat, c.lng)); } function batchDecode(pins) { return pins.map(core_1.getLatLngFromDigiPin); } return batch; } var cache = {}; var lruCache; var hasRequiredLruCache; function requireLruCache () { if (hasRequiredLruCache) return lruCache; hasRequiredLruCache = 1; const perf = typeof performance === 'object' && performance && typeof performance.now === 'function' ? performance : Date; const hasAbortController = typeof AbortController === 'function'; // minimal backwards-compatibility polyfill // this doesn't have nearly all the checks and whatnot that // actual AbortController/Signal has, but it's enough for // our purposes, and if used properly, behaves the same. const AC = hasAbortController ? AbortController : class AbortController { constructor() { this.signal = new AS(); } abort(reason = new Error('This operation was aborted')) { this.signal.reason = this.signal.reason || reason; this.signal.aborted = true; this.signal.dispatchEvent({ type: 'abort', target: this.signal, }); } }; const hasAbortSignal = typeof AbortSignal === 'function'; // Some polyfills put this on the AC class, not global const hasACAbortSignal = typeof AC.AbortSignal === 'function'; const AS = hasAbortSignal ? AbortSignal : hasACAbortSignal ? AC.AbortController : class AbortSignal { constructor() { this.reason = undefined; this.aborted = false; this._listeners = []; } dispatchEvent(e) { if (e.type === 'abort') { this.aborted = true; this.onabort(e); this._listeners.forEach(f => f(e), this); } } onabort() {} addEventListener(ev, fn) { if (ev === 'abort') { this._listeners.push(fn); } } removeEventListener(ev, fn) { if (ev === 'abort') { this._listeners = this._listeners.filter(f => f !== fn); } } }; const warned = new Set(); const deprecatedOption = (opt, instead) => { const code = `LRU_CACHE_OPTION_${opt}`; if (shouldWarn(code)) { warn(code, `${opt} option`, `options.${instead}`, LRUCache); } }; const deprecatedMethod = (method, instead) => { const code = `LRU_CACHE_METHOD_${method}`; if (shouldWarn(code)) { const { prototype } = LRUCache; const { get } = Object.getOwnPropertyDescriptor(prototype, method); warn(code, `${method} method`, `cache.${instead}()`, get); } }; const deprecatedProperty = (field, instead) => { const code = `LRU_CACHE_PROPERTY_${field}`; if (shouldWarn(code)) { const { prototype } = LRUCache; const { get } = Object.getOwnPropertyDescriptor(prototype, field); warn(code, `${field} property`, `cache.${instead}`, get); } }; const emitWarning = (...a) => { typeof process === 'object' && process && typeof process.emitWarning === 'function' ? process.emitWarning(...a) : console.error(...a); }; const shouldWarn = code => !warned.has(code); const warn = (code, what, instead, fn) => { warned.add(code); const msg = `The ${what} is deprecated. Please use ${instead} instead.`; emitWarning(msg, 'DeprecationWarning', code, fn); }; const isPosInt = n => n && n === Math.floor(n) && n > 0 && isFinite(n); /* istanbul ignore next - This is a little bit ridiculous, tbh. * The maximum array length is 2^32-1 or thereabouts on most JS impls. * And well before that point, you're caching the entire world, I mean, * that's ~32GB of just integers for the next/prev links, plus whatever * else to hold that many keys and values. Just filling the memory with * zeroes at init time is brutal when you get that big. * But why not be complete? * Maybe in the future, these limits will have expanded. */ const getUintArray = max => !isPosInt(max) ? null : max <= Math.pow(2, 8) ? Uint8Array : max <= Math.pow(2, 16) ? Uint16Array : max <= Math.pow(2, 32) ? Uint32Array : max <= Number.MAX_SAFE_INTEGER ? ZeroArray : null; class ZeroArray extends Array { constructor(size) { super(size); this.fill(0); } } class Stack { constructor(max) { if (max === 0) { return [] } const UintArray = getUintArray(max); this.heap = new UintArray(max); this.length = 0; } push(n) { this.heap[this.length++] = n; } pop() { return this.heap[--this.length] } } class LRUCache { constructor(options = {}) { const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, fetchContext, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, } = options; // deprecated options, don't trigger a warning for getting them if // the thing being passed in is another LRUCache we're copying. const { length, maxAge, stale } = options instanceof LRUCache ? {} : options; if (max !== 0 && !isPosInt(max)) { throw new TypeError('max option must be a nonnegative integer') } const UintArray = max ? getUintArray(max) : Array; if (!UintArray) { throw new Error('invalid max value: ' + max) } this.max = max; this.maxSize = maxSize; this.maxEntrySize = maxEntrySize || this.maxSize; this.sizeCalculation = sizeCalculation || length; if (this.sizeCalculation) { if (!this.maxSize && !this.maxEntrySize) { throw new TypeError( 'cannot set sizeCalculation without setting maxSize or maxEntrySize' ) } if (typeof this.sizeCalculation !== 'function') { throw new TypeError('sizeCalculation set to non-function') } } this.fetchMethod = fetchMethod || null; if (this.fetchMethod && typeof this.fetchMethod !== 'function') { throw new TypeError( 'fetchMethod must be a function if specified' ) } this.fetchContext = fetchContext; if (!this.fetchMethod && fetchContext !== undefined) { throw new TypeError( 'cannot set fetchContext without fetchMethod' ) } this.keyMap = new Map(); this.keyList = new Array(max).fill(null); this.valList = new Array(max).fill(null); this.next = new UintArray(max); this.prev = new UintArray(max); this.head = 0; this.tail = 0; this.free = new Stack(max); this.initialFill = 1; this.size = 0; if (typeof dispose === 'function') { this.dispose = dispose; } if (typeof disposeAfter === 'function') { this.disposeAfter = disposeAfter; this.disposed = []; } else { this.disposeAfter = null; this.disposed = null; } this.noDisposeOnSet = !!noDisposeOnSet; this.noUpdateTTL = !!noUpdateTTL; this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection; this.allowStaleOnFetchRejection = !!allowStaleOnFetchRejection; this.allowStaleOnFetchAbort = !!allowStaleOnFetchAbort; this.ignoreFetchAbort = !!ignoreFetchAbort; // NB: maxEntrySize is set to maxSize if it's set if (this.maxEntrySize !== 0) { if (this.maxSize !== 0) { if (!isPosInt(this.maxSize)) { throw new TypeError( 'maxSize must be a positive integer if specified' ) } } if (!isPosInt(this.maxEntrySize)) { throw new TypeError( 'maxEntrySize must be a positive integer if specified' ) } this.initializeSizeTracking(); } this.allowStale = !!allowStale || !!stale; this.noDeleteOnStaleGet = !!noDeleteOnStaleGet; this.updateAgeOnGet = !!updateAgeOnGet; this.updateAgeOnHas = !!updateAgeOnHas; this.ttlResolution = isPosInt(ttlResolution) || ttlResolution === 0 ? ttlResolution : 1; this.ttlAutopurge = !!ttlAutopurge; this.ttl = ttl || maxAge || 0; if (this.ttl) { if (!isPosInt(this.ttl)) { throw new TypeError( 'ttl must be a positive integer if specified' ) } this.initializeTTLTracking(); } // do not allow completely unbounded caches if (this.max === 0 && this.ttl === 0 && this.maxSize === 0) { throw new TypeError( 'At least one of max, maxSize, or ttl is required' ) } if (!this.ttlAutopurge && !this.max && !this.maxSize) { const code = 'LRU_CACHE_UNBOUNDED'; if (shouldWarn(code)) { warned.add(code); const msg = 'TTL caching without ttlAutopurge, max, or maxSize can ' + 'result in unbounded memory consumption.'; emitWarning(msg, 'UnboundedCacheWarning', code, LRUCache); } } if (stale) { deprecatedOption('stale', 'allowStale'); } if (maxAge) { deprecatedOption('maxAge', 'ttl'); } if (length) { deprecatedOption('length', 'sizeCalculation'); } } getRemainingTTL(key) { return this.has(key, { updateAgeOnHas: false }) ? Infinity : 0 } initializeTTLTracking() { this.ttls = new ZeroArray(this.max); this.starts = new ZeroArray(this.max); this.setItemTTL = (index, ttl, start = perf.now()) => { this.starts[index] = ttl !== 0 ? start : 0; this.ttls[index] = ttl; if (ttl !== 0 && this.ttlAutopurge) { const t = setTimeout(() => { if (this.isStale(index)) { this.delete(this.keyList[index]); } }, ttl + 1); /* istanbul ignore else - unref() not supported on all platforms */ if (t.unref) { t.unref(); } } }; this.updateItemAge = index => { this.starts[index] = this.ttls[index] !== 0 ? perf.now() : 0; }; this.statusTTL = (status, index) => { if (status) { status.ttl = this.ttls[index]; status.start = this.starts[index]; status.now = cachedNow || getNow(); status.remainingTTL = status.now + status.ttl - status.start; } }; // debounce calls to perf.now() to 1s so we're not hitting // that costly call repeatedly. let cachedNow = 0; const getNow = () => { const n = perf.now(); if (this.ttlResolution > 0) { cachedNow = n; const t = setTimeout( () => (cachedNow = 0), this.ttlResolution ); /* istanbul ignore else - not available on all platforms */ if (t.unref) { t.unref(); } } return n }; this.getRemainingTTL = key => { const index = this.keyMap.get(key); if (index === undefined) { return 0 } return this.ttls[index] === 0 || this.starts[index] === 0 ? Infinity : this.starts[index] + this.ttls[index] - (cachedNow || getNow()) }; this.isStale = index => { return ( this.ttls[index] !== 0 && this.starts[index] !== 0 && (cachedNow || getNow()) - this.starts[index] > this.ttls[index] ) }; } updateItemAge(_index) {} statusTTL(_status, _index) {} setItemTTL(_index, _ttl, _start) {} isStale(_index) { return false } initializeSizeTracking() { this.calculatedSize = 0; this.sizes = new ZeroArray(this.max); this.removeItemSize = index => { this.calculatedSize -= this.sizes[index]; this.sizes[index] = 0; }; this.requireSize = (k, v, size, sizeCalculation) => { // provisionally accept background fetches. // actual value size will be checked when they return. if (this.isBackgroundFetch(v)) { return 0 } if (!isPosInt(size)) { if (sizeCalculation) { if (typeof sizeCalculation !== 'function') { throw new TypeError('sizeCalculation must be a function') } size = sizeCalculation(v, k); if (!isPosInt(size)) { throw new TypeError( 'sizeCalculation return invalid (expect positive integer)' ) } } else { throw new TypeError( 'invalid size value (must be positive integer). ' + 'When maxSize or maxEntrySize is used, sizeCalculation or size ' + 'must be set.' ) } } return size }; this.addItemSize = (index, size, status) => { this.sizes[index] = size; if (this.maxSize) { const maxSize = this.maxSize - this.sizes[index]; while (this.calculatedSize > maxSize) { this.evict(true); } } this.calculatedSize += this.sizes[index]; if (status) { status.entrySize = size; status.totalCalculatedSize = this.calculatedSize; } }; } removeItemSize(_index) {} addItemSize(_index, _size) {} requireSize(_k, _v, size, sizeCalculation) { if (size || sizeCalculation) { throw new TypeError( 'cannot set size without setting maxSize or maxEntrySize on cache' ) } } *indexes({ allowStale = this.allowStale } = {}) { if (this.size) { for (let i = this.tail; true; ) { if (!this.isValidIndex(i)) { break } if (allowStale || !this.isStale(i)) { yield i; } if (i === this.head) { break } else { i = this.prev[i]; } } } } *rindexes({ allowStale = this.allowStale } = {}) { if (this.size) { for (let i = this.head; true; ) { if (!this.isValidIndex(i)) { break } if (allowStale || !this.isStale(i)) { yield i; } if (i === this.tail) { break } else { i = this.next[i]; } } } } isValidIndex(index) { return ( index !== undefined && this.keyMap.get(this.keyList[index]) === index ) } *entries() { for (const i of this.indexes()) { if ( this.valList[i] !== undefined && this.keyList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield [this.keyList[i], this.valList[i]]; } } } *rentries() { for (const i of this.rindexes()) { if ( this.valList[i] !== undefined && this.keyList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield [this.keyList[i], this.valList[i]]; } } } *keys() { for (const i of this.indexes()) { if ( this.keyList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield this.keyList[i]; } } } *rkeys() { for (const i of this.rindexes()) { if ( this.keyList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield this.keyList[i]; } } } *values() { for (const i of this.indexes()) { if ( this.valList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield this.valList[i]; } } } *rvalues() { for (const i of this.rindexes()) { if ( this.valList[i] !== undefined && !this.isBackgroundFetch(this.valList[i]) ) { yield this.valList[i]; } } } [Symbol.iterator]() { return this.entries() } find(fn, getOptions) { for (const i of this.indexes()) { const v = this.valList[i]; const value = this.isBackgroundFetch(v) ? v.__staleWhileFetching : v; if (value === undefined) continue if (fn(value, this.keyList[i], this)) { return this.get(this.keyList[i], getOptions) } } } forEach(fn, thisp = this) { for (const i of this.indexes()) { const v = this.valList[i]; const value = this.isBackgroundFetch(v) ? v.__staleWhileFetching : v; if (value === undefined) continue fn.call(thisp, value, this.keyList[i], this); } } rforEach(fn, thisp = this) { for (const i of this.rindexes()) { const v = this.valList[i]; const value = this.isBackgroundFetch(v) ? v.__staleWhileFetching : v; if (value === undefined) continue fn.call(thisp, value, this.keyList[i], this); } } get prune() { deprecatedMethod('prune', 'purgeStale'); return this.purgeStale } purgeStale() { let deleted = false; for (const i of this.rindexes({ allowStale: true })) { if (this.isStale(i)) { this.delete(this.keyList[i]); deleted = true; } } return deleted } dump() { const arr = []; for (const i of this.indexes({ allowStale: true })) { const key = this.keyList[i]; const v = this.valList[i]; const value = this.isBackgroundFetch(v) ? v.__staleWhileFetching : v; if (value === undefined) continue const entry = { value }; if (this.ttls) { entry.ttl = this.ttls[i]; // always dump the start relative to a portable timestamp // it's ok for this to be a bit slow, it's a rare operation. const age = perf.now() - this.starts[i]; entry.start = Math.floor(Date.now() - age); } if (this.sizes) { entry.size = this.sizes[i]; } arr.unshift([key, entry]); } return arr } load(arr) { this.clear(); for (const [key, entry] of arr) { if (entry.start) { // entry.start is a portable timestamp, but we may be using // node's performance.now(), so calculate the offset. // it's ok for this to be a bit slow, it's a rare operation. const age = Date.now() - entry.start; entry.start = perf.now() - age; } this.set(key, entry.value, entry); } } dispose(_v, _k, _reason) {} set( k, v, { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, size = 0, sizeCalculation = this.sizeCalculation, noUpdateTTL = this.noUpdateTTL, status, } = {} ) { size = this.requireSize(k, v, size, sizeCalculation); // if the item doesn't fit, don't do anything // NB: maxEntrySize set to maxSize by default if (this.maxEntrySize && size > this.maxEntrySize) { if (status) { status.set = 'miss'; status.maxEntrySizeExceeded = true; } // have to delete, in case a background fetch is there already. // in non-async cases, this is a no-op this.delete(k); return this } let index = this.size === 0 ? undefined : this.keyMap.get(k); if (index === undefined) { // addition index = this.newIndex(); this.keyList[index] = k; this.valList[index] = v; this.keyMap.set(k, index); this.next[this.tail] = index; this.prev[index] = this.tail; this.tail = index; this.size++; this.addItemSize(index, size, status); if (status) { status.set = 'add'; } noUpdateTTL = false; } else { // update this.moveToTail(index); const oldVal = this.valList[index]; if (v !== oldVal) { if (this.isBackgroundFetch(oldVal)) { oldVal.__abortController.abort(new Error('replaced')); } else { if (!noDisposeOnSet) { this.dispose(oldVal, k, 'set'); if (this.disposeAfter) { this.disposed.push([oldVal, k, 'set']); } } } this.removeItemSize(index); this.valList[index] = v; this.addItemSize(index, size, status); if (status) { status.set = 'replace'; const oldValue = oldVal && this.isBackgroundFetch(oldVal) ? oldVal.__staleWhileFetching : oldVal; if (oldValue !== undefined) status.oldValue = oldValue; } } else if (status) { status.set = 'update'; } } if (ttl !== 0 && this.ttl === 0 && !this.ttls) { this.initializeTTLTracking(); } if (!noUpdateTTL) { this.setItemTTL(index, ttl, start); } this.statusTTL(status, index); if (this.disposeAfter) { while (this.disposed.length) { this.disposeAfter(...this.disposed.shift()); } } return this } newIndex() { if (this.size === 0) { return this.tail } if (this.size === this.max && this.max !== 0) { return this.evict(false) } if (this.free.length !== 0) { return this.free.pop() } // initial fill, just keep writing down the list return this.initialFill++ } pop() { if (this.size) { const val = this.valList[this.head]; this.evict(true); return val } } evict(free) { const head = this.head; const k = this.keyList[head]; const v = this.valList[head]; if (this.isBackgroundFetch(v)) { v.__abortController.abort(new Error('evicted')); } else { this.dispose(v, k, 'evict'); if (this.disposeAfter) { this.disposed.push([v, k, 'evict']); } } this.removeItemSize(head); // if we aren't about to use the index, then null these out if (free) { this.keyList[head] = null; this.valList[head] = null; this.free.push(head); } this.head = this.next[head]; this.keyMap.delete(k); this.size--; return head } has(k, { updateAgeOnHas = this.updateAgeOnHas, status } = {}) { const index = this.keyMap.get(k); if (index !== undefined) { if (!this.isStale(index)) { if (updateAgeOnHas) { this.updateItemAge(index); } if (status) status.has = 'hit'; this.statusTTL(status, index); return true } else if (status) { status.has = 'stale'; this.statusTTL(status, index); } } else if (status) { status.has = 'miss'; } return false } // like get(), but without any LRU updating or TTL expiration peek(k, { allowStale = this.allowStale } = {}) { const index = this.keyMap.get(k); if (index !== undefined && (allowStale || !this.isStale(index))) { const v = this.valList[index]; // either stale and allowed, or forcing a refresh of non-stale value return this.isBackgroundFetch(v) ? v.__staleWhileFetching : v } } backgroundFetch(k, index, options, context) { const v = index === undefined ? undefined : this.valList[index]; if (this.isBackgroundFetch(v)) { return v } const ac = new AC(); if (options.signal) { options.signal.addEventListener('abort', () => ac.abort(options.signal.reason) ); } const fetchOpts = { signal: ac.signal, options, context, }; const cb = (v, updateCache = false) => { const { aborted } = ac.signal; const ignoreAbort = options.ignoreFetchAbort && v !== undefined; if (options.status) { if (aborted && !updateCache) { options.status.fetchAborted = true; options.status.fetchError = ac.signal.reason; if (ignoreAbort) options.status.fetchAbortIgnored = true; } else { options.status.fetchResolved = true; } } if (aborted && !ignoreAbort && !updateCache) { return fetchFail(ac.signal.reason) } // either we didn't abort, and are still here, or we did, and ignored if (this.valList[index] === p) { if (v === undefined) { if (p.__staleWhileFetching) { this.valList[index] = p.__staleWhileFetching; } else { this.delete(k); } } else { if (options.status) options.status.fetchUpdated = true; this.set(k, v, fetchOpts.options); } } return v }; const eb = er => { if (options.status) { options.status.fetchRejected = true; options.status.fetchError = er; } return fetchFail(er) }; const fetchFail = er => { const { aborted } = ac.signal; const allowStaleAborted = aborted && options.allowStaleOnFetchAbort; const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection; const noDelete = allowStale || options.noDeleteOnFetchRejection; if (this.valList[index] === p) { // if we allow stale on fetch rejections, then we need to ensure that // the stale value is not removed from the cache when the fetch fails. const del = !noDelete || p.__staleWhileFetching === undefined; if (del) { this.delete(k); } else if (!allowStaleAborted) { // still replace the *promise* with the stale value, // since we are done with the promise at this point. // leave it untouched if we're still waiting for an // aborted background fetch that hasn't yet returned. this.valList[index] = p.__staleWhileFetching; } } if (allowStale) { if (options.status && p.__staleWhileFetching !== undefined) { options.status.returnedStale = true; } return p.__staleWhileFetching } else if (p.__returned === p) { throw er } }; const pcall = (res, rej) => { this.fetchMethod(k, v, fetchOpts).then(v => res(v), rej); // ignored, we go until we finish, regardless. // defer check until we are actually aborting, // so fetchMethod can override. ac.signal.addEventListener('abort', () => { if ( !options.ignoreFetchAbort || options.allowStaleOnFetchAbort ) { res(); // when it eventually resolves, update the cache. if (options.allowStaleOnFetchAbort) { res = v => cb(v, true); } } }); }; if (options.status) options.status.fetchDispatched = true; const p = new Promise(pcall).then(cb, eb); p.__abortController = ac; p.__staleWhileFetching = v; p.__returned = null; if (index === undefined) { // internal, don't expose status. this.set(k, p, { ...fetchOpts.options, status: undefined }); index = this.keyMap.get(k); } else { this.valList[index] = p; } return p } isBackgroundFetch(p) { return ( p && typeof p === 'object' && typeof p.then === 'function' && Object.prototype.hasOwnProperty.call( p, '__staleWhileFetching' ) && Object.prototype.hasOwnProperty.call(p, '__returned') && (p.__returned === p || p.__returned === null) ) } // this takes the union of get() and set() opts, because it does both async fetch( k, { // get options allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet, // set options ttl = this.ttl, noDisposeOnSet = this.noDisposeOnSet, size = 0, sizeCalculation = this.sizeCalculation, noUpdateTTL = this.noUpdateTTL, // fetch exclusive options noDeleteOnFetchRejection = this.noDeleteOnFetchRejection, allowStaleOnFetchRejection = this.allowStaleOnFetchRejection, ignoreFetchAbort = this.ignoreFetchAbort, allowStaleOnFetchAbort = this.allowStaleOnFetchAbort, fetchContext = this.fetchContext, forceRefresh = false, status, signal, } = {} ) { if (!this.fetchMethod) { if (status) status.fetch = 'get'; return this.get(k, { allowStale, updateAgeOnGet, noDeleteOnStaleGet, status, }) } const options = { allowStale, updateAgeOnGet, noDeleteOnStaleGet, ttl, noDisposeOnSet, size, sizeCalculation, noUpdateTTL, noDeleteOnFetchRejection, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort, status, signal, }; let index = this.keyMap.get(k); if (index === undefined) { if (status) status.fetch = 'miss'; const p = this.backgroundFetch(k, index, options, fetchContext); return (p.__returned = p) } else { // in cache, maybe already fetching const v = this.valList[index]; if (this.isBackgroundFetch(v)) { const stale = allowStale && v.__staleWhileFetching !== undefined; if (status) { status.fetch = 'inflight'; if (stale) status.returnedStale = true; } return stale ? v.__staleWhileFetching : (v.__returned = v) } // if we force a refresh, that means do NOT serve the cached value, // unless we are already in the process of refreshing the cache. const isStale = this.isStale(index); if (!forceRefresh && !isStale) { if (status) status.fetch = 'hit'; this.moveToTail(index); if (updateAgeOnGet) { this.updateItemAge(index); } this.statusTTL(status, index); return v } // ok, it is stale or a forced refresh, and not already fetching. // refresh the cache. const p = this.backgroundFetch(k, index, options, fetchContext); const hasStale = p.__staleWhileFetching !== undefined; const staleVal = hasStale && allowStale; if (status) { status.fetch = hasStale && isStale ? 'stale' : 'refresh'; if (staleVal && isStale) status.returnedStale = true; } return staleVal ? p.__staleWhileFetching : (p.__returned = p) } } get( k, { allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet, status, } = {} ) { const index = this.keyMap.get(k); if (index !== undefined) { const value = this.valList[index]; const fetching = this.isBackgroundFetch(value); this.statusTTL(status, index); if (this.isStale(index)) { if (status) status.get = 'stale'; // delete only if not an in-flight background fetch if (!fetching) { if (!noDeleteOnStaleGet) { this.delete(k); } if (status) status.returnedStale = allowStale; return allowStale ? value : undefined } else { if (status) { status.returnedStale = allowStale && value.__staleWhileFetching !== undefined; } return allowStale ? value.__staleWhileFetching : undefined } } else { if (status) status.get = 'hit'; // if we're currently fetching it, we don't actually have it yet // it's not stale, which means this isn't a staleWhileRefetching.