mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-04-29 00:59:58 -07:00
global: snapshot
This commit is contained in:
@@ -19,6 +19,7 @@ import { throttle } from "../utils/timing.js";
|
||||
import { serdeBool } from "../utils/serde.js";
|
||||
import { stringToId } from "../utils/format.js";
|
||||
import { style } from "../utils/elements.js";
|
||||
import { resources } from "../resources.js";
|
||||
|
||||
/**
|
||||
* @typedef {Object} Valued
|
||||
@@ -70,20 +71,18 @@ const lineWidth = /** @type {any} */ (1.5);
|
||||
* @param {HTMLElement} args.parent
|
||||
* @param {Signals} args.signals
|
||||
* @param {Colors} args.colors
|
||||
* @param {Resources} args.resources
|
||||
* @param {BrkClient} args.brk
|
||||
* @param {Accessor<ChartableIndex>} args.index
|
||||
* @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback]
|
||||
* @param {true} [args.fitContent]
|
||||
* @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config]
|
||||
*/
|
||||
function createChartElement({
|
||||
export function createChartElement({
|
||||
parent,
|
||||
signals,
|
||||
colors,
|
||||
id: chartId,
|
||||
index,
|
||||
resources,
|
||||
brk,
|
||||
timeScaleSetCallback,
|
||||
fitContent,
|
||||
@@ -1076,5 +1075,3 @@ function numberToUSFormat(value, digits, options) {
|
||||
* @typedef {typeof createChartElement} CreateChartElement
|
||||
* @typedef {ReturnType<createChartElement>} Chart
|
||||
*/
|
||||
|
||||
export default { createChartElement };
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* @import { Signal, Signals, Accessor } from "./signals.js";
|
||||
*
|
||||
* @import * as Brk from "./modules/brk-client/index.js"
|
||||
* @import { BrkClient} from "./modules/brk-client/index.js"
|
||||
* @import { BrkClient, Index, Metric, MetricData } from "./modules/brk-client/index.js"
|
||||
*
|
||||
* @import { Resources, MetricResource } from './resources.js'
|
||||
*
|
||||
@@ -28,23 +28,91 @@
|
||||
// import uFuzzy = require("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.d.ts");
|
||||
|
||||
/**
|
||||
* @typedef {typeof import("./lazy")["default"]} Modules
|
||||
* @typedef {[number, number, number, number]} OHLCTuple
|
||||
*
|
||||
* Brk type aliases
|
||||
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts} UtxoCohortTree
|
||||
* @typedef {Brk.MetricsTree_Distribution_AddressCohorts} AddressCohortTree
|
||||
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_All} AllUtxoPattern
|
||||
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_Term_Short} ShortTermPattern
|
||||
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_Term_Long} LongTermPattern
|
||||
* @typedef {Brk._10yPattern} MaxAgePattern
|
||||
* @typedef {Brk._10yTo12yPattern} AgeRangePattern
|
||||
* @typedef {Brk._0satsPattern2} UtxoAmountPattern
|
||||
* @typedef {Brk._0satsPattern} AddressAmountPattern
|
||||
* @typedef {Brk._100btcPattern} BasicUtxoPattern
|
||||
* @typedef {Brk._0satsPattern2} EpochPattern
|
||||
* @typedef {Brk.Ratio1ySdPattern} Ratio1ySdPattern
|
||||
* @typedef {Brk.Dollars} Dollars
|
||||
* @typedef {Brk.Price111dSmaPattern} EmaRatioPattern
|
||||
* @typedef {Brk.CoinbasePattern} CoinbasePattern
|
||||
* @typedef {Brk.ActivePriceRatioPattern} ActivePriceRatioPattern
|
||||
* @typedef {Brk.UnclaimedRewardsPattern} ValuePattern
|
||||
* @typedef {Brk.AnyMetricPattern} AnyMetricPattern
|
||||
* @typedef {Brk.AnyMetricEndpointBuilder} AnyMetricEndpoint
|
||||
* @typedef {Brk.AnyMetricData} AnyMetricData
|
||||
* @typedef {Brk.AddrCountPattern} AddrCountPattern
|
||||
* @typedef {Brk.MetricsTree_Blocks_Interval} IntervalPattern
|
||||
* @typedef {Brk.MetricsTree_Supply_Circulating} SupplyPattern
|
||||
* @typedef {Brk.RelativePattern} GlobalRelativePattern
|
||||
* @typedef {Brk.RelativePattern2} OwnRelativePattern
|
||||
* @typedef {Brk.RelativePattern5} FullRelativePattern
|
||||
* @typedef {Brk.MetricsTree_Distribution_UtxoCohorts_All_Relative} AllRelativePattern
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.BlockCountPattern<T>} BlockCountPattern
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.FullnessPattern<T>} FullnessPattern
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.FeeRatePattern<T>} FeeRatePattern
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.MetricEndpointBuilder<T>} MetricEndpoint
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.DollarsPattern<T>} SizePattern
|
||||
*/
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {Brk.CountPattern2<T>} CountStatsPattern
|
||||
*/
|
||||
/**
|
||||
* @typedef {Brk.MetricsTree_Blocks_Size} BlockSizePattern
|
||||
*/
|
||||
/**
|
||||
* Stats pattern union - accepts both CountStatsPattern and BlockSizePattern
|
||||
* @typedef {CountStatsPattern<any> | BlockSizePattern} AnyStatsPattern
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @typedef {InstanceType<typeof BrkClient>["INDEXES"]} Indexes
|
||||
* @typedef {Indexes[number]} IndexName
|
||||
* @typedef {InstanceType<typeof BrkClient>["POOL_ID_TO_POOL_NAME"]} PoolIdToPoolName
|
||||
* @typedef {keyof PoolIdToPoolName} PoolId
|
||||
*
|
||||
* Tree branch types
|
||||
* @typedef {Brk.MetricsTree_Market} Market
|
||||
* @typedef {Brk.MetricsTree_Market_MovingAverage} MarketMovingAverage
|
||||
* @typedef {Brk.MetricsTree_Market_Dca} MarketDca
|
||||
*
|
||||
* Pattern unions by cohort type
|
||||
* @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} UtxoCohortPattern
|
||||
* @typedef {AddressAmountPattern} AddressCohortPattern
|
||||
* @typedef {UtxoCohortPattern | AddressCohortPattern} CohortPattern
|
||||
*
|
||||
* Relative pattern capability types
|
||||
* @typedef {RelativePattern | RelativePattern5} RelativeWithMarketCap
|
||||
* @typedef {RelativePattern2 | RelativePattern5} RelativeWithOwnMarketCap
|
||||
* @typedef {RelativePattern2 | RelativePattern5 | AllRelativePattern} RelativeWithOwnPnl
|
||||
* @typedef {GlobalRelativePattern | FullRelativePattern} RelativeWithMarketCap
|
||||
* @typedef {OwnRelativePattern | FullRelativePattern} RelativeWithOwnMarketCap
|
||||
* @typedef {OwnRelativePattern | FullRelativePattern | AllRelativePattern} RelativeWithOwnPnl
|
||||
*
|
||||
* Capability-based pattern groupings (patterns that have specific properties)
|
||||
* @typedef {AllUtxoPattern | AgeRangePattern | UtxoAmountPattern} PatternWithRealizedPrice
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
const imports = {
|
||||
async signals() {
|
||||
return import("./signals.js").then((d) => d.default);
|
||||
},
|
||||
async leanQr() {
|
||||
return import("./modules/lean-qr/2.6.1/index.mjs").then((d) => d);
|
||||
},
|
||||
async ufuzzy() {
|
||||
return import("./modules/leeoniya-ufuzzy/1.0.19/dist/uFuzzy.mjs").then(
|
||||
({ default: d }) => d,
|
||||
);
|
||||
},
|
||||
async brkClient() {
|
||||
return import("./modules/brk-client/index.js").then((d) => d);
|
||||
},
|
||||
async resources() {
|
||||
return import("./resources.js").then((d) => d);
|
||||
},
|
||||
|
||||
async chart() {
|
||||
return window.document.fonts.ready.then(() =>
|
||||
import("./chart/index.js").then((d) => d.default),
|
||||
);
|
||||
},
|
||||
async options() {
|
||||
return import("./options/full.js").then((d) => d);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @template {keyof typeof imports} K
|
||||
* @param {K} key
|
||||
*/
|
||||
function lazyImport(key) {
|
||||
/** @type {any | null} */
|
||||
let packagePromise = null;
|
||||
|
||||
return function () {
|
||||
if (!packagePromise) {
|
||||
packagePromise = imports[key]();
|
||||
}
|
||||
return /** @type {ReturnType<typeof imports[K]>} */ (packagePromise);
|
||||
};
|
||||
}
|
||||
|
||||
export default /** @type {{ [K in keyof typeof imports]: () => ReturnType<typeof imports[K]> }} */ (
|
||||
Object.fromEntries(
|
||||
Object.keys(imports).map((key) => [
|
||||
key,
|
||||
lazyImport(/** @type {keyof typeof imports} */ (key)),
|
||||
]),
|
||||
)
|
||||
);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -178,7 +178,7 @@ export function fromBitcoin(colors, pattern, title, color) {
|
||||
/**
|
||||
* Create series from a SizePattern ({ sum, cumulative, average, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {SizePattern} pattern
|
||||
* @param {AnyStatsPattern} pattern
|
||||
* @param {string} title
|
||||
* @param {Color} [color]
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
@@ -241,7 +241,7 @@ export function fromBlockSize(colors, pattern, title, color) {
|
||||
/**
|
||||
* Create series from a SizePattern ({ average, sum, cumulative, min, max, percentiles })
|
||||
* @param {Colors} colors
|
||||
* @param {SizePattern} pattern
|
||||
* @param {AnyStatsPattern} pattern
|
||||
* @param {string} title
|
||||
* @param {Unit} unit
|
||||
* @returns {AnyFetchedSeriesBlueprint[]}
|
||||
|
||||
@@ -238,8 +238,8 @@
|
||||
* @property {HistogramSeriesFn} histogram
|
||||
* @property {(pattern: BlockCountPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockCount
|
||||
* @property {(pattern: FullnessPattern<any>, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBitcoin
|
||||
* @property {(pattern: SizePattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
* @property {(pattern: SizePattern, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromSizePattern
|
||||
* @property {(pattern: AnyStatsPattern, title: string, color?: Color) => AnyFetchedSeriesBlueprint[]} fromBlockSize
|
||||
* @property {(pattern: AnyStatsPattern, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromSizePattern
|
||||
* @property {(pattern: FullnessPattern<any>, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromFullnessPattern
|
||||
* @property {(pattern: FeeRatePattern<any>, title: string, unit: Unit) => AnyFetchedSeriesBlueprint[]} fromFeeRatePattern
|
||||
* @property {(pattern: CoinbasePattern, title: string) => AnyFetchedSeriesBlueprint[]} fromCoinbasePattern
|
||||
|
||||
@@ -8,6 +8,9 @@ import { ios, canShare } from "../../utils/env.js";
|
||||
import { serdeChartableIndex, serdeOptNumber } from "../../utils/serde.js";
|
||||
import { throttle } from "../../utils/timing.js";
|
||||
import { Unit } from "../../utils/units.js";
|
||||
import signals from "../../signals.js";
|
||||
import { createChartElement } from "../../chart/index.js";
|
||||
import { webSockets } from "../../utils/ws.js";
|
||||
|
||||
const keyPrefix = "chart";
|
||||
const ONE_BTC_IN_SATS = 100_000_000;
|
||||
@@ -22,20 +25,12 @@ const CANDLE = "candle";
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {CreateChartElement} args.createChartElement
|
||||
* @param {Accessor<ChartOption>} args.option
|
||||
* @param {Signals} args.signals
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Resources} args.resources
|
||||
* @param {BrkClient} args.brk
|
||||
*/
|
||||
export function init({
|
||||
colors,
|
||||
createChartElement,
|
||||
option,
|
||||
signals,
|
||||
webSockets,
|
||||
resources,
|
||||
brk,
|
||||
}) {
|
||||
chartElement.append(createShadow("left"));
|
||||
@@ -44,10 +39,7 @@ export function init({
|
||||
const { headerElement, headingElement } = createHeader();
|
||||
chartElement.append(headerElement);
|
||||
|
||||
const { index, fieldset } = createIndexSelector({
|
||||
option,
|
||||
signals,
|
||||
});
|
||||
const { index, fieldset } = createIndexSelector(option);
|
||||
|
||||
const TIMERANGE_LS_KEY = signals.createMemo(
|
||||
() => `chart-timerange-${index()}`,
|
||||
@@ -77,7 +69,6 @@ export function init({
|
||||
signals,
|
||||
colors,
|
||||
id: "charts",
|
||||
resources,
|
||||
brk,
|
||||
index,
|
||||
timeScaleSetCallback: (unknownTimeScaleCallback) => {
|
||||
@@ -525,11 +516,9 @@ export function init({
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Accessor<ChartOption>} args.option
|
||||
* @param {Signals} args.signals
|
||||
* @param {Accessor<ChartOption>} option
|
||||
*/
|
||||
function createIndexSelector({ option, signals }) {
|
||||
function createIndexSelector(option) {
|
||||
const choices_ = /** @satisfies {ChartableIndexName[]} */ ([
|
||||
"timestamp",
|
||||
"date",
|
||||
|
||||
@@ -1,25 +1,7 @@
|
||||
import { randomFromArray } from "../utils/array.js";
|
||||
import { explorerElement } from "../utils/elements.js";
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {CreateChartElement} args.createChartElement
|
||||
* @param {Accessor<ChartOption>} args.option
|
||||
* @param {Signals} args.signals
|
||||
* @param {WebSockets} args.webSockets
|
||||
* @param {Resources} args.resources
|
||||
* @param {BrkClient} args.brk
|
||||
*/
|
||||
export function init({
|
||||
colors: _colors,
|
||||
createChartElement: _createChartElement,
|
||||
option: _option,
|
||||
signals: _signals,
|
||||
webSockets: _webSockets,
|
||||
resources: _resources,
|
||||
brk: _brk,
|
||||
}) {
|
||||
export function init() {
|
||||
const chain = window.document.createElement("div");
|
||||
chain.id = "chain";
|
||||
explorerElement.append(chain);
|
||||
|
||||
@@ -18,15 +18,15 @@ import {
|
||||
numberToUSNumber,
|
||||
} from "../utils/format.js";
|
||||
import { serdeDate, serdeOptDate, serdeOptNumber } from "../utils/serde.js";
|
||||
import signals from "../signals.js";
|
||||
import { createChartElement } from "../chart/index.js";
|
||||
import { resources } from "../resources.js";
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Colors} args.colors
|
||||
* @param {CreateChartElement} args.createChartElement
|
||||
* @param {Signals} args.signals
|
||||
* @param {Resources} args.resources
|
||||
*/
|
||||
export function init({ colors, createChartElement, signals, resources }) {
|
||||
export function init({ colors }) {
|
||||
/**
|
||||
* @typedef {Object} Frequency
|
||||
* @property {string} name
|
||||
|
||||
@@ -6,14 +6,7 @@ import { tableElement } from "../utils/elements.js";
|
||||
import { serdeMetrics, serdeString } from "../utils/serde.js";
|
||||
import { resetParams } from "../utils/url.js";
|
||||
|
||||
/**
|
||||
* @param {Object} args
|
||||
* @param {Signals} args.signals
|
||||
* @param {Option} args.option
|
||||
* @param {Resources} args.resources
|
||||
* @param {BrkClient} args.brk
|
||||
*/
|
||||
export function init({ signals, option, resources, brk }) {
|
||||
export function init() {
|
||||
tableElement.innerHTML = "wip, will hopefuly be back soon, sorry !";
|
||||
|
||||
// const parent = tableElement;
|
||||
|
||||
@@ -25,105 +25,106 @@
|
||||
/** @typedef {MetricResource<unknown>} AnyMetricResource */
|
||||
|
||||
/**
|
||||
* @typedef {ReturnType<typeof createResources>} Resources
|
||||
* @typedef {{ createResource: typeof createResource, useMetricEndpoint: typeof useMetricEndpoint }} Resources
|
||||
*/
|
||||
|
||||
import signals from "./signals.js";
|
||||
|
||||
/**
|
||||
* @param {Signals} signals
|
||||
* Create a generic reactive resource wrapper for any async fetcher
|
||||
* @template T
|
||||
* @template {any[]} Args
|
||||
* @param {(...args: Args) => Promise<T>} fetcher
|
||||
* @returns {Resource<T>}
|
||||
*/
|
||||
export function createResources(signals) {
|
||||
function createResource(fetcher) {
|
||||
const owner = signals.getOwner();
|
||||
return signals.runWithOwner(owner, () => {
|
||||
const data = signals.createSignal(/** @type {T | null} */ (null));
|
||||
const loading = signals.createSignal(false);
|
||||
const error = signals.createSignal(/** @type {Error | null} */ (null));
|
||||
|
||||
/**
|
||||
* Create a generic reactive resource wrapper for any async fetcher
|
||||
* @template T
|
||||
* @template {any[]} Args
|
||||
* @param {(...args: Args) => Promise<T>} fetcher
|
||||
* @returns {Resource<T>}
|
||||
*/
|
||||
function createResource(fetcher) {
|
||||
return signals.runWithOwner(owner, () => {
|
||||
const data = signals.createSignal(/** @type {T | null} */ (null));
|
||||
const loading = signals.createSignal(false);
|
||||
const error = signals.createSignal(/** @type {Error | null} */ (null));
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
/**
|
||||
* @param {Args} args
|
||||
*/
|
||||
async fetch(...args) {
|
||||
loading.set(true);
|
||||
error.set(null);
|
||||
try {
|
||||
const result = await fetcher(...args);
|
||||
data.set(() => result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
error.set(e instanceof Error ? e : new Error(String(e)));
|
||||
return null;
|
||||
} finally {
|
||||
loading.set(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reactive resource wrapper for a MetricEndpoint with multi-range support
|
||||
* @template T
|
||||
* @param {MetricEndpoint<T>} endpoint
|
||||
* @returns {MetricResource<T>}
|
||||
*/
|
||||
function useMetricEndpoint(endpoint) {
|
||||
return signals.runWithOwner(owner, () => {
|
||||
/** @type {Map<string, RangeState<T>>} */
|
||||
const ranges = new Map();
|
||||
|
||||
return {
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
/**
|
||||
* Get or create range state
|
||||
* @param {number} [from=-10000]
|
||||
* @param {number} [to]
|
||||
* @returns {RangeState<T>}
|
||||
* @param {Args} args
|
||||
*/
|
||||
function range(from = -10000, to) {
|
||||
const key = `${from}-${to ?? ""}`;
|
||||
const existing = ranges.get(key);
|
||||
if (existing) return existing;
|
||||
|
||||
/** @type {RangeState<T>} */
|
||||
const state = {
|
||||
response: signals.createSignal(/** @type {MetricData<T> | null} */ (null)),
|
||||
loading: signals.createSignal(false),
|
||||
};
|
||||
ranges.set(key, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
path: endpoint.path,
|
||||
range,
|
||||
/**
|
||||
* Fetch data for a range
|
||||
* @param {number} [from=-10000]
|
||||
* @param {number} [to]
|
||||
*/
|
||||
async fetch(from = -10000, to) {
|
||||
const r = range(from, to);
|
||||
r.loading.set(true);
|
||||
try {
|
||||
const result = await endpoint.range(from, to, r.response.set);
|
||||
return result;
|
||||
} finally {
|
||||
r.loading.set(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return { createResource, useMetricEndpoint };
|
||||
async fetch(...args) {
|
||||
loading.set(true);
|
||||
error.set(null);
|
||||
try {
|
||||
const result = await fetcher(...args);
|
||||
data.set(() => result);
|
||||
return result;
|
||||
} catch (e) {
|
||||
error.set(e instanceof Error ? e : new Error(String(e)));
|
||||
return null;
|
||||
} finally {
|
||||
loading.set(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a reactive resource wrapper for a MetricEndpoint with multi-range support
|
||||
* @template T
|
||||
* @param {MetricEndpoint<T>} endpoint
|
||||
* @returns {MetricResource<T>}
|
||||
*/
|
||||
function useMetricEndpoint(endpoint) {
|
||||
const owner = signals.getOwner();
|
||||
return signals.runWithOwner(owner, () => {
|
||||
/** @type {Map<string, RangeState<T>>} */
|
||||
const ranges = new Map();
|
||||
|
||||
/**
|
||||
* Get or create range state
|
||||
* @param {number} [from=-10000]
|
||||
* @param {number} [to]
|
||||
* @returns {RangeState<T>}
|
||||
*/
|
||||
function range(from = -10000, to) {
|
||||
const key = `${from}-${to ?? ""}`;
|
||||
const existing = ranges.get(key);
|
||||
if (existing) return existing;
|
||||
|
||||
/** @type {RangeState<T>} */
|
||||
const state = {
|
||||
response: signals.createSignal(
|
||||
/** @type {MetricData<T> | null} */ (null),
|
||||
),
|
||||
loading: signals.createSignal(false),
|
||||
};
|
||||
ranges.set(key, state);
|
||||
return state;
|
||||
}
|
||||
|
||||
return {
|
||||
path: endpoint.path,
|
||||
range,
|
||||
/**
|
||||
* Fetch data for a range
|
||||
* @param {number} [start=-10000]
|
||||
* @param {number} [end]
|
||||
*/
|
||||
async fetch(start = -10000, end) {
|
||||
const r = range(start, end);
|
||||
r.loading.set(true);
|
||||
try {
|
||||
const result = await endpoint
|
||||
.slice(start, end)
|
||||
.fetch(r.response.set);
|
||||
return result;
|
||||
} finally {
|
||||
r.loading.set(false);
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export const resources = { createResource, useMetricEndpoint };
|
||||
|
||||
@@ -1,128 +1,114 @@
|
||||
import signals from "../signals.js";
|
||||
|
||||
/**
|
||||
* @param {Signals} signals
|
||||
* @template T
|
||||
* @param {(callback: (value: T) => void) => WebSocket} creator
|
||||
*/
|
||||
export function createWebSockets(signals) {
|
||||
/**
|
||||
* @template T
|
||||
* @param {(callback: (value: T) => void) => WebSocket} creator
|
||||
*/
|
||||
function createWebsocket(creator) {
|
||||
let ws = /** @type {WebSocket | null} */ (null);
|
||||
function createWebsocket(creator) {
|
||||
let ws = /** @type {WebSocket | null} */ (null);
|
||||
|
||||
const live = signals.createSignal(false);
|
||||
const latest = signals.createSignal(/** @type {T | null} */ (null));
|
||||
const live = signals.createSignal(false);
|
||||
const latest = signals.createSignal(/** @type {T | null} */ (null));
|
||||
|
||||
function reinitWebSocket() {
|
||||
if (!ws || ws.readyState === ws.CLOSED) {
|
||||
console.log("ws: reinit");
|
||||
resource.open();
|
||||
}
|
||||
function reinitWebSocket() {
|
||||
if (!ws || ws.readyState === ws.CLOSED) {
|
||||
console.log("ws: reinit");
|
||||
resource.open();
|
||||
}
|
||||
}
|
||||
|
||||
function reinitWebSocketIfDocumentNotHidden() {
|
||||
!window.document.hidden && reinitWebSocket();
|
||||
}
|
||||
function reinitWebSocketIfDocumentNotHidden() {
|
||||
!window.document.hidden && reinitWebSocket();
|
||||
}
|
||||
|
||||
const resource = {
|
||||
live,
|
||||
latest,
|
||||
open() {
|
||||
ws = creator((value) => latest.set(() => value));
|
||||
const resource = {
|
||||
live,
|
||||
latest,
|
||||
open() {
|
||||
ws = creator((value) => latest.set(() => value));
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
console.log("ws: open");
|
||||
live.set(true);
|
||||
});
|
||||
ws.addEventListener("open", () => {
|
||||
console.log("ws: open");
|
||||
live.set(true);
|
||||
});
|
||||
|
||||
ws.addEventListener("close", () => {
|
||||
console.log("ws: close");
|
||||
live.set(false);
|
||||
});
|
||||
|
||||
window.document.addEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
|
||||
window.document.addEventListener("online", reinitWebSocket);
|
||||
},
|
||||
close() {
|
||||
ws?.close();
|
||||
window.document.removeEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
window.document.removeEventListener("online", reinitWebSocket);
|
||||
ws.addEventListener("close", () => {
|
||||
console.log("ws: close");
|
||||
live.set(false);
|
||||
ws = null;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(candle: CandlestickData) => void} callback
|
||||
*/
|
||||
function krakenCandleWebSocketCreator(callback) {
|
||||
const ws = new WebSocket("wss://ws.kraken.com/v2");
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
method: "subscribe",
|
||||
params: {
|
||||
channel: "ohlc",
|
||||
symbol: ["BTC/USD"],
|
||||
interval: 1440,
|
||||
},
|
||||
}),
|
||||
window.document.addEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
});
|
||||
|
||||
ws.addEventListener("message", (message) => {
|
||||
const result = JSON.parse(message.data);
|
||||
window.document.addEventListener("online", reinitWebSocket);
|
||||
},
|
||||
close() {
|
||||
ws?.close();
|
||||
window.document.removeEventListener(
|
||||
"visibilitychange",
|
||||
reinitWebSocketIfDocumentNotHidden,
|
||||
);
|
||||
window.document.removeEventListener("online", reinitWebSocket);
|
||||
live.set(false);
|
||||
ws = null;
|
||||
},
|
||||
};
|
||||
|
||||
if (result.channel !== "ohlc") return;
|
||||
return resource;
|
||||
}
|
||||
|
||||
const { interval_begin, open, high, low, close } = result.data.at(-1);
|
||||
/**
|
||||
* @param {(candle: CandlestickData) => void} callback
|
||||
*/
|
||||
function krakenCandleWebSocketCreator(callback) {
|
||||
const ws = new WebSocket("wss://ws.kraken.com/v2");
|
||||
|
||||
/** @type {CandlestickData} */
|
||||
const candle = {
|
||||
// index: -1,
|
||||
time: new Date(interval_begin).valueOf() / 1000,
|
||||
open: Number(open),
|
||||
high: Number(high),
|
||||
low: Number(low),
|
||||
close: Number(close),
|
||||
};
|
||||
|
||||
candle && callback({ ...candle });
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
|
||||
const kraken1dCandle = createWebsocket((callback) =>
|
||||
krakenCandleWebSocketCreator(callback),
|
||||
);
|
||||
|
||||
kraken1dCandle.open();
|
||||
|
||||
signals.createEffect(kraken1dCandle.latest, (latest) => {
|
||||
if (latest) {
|
||||
const close = latest.close;
|
||||
console.log("close:", close);
|
||||
|
||||
window.document.title = `${latest.close.toLocaleString("en-us")} | ${
|
||||
window.location.host
|
||||
}`;
|
||||
}
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(
|
||||
JSON.stringify({
|
||||
method: "subscribe",
|
||||
params: {
|
||||
channel: "ohlc",
|
||||
symbol: ["BTC/USD"],
|
||||
interval: 1440,
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
kraken1dCandle,
|
||||
};
|
||||
ws.addEventListener("message", (message) => {
|
||||
const result = JSON.parse(message.data);
|
||||
|
||||
if (result.channel !== "ohlc") return;
|
||||
|
||||
const { interval_begin, open, high, low, close } = result.data.at(-1);
|
||||
|
||||
/** @type {CandlestickData} */
|
||||
const candle = {
|
||||
// index: -1,
|
||||
time: new Date(interval_begin).valueOf() / 1000,
|
||||
open: Number(open),
|
||||
high: Number(high),
|
||||
low: Number(low),
|
||||
close: Number(close),
|
||||
};
|
||||
|
||||
candle && callback({ ...candle });
|
||||
});
|
||||
|
||||
return ws;
|
||||
}
|
||||
/** @typedef {ReturnType<typeof createWebSockets>} WebSockets */
|
||||
|
||||
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
|
||||
const kraken1dCandle = createWebsocket((callback) =>
|
||||
krakenCandleWebSocketCreator(callback),
|
||||
);
|
||||
|
||||
kraken1dCandle.open();
|
||||
|
||||
export const webSockets = {
|
||||
kraken1dCandle,
|
||||
};
|
||||
/** @typedef {typeof webSockets} WebSockets */
|
||||
|
||||
Reference in New Issue
Block a user