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
JavaScript
'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.