@1delta/data-sdk 0.0.55 → 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
@@ -298,6 +298,7 @@ function parseGearboxV3LenderKey(lenderKey) {
298
298
 
299
299
  // src/lending/dolomite.ts
300
300
  var dolomiteConfigs = () => getGlobalData2()?.dolomiteConfigs;
301
+ var dolomiteOdosExecutorShim = (chainId) => getGlobalData2()?.dolomiteConfigs?.[chainId]?.odosExecutor;
301
302
  var dolomiteChains = () => Object.keys(getGlobalData2()?.dolomiteConfigs ?? {});
302
303
  var dolomiteEmode = () => getGlobalData2()?.dolomiteEmode;
303
304
  var dolomiteMarketIdByToken = (chainId, token) => {
@@ -1033,17 +1034,32 @@ var parseFraxlendPairKey = (lender) => {
1033
1034
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1034
1035
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1035
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;
1036
1039
  var STATE_KEY = "__1delta_remote_cache__";
1037
1040
  function getState() {
1038
1041
  const g = globalThis;
1039
- 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
+ }
1040
1051
  const state = g[STATE_KEY];
1041
- 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();
1042
1055
  return state;
1043
1056
  }
1044
1057
  function setRemoteCacheStore(next) {
1045
1058
  getState().store = next;
1046
1059
  }
1060
+ function setRemoteCacheWaitUntil(next) {
1061
+ getState().waitUntil = next;
1062
+ }
1047
1063
  function setRemoteCacheGithubToken(token) {
1048
1064
  getState().githubToken = token;
1049
1065
  }
@@ -1051,7 +1067,11 @@ function clearRemoteCache() {
1051
1067
  const state = getState();
1052
1068
  state.memo.clear();
1053
1069
  state.inflight.clear();
1070
+ state.revalidating.clear();
1071
+ state.missing.clear();
1072
+ state.loaders.clear();
1054
1073
  }
1074
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1055
1075
  var RAW_HOST = "https://raw.githubusercontent.com/";
1056
1076
  function githubApiMirror(url) {
1057
1077
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1099,11 +1119,21 @@ async function readStore(key, isValid) {
1099
1119
  const { store } = getState();
1100
1120
  if (!store) return void 0;
1101
1121
  try {
1102
- 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
+ }
1103
1131
  if (!raw) return void 0;
1104
1132
  const parsed = JSON.parse(raw);
1105
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1106
- 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 };
1107
1137
  } catch {
1108
1138
  return void 0;
1109
1139
  }
@@ -1112,50 +1142,106 @@ async function writeStore(key, entry) {
1112
1142
  const { store } = getState();
1113
1143
  if (!store) return;
1114
1144
  try {
1115
- 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
+ }
1116
1151
  } catch {
1117
1152
  }
1118
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
+ }
1119
1198
  async function loadCached(key, load, isValid) {
1120
- 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);
1121
1203
  const cached = memo.get(key);
1122
- 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);
1123
1214
  const pending = inflight.get(key);
1124
1215
  if (pending) return pending;
1125
1216
  const task = (async () => {
1126
1217
  const stored = await readStore(key, isValid);
1127
1218
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1128
- memo.set(key, stored);
1219
+ adopt(key, stored);
1129
1220
  return stored.value;
1130
1221
  }
1131
1222
  if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
1132
- memo.set(key, stored);
1133
- if (revalidating.has(key)) return stored.value;
1134
- revalidating.add(key);
1135
- void (async () => {
1136
- try {
1137
- const value = await load();
1138
- const entry = { at: Date.now(), value };
1139
- const current = memo.get(key);
1140
- if (!current || current.at <= entry.at) memo.set(key, entry);
1141
- await writeStore(key, entry);
1142
- } catch {
1143
- } finally {
1144
- revalidating.delete(key);
1145
- }
1146
- })();
1223
+ adopt(key, stored);
1224
+ background(revalidate(key, loader, false));
1147
1225
  return stored.value;
1148
1226
  }
1149
1227
  try {
1150
1228
  const value = await load();
1151
1229
  const entry = { at: Date.now(), value };
1152
- memo.set(key, entry);
1153
- 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;
1154
1235
  return value;
1155
1236
  } catch (error) {
1156
1237
  const stale = memo.get(key) ?? stored;
1157
- if (!stale) throw error;
1158
- 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);
1159
1245
  return stale.value;
1160
1246
  }
1161
1247
  })().finally(() => {
@@ -1164,6 +1250,14 @@ async function loadCached(key, load, isValid) {
1164
1250
  inflight.set(key, task);
1165
1251
  return task;
1166
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
+ }
1167
1261
  async function loadCachedJson(key, url, isValid, label = key) {
1168
1262
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1169
1263
  }
@@ -1256,6 +1350,7 @@ exports.dolomiteChains = dolomiteChains;
1256
1350
  exports.dolomiteConfigs = dolomiteConfigs;
1257
1351
  exports.dolomiteEmode = dolomiteEmode;
1258
1352
  exports.dolomiteMarketIdByToken = dolomiteMarketIdByToken;
1353
+ exports.dolomiteOdosExecutorShim = dolomiteOdosExecutorShim;
1259
1354
  exports.dssChainData = dssChainData;
1260
1355
  exports.dssConfigFor = dssConfigFor;
1261
1356
  exports.dssDebtJoin = dssDebtJoin;
@@ -1370,6 +1465,7 @@ exports.parseMidnightRef = parseMidnightRef;
1370
1465
  exports.parseSiloV2LenderKey = parseSiloV2LenderKey;
1371
1466
  exports.parseSiloV3LenderKey = parseSiloV3LenderKey;
1372
1467
  exports.parseTermMaxLenderKey = parseTermMaxLenderKey;
1468
+ exports.refreshRemoteCache = refreshRemoteCache;
1373
1469
  exports.resolveMidnightApiBase = resolveMidnightApiBase;
1374
1470
  exports.resolveTenorApiUrl = resolveTenorApiUrl;
1375
1471
  exports.resolveTermApiBase = resolveTermApiBase;
@@ -1386,6 +1482,7 @@ exports.riverMarkets = riverMarkets;
1386
1482
  exports.setMidnightApiBase = setMidnightApiBase;
1387
1483
  exports.setRemoteCacheGithubToken = setRemoteCacheGithubToken;
1388
1484
  exports.setRemoteCacheStore = setRemoteCacheStore;
1485
+ exports.setRemoteCacheWaitUntil = setRemoteCacheWaitUntil;
1389
1486
  exports.setTenorApiUrl = setTenorApiUrl;
1390
1487
  exports.setTermApiBase = setTermApiBase;
1391
1488
  exports.setTermOrderStoreBase = setTermOrderStoreBase;
@@ -1417,6 +1514,7 @@ exports.termMaxConfig = termMaxConfig;
1417
1514
  exports.termMaxConfigByChain = termMaxConfigByChain;
1418
1515
  exports.termMaxLenderKey = termMaxLenderKey;
1419
1516
  exports.termOrderStoreBaseOverride = termOrderStoreBaseOverride;
1517
+ exports.tokenListCacheKey = tokenListCacheKey;
1420
1518
  exports.twyneBaseKey = twyneBaseKey;
1421
1519
  exports.twyneBeaconFor = twyneBeaconFor;
1422
1520
  exports.twyneChainData = twyneChainData;
package/dist/index.mjs CHANGED
@@ -296,6 +296,7 @@ function parseGearboxV3LenderKey(lenderKey) {
296
296
 
297
297
  // src/lending/dolomite.ts
298
298
  var dolomiteConfigs = () => getGlobalData2()?.dolomiteConfigs;
299
+ var dolomiteOdosExecutorShim = (chainId) => getGlobalData2()?.dolomiteConfigs?.[chainId]?.odosExecutor;
299
300
  var dolomiteChains = () => Object.keys(getGlobalData2()?.dolomiteConfigs ?? {});
300
301
  var dolomiteEmode = () => getGlobalData2()?.dolomiteEmode;
301
302
  var dolomiteMarketIdByToken = (chainId, token) => {
@@ -1031,17 +1032,32 @@ var parseFraxlendPairKey = (lender) => {
1031
1032
  var FRESH_TTL_MS = 5 * 60 * 1e3;
1032
1033
  var STALE_WHILE_REVALIDATE_MS = 12 * 60 * 60 * 1e3;
1033
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;
1034
1037
  var STATE_KEY = "__1delta_remote_cache__";
1035
1038
  function getState() {
1036
1039
  const g = globalThis;
1037
- 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
+ }
1038
1049
  const state = g[STATE_KEY];
1039
- 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();
1040
1053
  return state;
1041
1054
  }
1042
1055
  function setRemoteCacheStore(next) {
1043
1056
  getState().store = next;
1044
1057
  }
1058
+ function setRemoteCacheWaitUntil(next) {
1059
+ getState().waitUntil = next;
1060
+ }
1045
1061
  function setRemoteCacheGithubToken(token) {
1046
1062
  getState().githubToken = token;
1047
1063
  }
@@ -1049,7 +1065,11 @@ function clearRemoteCache() {
1049
1065
  const state = getState();
1050
1066
  state.memo.clear();
1051
1067
  state.inflight.clear();
1068
+ state.revalidating.clear();
1069
+ state.missing.clear();
1070
+ state.loaders.clear();
1052
1071
  }
1072
+ var tokenListCacheKey = (chainId) => `token-list:${chainId}`;
1053
1073
  var RAW_HOST = "https://raw.githubusercontent.com/";
1054
1074
  function githubApiMirror(url) {
1055
1075
  if (!url.startsWith(RAW_HOST)) return void 0;
@@ -1097,11 +1117,21 @@ async function readStore(key, isValid) {
1097
1117
  const { store } = getState();
1098
1118
  if (!store) return void 0;
1099
1119
  try {
1100
- 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
+ }
1101
1129
  if (!raw) return void 0;
1102
1130
  const parsed = JSON.parse(raw);
1103
- if (typeof parsed?.at !== "number" || !isValid(parsed.value)) return void 0;
1104
- 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 };
1105
1135
  } catch {
1106
1136
  return void 0;
1107
1137
  }
@@ -1110,50 +1140,106 @@ async function writeStore(key, entry) {
1110
1140
  const { store } = getState();
1111
1141
  if (!store) return;
1112
1142
  try {
1113
- 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
+ }
1114
1149
  } catch {
1115
1150
  }
1116
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
+ }
1117
1196
  async function loadCached(key, load, isValid) {
1118
- 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);
1119
1201
  const cached = memo.get(key);
1120
- 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);
1121
1212
  const pending = inflight.get(key);
1122
1213
  if (pending) return pending;
1123
1214
  const task = (async () => {
1124
1215
  const stored = await readStore(key, isValid);
1125
1216
  if (stored && Date.now() - stored.at < FRESH_TTL_MS) {
1126
- memo.set(key, stored);
1217
+ adopt(key, stored);
1127
1218
  return stored.value;
1128
1219
  }
1129
1220
  if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
1130
- memo.set(key, stored);
1131
- if (revalidating.has(key)) return stored.value;
1132
- revalidating.add(key);
1133
- void (async () => {
1134
- try {
1135
- const value = await load();
1136
- const entry = { at: Date.now(), value };
1137
- const current = memo.get(key);
1138
- if (!current || current.at <= entry.at) memo.set(key, entry);
1139
- await writeStore(key, entry);
1140
- } catch {
1141
- } finally {
1142
- revalidating.delete(key);
1143
- }
1144
- })();
1221
+ adopt(key, stored);
1222
+ background(revalidate(key, loader, false));
1145
1223
  return stored.value;
1146
1224
  }
1147
1225
  try {
1148
1226
  const value = await load();
1149
1227
  const entry = { at: Date.now(), value };
1150
- memo.set(key, entry);
1151
- 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;
1152
1233
  return value;
1153
1234
  } catch (error) {
1154
1235
  const stale = memo.get(key) ?? stored;
1155
- if (!stale) throw error;
1156
- 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);
1157
1243
  return stale.value;
1158
1244
  }
1159
1245
  })().finally(() => {
@@ -1162,6 +1248,14 @@ async function loadCached(key, load, isValid) {
1162
1248
  inflight.set(key, task);
1163
1249
  return task;
1164
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
+ }
1165
1259
  async function loadCachedJson(key, url, isValid, label = key) {
1166
1260
  return loadCached(key, () => fetchRemoteJson(url, label), isValid);
1167
1261
  }
@@ -1210,4 +1304,4 @@ async function fetchMainTokensMap() {
1210
1304
  }
1211
1305
  }
1212
1306
 
1213
- 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, 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 };
@@ -17,6 +17,25 @@ export type DolomiteConfigEntry = {
17
17
  aggregatorTraders?: {
18
18
  [K in DolomiteAggregator]?: string;
19
19
  };
20
+ /**
21
+ * Our `OdosExecutor` (`contracts-delegation`,
22
+ * `contracts/1delta/composer/generic/OdosExecutor.sol`) — a contract carrying
23
+ * the Odos executor selector (`executePath(bytes,uint256[],address)`) that
24
+ * simply forwards to another aggregator's router. Sibling of the same repo's
25
+ * `AggregationExecutor` (1inch) and `AllowanceHolderExecutor` (0x).
26
+ *
27
+ * It exists because Dolomite swaps must route through a Dolomite-DEPLOYED
28
+ * `IExchangeWrapper` and we cannot add one, so a chain is limited to the
29
+ * wrappers Dolomite happens to have deployed. `OdosRouterV2` takes a
30
+ * caller-supplied `executor` and `pathDefinition` and validates NEITHER (its
31
+ * only mapping is `referralLookup`), and `OdosAggregatorTrader` forwards both
32
+ * verbatim — so pointing `executor` at this shim runs a KyberSwap/0x/1inch
33
+ * route through the `odos` wrapper without Odos's API being involved.
34
+ *
35
+ * Absent = the shim is not deployed on this chain and the passthrough route
36
+ * must not be offered (fail closed).
37
+ */
38
+ odosExecutor?: string;
20
39
  };
21
40
  export type DolomiteConfigsType = {
22
41
  [chainId: string]: DolomiteConfigEntry;
@@ -49,6 +68,12 @@ export type DolomiteEmodeType = {
49
68
  [chainId: string]: DolomiteEmodeChain;
50
69
  };
51
70
  export declare const dolomiteConfigs: () => DolomiteConfigsType;
71
+ /**
72
+ * The `OdosExecutor` deployed on a chain, or `undefined` when the
73
+ * passthrough route is unavailable there. Callers MUST treat `undefined` as
74
+ * "not supported" — never substitute a default address.
75
+ */
76
+ export declare const dolomiteOdosExecutorShim: (chainId: string) => string | undefined;
52
77
  export declare const dolomiteChains: () => string[];
53
78
  export declare const dolomiteEmode: () => DolomiteEmodeType;
54
79
  export declare const dolomiteMarketIdByToken: (chainId: string, token: string) => string | undefined;
@@ -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.55",
7
+ "version": "0.0.57",
8
8
  "description": "Hold and initialize lending protocol data across a stack",
9
9
  "files": [
10
10
  "dist"