@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 +124 -28
- package/dist/index.mjs +122 -29
- package/dist/remoteCache.d.ts +82 -0
- package/package.json +1 -1
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])
|
|
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
|
|
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
|
-
|
|
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 (
|
|
1107
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1219
|
+
adopt(key, stored);
|
|
1130
1220
|
return stored.value;
|
|
1131
1221
|
}
|
|
1132
1222
|
if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
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)
|
|
1159
|
-
|
|
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])
|
|
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
|
|
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
|
-
|
|
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 (
|
|
1105
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
1217
|
+
adopt(key, stored);
|
|
1128
1218
|
return stored.value;
|
|
1129
1219
|
}
|
|
1130
1220
|
if (stored && Date.now() - stored.at < STALE_WHILE_REVALIDATE_MS) {
|
|
1131
|
-
|
|
1132
|
-
|
|
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
|
-
|
|
1152
|
-
|
|
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)
|
|
1157
|
-
|
|
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 };
|
package/dist/remoteCache.d.ts
CHANGED
|
@@ -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>;
|