@1delta/data-sdk 0.0.56 → 0.0.58

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
@@ -105,6 +105,7 @@ var LENDER_DATA_DEFAULTS = () => ({
105
105
  eulerVaults: {},
106
106
  dolomiteConfigs: {},
107
107
  dolomiteEmode: {},
108
+ dolomiteIsolation: {},
108
109
  siloMarkets: {},
109
110
  siloPeripherals: {},
110
111
  siloMarketsV3: {},
@@ -298,9 +299,24 @@ function parseGearboxV3LenderKey(lenderKey) {
298
299
 
299
300
  // src/lending/dolomite.ts
300
301
  var dolomiteConfigs = () => getGlobalData2()?.dolomiteConfigs;
302
+ var DOLOMITE_ROUTER_DEFAULTS = {
303
+ depositWithdrawalRouter: "0xf8b2c637A68cF6A17b1DF9F8992EeBeFf63d2dFf",
304
+ borrowPositionRouter: "0xF579b345cdA0860668b857De10ABD62442133D0F",
305
+ genericTraderRouter: "0x7b61CbA306CfdB02493b94757143132B1b72Bc6b"
306
+ };
307
+ var dolomiteRouters = (chainId) => {
308
+ const cfg = getGlobalData2()?.dolomiteConfigs?.[chainId];
309
+ return {
310
+ depositWithdrawalRouter: cfg?.depositWithdrawalRouter ?? DOLOMITE_ROUTER_DEFAULTS.depositWithdrawalRouter,
311
+ borrowPositionRouter: cfg?.borrowPositionRouter ?? DOLOMITE_ROUTER_DEFAULTS.borrowPositionRouter,
312
+ genericTraderRouter: cfg?.genericTraderRouter ?? DOLOMITE_ROUTER_DEFAULTS.genericTraderRouter
313
+ };
314
+ };
301
315
  var dolomiteOdosExecutorShim = (chainId) => getGlobalData2()?.dolomiteConfigs?.[chainId]?.odosExecutor;
302
316
  var dolomiteChains = () => Object.keys(getGlobalData2()?.dolomiteConfigs ?? {});
303
317
  var dolomiteEmode = () => getGlobalData2()?.dolomiteEmode;
318
+ var dolomiteIsolation = () => getGlobalData2()?.dolomiteIsolation;
319
+ var dolomiteIsolationMarket = (chainId, marketId) => getGlobalData2()?.dolomiteIsolation?.[chainId]?.[String(marketId)];
304
320
  var dolomiteMarketIdByToken = (chainId, token) => {
305
321
  const markets = getGlobalData2()?.dolomiteConfigs?.[chainId]?.markets;
306
322
  if (!markets) return void 0;
@@ -1034,17 +1050,32 @@ var parseFraxlendPairKey = (lender) => {
1034
1050
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1035
1051
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1036
1052
  var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
1053
+ var STUCK_REVALIDATE_MS = 60 * 1e3;
1054
+ var MISSING_TTL_MS = FRESH_TTL_MS;
1037
1055
  var STATE_KEY = "__1delta_remote_cache__";
1038
1056
  function getState() {
1039
1057
  const g = globalThis;
1040
- if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map(), revalidating: /* @__PURE__ */ new Set() };
1058
+ if (!g[STATE_KEY]) {
1059
+ g[STATE_KEY] = {
1060
+ memo: /* @__PURE__ */ new Map(),
1061
+ inflight: /* @__PURE__ */ new Map(),
1062
+ revalidating: /* @__PURE__ */ new Map(),
1063
+ missing: /* @__PURE__ */ new Map(),
1064
+ loaders: /* @__PURE__ */ new Map()
1065
+ };
1066
+ }
1041
1067
  const state = g[STATE_KEY];
1042
- if (!state.revalidating) state.revalidating = /* @__PURE__ */ new Set();
1068
+ if (!(state.revalidating instanceof Map)) state.revalidating = /* @__PURE__ */ new Map();
1069
+ if (!state.missing) state.missing = /* @__PURE__ */ new Map();
1070
+ if (!state.loaders) state.loaders = /* @__PURE__ */ new Map();
1043
1071
  return state;
1044
1072
  }
1045
1073
  function setRemoteCacheStore(next) {
1046
1074
  getState().store = next;
1047
1075
  }
1076
+ function setRemoteCacheWaitUntil(next) {
1077
+ getState().waitUntil = next;
1078
+ }
1048
1079
  function setRemoteCacheGithubToken(token) {
1049
1080
  getState().githubToken = token;
1050
1081
  }
@@ -1052,7 +1083,11 @@ function clearRemoteCache() {
1052
1083
  const state = getState();
1053
1084
  state.memo.clear();
1054
1085
  state.inflight.clear();
1086
+ state.revalidating.clear();
1087
+ state.missing.clear();
1088
+ state.loaders.clear();
1055
1089
  }
1090
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1056
1091
  var RAW_HOST = "https://raw.githubusercontent.com/";
1057
1092
  function githubApiMirror(url) {
1058
1093
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1100,11 +1135,21 @@ async function readStore(key, isValid) {
1100
1135
  const { store } = getState();
1101
1136
  if (!store) return void 0;
1102
1137
  try {
1103
- const raw = await store.get(storeKey(key));
1138
+ let raw;
1139
+ let metaAt;
1140
+ if (store.getWithMetadata) {
1141
+ const got = await store.getWithMetadata(storeKey(key));
1142
+ raw = got.value;
1143
+ if (typeof got.metadata?.at === "number") metaAt = got.metadata.at;
1144
+ } else {
1145
+ raw = await store.get(storeKey(key));
1146
+ }
1104
1147
  if (!raw) return void 0;
1105
1148
  const parsed = JSON.parse(raw);
1106
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1107
- return parsed;
1149
+ if (!isValid(parsed?.value)) return void 0;
1150
+ const at = metaAt ?? parsed?.at;
1151
+ if (typeof at !== "number") return void 0;
1152
+ return { at, value: parsed.value };
1108
1153
  } catch {
1109
1154
  return void 0;
1110
1155
  }
@@ -1113,50 +1158,106 @@ async function writeStore(key, entry) {
1113
1158
  const { store } = getState();
1114
1159
  if (!store) return;
1115
1160
  try {
1116
- await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1161
+ const body = JSON.stringify(entry);
1162
+ if (store.putWithMetadata) {
1163
+ await store.putWithMetadata(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S, { at: entry.at });
1164
+ } else {
1165
+ await store.put(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S);
1166
+ }
1167
+ } catch {
1168
+ }
1169
+ }
1170
+ function background(promise) {
1171
+ const { waitUntil } = getState();
1172
+ if (!waitUntil) return;
1173
+ try {
1174
+ waitUntil(promise);
1117
1175
  } catch {
1118
1176
  }
1119
1177
  }
1178
+ function adopt(key, entry) {
1179
+ const { memo } = getState();
1180
+ const current = memo.get(key);
1181
+ if (!current || current.at <= entry.at) memo.set(key, entry);
1182
+ }
1183
+ var negativeCacheError = (entry) => new RemoteFetchError(entry.message, entry.status);
1184
+ function revalidate(key, loader, preferStore) {
1185
+ const state = getState();
1186
+ const existing = state.revalidating.get(key);
1187
+ if (existing && Date.now() - existing.startedAt < STUCK_REVALIDATE_MS) return existing.promise;
1188
+ const entry = { startedAt: Date.now(), promise: Promise.resolve(false) };
1189
+ entry.promise = (async () => {
1190
+ try {
1191
+ const mine = state.memo.get(key);
1192
+ if (preferStore && state.store) {
1193
+ const stored = await readStore(key, loader.isValid);
1194
+ if (stored && stored.at > (mine?.at ?? 0) && Date.now() - stored.at < FRESH_TTL_MS) {
1195
+ adopt(key, stored);
1196
+ return true;
1197
+ }
1198
+ }
1199
+ const value = await loader.load();
1200
+ const fresh = { at: Date.now(), value };
1201
+ adopt(key, fresh);
1202
+ state.missing.delete(key);
1203
+ await writeStore(key, fresh);
1204
+ return true;
1205
+ } catch {
1206
+ return false;
1207
+ } finally {
1208
+ if (state.revalidating.get(key) === entry) state.revalidating.delete(key);
1209
+ }
1210
+ })();
1211
+ state.revalidating.set(key, entry);
1212
+ return entry.promise;
1213
+ }
1120
1214
  async function loadCached(key, load, isValid) {
1121
- const { memo, inflight, revalidating } = getState();
1215
+ const state = getState();
1216
+ const { memo, inflight } = state;
1217
+ const loader = { load, isValid };
1218
+ state.loaders.set(key, loader);
1122
1219
  const cached = memo.get(key);
1123
- if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1220
+ if (cached) {
1221
+ const age = Date.now() - cached.at;
1222
+ if (age < FRESH_TTL_MS) return cached.value;
1223
+ if (age < STALE_WHILE_REVALIDATE_MS) {
1224
+ background(revalidate(key, loader, true));
1225
+ return cached.value;
1226
+ }
1227
+ }
1228
+ const missing = state.missing.get(key);
1229
+ if (missing && Date.now() - missing.at < MISSING_TTL_MS) throw negativeCacheError(missing);
1124
1230
  const pending = inflight.get(key);
1125
1231
  if (pending) return pending;
1126
1232
  const task = (async () => {
1127
1233
  const stored = await readStore(key, isValid);
1128
1234
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1129
- memo.set(key, stored);
1235
+ adopt(key, stored);
1130
1236
  return stored.value;
1131
1237
  }
1132
1238
  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
- })();
1239
+ adopt(key, stored);
1240
+ background(revalidate(key, loader, false));
1148
1241
  return stored.value;
1149
1242
  }
1150
1243
  try {
1151
1244
  const value = await load();
1152
1245
  const entry = { at: Date.now(), value };
1153
- memo.set(key, entry);
1154
- await writeStore(key, entry);
1246
+ adopt(key, entry);
1247
+ state.missing.delete(key);
1248
+ const put = writeStore(key, entry);
1249
+ if (state.waitUntil) background(put);
1250
+ else await put;
1155
1251
  return value;
1156
1252
  } catch (error) {
1157
1253
  const stale = memo.get(key) ?? stored;
1158
- if (!stale) throw error;
1159
- memo.set(key, stale);
1254
+ if (!stale) {
1255
+ if (error instanceof RemoteFetchError && error.isMissing) {
1256
+ state.missing.set(key, { at: Date.now(), status: error.status, message: error.message });
1257
+ }
1258
+ throw error;
1259
+ }
1260
+ adopt(key, stale);
1160
1261
  return stale.value;
1161
1262
  }
1162
1263
  })().finally(() => {
@@ -1165,6 +1266,14 @@ async function loadCached(key, load, isValid) {
1165
1266
  inflight.set(key, task);
1166
1267
  return task;
1167
1268
  }
1269
+ async function refreshRemoteCache(key, opts = {}) {
1270
+ const state = getState();
1271
+ const loader = state.loaders.get(key);
1272
+ if (!loader) return "unknown";
1273
+ const cached = state.memo.get(key);
1274
+ if (cached && opts.maxAgeMs !== void 0 && Date.now() - cached.at < opts.maxAgeMs) return "fresh";
1275
+ return await revalidate(key, loader, false) ? "refreshed" : "failed";
1276
+ }
1168
1277
  async function loadCachedJson(key, url, isValid, label = key) {
1169
1278
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1170
1279
  }
@@ -1215,6 +1324,7 @@ async function fetchMainTokensMap() {
1215
1324
 
1216
1325
  exports.COOLER_ETHEREUM_CONFIG = COOLER_ETHEREUM_CONFIG;
1217
1326
  exports.CURVANCE_MONAD_CONFIG = CURVANCE_MONAD_CONFIG;
1327
+ exports.DOLOMITE_ROUTER_DEFAULTS = DOLOMITE_ROUTER_DEFAULTS;
1218
1328
  exports.FRAXLEND_ETHEREUM_CONFIG = FRAXLEND_ETHEREUM_CONFIG;
1219
1329
  exports.GEARBOX_ACCOUNT_COMPRESSOR_V310 = GEARBOX_ACCOUNT_COMPRESSOR_V310;
1220
1330
  exports.REMOTE_CACHE_STORE_TTL_S = REMOTE_CACHE_STORE_TTL_S;
@@ -1256,8 +1366,11 @@ exports.curvancePositionManagersFor = curvancePositionManagersFor;
1256
1366
  exports.dolomiteChains = dolomiteChains;
1257
1367
  exports.dolomiteConfigs = dolomiteConfigs;
1258
1368
  exports.dolomiteEmode = dolomiteEmode;
1369
+ exports.dolomiteIsolation = dolomiteIsolation;
1370
+ exports.dolomiteIsolationMarket = dolomiteIsolationMarket;
1259
1371
  exports.dolomiteMarketIdByToken = dolomiteMarketIdByToken;
1260
1372
  exports.dolomiteOdosExecutorShim = dolomiteOdosExecutorShim;
1373
+ exports.dolomiteRouters = dolomiteRouters;
1261
1374
  exports.dssChainData = dssChainData;
1262
1375
  exports.dssConfigFor = dssConfigFor;
1263
1376
  exports.dssDebtJoin = dssDebtJoin;
@@ -1372,6 +1485,7 @@ exports.parseMidnightRef = parseMidnightRef;
1372
1485
  exports.parseSiloV2LenderKey = parseSiloV2LenderKey;
1373
1486
  exports.parseSiloV3LenderKey = parseSiloV3LenderKey;
1374
1487
  exports.parseTermMaxLenderKey = parseTermMaxLenderKey;
1488
+ exports.refreshRemoteCache = refreshRemoteCache;
1375
1489
  exports.resolveMidnightApiBase = resolveMidnightApiBase;
1376
1490
  exports.resolveTenorApiUrl = resolveTenorApiUrl;
1377
1491
  exports.resolveTermApiBase = resolveTermApiBase;
@@ -1388,6 +1502,7 @@ exports.riverMarkets = riverMarkets;
1388
1502
  exports.setMidnightApiBase = setMidnightApiBase;
1389
1503
  exports.setRemoteCacheGithubToken = setRemoteCacheGithubToken;
1390
1504
  exports.setRemoteCacheStore = setRemoteCacheStore;
1505
+ exports.setRemoteCacheWaitUntil = setRemoteCacheWaitUntil;
1391
1506
  exports.setTenorApiUrl = setTenorApiUrl;
1392
1507
  exports.setTermApiBase = setTermApiBase;
1393
1508
  exports.setTermOrderStoreBase = setTermOrderStoreBase;
@@ -1419,6 +1534,7 @@ exports.termMaxConfig = termMaxConfig;
1419
1534
  exports.termMaxConfigByChain = termMaxConfigByChain;
1420
1535
  exports.termMaxLenderKey = termMaxLenderKey;
1421
1536
  exports.termOrderStoreBaseOverride = termOrderStoreBaseOverride;
1537
+ exports.tokenListCacheKey = tokenListCacheKey;
1422
1538
  exports.twyneBaseKey = twyneBaseKey;
1423
1539
  exports.twyneBeaconFor = twyneBeaconFor;
1424
1540
  exports.twyneChainData = twyneChainData;
package/dist/index.mjs CHANGED
@@ -103,6 +103,7 @@ var LENDER_DATA_DEFAULTS = () => ({
103
103
  eulerVaults: {},
104
104
  dolomiteConfigs: {},
105
105
  dolomiteEmode: {},
106
+ dolomiteIsolation: {},
106
107
  siloMarkets: {},
107
108
  siloPeripherals: {},
108
109
  siloMarketsV3: {},
@@ -296,9 +297,24 @@ function parseGearboxV3LenderKey(lenderKey) {
296
297
 
297
298
  // src/lending/dolomite.ts
298
299
  var dolomiteConfigs = () => getGlobalData2()?.dolomiteConfigs;
300
+ var DOLOMITE_ROUTER_DEFAULTS = {
301
+ depositWithdrawalRouter: "0xf8b2c637A68cF6A17b1DF9F8992EeBeFf63d2dFf",
302
+ borrowPositionRouter: "0xF579b345cdA0860668b857De10ABD62442133D0F",
303
+ genericTraderRouter: "0x7b61CbA306CfdB02493b94757143132B1b72Bc6b"
304
+ };
305
+ var dolomiteRouters = (chainId) => {
306
+ const cfg = getGlobalData2()?.dolomiteConfigs?.[chainId];
307
+ return {
308
+ depositWithdrawalRouter: cfg?.depositWithdrawalRouter ?? DOLOMITE_ROUTER_DEFAULTS.depositWithdrawalRouter,
309
+ borrowPositionRouter: cfg?.borrowPositionRouter ?? DOLOMITE_ROUTER_DEFAULTS.borrowPositionRouter,
310
+ genericTraderRouter: cfg?.genericTraderRouter ?? DOLOMITE_ROUTER_DEFAULTS.genericTraderRouter
311
+ };
312
+ };
299
313
  var dolomiteOdosExecutorShim = (chainId) => getGlobalData2()?.dolomiteConfigs?.[chainId]?.odosExecutor;
300
314
  var dolomiteChains = () => Object.keys(getGlobalData2()?.dolomiteConfigs ?? {});
301
315
  var dolomiteEmode = () => getGlobalData2()?.dolomiteEmode;
316
+ var dolomiteIsolation = () => getGlobalData2()?.dolomiteIsolation;
317
+ var dolomiteIsolationMarket = (chainId, marketId) => getGlobalData2()?.dolomiteIsolation?.[chainId]?.[String(marketId)];
302
318
  var dolomiteMarketIdByToken = (chainId, token) => {
303
319
  const markets = getGlobalData2()?.dolomiteConfigs?.[chainId]?.markets;
304
320
  if (!markets) return void 0;
@@ -1032,17 +1048,32 @@ var parseFraxlendPairKey = (lender) => {
1032
1048
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1033
1049
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1034
1050
  var REMOTE_CACHE_STORE_TTL_S = 7 * 24 * 60 * 60;
1051
+ var STUCK_REVALIDATE_MS = 60 * 1e3;
1052
+ var MISSING_TTL_MS = FRESH_TTL_MS;
1035
1053
  var STATE_KEY = "__1delta_remote_cache__";
1036
1054
  function getState() {
1037
1055
  const g = globalThis;
1038
- if (!g[STATE_KEY]) g[STATE_KEY] = { memo: /* @__PURE__ */ new Map(), inflight: /* @__PURE__ */ new Map(), revalidating: /* @__PURE__ */ new Set() };
1056
+ if (!g[STATE_KEY]) {
1057
+ g[STATE_KEY] = {
1058
+ memo: /* @__PURE__ */ new Map(),
1059
+ inflight: /* @__PURE__ */ new Map(),
1060
+ revalidating: /* @__PURE__ */ new Map(),
1061
+ missing: /* @__PURE__ */ new Map(),
1062
+ loaders: /* @__PURE__ */ new Map()
1063
+ };
1064
+ }
1039
1065
  const state = g[STATE_KEY];
1040
- if (!state.revalidating) state.revalidating = /* @__PURE__ */ new Set();
1066
+ if (!(state.revalidating instanceof Map)) state.revalidating = /* @__PURE__ */ new Map();
1067
+ if (!state.missing) state.missing = /* @__PURE__ */ new Map();
1068
+ if (!state.loaders) state.loaders = /* @__PURE__ */ new Map();
1041
1069
  return state;
1042
1070
  }
1043
1071
  function setRemoteCacheStore(next) {
1044
1072
  getState().store = next;
1045
1073
  }
1074
+ function setRemoteCacheWaitUntil(next) {
1075
+ getState().waitUntil = next;
1076
+ }
1046
1077
  function setRemoteCacheGithubToken(token) {
1047
1078
  getState().githubToken = token;
1048
1079
  }
@@ -1050,7 +1081,11 @@ function clearRemoteCache() {
1050
1081
  const state = getState();
1051
1082
  state.memo.clear();
1052
1083
  state.inflight.clear();
1084
+ state.revalidating.clear();
1085
+ state.missing.clear();
1086
+ state.loaders.clear();
1053
1087
  }
1088
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1054
1089
  var RAW_HOST = "https://raw.githubusercontent.com/";
1055
1090
  function githubApiMirror(url) {
1056
1091
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1098,11 +1133,21 @@ async function readStore(key, isValid) {
1098
1133
  const { store } = getState();
1099
1134
  if (!store) return void 0;
1100
1135
  try {
1101
- const raw = await store.get(storeKey(key));
1136
+ let raw;
1137
+ let metaAt;
1138
+ if (store.getWithMetadata) {
1139
+ const got = await store.getWithMetadata(storeKey(key));
1140
+ raw = got.value;
1141
+ if (typeof got.metadata?.at === "number") metaAt = got.metadata.at;
1142
+ } else {
1143
+ raw = await store.get(storeKey(key));
1144
+ }
1102
1145
  if (!raw) return void 0;
1103
1146
  const parsed = JSON.parse(raw);
1104
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1105
- return parsed;
1147
+ if (!isValid(parsed?.value)) return void 0;
1148
+ const at = metaAt ?? parsed?.at;
1149
+ if (typeof at !== "number") return void 0;
1150
+ return { at, value: parsed.value };
1106
1151
  } catch {
1107
1152
  return void 0;
1108
1153
  }
@@ -1111,50 +1156,106 @@ async function writeStore(key, entry) {
1111
1156
  const { store } = getState();
1112
1157
  if (!store) return;
1113
1158
  try {
1114
- await store.put(storeKey(key), JSON.stringify(entry), REMOTE_CACHE_STORE_TTL_S);
1159
+ const body = JSON.stringify(entry);
1160
+ if (store.putWithMetadata) {
1161
+ await store.putWithMetadata(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S, { at: entry.at });
1162
+ } else {
1163
+ await store.put(storeKey(key), body, REMOTE_CACHE_STORE_TTL_S);
1164
+ }
1165
+ } catch {
1166
+ }
1167
+ }
1168
+ function background(promise) {
1169
+ const { waitUntil } = getState();
1170
+ if (!waitUntil) return;
1171
+ try {
1172
+ waitUntil(promise);
1115
1173
  } catch {
1116
1174
  }
1117
1175
  }
1176
+ function adopt(key, entry) {
1177
+ const { memo } = getState();
1178
+ const current = memo.get(key);
1179
+ if (!current || current.at <= entry.at) memo.set(key, entry);
1180
+ }
1181
+ var negativeCacheError = (entry) => new RemoteFetchError(entry.message, entry.status);
1182
+ function revalidate(key, loader, preferStore) {
1183
+ const state = getState();
1184
+ const existing = state.revalidating.get(key);
1185
+ if (existing && Date.now() - existing.startedAt < STUCK_REVALIDATE_MS) return existing.promise;
1186
+ const entry = { startedAt: Date.now(), promise: Promise.resolve(false) };
1187
+ entry.promise = (async () => {
1188
+ try {
1189
+ const mine = state.memo.get(key);
1190
+ if (preferStore && state.store) {
1191
+ const stored = await readStore(key, loader.isValid);
1192
+ if (stored && stored.at > (mine?.at ?? 0) && Date.now() - stored.at < FRESH_TTL_MS) {
1193
+ adopt(key, stored);
1194
+ return true;
1195
+ }
1196
+ }
1197
+ const value = await loader.load();
1198
+ const fresh = { at: Date.now(), value };
1199
+ adopt(key, fresh);
1200
+ state.missing.delete(key);
1201
+ await writeStore(key, fresh);
1202
+ return true;
1203
+ } catch {
1204
+ return false;
1205
+ } finally {
1206
+ if (state.revalidating.get(key) === entry) state.revalidating.delete(key);
1207
+ }
1208
+ })();
1209
+ state.revalidating.set(key, entry);
1210
+ return entry.promise;
1211
+ }
1118
1212
  async function loadCached(key, load, isValid) {
1119
- const { memo, inflight, revalidating } = getState();
1213
+ const state = getState();
1214
+ const { memo, inflight } = state;
1215
+ const loader = { load, isValid };
1216
+ state.loaders.set(key, loader);
1120
1217
  const cached = memo.get(key);
1121
- if (cached && Date.now() - cached.at < FRESH_TTL_MS) return cached.value;
1218
+ if (cached) {
1219
+ const age = Date.now() - cached.at;
1220
+ if (age < FRESH_TTL_MS) return cached.value;
1221
+ if (age < STALE_WHILE_REVALIDATE_MS) {
1222
+ background(revalidate(key, loader, true));
1223
+ return cached.value;
1224
+ }
1225
+ }
1226
+ const missing = state.missing.get(key);
1227
+ if (missing && Date.now() - missing.at < MISSING_TTL_MS) throw negativeCacheError(missing);
1122
1228
  const pending = inflight.get(key);
1123
1229
  if (pending) return pending;
1124
1230
  const task = (async () => {
1125
1231
  const stored = await readStore(key, isValid);
1126
1232
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1127
- memo.set(key, stored);
1233
+ adopt(key, stored);
1128
1234
  return stored.value;
1129
1235
  }
1130
1236
  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
- })();
1237
+ adopt(key, stored);
1238
+ background(revalidate(key, loader, false));
1146
1239
  return stored.value;
1147
1240
  }
1148
1241
  try {
1149
1242
  const value = await load();
1150
1243
  const entry = { at: Date.now(), value };
1151
- memo.set(key, entry);
1152
- await writeStore(key, entry);
1244
+ adopt(key, entry);
1245
+ state.missing.delete(key);
1246
+ const put = writeStore(key, entry);
1247
+ if (state.waitUntil) background(put);
1248
+ else await put;
1153
1249
  return value;
1154
1250
  } catch (error) {
1155
1251
  const stale = memo.get(key) ?? stored;
1156
- if (!stale) throw error;
1157
- memo.set(key, stale);
1252
+ if (!stale) {
1253
+ if (error instanceof RemoteFetchError && error.isMissing) {
1254
+ state.missing.set(key, { at: Date.now(), status: error.status, message: error.message });
1255
+ }
1256
+ throw error;
1257
+ }
1258
+ adopt(key, stale);
1158
1259
  return stale.value;
1159
1260
  }
1160
1261
  })().finally(() => {
@@ -1163,6 +1264,14 @@ async function loadCached(key, load, isValid) {
1163
1264
  inflight.set(key, task);
1164
1265
  return task;
1165
1266
  }
1267
+ async function refreshRemoteCache(key, opts = {}) {
1268
+ const state = getState();
1269
+ const loader = state.loaders.get(key);
1270
+ if (!loader) return "unknown";
1271
+ const cached = state.memo.get(key);
1272
+ if (cached && opts.maxAgeMs !== void 0 && Date.now() - cached.at < opts.maxAgeMs) return "fresh";
1273
+ return await revalidate(key, loader, false) ? "refreshed" : "failed";
1274
+ }
1166
1275
  async function loadCachedJson(key, url, isValid, label = key) {
1167
1276
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1168
1277
  }
@@ -1211,4 +1320,4 @@ async function fetchMainTokensMap() {
1211
1320
  }
1212
1321
  }
1213
1322
 
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 };
1323
+ export { COOLER_ETHEREUM_CONFIG, CURVANCE_MONAD_CONFIG, DOLOMITE_ROUTER_DEFAULTS, 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, dolomiteIsolation, dolomiteIsolationMarket, dolomiteMarketIdByToken, dolomiteOdosExecutorShim, dolomiteRouters, 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 };
@@ -12,6 +12,15 @@ export type DolomiteConfigEntry = {
12
12
  * ("invalid ETH sender"); ERC-20 deposits/withdrawals still use the proxy.
13
13
  */
14
14
  depositWithdrawalRouter?: string;
15
+ /**
16
+ * Modern `BorrowPositionRouter` / `GenericTraderRouter` — ONE address on
17
+ * every Dolomite chain (see `DOLOMITE_ROUTER_DEFAULTS`); the config may
18
+ * override per chain. Both take a leading `isolationModeMarketId` and are
19
+ * the ONLY way to act on an isolation-mode (dGM/dsavETH/…) position, whose
20
+ * DolomiteMargin account is owned by the user's vault.
21
+ */
22
+ borrowPositionRouter?: string;
23
+ genericTraderRouter?: string;
15
24
  borrowPositionProxy?: string;
16
25
  genericTraderProxy?: string;
17
26
  aggregatorTraders?: {
@@ -67,7 +76,45 @@ export type DolomiteEmodeChain = {
67
76
  export type DolomiteEmodeType = {
68
77
  [chainId: string]: DolomiteEmodeChain;
69
78
  };
79
+ export type DolomiteIsolationMarket = {
80
+ factory: string;
81
+ underlying: string;
82
+ underlyingSymbol: string;
83
+ underlyingDecimals: number;
84
+ allowableDebtMarketIds: string[];
85
+ allowableCollateralMarketIds: string[];
86
+ wrapper: string | null;
87
+ unwrapper: string | null;
88
+ wrapperInputMarketIds: string[];
89
+ unwrapperOutputMarketIds: string[];
90
+ async: boolean;
91
+ executionFeeWei: string | null;
92
+ };
93
+ export type DolomiteIsolationType = {
94
+ [chainId: string]: {
95
+ [marketId: string]: DolomiteIsolationMarket;
96
+ };
97
+ };
70
98
  export declare const dolomiteConfigs: () => DolomiteConfigsType;
99
+ /**
100
+ * The three Dolomite routers as deployed by `dolomite-margin-modules`
101
+ * (`packages/deployment/src/deploy/deployments.json`): one CREATE2 address per
102
+ * router on every chain Dolomite runs on (1, 56, 196, 1101, 3637, 5000, 5330,
103
+ * 8453, 42161, 57073, 80094). Probed 2026-09-18 on Arbitrum: all three are
104
+ * `getIsGlobalOperator == true` and `isTokenConverterTrusted == true` on every
105
+ * live isolation factory.
106
+ */
107
+ export declare const DOLOMITE_ROUTER_DEFAULTS: {
108
+ readonly depositWithdrawalRouter: "0xf8b2c637A68cF6A17b1DF9F8992EeBeFf63d2dFf";
109
+ readonly borrowPositionRouter: "0xF579b345cdA0860668b857De10ABD62442133D0F";
110
+ readonly genericTraderRouter: "0x7b61CbA306CfdB02493b94757143132B1b72Bc6b";
111
+ };
112
+ /** Router addresses for a chain: the config's, else the shared CREATE2 defaults. */
113
+ export declare const dolomiteRouters: (chainId: string) => {
114
+ depositWithdrawalRouter: string;
115
+ borrowPositionRouter: string;
116
+ genericTraderRouter: string;
117
+ };
71
118
  /**
72
119
  * The `OdosExecutor` deployed on a chain, or `undefined` when the
73
120
  * passthrough route is unavailable there. Callers MUST treat `undefined` as
@@ -76,4 +123,7 @@ export declare const dolomiteConfigs: () => DolomiteConfigsType;
76
123
  export declare const dolomiteOdosExecutorShim: (chainId: string) => string | undefined;
77
124
  export declare const dolomiteChains: () => string[];
78
125
  export declare const dolomiteEmode: () => DolomiteEmodeType;
126
+ export declare const dolomiteIsolation: () => DolomiteIsolationType;
127
+ /** The isolation-mode row for a (chain, marketId), or undefined for an ordinary market. */
128
+ export declare const dolomiteIsolationMarket: (chainId: string, marketId: string | number | bigint) => DolomiteIsolationMarket | undefined;
79
129
  export declare const dolomiteMarketIdByToken: (chainId: string, token: string) => string | undefined;
@@ -26,7 +26,7 @@ import type { CompoundV3BaseDataType, CompoundV3BulkerType, CompoundV3OracleData
26
26
  import type { CurvanceConfigType } from './curvance';
27
27
  import type { CoolerConfigType } from './cooler';
28
28
  import type { TwyneConfigType, TwyneMarketsType } from './twyne';
29
- import type { DolomiteConfigsType, DolomiteEmodeType } from './dolomite';
29
+ import type { DolomiteConfigsType, DolomiteEmodeType, DolomiteIsolationType } from './dolomite';
30
30
  import type { UsddConfigType, UsddMarketsType } from './dss';
31
31
  import type { EulerConfigsType, EulerVaultsType } from './euler';
32
32
  import type { ExactlyConfigType, ExactlyMarketsType } from './exactly';
@@ -118,6 +118,7 @@ export interface GlobalLenderDataRegistry {
118
118
  eulerVaults: EulerVaultsType;
119
119
  dolomiteConfigs: DolomiteConfigsType;
120
120
  dolomiteEmode: DolomiteEmodeType;
121
+ dolomiteIsolation: DolomiteIsolationType;
121
122
  siloMarkets: SiloMarketsType;
122
123
  siloPeripherals: SiloPeripheralsType;
123
124
  siloMarketsV3: SiloMarketsType;
@@ -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.58",
8
8
  "description": "Hold and initialize lending protocol data across a stack",
9
9
  "files": [
10
10
  "dist"