@1delta/data-sdk 0.0.56 → 0.0.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1034,17 +1034,32 @@ var parseFraxlendPairKey = (lender) => {
1034
1034
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1035
1035
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1036
1036
  var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
1037
+ var STUCK_REVALIDATE_MS = 60 * 1e3;
1038
+ var MISSING_TTL_MS = FRESH_TTL_MS;
1037
1039
  var STATE_KEY = "__1delta_remote_cache__";
1038
1040
  function getState() {
1039
1041
  const g = globalThis;
1040
- if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map(), revalidating: /* @__PURE__ */ new Set() };
1042
+ if (!g[STATE_KEY]) {
1043
+ g[STATE_KEY] = {
1044
+ memo: /* @__PURE__ */ new Map(),
1045
+ inflight: /* @__PURE__ */ new Map(),
1046
+ revalidating: /* @__PURE__ */ new Map(),
1047
+ missing: /* @__PURE__ */ new Map(),
1048
+ loaders: /* @__PURE__ */ new Map()
1049
+ };
1050
+ }
1041
1051
  const state = g[STATE_KEY];
1042
- if (!state.revalidating) state.revalidating = /* @__PURE__ */ new Set();
1052
+ if (!(state.revalidating instanceof Map)) state.revalidating = /* @__PURE__ */ new Map();
1053
+ if (!state.missing) state.missing = /* @__PURE__ */ new Map();
1054
+ if (!state.loaders) state.loaders = /* @__PURE__ */ new Map();
1043
1055
  return state;
1044
1056
  }
1045
1057
  function setRemoteCacheStore(next) {
1046
1058
  getState().store = next;
1047
1059
  }
1060
+ function setRemoteCacheWaitUntil(next) {
1061
+ getState().waitUntil = next;
1062
+ }
1048
1063
  function setRemoteCacheGithubToken(token) {
1049
1064
  getState().githubToken = token;
1050
1065
  }
@@ -1052,7 +1067,11 @@ function clearRemoteCache() {
1052
1067
  const state = getState();
1053
1068
  state.memo.clear();
1054
1069
  state.inflight.clear();
1070
+ state.revalidating.clear();
1071
+ state.missing.clear();
1072
+ state.loaders.clear();
1055
1073
  }
1074
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1056
1075
  var RAW_HOST = "https://raw.githubusercontent.com/";
1057
1076
  function githubApiMirror(url) {
1058
1077
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1100,11 +1119,21 @@ async function readStore(key, isValid) {
1100
1119
  const { store } = getState();
1101
1120
  if (!store) return void 0;
1102
1121
  try {
1103
- const raw = await store.get(storeKey(key));
1122
+ let raw;
1123
+ let metaAt;
1124
+ if (store.getWithMetadata) {
1125
+ const got = await store.getWithMetadata(storeKey(key));
1126
+ raw = got.value;
1127
+ if (typeof got.metadata?.at === "number") metaAt = got.metadata.at;
1128
+ } else {
1129
+ raw = await store.get(storeKey(key));
1130
+ }
1104
1131
  if (!raw) return void 0;
1105
1132
  const parsed = JSON.parse(raw);
1106
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1107
- return parsed;
1133
+ if (!isValid(parsed?.value)) return void 0;
1134
+ const at = metaAt ?? parsed?.at;
1135
+ if (typeof at !== "number") return void 0;
1136
+ return { at, value: parsed.value };
1108
1137
  } catch {
1109
1138
  return void 0;
1110
1139
  }
@@ -1113,50 +1142,106 @@ async function writeStore(key, entry) {
1113
1142
  const { store } = getState();
1114
1143
  if (!store) return;
1115
1144
  try {
1116
- await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1145
+ const body = JSON.stringify(entry);
1146
+ if (store.putWithMetadata) {
1147
+ await store.putWithMetadata(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S, { at: entry.at });
1148
+ } else {
1149
+ await store.put(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S);
1150
+ }
1117
1151
  } catch {
1118
1152
  }
1119
1153
  }
1154
+ function background(promise) {
1155
+ const { waitUntil } = getState();
1156
+ if (!waitUntil) return;
1157
+ try {
1158
+ waitUntil(promise);
1159
+ } catch {
1160
+ }
1161
+ }
1162
+ function adopt(key, entry) {
1163
+ const { memo } = getState();
1164
+ const current = memo.get(key);
1165
+ if (!current || current.at <= entry.at) memo.set(key, entry);
1166
+ }
1167
+ var negativeCacheError = (entry) => new RemoteFetchError(entry.message, entry.status);
1168
+ function revalidate(key, loader, preferStore) {
1169
+ const state = getState();
1170
+ const existing = state.revalidating.get(key);
1171
+ if (existing && Date.now() - existing.startedAt < STUCK_REVALIDATE_MS) return existing.promise;
1172
+ const entry = { startedAt: Date.now(), promise: Promise.resolve(false) };
1173
+ entry.promise = (async () => {
1174
+ try {
1175
+ const mine = state.memo.get(key);
1176
+ if (preferStore && state.store) {
1177
+ const stored = await readStore(key, loader.isValid);
1178
+ if (stored && stored.at > (mine?.at ?? 0) && Date.now() - stored.at < FRESH_TTL_MS) {
1179
+ adopt(key, stored);
1180
+ return true;
1181
+ }
1182
+ }
1183
+ const value = await loader.load();
1184
+ const fresh = { at: Date.now(), value };
1185
+ adopt(key, fresh);
1186
+ state.missing.delete(key);
1187
+ await writeStore(key, fresh);
1188
+ return true;
1189
+ } catch {
1190
+ return false;
1191
+ } finally {
1192
+ if (state.revalidating.get(key) === entry) state.revalidating.delete(key);
1193
+ }
1194
+ })();
1195
+ state.revalidating.set(key, entry);
1196
+ return entry.promise;
1197
+ }
1120
1198
  async function loadCached(key, load, isValid) {
1121
- const { memo, inflight, revalidating } = getState();
1199
+ const state = getState();
1200
+ const { memo, inflight } = state;
1201
+ const loader = { load, isValid };
1202
+ state.loaders.set(key, loader);
1122
1203
  const cached = memo.get(key);
1123
- if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1204
+ if (cached) {
1205
+ const age = Date.now() - cached.at;
1206
+ if (age < FRESH_TTL_MS) return cached.value;
1207
+ if (age < STALE_WHILE_REVALIDATE_MS) {
1208
+ background(revalidate(key, loader, true));
1209
+ return cached.value;
1210
+ }
1211
+ }
1212
+ const missing = state.missing.get(key);
1213
+ if (missing && Date.now() - missing.at < MISSING_TTL_MS) throw negativeCacheError(missing);
1124
1214
  const pending = inflight.get(key);
1125
1215
  if (pending) return pending;
1126
1216
  const task = (async () => {
1127
1217
  const stored = await readStore(key, isValid);
1128
1218
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1129
- memo.set(key, stored);
1219
+ adopt(key, stored);
1130
1220
  return stored.value;
1131
1221
  }
1132
1222
  if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
1133
- memo.set(key, stored);
1134
- if (revalidating.has(key)) return stored.value;
1135
- revalidating.add(key);
1136
- void (async () => {
1137
- try {
1138
- const value = await load();
1139
- const entry = { at: Date.now(), value };
1140
- const current = memo.get(key);
1141
- if (!current || current.at <= entry.at) memo.set(key, entry);
1142
- await writeStore(key, entry);
1143
- } catch {
1144
- } finally {
1145
- revalidating.delete(key);
1146
- }
1147
- })();
1223
+ adopt(key, stored);
1224
+ background(revalidate(key, loader, false));
1148
1225
  return stored.value;
1149
1226
  }
1150
1227
  try {
1151
1228
  const value = await load();
1152
1229
  const entry = { at: Date.now(), value };
1153
- memo.set(key, entry);
1154
- await writeStore(key, entry);
1230
+ adopt(key, entry);
1231
+ state.missing.delete(key);
1232
+ const put = writeStore(key, entry);
1233
+ if (state.waitUntil) background(put);
1234
+ else await put;
1155
1235
  return value;
1156
1236
  } catch (error) {
1157
1237
  const stale = memo.get(key) ?? stored;
1158
- if (!stale) throw error;
1159
- memo.set(key, stale);
1238
+ if (!stale) {
1239
+ if (error instanceof RemoteFetchError && error.isMissing) {
1240
+ state.missing.set(key, { at: Date.now(), status: error.status, message: error.message });
1241
+ }
1242
+ throw error;
1243
+ }
1244
+ adopt(key, stale);
1160
1245
  return stale.value;
1161
1246
  }
1162
1247
  })().finally(() => {
@@ -1165,6 +1250,14 @@ async function loadCached(key, load, isValid) {
1165
1250
  inflight.set(key, task);
1166
1251
  return task;
1167
1252
  }
1253
+ async function refreshRemoteCache(key, opts = {}) {
1254
+ const state = getState();
1255
+ const loader = state.loaders.get(key);
1256
+ if (!loader) return "unknown";
1257
+ const cached = state.memo.get(key);
1258
+ if (cached && opts.maxAgeMs !== void 0 && Date.now() - cached.at < opts.maxAgeMs) return "fresh";
1259
+ return await revalidate(key, loader, false) ? "refreshed" : "failed";
1260
+ }
1168
1261
  async function loadCachedJson(key, url, isValid, label = key) {
1169
1262
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1170
1263
  }
@@ -1372,6 +1465,7 @@ exports.parseMidnightRef = parseMidnightRef;
1372
1465
  exports.parseSiloV2LenderKey = parseSiloV2LenderKey;
1373
1466
  exports.parseSiloV3LenderKey = parseSiloV3LenderKey;
1374
1467
  exports.parseTermMaxLenderKey = parseTermMaxLenderKey;
1468
+ exports.refreshRemoteCache = refreshRemoteCache;
1375
1469
  exports.resolveMidnightApiBase = resolveMidnightApiBase;
1376
1470
  exports.resolveTenorApiUrl = resolveTenorApiUrl;
1377
1471
  exports.resolveTermApiBase = resolveTermApiBase;
@@ -1388,6 +1482,7 @@ exports.riverMarkets = riverMarkets;
1388
1482
  exports.setMidnightApiBase = setMidnightApiBase;
1389
1483
  exports.setRemoteCacheGithubToken = setRemoteCacheGithubToken;
1390
1484
  exports.setRemoteCacheStore = setRemoteCacheStore;
1485
+ exports.setRemoteCacheWaitUntil = setRemoteCacheWaitUntil;
1391
1486
  exports.setTenorApiUrl = setTenorApiUrl;
1392
1487
  exports.setTermApiBase = setTermApiBase;
1393
1488
  exports.setTermOrderStoreBase = setTermOrderStoreBase;
@@ -1419,6 +1514,7 @@ exports.termMaxConfig = termMaxConfig;
1419
1514
  exports.termMaxConfigByChain = termMaxConfigByChain;
1420
1515
  exports.termMaxLenderKey = termMaxLenderKey;
1421
1516
  exports.termOrderStoreBaseOverride = termOrderStoreBaseOverride;
1517
+ exports.tokenListCacheKey = tokenListCacheKey;
1422
1518
  exports.twyneBaseKey = twyneBaseKey;
1423
1519
  exports.twyneBeaconFor = twyneBeaconFor;
1424
1520
  exports.twyneChainData = twyneChainData;
package/dist/index.mjs CHANGED
@@ -1032,17 +1032,32 @@ var parseFraxlendPairKey = (lender) => {
1032
1032
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1033
1033
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1034
1034
  var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
1035
+ var STUCK_REVALIDATE_MS = 60 * 1e3;
1036
+ var MISSING_TTL_MS = FRESH_TTL_MS;
1035
1037
  var STATE_KEY = "__1delta_remote_cache__";
1036
1038
  function getState() {
1037
1039
  const g = globalThis;
1038
- if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map(), revalidating: /* @__PURE__ */ new Set() };
1040
+ if (!g[STATE_KEY]) {
1041
+ g[STATE_KEY] = {
1042
+ memo: /* @__PURE__ */ new Map(),
1043
+ inflight: /* @__PURE__ */ new Map(),
1044
+ revalidating: /* @__PURE__ */ new Map(),
1045
+ missing: /* @__PURE__ */ new Map(),
1046
+ loaders: /* @__PURE__ */ new Map()
1047
+ };
1048
+ }
1039
1049
  const state = g[STATE_KEY];
1040
- if (!state.revalidating) state.revalidating = /* @__PURE__ */ new Set();
1050
+ if (!(state.revalidating instanceof Map)) state.revalidating = /* @__PURE__ */ new Map();
1051
+ if (!state.missing) state.missing = /* @__PURE__ */ new Map();
1052
+ if (!state.loaders) state.loaders = /* @__PURE__ */ new Map();
1041
1053
  return state;
1042
1054
  }
1043
1055
  function setRemoteCacheStore(next) {
1044
1056
  getState().store = next;
1045
1057
  }
1058
+ function setRemoteCacheWaitUntil(next) {
1059
+ getState().waitUntil = next;
1060
+ }
1046
1061
  function setRemoteCacheGithubToken(token) {
1047
1062
  getState().githubToken = token;
1048
1063
  }
@@ -1050,7 +1065,11 @@ function clearRemoteCache() {
1050
1065
  const state = getState();
1051
1066
  state.memo.clear();
1052
1067
  state.inflight.clear();
1068
+ state.revalidating.clear();
1069
+ state.missing.clear();
1070
+ state.loaders.clear();
1053
1071
  }
1072
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1054
1073
  var RAW_HOST = "https://raw.githubusercontent.com/";
1055
1074
  function githubApiMirror(url) {
1056
1075
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1098,11 +1117,21 @@ async function readStore(key, isValid) {
1098
1117
  const { store } = getState();
1099
1118
  if (!store) return void 0;
1100
1119
  try {
1101
- const raw = await store.get(storeKey(key));
1120
+ let raw;
1121
+ let metaAt;
1122
+ if (store.getWithMetadata) {
1123
+ const got = await store.getWithMetadata(storeKey(key));
1124
+ raw = got.value;
1125
+ if (typeof got.metadata?.at === "number") metaAt = got.metadata.at;
1126
+ } else {
1127
+ raw = await store.get(storeKey(key));
1128
+ }
1102
1129
  if (!raw) return void 0;
1103
1130
  const parsed = JSON.parse(raw);
1104
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1105
- return parsed;
1131
+ if (!isValid(parsed?.value)) return void 0;
1132
+ const at = metaAt ?? parsed?.at;
1133
+ if (typeof at !== "number") return void 0;
1134
+ return { at, value: parsed.value };
1106
1135
  } catch {
1107
1136
  return void 0;
1108
1137
  }
@@ -1111,50 +1140,106 @@ async function writeStore(key, entry) {
1111
1140
  const { store } = getState();
1112
1141
  if (!store) return;
1113
1142
  try {
1114
- await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1143
+ const body = JSON.stringify(entry);
1144
+ if (store.putWithMetadata) {
1145
+ await store.putWithMetadata(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S, { at: entry.at });
1146
+ } else {
1147
+ await store.put(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S);
1148
+ }
1115
1149
  } catch {
1116
1150
  }
1117
1151
  }
1152
+ function background(promise) {
1153
+ const { waitUntil } = getState();
1154
+ if (!waitUntil) return;
1155
+ try {
1156
+ waitUntil(promise);
1157
+ } catch {
1158
+ }
1159
+ }
1160
+ function adopt(key, entry) {
1161
+ const { memo } = getState();
1162
+ const current = memo.get(key);
1163
+ if (!current || current.at <= entry.at) memo.set(key, entry);
1164
+ }
1165
+ var negativeCacheError = (entry) => new RemoteFetchError(entry.message, entry.status);
1166
+ function revalidate(key, loader, preferStore) {
1167
+ const state = getState();
1168
+ const existing = state.revalidating.get(key);
1169
+ if (existing && Date.now() - existing.startedAt < STUCK_REVALIDATE_MS) return existing.promise;
1170
+ const entry = { startedAt: Date.now(), promise: Promise.resolve(false) };
1171
+ entry.promise = (async () => {
1172
+ try {
1173
+ const mine = state.memo.get(key);
1174
+ if (preferStore && state.store) {
1175
+ const stored = await readStore(key, loader.isValid);
1176
+ if (stored && stored.at > (mine?.at ?? 0) && Date.now() - stored.at < FRESH_TTL_MS) {
1177
+ adopt(key, stored);
1178
+ return true;
1179
+ }
1180
+ }
1181
+ const value = await loader.load();
1182
+ const fresh = { at: Date.now(), value };
1183
+ adopt(key, fresh);
1184
+ state.missing.delete(key);
1185
+ await writeStore(key, fresh);
1186
+ return true;
1187
+ } catch {
1188
+ return false;
1189
+ } finally {
1190
+ if (state.revalidating.get(key) === entry) state.revalidating.delete(key);
1191
+ }
1192
+ })();
1193
+ state.revalidating.set(key, entry);
1194
+ return entry.promise;
1195
+ }
1118
1196
  async function loadCached(key, load, isValid) {
1119
- const { memo, inflight, revalidating } = getState();
1197
+ const state = getState();
1198
+ const { memo, inflight } = state;
1199
+ const loader = { load, isValid };
1200
+ state.loaders.set(key, loader);
1120
1201
  const cached = memo.get(key);
1121
- if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1202
+ if (cached) {
1203
+ const age = Date.now() - cached.at;
1204
+ if (age < FRESH_TTL_MS) return cached.value;
1205
+ if (age < STALE_WHILE_REVALIDATE_MS) {
1206
+ background(revalidate(key, loader, true));
1207
+ return cached.value;
1208
+ }
1209
+ }
1210
+ const missing = state.missing.get(key);
1211
+ if (missing && Date.now() - missing.at < MISSING_TTL_MS) throw negativeCacheError(missing);
1122
1212
  const pending = inflight.get(key);
1123
1213
  if (pending) return pending;
1124
1214
  const task = (async () => {
1125
1215
  const stored = await readStore(key, isValid);
1126
1216
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1127
- memo.set(key, stored);
1217
+ adopt(key, stored);
1128
1218
  return stored.value;
1129
1219
  }
1130
1220
  if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
1131
- memo.set(key, stored);
1132
- if (revalidating.has(key)) return stored.value;
1133
- revalidating.add(key);
1134
- void (async () => {
1135
- try {
1136
- const value = await load();
1137
- const entry = { at: Date.now(), value };
1138
- const current = memo.get(key);
1139
- if (!current || current.at <= entry.at) memo.set(key, entry);
1140
- await writeStore(key, entry);
1141
- } catch {
1142
- } finally {
1143
- revalidating.delete(key);
1144
- }
1145
- })();
1221
+ adopt(key, stored);
1222
+ background(revalidate(key, loader, false));
1146
1223
  return stored.value;
1147
1224
  }
1148
1225
  try {
1149
1226
  const value = await load();
1150
1227
  const entry = { at: Date.now(), value };
1151
- memo.set(key, entry);
1152
- await writeStore(key, entry);
1228
+ adopt(key, entry);
1229
+ state.missing.delete(key);
1230
+ const put = writeStore(key, entry);
1231
+ if (state.waitUntil) background(put);
1232
+ else await put;
1153
1233
  return value;
1154
1234
  } catch (error) {
1155
1235
  const stale = memo.get(key) ?? stored;
1156
- if (!stale) throw error;
1157
- memo.set(key, stale);
1236
+ if (!stale) {
1237
+ if (error instanceof RemoteFetchError && error.isMissing) {
1238
+ state.missing.set(key, { at: Date.now(), status: error.status, message: error.message });
1239
+ }
1240
+ throw error;
1241
+ }
1242
+ adopt(key, stale);
1158
1243
  return stale.value;
1159
1244
  }
1160
1245
  })().finally(() => {
@@ -1163,6 +1248,14 @@ async function loadCached(key, load, isValid) {
1163
1248
  inflight.set(key, task);
1164
1249
  return task;
1165
1250
  }
1251
+ async function refreshRemoteCache(key, opts = {}) {
1252
+ const state = getState();
1253
+ const loader = state.loaders.get(key);
1254
+ if (!loader) return "unknown";
1255
+ const cached = state.memo.get(key);
1256
+ if (cached && opts.maxAgeMs !== void 0 && Date.now() - cached.at < opts.maxAgeMs) return "fresh";
1257
+ return await revalidate(key, loader, false) ? "refreshed" : "failed";
1258
+ }
1166
1259
  async function loadCachedJson(key, url, isValid, label = key) {
1167
1260
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1168
1261
  }
@@ -1211,4 +1304,4 @@ async function fetchMainTokensMap() {
1211
1304
  }
1212
1305
  }
1213
1306
 
1214
- export { COOLER_ETHEREUM_CONFIG, CURVANCE_MONAD_CONFIG, FRAXLEND_ETHEREUM_CONFIG, GEARBOX_ACCOUNT_COMPRESSOR_V310, REMOTE_CACHE_STORE_TTL_S, RESUPPLY_ETHEREUM_CONFIG, RemoteFetchError, aaveOracles, aaveOraclesConfig, aavePools, aaveReserves, aaveTokens, aaveV4Oracles, aaveV4Peripherals, aaveV4SpokeLenderKey, aaveV4Spokes, aaveWethGateway, assertTwyneKeysUnique, chainLogoURI, chains, clearRemoteCache, compoundV2Oracles, compoundV2OraclesConfig, compoundV2Pools, compoundV2Reserves, compoundV2TokenArray, compoundV2Tokens, compoundV3BaseData, compoundV3Bulker, compoundV3OraclesData, compoundV3Pools, compoundV3Reserves, coolerConfig, coolerConfigFor, coolerLendersByChain, curvanceConfig, curvanceConfigFor, curvanceLendersByChain, curvancePositionManager, curvancePositionManagersFor, dolomiteChains, dolomiteConfigs, dolomiteEmode, dolomiteMarketIdByToken, dolomiteOdosExecutorShim, dssChainData, dssConfigFor, dssDebtJoin, dssDebtToken, dssMarketByIlk, eulerConfigs, eulerVaults, exactlyConfig, exactlyMarketByAddress, exactlyMarketByAsset, exactlyMarkets, exactlyMarketsByChain, fetchMainTokensMap, fetchRemoteJson, fetchTokenList, fetchTokenListFile, fetchTokenLists, findAaveV4PositionManager, fluidChains, fluidResolvers, fluidVaultEntries, fluidVaults, flyingTulipAssets, flyingTulipAssetsFor, flyingTulipConfig, flyingTulipConfigFor, flyingTulipLendersByChain, frankencoinChainData, frankencoinConfig, frankencoinConfigFor, frankencoinLendersByChain, frankencoinMarketByPosition, frankencoinMarkets, fraxlendConfig, fraxlendConfigFor, fraxlendLendersByChain, fraxlendPairKey, fraxlendPairsFor, gearboxAccountCompressor, gearboxAddressProviderV310, gearboxBotListV3, gearboxChains, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxResolvers, gearboxV3LenderKey, getAaveV4GatewayAddresses, getAaveV4PositionManagers, getAaveV4SpokeAccess, getAaveV4SpokeEntry, getAaveV4SpokePeripherals, getChainEntries, getSiloV2MarketEntry, getSiloV3MarketEntry, initConfig, initializeChainData, initializeLenderData, inverseChainData, inverseConfig, inverseConfigFor, inverseLendersByChain, inverseMarketByAddress, inverseMarkets, isAaveV4OperationRestricted, isListaSmartLpAsset, isMidnightCollateralRef, liquityBranchByIndex, liquityBranchByTroveManager, liquityBranchesByChain, liquityConfig, liquityConfigFor, liquityLendersByChain, liquityMarkets, listaCollateralProvider, listaCollateralProviders, listaNativeProvider, listaSmartLpByCollateral, llamaLendApiBaseUrl, llamaLendChainData, llamaLendConfig, llamaLendConfigFor, llamaLendLenderKey, llamaLendLendersByChain, llamaLendLeverageZap, llamaLendMarketByController, llamaLendMarketByVault, llamaLendMarkets, llamaLendSupportsDelegation, llamaLendSupportsLeverage, loadCached, loadCachedJson, midnightApiBaseOverride, midnightCollateralRef, midnightConfig, midnightLoanRef, midnightMarketById, midnightMarketIsEnterable, midnightMarkets, midnightMarketsByChain, midnightNotEnterableReason, morphoBundler3, morphoOracles, morphoPools, morphoTypeMarkets, morphoTypeOracles, morphoTypeVaults, parseAaveV4SpokeLenderKey, parseFraxlendPairKey, parseGearboxV3LenderKey, parseLlamaLendLenderKey, parseMidnightRef, parseSiloV2LenderKey, parseSiloV3LenderKey, parseTermMaxLenderKey, resolveMidnightApiBase, resolveTenorApiUrl, resolveTermApiBase, resolveTermOrderStoreBase, resupplyConfig, resupplyConfigFor, resupplyLendersByChain, riverChainData, riverConfig, riverConfigFor, riverLendersByChain, riverMarketByIndex, riverMarkets, setMidnightApiBase, setRemoteCacheGithubToken, setRemoteCacheStore, setTenorApiUrl, setTermApiBase, setTermOrderStoreBase, siloMarkets, siloMarketsV3, siloPeripherals, siloPeripheralsV3, siloV2LenderKey, siloV3LenderKey, skyChainData, skyConfig, skyConfigFor, skyLendersByChain, skyMarkets, tellerConfig, tellerConfigByChain, tellerPoolByAddress, tellerPools, tellerPoolsByChain, tenorApiUrlOverride, termApiBaseOverride, termConfig, termMarketById, termMarkets, termMarketsByChain, termMaxApiBaseUrl, termMaxChainIds, termMaxConfig, termMaxConfigByChain, termMaxLenderKey, termOrderStoreBaseOverride, twyneBaseKey, twyneBeaconFor, twyneChainData, twyneConfig, twyneConfigFor, twyneLendersByChain, twyneMarketByKey, twyneMarketFor, twyneMarketKey, twyneMarkets, twyneMarketsForIntermediateVault, twyneOperator, usddChainData, usddConfig, usddConfigFor, usddLendersByChain, usddMarketByIlk, usddMarkets };
1307
+ export { COOLER_ETHEREUM_CONFIG, CURVANCE_MONAD_CONFIG, FRAXLEND_ETHEREUM_CONFIG, GEARBOX_ACCOUNT_COMPRESSOR_V310, REMOTE_CACHE_STORE_TTL_S, RESUPPLY_ETHEREUM_CONFIG, RemoteFetchError, aaveOracles, aaveOraclesConfig, aavePools, aaveReserves, aaveTokens, aaveV4Oracles, aaveV4Peripherals, aaveV4SpokeLenderKey, aaveV4Spokes, aaveWethGateway, assertTwyneKeysUnique, chainLogoURI, chains, clearRemoteCache, compoundV2Oracles, compoundV2OraclesConfig, compoundV2Pools, compoundV2Reserves, compoundV2TokenArray, compoundV2Tokens, compoundV3BaseData, compoundV3Bulker, compoundV3OraclesData, compoundV3Pools, compoundV3Reserves, coolerConfig, coolerConfigFor, coolerLendersByChain, curvanceConfig, curvanceConfigFor, curvanceLendersByChain, curvancePositionManager, curvancePositionManagersFor, dolomiteChains, dolomiteConfigs, dolomiteEmode, dolomiteMarketIdByToken, dolomiteOdosExecutorShim, dssChainData, dssConfigFor, dssDebtJoin, dssDebtToken, dssMarketByIlk, eulerConfigs, eulerVaults, exactlyConfig, exactlyMarketByAddress, exactlyMarketByAsset, exactlyMarkets, exactlyMarketsByChain, fetchMainTokensMap, fetchRemoteJson, fetchTokenList, fetchTokenListFile, fetchTokenLists, findAaveV4PositionManager, fluidChains, fluidResolvers, fluidVaultEntries, fluidVaults, flyingTulipAssets, flyingTulipAssetsFor, flyingTulipConfig, flyingTulipConfigFor, flyingTulipLendersByChain, frankencoinChainData, frankencoinConfig, frankencoinConfigFor, frankencoinLendersByChain, frankencoinMarketByPosition, frankencoinMarkets, fraxlendConfig, fraxlendConfigFor, fraxlendLendersByChain, fraxlendPairKey, fraxlendPairsFor, gearboxAccountCompressor, gearboxAddressProviderV310, gearboxBotListV3, gearboxChains, gearboxMarketCompressor, gearboxMarketConfigurators, gearboxResolvers, gearboxV3LenderKey, getAaveV4GatewayAddresses, getAaveV4PositionManagers, getAaveV4SpokeAccess, getAaveV4SpokeEntry, getAaveV4SpokePeripherals, getChainEntries, getSiloV2MarketEntry, getSiloV3MarketEntry, initConfig, initializeChainData, initializeLenderData, inverseChainData, inverseConfig, inverseConfigFor, inverseLendersByChain, inverseMarketByAddress, inverseMarkets, isAaveV4OperationRestricted, isListaSmartLpAsset, isMidnightCollateralRef, liquityBranchByIndex, liquityBranchByTroveManager, liquityBranchesByChain, liquityConfig, liquityConfigFor, liquityLendersByChain, liquityMarkets, listaCollateralProvider, listaCollateralProviders, listaNativeProvider, listaSmartLpByCollateral, llamaLendApiBaseUrl, llamaLendChainData, llamaLendConfig, llamaLendConfigFor, llamaLendLenderKey, llamaLendLendersByChain, llamaLendLeverageZap, llamaLendMarketByController, llamaLendMarketByVault, llamaLendMarkets, llamaLendSupportsDelegation, llamaLendSupportsLeverage, loadCached, loadCachedJson, midnightApiBaseOverride, midnightCollateralRef, midnightConfig, midnightLoanRef, midnightMarketById, midnightMarketIsEnterable, midnightMarkets, midnightMarketsByChain, midnightNotEnterableReason, morphoBundler3, morphoOracles, morphoPools, morphoTypeMarkets, morphoTypeOracles, morphoTypeVaults, parseAaveV4SpokeLenderKey, parseFraxlendPairKey, parseGearboxV3LenderKey, parseLlamaLendLenderKey, parseMidnightRef, parseSiloV2LenderKey, parseSiloV3LenderKey, parseTermMaxLenderKey, refreshRemoteCache, resolveMidnightApiBase, resolveTenorApiUrl, resolveTermApiBase, resolveTermOrderStoreBase, resupplyConfig, resupplyConfigFor, resupplyLendersByChain, riverChainData, riverConfig, riverConfigFor, riverLendersByChain, riverMarketByIndex, riverMarkets, setMidnightApiBase, setRemoteCacheGithubToken, setRemoteCacheStore, setRemoteCacheWaitUntil, setTenorApiUrl, setTermApiBase, setTermOrderStoreBase, siloMarkets, siloMarketsV3, siloPeripherals, siloPeripheralsV3, siloV2LenderKey, siloV3LenderKey, skyChainData, skyConfig, skyConfigFor, skyLendersByChain, skyMarkets, tellerConfig, tellerConfigByChain, tellerPoolByAddress, tellerPools, tellerPoolsByChain, tenorApiUrlOverride, termApiBaseOverride, termConfig, termMarketById, termMarkets, termMarketsByChain, termMaxApiBaseUrl, termMaxChainIds, termMaxConfig, termMaxConfigByChain, termMaxLenderKey, termOrderStoreBaseOverride, tokenListCacheKey, twyneBaseKey, twyneBeaconFor, twyneChainData, twyneConfig, twyneConfigFor, twyneLendersByChain, twyneMarketByKey, twyneMarketFor, twyneMarketKey, twyneMarkets, twyneMarketsForIntermediateVault, twyneOperator, usddChainData, usddConfig, usddConfigFor, usddLendersByChain, usddMarketByIlk, usddMarkets };
@@ -11,21 +11,70 @@
11
11
  * These files change on the order of days, so serving a stale copy is strictly
12
12
  * better than failing — hence the stale-on-error fallback, which is the part
13
13
  * that actually survives an outage.
14
+ *
15
+ * Read order, and what each layer is for:
16
+ *
17
+ * memory — served IMMEDIATELY whenever a copy exists and is inside the SWR
18
+ * window; older than `FRESH_TTL_MS` it also kicks a background
19
+ * refresh. The request path never awaits the store or the network
20
+ * while memory is warm — a 6.66 MB token list re-read from KV every
21
+ * five minutes used to land on whichever user request crossed the
22
+ * boundary.
23
+ * store — a KV namespace in the workers. Consulted only when memory has
24
+ * nothing usable (a cold isolate), so a fresh isolate never touches
25
+ * GitHub at all.
26
+ * network — raw.githubusercontent.com, then the api.github.com mirror.
14
27
  */
15
28
  /** How long a persisted copy is kept around purely as the stale fallback. */
16
29
  export declare const REMOTE_CACHE_STORE_TTL_S: number;
30
+ /** What the store carries next to a value, when it can carry anything. */
31
+ export interface RemoteCacheEntryMetadata {
32
+ /** Epoch ms of the upstream fetch that produced the value. */
33
+ at: number;
34
+ }
17
35
  /**
18
36
  * Durable backing store — a Cloudflare KV namespace in the workers, absent
19
37
  * everywhere else. Without one the cache is per-process, which still collapses
20
38
  * N requests into one upstream fetch but cannot help a cold start; with one, a
21
39
  * fresh isolate never touches GitHub at all.
40
+ *
41
+ * The two `*WithMetadata` members are optional. When present, the fetch
42
+ * timestamp is written as store METADATA as well as inside the value, so a
43
+ * store that can list keys with their metadata (KV's `list()` can) can answer
44
+ * "how old is this entry?" without transferring a multi-MB value. Note that
45
+ * KV's `getWithMetadata` still returns the value — only `list` is
46
+ * metadata-only — so reads through this interface do not get cheaper; what
47
+ * the metadata buys is an inspectable freshness signal for operators and for
48
+ * a future probe that stays off the value read.
22
49
  */
23
50
  export interface RemoteCacheStore {
24
51
  get(key: string): Promise<string | null>;
25
52
  put(key: string, value: string, ttlSeconds: number): Promise<void>;
53
+ getWithMetadata?(key: string): Promise<{
54
+ value: string | null;
55
+ metadata: RemoteCacheEntryMetadata | null;
56
+ }>;
57
+ putWithMetadata?(key: string, value: string, ttlSeconds: number, metadata: RemoteCacheEntryMetadata): Promise<void>;
26
58
  }
59
+ /** `ctx.waitUntil`-shaped hook: keeps a background promise alive post-response. */
60
+ export type RemoteCacheWaitUntil = (promise: Promise<unknown>) => void;
27
61
  /** Installs (or with `undefined`, removes) the durable store. */
28
62
  export declare function setRemoteCacheStore(next: RemoteCacheStore | undefined): void;
63
+ /**
64
+ * Installs (or with `undefined`, removes) the hook that keeps background work
65
+ * alive after a response — `ctx.waitUntil` on Cloudflare Workers.
66
+ *
67
+ * The LATEST hook wins. Workers hand each request its own context, and a
68
+ * module-level slot can only hold one, so a refresh kicked by request A may be
69
+ * registered against request B's context if B arrived in between. That is
70
+ * harmless in the direction that matters (B's context keeps A's refresh alive
71
+ * at least as long as B runs) and the stuck guard covers the other direction
72
+ * (a context that closed before the refresh settled). The hook is called under
73
+ * try/catch for the same reason: a closed context may throw.
74
+ *
75
+ * Without a hook (Node, tests) background work is simply detached.
76
+ */
77
+ export declare function setRemoteCacheWaitUntil(next: RemoteCacheWaitUntil | undefined): void;
29
78
  /**
30
79
  * Optional GitHub token for the API mirror below. Unauthenticated it allows 60
31
80
  * requests/hour per IP, which is thin for a shared Worker egress; a token
@@ -34,6 +83,12 @@ export declare function setRemoteCacheStore(next: RemoteCacheStore | undefined):
34
83
  export declare function setRemoteCacheGithubToken(token: string | undefined): void;
35
84
  /** Drops every cached value. Intended for tests. */
36
85
  export declare function clearRemoteCache(): void;
86
+ /**
87
+ * The remote-cache key of a chain's token list. `tokens.ts` builds the same
88
+ * string inline; this is exported so a pre-warm can name the key without
89
+ * importing the loader.
90
+ */
91
+ export declare const tokenListCacheKey: (chainId: string) => string;
37
92
  /**
38
93
  * A non-2xx from the remote. Carries the ORIGIN status so a caller can tell a
39
94
  * settled answer (404/410 — the file is not published) from a transient one
@@ -57,6 +112,10 @@ export declare function fetchRemoteJson<T>(url: string, label: string): Promise<
57
112
  * failed `load()` falls back to whatever stale copy either layer holds, only
58
113
  * rethrowing when there is nothing to fall back to.
59
114
  *
115
+ * Memory is served WITHOUT awaiting anything for as long as the copy is inside
116
+ * the SWR window; past `FRESH_TTL_MS` a refresh runs behind it. The store is
117
+ * consulted only when memory has nothing inside that window.
118
+ *
60
119
  * Concurrent callers for the same key share one in-flight load.
61
120
  *
62
121
  * `isValid` guards the durable layer: a truncated or shape-changed entry is
@@ -64,5 +123,28 @@ export declare function fetchRemoteJson<T>(url: string, label: string): Promise<
64
123
  * mis-decimals every balance built from it.
65
124
  */
66
125
  export declare function loadCached<T>(key: string, load: () => Promise<T>, isValid: (value: any) => boolean): Promise<T>;
126
+ /** Outcome of {@link refreshRemoteCache}. */
127
+ export type RemoteCacheRefreshResult =
128
+ /** Upstream was fetched and the store written. */
129
+ 'refreshed'
130
+ /** The memory copy was younger than `maxAgeMs`; nothing was done. */
131
+ | 'fresh'
132
+ /** Upstream failed; the previous copy (if any) is still served. */
133
+ | 'failed'
134
+ /** Nothing has loaded this key in this process, so there is no loader to run. */
135
+ | 'unknown';
136
+ /**
137
+ * Forces a refresh of `key` from UPSTREAM (never the store), and awaits the
138
+ * store write. For a pre-warm job: the routine path only refreshes once a
139
+ * copy is `FRESH_TTL_MS` old, so a job that runs more often than that would
140
+ * otherwise never write anything.
141
+ *
142
+ * `maxAgeMs` skips the work when the memory copy is younger than that — so a
143
+ * job that has just loaded the key cold does not immediately fetch it again.
144
+ * Joins an in-flight refresh rather than starting a second one.
145
+ */
146
+ export declare function refreshRemoteCache(key: string, opts?: {
147
+ maxAgeMs?: number;
148
+ }): Promise<RemoteCacheRefreshResult>;
67
149
  /** `loadCached` over a JSON URL, failing on any non-2xx so stale can take over. */
68
150
  export declare function loadCachedJson<T>(key: string, url: string, isValid: (value: any) => boolean, label?: string): Promise<T>;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "version": "0.0.56",
7
+ "version": "0.0.57",
8
8
  "description": "Hold and initialize lending protocol data across a stack",
9
9
  "files": [
10
10
  "dist"