Compare commits

...

19 Commits

Author SHA1 Message Date
nym21 810cdbd790 chore: Release 2025-04-05 12:13:31 +02:00
nym21 0d4f4aec4e computer: part 4 2025-04-05 12:12:55 +02:00
nym21 6b1863d3b4 chore: Release 2025-04-05 00:58:50 +02:00
nym21 27f5a3b16b dist: move config to config.toml 2025-04-05 00:55:22 +02:00
nym21 876cd8291b disk: init 2025-04-05 00:33:31 +02:00
nym21 d0c46e4ef3 server: yet another fix + release: v0.0.16 2025-04-04 19:20:28 +02:00
nym21 feb8898ebf server: forgot a 'v' + release: v0.0.15 2025-04-04 18:59:49 +02:00
nym21 4fef8c5cfd release: v0.0.14 2025-04-04 18:49:41 +02:00
nym21 7d56d8e35b server: fix downloaded repo version path 2025-04-04 18:49:21 +02:00
nym21 5f1a3a9c8f release: v0.0.13 2025-04-04 18:31:31 +02:00
nym21 0767b3156d global: snapshot 2025-04-04 18:31:10 +02:00
nym21 9f16379b41 readme: add warning 2025-04-04 18:23:03 +02:00
nym21 be632aaf37 kibo: fix simulation 2025-04-04 17:44:22 +02:00
nym21 118c87faf7 kibo: snapshot 2025-04-04 11:30:59 +02:00
nym21 ec1e53d566 kibo: finished converting ts types to jsdoc 2025-04-04 10:54:44 +02:00
nym21 6a17ee414a kibo: move types around 2025-04-04 00:40:40 +02:00
nym21 6700686e4b website: signals: upgrade to tresshaked v0.2.4 2025-04-03 15:15:55 +02:00
nym21 e8c34dd59b global: snapshot 2025-04-03 14:31:39 +02:00
nym21 4c2da31bb3 server: version repo url when not in dev mode 2025-04-02 16:49:35 +02:00
90 changed files with 4282 additions and 12324 deletions
+291
View File
@@ -0,0 +1,291 @@
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
#
# CI that:
#
# * checks for a Git Tag that looks like a release
# * builds artifacts with dist (archives, installers, hashes)
# * uploads those artifacts to temporary workflow zip
# * on success, uploads the artifacts to a GitHub Release
#
# Note that the GitHub Release will be created with a generated
# title/body based on your changelogs.
name: Release
permissions:
"contents": "write"
# This task will run whenever you push a git tag that looks like a version
# like "1.0.0", "v0.1.0-prerelease.1", "my-app/0.1.0", "releases/v1.0.0", etc.
# Various formats will be parsed into a VERSION and an optional PACKAGE_NAME, where
# PACKAGE_NAME must be the name of a Cargo package in your workspace, and VERSION
# must be a Cargo-style SemVer Version (must have at least major.minor.patch).
#
# If PACKAGE_NAME is specified, then the announcement will be for that
# package (erroring out if it doesn't have the given version or isn't dist-able).
#
# If PACKAGE_NAME isn't specified, then the announcement will be for all
# (dist-able) packages in the workspace with that version (this mode is
# intended for workspaces with only one dist-able package, or with all dist-able
# packages versioned/released in lockstep).
#
# If you push multiple tags at once, separate instances of this workflow will
# spin up, creating an independent announcement for each one. However, GitHub
# will hard limit this to 3 tags per commit, as it will assume more tags is a
# mistake.
#
# If there's a prerelease-style suffix to the version, then the release(s)
# will be marked as a prerelease.
on:
pull_request:
push:
tags:
- '**[0-9]+.[0-9]+.[0-9]+*'
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-20.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
tag-flag: ${{ !github.event.pull_request && format('--tag={0}', github.ref_name) || '' }}
publishing: ${{ !github.event.pull_request }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/dist
# sure would be cool if github gave us proper conditionals...
# so here's a doubly-nested ternary-via-truthiness to try to provide the best possible
# functionality based on whether this is a pull_request, and whether it's from a fork.
# (PRs run on the *source* but secrets are usually on the *target* -- that's *good*
# but also really annoying to build CI around when it needs secrets to work right.)
- id: plan
run: |
dist ${{ (!github.event.pull_request && format('host --steps=create --tag={0}', github.ref_name)) || 'plan' }} --output-format=json > plan-dist-manifest.json
echo "dist ran successfully"
cat plan-dist-manifest.json
echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
name: artifacts-plan-dist-manifest
path: plan-dist-manifest.json
# Build and packages all the platform-specific things
build-local-artifacts:
name: build-local-artifacts (${{ join(matrix.targets, ', ') }})
# Let the initial task tell us to not run (currently very blunt)
needs:
- plan
if: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix.include != null && (needs.plan.outputs.publishing == 'true' || fromJson(needs.plan.outputs.val).ci.github.pr_run_mode == 'upload') }}
strategy:
fail-fast: false
# Target platforms/runners are computed by dist in create-release.
# Each member of the matrix has the following arguments:
#
# - runner: the github runner
# - dist-args: cli flags to pass to dist
# - install-dist: expression to run to install dist on the runner
#
# Typically there will be:
# - 1 "global" task that builds universal installers
# - N "local" tasks that build each platform's binaries and platform-specific installers
matrix: ${{ fromJson(needs.plan.outputs.val).ci.github.artifacts_matrix }}
runs-on: ${{ matrix.runner }}
container: ${{ matrix.container && matrix.container.image || null }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/${{ join(matrix.targets, '-') }}-dist-manifest.json
steps:
- name: enable windows longpaths
run: |
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
run: |
if ! command -v cargo > /dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
fi
- name: Install dist
run: ${{ matrix.install_dist.run }}
# Get the dist-manifest
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- name: Install dependencies
run: |
${{ matrix.packages_install }}
- name: Build artifacts
run: |
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"
- id: cargo-dist
name: Post-build
# We force bash here just because github makes it really hard to get values up
# to "real" actions without writing to env-vars, and writing to env-vars has
# inconsistent syntax between shell and powershell.
shell: bash
run: |
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
dist print-upload-files-from-manifest --manifest dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-local-${{ join(matrix.targets, '_') }}
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Build and package all the platform-agnostic(ish) things
build-global-artifacts:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Get all the local artifacts for the global tasks to use (for e.g. checksums)
- name: Fetch local artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: cargo-dist
shell: bash
run: |
dist build ${{ needs.plan.outputs.tag-flag }} --output-format=json "--artifacts=global" > dist-manifest.json
echo "dist ran successfully"
# Parse out what we just built and upload it to scratch storage
echo "paths<<EOF" >> "$GITHUB_OUTPUT"
jq --raw-output ".upload_files[]" dist-manifest.json >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
cp dist-manifest.json "$BUILD_MANIFEST_NAME"
- name: "Upload artifacts"
uses: actions/upload-artifact@v4
with:
name: artifacts-build-global
path: |
${{ steps.cargo-dist.outputs.paths }}
${{ env.BUILD_MANIFEST_NAME }}
# Determines if we should publish/announce
host:
needs:
- plan
- build-local-artifacts
- build-global-artifacts
# Only run if we're "publishing", and only if local and global didn't fail (skipped is fine)
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-20.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
with:
name: cargo-dist-cache
path: ~/.cargo/bin/
- run: chmod +x ~/.cargo/bin/dist
# Fetch artifacts from scratch-storage
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: target/distrib/
merge-multiple: true
- id: host
shell: bash
run: |
dist host ${{ needs.plan.outputs.tag-flag }} --steps=upload --steps=release --output-format=json > dist-manifest.json
echo "artifacts uploaded and released successfully"
cat dist-manifest.json
echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT"
- name: "Upload dist-manifest.json"
uses: actions/upload-artifact@v4
with:
# Overwrite the previous copy
name: artifacts-dist-manifest
path: dist-manifest.json
# Create a GitHub Release while uploading all files to it
- name: "Download GitHub Artifacts"
uses: actions/download-artifact@v4
with:
pattern: artifacts-*
path: artifacts
merge-multiple: true
- name: Cleanup
run: |
# Remove the granular manifests
rm -f artifacts/*-dist-manifest.json
- name: Create GitHub Release
env:
PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}"
ANNOUNCEMENT_TITLE: "${{ fromJson(steps.host.outputs.manifest).announcement_title }}"
ANNOUNCEMENT_BODY: "${{ fromJson(steps.host.outputs.manifest).announcement_github_body }}"
RELEASE_COMMIT: "${{ github.sha }}"
run: |
# Write and read notes from a file to avoid quoting breaking things
echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt
gh release create "${{ needs.plan.outputs.tag }}" --target "$RELEASE_COMMIT" $PRERELEASE_FLAG --title "$ANNOUNCEMENT_TITLE" --notes-file "$RUNNER_TEMP/notes.txt" artifacts/*
announce:
needs:
- plan
- host
# use "always() && ..." to allow us to wait for all publish jobs while
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-20.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
-1
View File
@@ -29,7 +29,6 @@ docker/kibo
# Types
paths.d.ts
vecid-to-indexes.d.ts
# Outputs
_outputs
Generated
+103 -44
View File
@@ -368,7 +368,7 @@ dependencies = [
[[package]]
name = "brk"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_cli",
"brk_computer",
@@ -385,7 +385,7 @@ dependencies = [
[[package]]
name = "brk_cli"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_computer",
"brk_core",
@@ -406,7 +406,7 @@ dependencies = [
[[package]]
name = "brk_computer"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_core",
"brk_exit",
@@ -421,7 +421,7 @@ dependencies = [
[[package]]
name = "brk_core"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"bitcoin",
"bitcoincore-rpc",
@@ -438,7 +438,7 @@ dependencies = [
[[package]]
name = "brk_exit"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_logger",
"ctrlc",
@@ -447,7 +447,7 @@ dependencies = [
[[package]]
name = "brk_fetcher"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_core",
"brk_logger",
@@ -460,7 +460,7 @@ dependencies = [
[[package]]
name = "brk_indexer"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"bitcoin",
"bitcoincore-rpc",
@@ -479,7 +479,7 @@ dependencies = [
[[package]]
name = "brk_logger"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"color-eyre",
"env_logger",
@@ -489,7 +489,7 @@ dependencies = [
[[package]]
name = "brk_parser"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"bitcoin",
"bitcoincore-rpc",
@@ -504,7 +504,7 @@ dependencies = [
[[package]]
name = "brk_query"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"brk_computer",
"brk_indexer",
@@ -520,7 +520,7 @@ dependencies = [
[[package]]
name = "brk_server"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"axum",
"brk_computer",
@@ -540,12 +540,13 @@ dependencies = [
"serde",
"tokio",
"tower-http",
"tracing",
"zip",
]
[[package]]
name = "brk_vec"
version = "0.0.12"
version = "0.0.18"
dependencies = [
"memmap2",
"rayon",
@@ -639,9 +640,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.17"
version = "1.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a"
checksum = "525046617d8376e3db1deffb079e91cef90a89fc3ca5c185bbf8c9ecdd15cd5c"
dependencies = [
"jobserver",
"libc",
@@ -905,9 +906,9 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.4.5"
version = "3.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90eeab0aa92f3f9b4e87f258c72b139c207d251f9cbc1080a0086b86a8870dd3"
checksum = "697b5419f348fd5ae2478e8018cb016c00a5881c7f46c717de98ffd135a5651c"
dependencies = [
"nix",
"windows-sys 0.59.0",
@@ -1066,9 +1067,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
dependencies = [
"libc",
"windows-sys 0.59.0",
@@ -1115,12 +1116,12 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide 0.8.5",
"miniz_oxide 0.8.7",
]
[[package]]
@@ -1348,9 +1349,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2"
dependencies = [
"bytes",
"futures-util",
@@ -1364,9 +1365,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
version = "0.1.62"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2fd658b06e56721792c5df4475705b6cda790e9298d19d2f8af083457bcd127"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -1411,9 +1412,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.8.0"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
dependencies = [
"equivalent",
"hashbrown 0.15.2",
@@ -1502,10 +1503,11 @@ dependencies = [
[[package]]
name = "jobserver"
version = "0.1.32"
version = "0.1.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
dependencies = [
"getrandom 0.3.2",
"libc",
]
@@ -1660,9 +1662,9 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.8.5"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
@@ -2194,7 +2196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset",
"indexmap 2.8.0",
"indexmap 2.9.0",
]
[[package]]
@@ -2474,9 +2476,9 @@ checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "1.0.3"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e56a18552996ac8d29ecc3b190b4fdbb2d91ca4ec396de7bbffaf43f3d637e96"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"errno",
@@ -2656,7 +2658,7 @@ dependencies = [
"chrono",
"hex",
"indexmap 1.9.3",
"indexmap 2.8.0",
"indexmap 2.9.0",
"serde",
"serde_derive",
"serde_json",
@@ -2731,9 +2733,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "smallvec"
version = "1.14.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "smawk"
@@ -2980,7 +2982,7 @@ version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap 2.8.0",
"indexmap 2.9.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -3020,6 +3022,7 @@ dependencies = [
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
@@ -3042,9 +3045,21 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
dependencies = [
"log",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "tracing-core"
version = "0.1.33"
@@ -3240,11 +3255,37 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
[[package]]
name = "windows-core"
version = "0.52.0"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980"
dependencies = [
"windows-targets",
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
name = "windows-interface"
version = "0.59.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.100",
]
[[package]]
@@ -3253,6 +3294,24 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38"
[[package]]
name = "windows-result"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
@@ -3410,9 +3469,9 @@ dependencies = [
[[package]]
name = "zip"
version = "2.5.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27c03817464f64e23f6f37574b4fdc8cf65925b5bfd2b0f2aedf959791941f88"
checksum = "1dcb24d0152526ae49b9b96c1dcf71850ca1e0b882e4e28ed898a93c41334744"
dependencies = [
"aes",
"arbitrary",
@@ -3424,7 +3483,7 @@ dependencies = [
"flate2",
"getrandom 0.3.2",
"hmac",
"indexmap 2.8.0",
"indexmap 2.9.0",
"lzma-rs",
"memchr",
"pbkdf2",
+19 -1
View File
@@ -4,7 +4,7 @@ members = ["crates/*"]
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
package.license = "MIT"
package.edition = "2024"
package.version = "0.0.12"
package.version = "0.0.18"
package.repository = "https://github.com/bitcoinresearchkit/brk"
[profile.release]
@@ -12,6 +12,9 @@ lto = "fat"
codegen-units = 1
panic = "abort"
[profile.dist]
inherits = "release"
[workspace.dependencies]
bitcoin = { version = "0.32.5", features = ["serde"] }
bitcoincore-rpc = "0.19.0"
@@ -39,3 +42,18 @@ serde = { version = "1.0.219", features = ["derive"] }
serde_json = { version = "1.0.140", features = ["float_roundtrip"] }
tabled = "0.18.0"
zerocopy = { version = "0.8.24", features = ["derive"] }
[workspace.metadata.release]
shared-version = true
tag-name = "v{{version}}"
[workspace.metadata.dist]
cargo-dist-version = "0.28.0"
ci = "github"
installers = []
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
]
+9 -1
View File
@@ -34,6 +34,14 @@
</a>
</p>
> **WARNING**
>
> This project is still a work in progress and while it's much better in many ways than its previous version ([kibo v0.5](https://github.com/kibo-money/kibo)), it doesn't yet include all of those datasets. If you're interested in having everything right now, please use the latter until feature parity is achieved.
>
> The explorer part (mempool.space/electrs) is also not viable just yet.
>
> Stay tuned and please be patient, it's a lot of work !
The Bitcoin Research Kit is a high-performance toolchain designed to parse, index, compute, serve and visualize data from a Bitcoin Core node, enabling users to gain deeper insights into the Bitcoin network.
In other words it's an alternative to [Glassnode](https://glassnode.com), [mempool.space](https://mempool.space/) and [electrs](https://github.com/romanz/electrs) all in one package with a particular focus on simplicity and the self-hosting experience.
@@ -82,7 +90,7 @@ Heartfelt thanks go out to every donor on [Nostr](https://primal.net/p/npub1jagm
If you'd like to have your own instance hosted for you please contact [hosting@bitcoinresearchkit.org](mailto:hosting@bitcoinresearchkit.org).
- 2 separate servers (1 GB/s each) with different ISPs and Cloudflare integration for enhanced performance and optimal availability
- 2 separate dedicated servers (1 GB/s each) with different ISPs and Cloudflare integration for enhanced performance and optimal availability
- Direct communication for feature requests and support
- Updates delivered at your convenience
- Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz`
+3
View File
@@ -26,3 +26,6 @@ toml = "0.8.20"
[[bin]]
name = "brk"
path = "src/main.rs"
[package.metadata.dist]
dist = false
-1
View File
@@ -1,5 +1,4 @@
use brk_computer::Computer;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_query::{Index, Output, Params as QueryParams, Query, Tabled, Value};
use tabled::settings::Style;
+4 -2
View File
@@ -12,8 +12,10 @@ pub struct Stores {
impl Stores {
pub fn import(path: &Path) -> color_eyre::Result<Self> {
let address_to_utxos_received = Store::import(&path.join("address_to_utxos_received"), Version::from(1))?;
let address_to_utxos_spent = Store::import(&path.join("address_to_utxos_spent"), Version::from(1))?;
let address_to_utxos_received =
Store::import(&path.join("address_to_utxos_received"), Version::ONE)?;
let address_to_utxos_spent =
Store::import(&path.join("address_to_utxos_spent"), Version::ONE)?;
Ok(Self {
address_to_utxos_received,
+14 -12
View File
@@ -9,17 +9,19 @@ use std::{
use brk_core::CheckedSub;
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed, Error, Result, StoredIndex, StoredType, Version};
use brk_vec::{
AnyStorableVec, Compressed, Error, Result, StorableVec, StoredIndex, StoredType, Version,
};
const FLUSH_EVERY: usize = 10_000;
#[derive(Debug)]
pub struct StorableVec<I, T> {
pub struct ComputedVec<I, T> {
computed_version: Option<Version>,
vec: brk_vec::StorableVec<I, T>,
vec: StorableVec<I, T>,
}
impl<I, T> StorableVec<I, T>
impl<I, T> ComputedVec<I, T>
where
I: StoredIndex,
T: StoredType,
@@ -137,7 +139,7 @@ where
F: FnMut((A, B, &mut Self, &mut brk_vec::StorableVec<A, B>)) -> (I, T),
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + other.version(),
Version::ZERO + self.version() + other.version(),
)?;
let index = max_from.min(A::from(self.len()));
@@ -160,7 +162,7 @@ where
T: StoredIndex,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + other.version(),
Version::ZERO + self.version() + other.version(),
)?;
let index = max_from.min(self.vec.get_last()?.cloned().unwrap_or_default());
@@ -188,7 +190,7 @@ where
T: StoredIndex,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + first_indexes.version() + last_indexes.version(),
Version::ZERO + self.version() + first_indexes.version() + last_indexes.version(),
)?;
let index = max_from.min(T::from(self.len()));
@@ -213,7 +215,7 @@ where
T: Copy + From<usize> + CheckedSub<T> + StoredIndex,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + first_indexes.version(),
Version::ZERO + self.version() + first_indexes.version(),
)?;
let index = max_from.min(I::from(self.len()));
@@ -250,7 +252,7 @@ where
<T2 as TryInto<T>>::Error: error::Error + 'static,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + first_indexes.version() + last_indexes.version(),
Version::ZERO + self.version() + first_indexes.version() + last_indexes.version(),
)?;
let index = max_from.min(I::from(self.len()));
@@ -278,7 +280,7 @@ where
A: StoredIndex + StoredType,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + self_to_other.version() + other_to_self.version(),
Version::ZERO + self.version() + self_to_other.version() + other_to_self.version(),
)?;
let index = max_from.min(I::from(self.len()));
@@ -302,7 +304,7 @@ where
<T2 as TryInto<T>>::Error: error::Error + 'static,
{
self.validate_computed_version_or_reset_file(
Version::from(0) + self.version() + first_indexes.version() + last_indexes.version(),
Version::ZERO + self.version() + first_indexes.version() + last_indexes.version(),
)?;
let index = max_from.min(I::from(self.len()));
@@ -316,7 +318,7 @@ where
}
}
impl<I, T> Clone for StorableVec<I, T>
impl<I, T> Clone for ComputedVec<I, T>
where
I: StoredIndex,
T: StoredType,
+32 -36
View File
@@ -1,21 +1,21 @@
use std::{fs, path::Path};
use brk_core::{CheckedSub, Dateindex, Height, Timestamp};
use brk_core::{CheckedSub, Dateindex, Timestamp};
use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyStorableVec, Compressed, Version};
use super::{
Indexes, StorableVec, indexes,
stats::{StorableVecGeneatorOptions, StorableVecsStatsFromHeight},
ComputedVec, Indexes,
grouped::{ComputedVecsFromHeight, StorableVecGeneatorOptions},
indexes,
};
#[derive(Clone)]
pub struct Vecs {
pub height_to_block_interval: StorableVec<Height, Timestamp>,
pub indexes_to_block_interval_stats: StorableVecsStatsFromHeight<Timestamp>,
pub dateindex_to_block_count: StorableVec<Dateindex, u16>,
pub dateindex_to_total_block_count: StorableVec<Dateindex, u32>,
pub indexes_to_block_interval: ComputedVecsFromHeight<Timestamp>,
pub dateindex_to_block_count: ComputedVec<Dateindex, u16>,
pub dateindex_to_total_block_count: ComputedVec<Dateindex, u32>,
}
impl Vecs {
@@ -23,27 +23,24 @@ impl Vecs {
fs::create_dir_all(path)?;
Ok(Self {
height_to_block_interval: StorableVec::forced_import(
&path.join("height_to_block_interval"),
Version::from(1),
compressed,
)?,
indexes_to_block_interval_stats: StorableVecsStatsFromHeight::forced_import(
&path.join("block_interval"),
indexes_to_block_interval: ComputedVecsFromHeight::forced_import(
path,
"block_interval",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default()
.add_percentiles()
.add_minmax()
.add_average(),
)?,
dateindex_to_block_count: StorableVec::forced_import(
dateindex_to_block_count: ComputedVec::forced_import(
&path.join("dateindex_to_block_count"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_total_block_count: StorableVec::forced_import(
dateindex_to_total_block_count: ComputedVec::forced_import(
&path.join("dateindex_to_total_block_count"),
Version::from(1),
Version::ONE,
compressed,
)?,
})
@@ -58,23 +55,23 @@ impl Vecs {
) -> color_eyre::Result<()> {
let indexer_vecs = indexer.mut_vecs();
self.height_to_block_interval.compute_transform(
starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(),
|(height, timestamp, _, height_to_timestamp)| {
let interval = height.decremented().map_or(Timestamp::ZERO, |prev_h| {
let prev_timestamp = *height_to_timestamp.get(prev_h).unwrap().unwrap();
timestamp
.checked_sub(prev_timestamp)
.unwrap_or(Timestamp::ZERO)
});
(height, interval)
self.indexes_to_block_interval.compute(
|v| {
v.compute_transform(
starting_indexes.height,
indexer_vecs.height_to_timestamp.mut_vec(),
|(height, timestamp, _, height_to_timestamp)| {
let interval = height.decremented().map_or(Timestamp::ZERO, |prev_h| {
let prev_timestamp = *height_to_timestamp.get(prev_h).unwrap().unwrap();
timestamp
.checked_sub(prev_timestamp)
.unwrap_or(Timestamp::ZERO)
});
(height, interval)
},
exit,
)
},
exit,
)?;
self.indexes_to_block_interval_stats.compute(
&mut self.height_to_block_interval,
indexes,
starting_indexes,
exit,
@@ -86,11 +83,10 @@ impl Vecs {
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
vec![
self.height_to_block_interval.any_vec(),
self.dateindex_to_block_count.any_vec(),
self.dateindex_to_total_block_count.any_vec(),
],
self.indexes_to_block_interval_stats.as_any_vecs(),
self.indexes_to_block_interval.any_vecs(),
]
.concat()
}
@@ -3,84 +3,99 @@ use std::path::Path;
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed, Result, StoredIndex, StoredType, Version};
use crate::storage::vecs::base::StorableVec;
use crate::storage::vecs::base::ComputedVec;
use super::ComputedType;
#[derive(Clone, Debug)]
pub struct StorableVecBuilder<I, T>
pub struct ComputedVecBuilder<I, T>
where
I: StoredIndex,
T: ComputedType,
{
pub first: Option<StorableVec<I, T>>,
pub average: Option<StorableVec<I, T>>,
pub sum: Option<StorableVec<I, T>>,
pub max: Option<StorableVec<I, T>>,
pub _90p: Option<StorableVec<I, T>>,
pub _75p: Option<StorableVec<I, T>>,
pub median: Option<StorableVec<I, T>>,
pub _25p: Option<StorableVec<I, T>>,
pub _10p: Option<StorableVec<I, T>>,
pub min: Option<StorableVec<I, T>>,
pub last: Option<StorableVec<I, T>>,
pub first: Option<ComputedVec<I, T>>,
pub average: Option<ComputedVec<I, T>>,
pub sum: Option<ComputedVec<I, T>>,
pub max: Option<ComputedVec<I, T>>,
pub _90p: Option<ComputedVec<I, T>>,
pub _75p: Option<ComputedVec<I, T>>,
pub median: Option<ComputedVec<I, T>>,
pub _25p: Option<ComputedVec<I, T>>,
pub _10p: Option<ComputedVec<I, T>>,
pub min: Option<ComputedVec<I, T>>,
pub last: Option<ComputedVec<I, T>>,
}
impl<I, T> StorableVecBuilder<I, T>
impl<I, T> ComputedVecBuilder<I, T>
where
I: StoredIndex,
T: ComputedType,
{
pub fn forced_import(
path: &Path,
name: &str,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let name = path.file_name().unwrap().to_str().unwrap().to_string();
// let name = path.file_name().unwrap().to_str().unwrap().to_string();
let key = I::to_string().split("::").last().unwrap().to_lowercase();
let prefix = |s: &str| path.with_file_name(format!("{key}_to_{s}_{name}"));
let suffix = |s: &str| path.with_file_name(format!("{key}_to_{name}_{s}"));
let only_one_active = options.is_only_one_active();
let prefix = |s: &str| {
if only_one_active {
path.with_file_name(format!("{key}_to_{name}"))
} else {
path.with_file_name(format!("{key}_to_{s}_{name}"))
}
};
let suffix = |s: &str| {
if only_one_active {
path.with_file_name(format!("{key}_to_{name}"))
} else {
path.with_file_name(format!("{key}_to_{name}_{s}"))
}
};
let s = Self {
first: options.first.then(|| {
StorableVec::forced_import(&prefix("first"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&prefix("first"), Version::ONE, compressed).unwrap()
}),
last: options.last.then(|| {
StorableVec::forced_import(
ComputedVec::forced_import(
&path.with_file_name(format!("{key}_to_{name}")),
Version::from(1),
Version::ONE,
compressed,
)
.unwrap()
}),
min: options.min.then(|| {
StorableVec::forced_import(&suffix("min"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("min"), Version::ONE, compressed).unwrap()
}),
max: options.max.then(|| {
StorableVec::forced_import(&suffix("max"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("max"), Version::ONE, compressed).unwrap()
}),
median: options.median.then(|| {
StorableVec::forced_import(&suffix("median"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("median"), Version::ONE, compressed).unwrap()
}),
average: options.average.then(|| {
StorableVec::forced_import(&suffix("average"), Version::from(1), compressed)
.unwrap()
ComputedVec::forced_import(&suffix("average"), Version::ONE, compressed).unwrap()
}),
sum: options.sum.then(|| {
StorableVec::forced_import(&suffix("sum"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("sum"), Version::ONE, compressed).unwrap()
}),
_90p: options._90p.then(|| {
StorableVec::forced_import(&suffix("90p"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("90p"), Version::ONE, compressed).unwrap()
}),
_75p: options._75p.then(|| {
StorableVec::forced_import(&suffix("75p"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("75p"), Version::ONE, compressed).unwrap()
}),
_25p: options._25p.then(|| {
StorableVec::forced_import(&suffix("25p"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("25p"), Version::ONE, compressed).unwrap()
}),
_10p: options._10p.then(|| {
StorableVec::forced_import(&suffix("10p"), Version::from(1), compressed).unwrap()
ComputedVec::forced_import(&suffix("10p"), Version::ONE, compressed).unwrap()
}),
};
@@ -90,7 +105,7 @@ where
pub fn compute<I2>(
&mut self,
max_from: I,
source: &mut StorableVec<I2, T>,
source: &mut ComputedVec<I2, T>,
first_indexes: &mut brk_vec::StorableVec<I, I2>,
last_indexes: &mut brk_vec::StorableVec<I, I2>,
exit: &Exit,
@@ -197,7 +212,7 @@ where
pub fn from_aligned<I2>(
&mut self,
max_from: I,
source: &mut StorableVecBuilder<I2, T>,
source: &mut ComputedVecBuilder<I2, T>,
first_indexes: &mut brk_vec::StorableVec<I, I2>,
last_indexes: &mut brk_vec::StorableVec<I, I2>,
exit: &Exit,
@@ -338,15 +353,11 @@ where
fn starting_index(&self, max_from: I) -> I {
max_from.min(I::from(
self.as_any_vecs()
.into_iter()
.map(|v| v.len())
.min()
.unwrap(),
self.any_vecs().into_iter().map(|v| v.len()).min().unwrap(),
))
}
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
pub fn any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
let mut v: Vec<&dyn AnyStorableVec> = vec![];
if let Some(first) = self.first.as_ref() {
@@ -564,4 +575,24 @@ impl StorableVecGeneatorOptions {
self._10p = false;
self
}
pub fn is_only_one_active(&self) -> bool {
[
self.average,
self.sum,
self.max,
self._90p,
self._75p,
self.median,
self._25p,
self._10p,
self.min,
self.first,
self.last,
]
.iter()
.filter(|b| **b)
.count()
== 1
}
}
@@ -2,55 +2,68 @@ use std::path::Path;
use brk_core::{Dateindex, Decadeindex, Monthindex, Quarterindex, Weekindex, Yearindex};
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed};
use brk_vec::{AnyStorableVec, Compressed, Result, Version};
use crate::storage::vecs::{Indexes, base::StorableVec, indexes};
use crate::storage::vecs::{Indexes, base::ComputedVec, indexes};
use super::{ComputedType, StorableVecBuilder, StorableVecGeneatorOptions};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
#[derive(Clone)]
pub struct StorableVecsStatsFromDate<T>
pub struct ComputedVecsFromDateindex<T>
where
T: ComputedType + PartialOrd,
{
pub weekindex: StorableVecBuilder<Weekindex, T>,
pub monthindex: StorableVecBuilder<Monthindex, T>,
pub quarterindex: StorableVecBuilder<Quarterindex, T>,
pub yearindex: StorableVecBuilder<Yearindex, T>,
pub decadeindex: StorableVecBuilder<Decadeindex, T>,
pub dateindex: ComputedVec<Dateindex, T>,
pub weekindex: ComputedVecBuilder<Weekindex, T>,
pub monthindex: ComputedVecBuilder<Monthindex, T>,
pub quarterindex: ComputedVecBuilder<Quarterindex, T>,
pub yearindex: ComputedVecBuilder<Yearindex, T>,
pub decadeindex: ComputedVecBuilder<Decadeindex, T>,
}
impl<T> StorableVecsStatsFromDate<T>
impl<T> ComputedVecsFromDateindex<T>
where
T: ComputedType + Ord + From<f64>,
f64: From<T>,
{
pub fn forced_import(
path: &Path,
name: &str,
version: Version,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let options = options.remove_percentiles();
Ok(Self {
weekindex: StorableVecBuilder::forced_import(path, compressed, options)?,
monthindex: StorableVecBuilder::forced_import(path, compressed, options)?,
quarterindex: StorableVecBuilder::forced_import(path, compressed, options)?,
yearindex: StorableVecBuilder::forced_import(path, compressed, options)?,
decadeindex: StorableVecBuilder::forced_import(path, compressed, options)?,
dateindex: ComputedVec::forced_import(
&path.join(format!("dateindex_to_{name}")),
version,
compressed,
)?,
weekindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
monthindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
quarterindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
yearindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
decadeindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
})
}
pub fn compute(
pub fn compute<F>(
&mut self,
source: &mut StorableVec<Dateindex, T>,
mut compute: F,
indexes: &mut indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()> {
) -> color_eyre::Result<()>
where
F: FnMut(&mut ComputedVec<Dateindex, T>) -> Result<()>,
{
compute(&mut self.dateindex)?;
self.weekindex.compute(
starting_indexes.weekindex,
source,
&mut self.dateindex,
indexes.weekindex_to_first_dateindex.mut_vec(),
indexes.weekindex_to_last_dateindex.mut_vec(),
exit,
@@ -58,7 +71,7 @@ where
self.monthindex.compute(
starting_indexes.monthindex,
source,
&mut self.dateindex,
indexes.monthindex_to_first_dateindex.mut_vec(),
indexes.monthindex_to_last_dateindex.mut_vec(),
exit,
@@ -91,13 +104,14 @@ where
Ok(())
}
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
pub fn any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
self.weekindex.as_any_vecs(),
self.monthindex.as_any_vecs(),
self.quarterindex.as_any_vecs(),
self.yearindex.as_any_vecs(),
self.decadeindex.as_any_vecs(),
vec![self.dateindex.any_vec()],
self.weekindex.any_vecs(),
self.monthindex.any_vecs(),
self.quarterindex.any_vecs(),
self.yearindex.any_vecs(),
self.decadeindex.any_vecs(),
]
.concat()
}
@@ -4,63 +4,78 @@ use brk_core::{
Dateindex, Decadeindex, Difficultyepoch, Height, Monthindex, Quarterindex, Weekindex, Yearindex,
};
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed};
use brk_vec::{AnyStorableVec, Compressed, Result, Version};
use crate::storage::vecs::{Indexes, base::StorableVec, indexes};
use crate::storage::vecs::{Indexes, base::ComputedVec, indexes};
use super::{ComputedType, StorableVecBuilder, StorableVecGeneatorOptions};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
#[derive(Clone)]
pub struct StorableVecsStatsFromHeight<T>
pub struct ComputedVecsFromHeight<T>
where
T: ComputedType + PartialOrd,
{
pub dateindex: StorableVecBuilder<Dateindex, T>,
pub weekindex: StorableVecBuilder<Weekindex, T>,
pub difficultyepoch: StorableVecBuilder<Difficultyepoch, T>,
pub monthindex: StorableVecBuilder<Monthindex, T>,
pub quarterindex: StorableVecBuilder<Quarterindex, T>,
pub yearindex: StorableVecBuilder<Yearindex, T>,
pub height: ComputedVec<Height, T>,
pub dateindex: ComputedVecBuilder<Dateindex, T>,
pub weekindex: ComputedVecBuilder<Weekindex, T>,
pub difficultyepoch: ComputedVecBuilder<Difficultyepoch, T>,
pub monthindex: ComputedVecBuilder<Monthindex, T>,
pub quarterindex: ComputedVecBuilder<Quarterindex, T>,
pub yearindex: ComputedVecBuilder<Yearindex, T>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
pub decadeindex: StorableVecBuilder<Decadeindex, T>,
pub decadeindex: ComputedVecBuilder<Decadeindex, T>,
}
impl<T> StorableVecsStatsFromHeight<T>
impl<T> ComputedVecsFromHeight<T>
where
T: ComputedType + Ord + From<f64>,
f64: From<T>,
{
pub fn forced_import(
path: &Path,
name: &str,
version: Version,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let dateindex = StorableVecBuilder::forced_import(path, compressed, options)?;
let height = ComputedVec::forced_import(
&path.join(format!("height_to_{name}")),
version,
compressed,
)?;
let dateindex = ComputedVecBuilder::forced_import(path, name, compressed, options)?;
let options = options.remove_percentiles();
Ok(Self {
height,
dateindex,
weekindex: StorableVecBuilder::forced_import(path, compressed, options)?,
difficultyepoch: StorableVecBuilder::forced_import(path, compressed, options)?,
monthindex: StorableVecBuilder::forced_import(path, compressed, options)?,
quarterindex: StorableVecBuilder::forced_import(path, compressed, options)?,
yearindex: StorableVecBuilder::forced_import(path, compressed, options)?,
// halvingepoch: StorableVecGeneator::forced_import(path, compressed, options)?,
decadeindex: StorableVecBuilder::forced_import(path, compressed, options)?,
weekindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
difficultyepoch: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
monthindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
quarterindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
yearindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, compressed, options)?,
decadeindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
})
}
pub fn compute(
pub fn compute<F>(
&mut self,
source: &mut StorableVec<Height, T>,
mut compute: F,
indexes: &mut indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()> {
) -> color_eyre::Result<()>
where
F: FnMut(&mut ComputedVec<Height, T>) -> Result<()>,
{
compute(&mut self.height)?;
self.dateindex.compute(
starting_indexes.dateindex,
source,
&mut self.height,
indexes.dateindex_to_first_height.mut_vec(),
indexes.dateindex_to_last_height.mut_vec(),
exit,
@@ -108,7 +123,7 @@ where
self.difficultyepoch.compute(
starting_indexes.difficultyepoch,
source,
&mut self.height,
indexes.difficultyepoch_to_first_height.mut_vec(),
indexes.difficultyepoch_to_last_height.mut_vec(),
exit,
@@ -117,16 +132,17 @@ where
Ok(())
}
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
pub fn any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
self.dateindex.as_any_vecs(),
self.weekindex.as_any_vecs(),
self.difficultyepoch.as_any_vecs(),
self.monthindex.as_any_vecs(),
self.quarterindex.as_any_vecs(),
self.yearindex.as_any_vecs(),
vec![self.height.any_vec()],
self.dateindex.any_vecs(),
self.weekindex.any_vecs(),
self.difficultyepoch.any_vecs(),
self.monthindex.any_vecs(),
self.quarterindex.any_vecs(),
self.yearindex.any_vecs(),
// self.halvingepoch.as_any_vecs(),
self.decadeindex.as_any_vecs(),
self.decadeindex.any_vecs(),
]
.concat()
}
@@ -0,0 +1,79 @@
use std::path::Path;
use brk_core::{Difficultyepoch, Height};
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed, Result, Version};
use crate::storage::vecs::{Indexes, base::ComputedVec, indexes};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
#[derive(Clone)]
pub struct ComputedVecsFromHeightStrict<T>
where
T: ComputedType + PartialOrd,
{
pub height: ComputedVec<Height, T>,
pub difficultyepoch: ComputedVecBuilder<Difficultyepoch, T>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
}
impl<T> ComputedVecsFromHeightStrict<T>
where
T: ComputedType + Ord + From<f64>,
f64: From<T>,
{
pub fn forced_import(
path: &Path,
name: &str,
version: Version,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let height = ComputedVec::forced_import(
&path.join(format!("height_to_{name}")),
version,
compressed,
)?;
let options = options.remove_percentiles();
Ok(Self {
height,
difficultyepoch: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, compressed, options)?,
})
}
pub fn compute<F>(
&mut self,
mut compute: F,
indexes: &mut indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()>
where
F: FnMut(&mut ComputedVec<Height, T>) -> Result<()>,
{
compute(&mut self.height)?;
self.difficultyepoch.compute(
starting_indexes.difficultyepoch,
&mut self.height,
indexes.difficultyepoch_to_first_height.mut_vec(),
indexes.difficultyepoch_to_last_height.mut_vec(),
exit,
)?;
Ok(())
}
pub fn any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
vec![self.height.any_vec()],
self.difficultyepoch.any_vecs(),
// self.halvingepoch.as_any_vecs(),
]
.concat()
}
}
@@ -0,0 +1,164 @@
use std::path::Path;
use brk_core::{
Dateindex, Decadeindex, Difficultyepoch, Height, Monthindex, Quarterindex, Txindex, Weekindex,
Yearindex,
};
use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyStorableVec, Compressed, Result, Version};
use crate::storage::vecs::{Indexes, base::ComputedVec, indexes};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
#[derive(Clone)]
pub struct ComputedVecsFromTxindex<T>
where
T: ComputedType + PartialOrd,
{
pub txindex: ComputedVec<Txindex, T>,
pub height: ComputedVecBuilder<Height, T>,
pub dateindex: ComputedVecBuilder<Dateindex, T>,
pub weekindex: ComputedVecBuilder<Weekindex, T>,
pub difficultyepoch: ComputedVecBuilder<Difficultyepoch, T>,
pub monthindex: ComputedVecBuilder<Monthindex, T>,
pub quarterindex: ComputedVecBuilder<Quarterindex, T>,
pub yearindex: ComputedVecBuilder<Yearindex, T>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
pub decadeindex: ComputedVecBuilder<Decadeindex, T>,
}
impl<T> ComputedVecsFromTxindex<T>
where
T: ComputedType + Ord + From<f64>,
f64: From<T>,
{
pub fn forced_import(
path: &Path,
name: &str,
version: Version,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let txindex = ComputedVec::forced_import(
&path.join(format!("txindex_to_{name}")),
version,
compressed,
)?;
let height = ComputedVecBuilder::forced_import(path, name, compressed, options)?;
let dateindex = ComputedVecBuilder::forced_import(path, name, compressed, options)?;
let options = options.remove_percentiles();
Ok(Self {
txindex,
height,
dateindex,
weekindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
difficultyepoch: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
monthindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
quarterindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
yearindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, compressed, options)?,
decadeindex: ComputedVecBuilder::forced_import(path, name, compressed, options)?,
})
}
pub fn compute<F>(
&mut self,
mut compute: F,
indexer: &mut Indexer,
indexes: &mut indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()>
where
F: FnMut(&mut ComputedVec<Txindex, T>) -> Result<()>,
{
compute(&mut self.txindex)?;
self.height.compute(
starting_indexes.height,
&mut self.txindex,
indexer.mut_vecs().height_to_first_txindex.mut_vec(),
indexes.height_to_last_txindex.mut_vec(),
exit,
)?;
self.dateindex.from_aligned(
starting_indexes.dateindex,
&mut self.height,
indexes.dateindex_to_first_height.mut_vec(),
indexes.dateindex_to_last_height.mut_vec(),
exit,
)?;
self.weekindex.from_aligned(
starting_indexes.weekindex,
&mut self.dateindex,
indexes.weekindex_to_first_dateindex.mut_vec(),
indexes.weekindex_to_last_dateindex.mut_vec(),
exit,
)?;
self.monthindex.from_aligned(
starting_indexes.monthindex,
&mut self.dateindex,
indexes.monthindex_to_first_dateindex.mut_vec(),
indexes.monthindex_to_last_dateindex.mut_vec(),
exit,
)?;
self.quarterindex.from_aligned(
starting_indexes.quarterindex,
&mut self.monthindex,
indexes.quarterindex_to_first_monthindex.mut_vec(),
indexes.quarterindex_to_last_monthindex.mut_vec(),
exit,
)?;
self.yearindex.from_aligned(
starting_indexes.yearindex,
&mut self.monthindex,
indexes.yearindex_to_first_monthindex.mut_vec(),
indexes.yearindex_to_last_monthindex.mut_vec(),
exit,
)?;
self.decadeindex.from_aligned(
starting_indexes.decadeindex,
&mut self.yearindex,
indexes.decadeindex_to_first_yearindex.mut_vec(),
indexes.decadeindex_to_last_yearindex.mut_vec(),
exit,
)?;
self.difficultyepoch.from_aligned(
starting_indexes.difficultyepoch,
&mut self.height,
indexes.difficultyepoch_to_first_height.mut_vec(),
indexes.difficultyepoch_to_last_height.mut_vec(),
exit,
)?;
Ok(())
}
pub fn any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
vec![self.txindex.any_vec()],
self.height.any_vecs(),
self.dateindex.any_vecs(),
self.weekindex.any_vecs(),
self.difficultyepoch.any_vecs(),
self.monthindex.any_vecs(),
self.quarterindex.any_vecs(),
self.yearindex.any_vecs(),
// self.halvingepoch.as_any_vecs(),
self.decadeindex.any_vecs(),
]
.concat()
}
}
@@ -1,11 +1,13 @@
mod from_date;
mod builder;
mod from_dateindex;
mod from_height;
mod from_height_strict;
mod generic;
mod from_txindex;
mod stored_type;
pub use from_date::*;
pub use builder::*;
pub use from_dateindex::*;
pub use from_height::*;
pub use from_height_strict::*;
pub use generic::*;
pub use from_txindex::*;
pub use stored_type::*;
+145 -145
View File
@@ -8,60 +8,60 @@ use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyStorableVec, Compressed, Version};
use super::StorableVec;
use super::ComputedVec;
#[derive(Clone)]
pub struct Vecs {
// pub height_to_last_addressindex: StorableVec<Height, Addressindex>,
// pub height_to_last_txoutindex: StorableVec<Height, Txoutindex>,
pub dateindex_to_date: StorableVec<Dateindex, Date>,
pub dateindex_to_dateindex: StorableVec<Dateindex, Dateindex>,
pub dateindex_to_first_height: StorableVec<Dateindex, Height>,
pub dateindex_to_last_height: StorableVec<Dateindex, Height>,
pub dateindex_to_monthindex: StorableVec<Dateindex, Monthindex>,
pub dateindex_to_timestamp: StorableVec<Dateindex, Timestamp>,
pub dateindex_to_weekindex: StorableVec<Dateindex, Weekindex>,
pub decadeindex_to_decadeindex: StorableVec<Decadeindex, Decadeindex>,
pub decadeindex_to_first_yearindex: StorableVec<Decadeindex, Yearindex>,
pub decadeindex_to_last_yearindex: StorableVec<Decadeindex, Yearindex>,
pub decadeindex_to_timestamp: StorableVec<Decadeindex, Timestamp>,
pub difficultyepoch_to_difficultyepoch: StorableVec<Difficultyepoch, Difficultyepoch>,
pub difficultyepoch_to_first_height: StorableVec<Difficultyepoch, Height>,
pub difficultyepoch_to_last_height: StorableVec<Difficultyepoch, Height>,
pub difficultyepoch_to_timestamp: StorableVec<Difficultyepoch, Timestamp>,
pub halvingepoch_to_first_height: StorableVec<Halvingepoch, Height>,
pub halvingepoch_to_halvingepoch: StorableVec<Halvingepoch, Halvingepoch>,
pub halvingepoch_to_last_height: StorableVec<Halvingepoch, Height>,
pub halvingepoch_to_timestamp: StorableVec<Halvingepoch, Timestamp>,
pub height_to_dateindex: StorableVec<Height, Dateindex>,
pub height_to_difficultyepoch: StorableVec<Height, Difficultyepoch>,
pub height_to_fixed_date: StorableVec<Height, Date>,
pub height_to_fixed_timestamp: StorableVec<Height, Timestamp>,
pub height_to_halvingepoch: StorableVec<Height, Halvingepoch>,
pub height_to_height: StorableVec<Height, Height>,
pub height_to_last_txindex: StorableVec<Height, Txindex>,
pub height_to_real_date: StorableVec<Height, Date>,
pub monthindex_to_first_dateindex: StorableVec<Monthindex, Dateindex>,
pub monthindex_to_last_dateindex: StorableVec<Monthindex, Dateindex>,
pub monthindex_to_monthindex: StorableVec<Monthindex, Monthindex>,
pub monthindex_to_quarterindex: StorableVec<Monthindex, Quarterindex>,
pub monthindex_to_timestamp: StorableVec<Monthindex, Timestamp>,
pub monthindex_to_yearindex: StorableVec<Monthindex, Yearindex>,
pub quarterindex_to_first_monthindex: StorableVec<Quarterindex, Monthindex>,
pub quarterindex_to_last_monthindex: StorableVec<Quarterindex, Monthindex>,
pub quarterindex_to_quarterindex: StorableVec<Quarterindex, Quarterindex>,
pub quarterindex_to_timestamp: StorableVec<Quarterindex, Timestamp>,
pub txindex_to_last_txinindex: StorableVec<Txindex, Txinindex>,
pub txindex_to_last_txoutindex: StorableVec<Txindex, Txoutindex>,
pub weekindex_to_first_dateindex: StorableVec<Weekindex, Dateindex>,
pub weekindex_to_last_dateindex: StorableVec<Weekindex, Dateindex>,
pub weekindex_to_timestamp: StorableVec<Weekindex, Timestamp>,
pub weekindex_to_weekindex: StorableVec<Weekindex, Weekindex>,
pub yearindex_to_decadeindex: StorableVec<Yearindex, Decadeindex>,
pub yearindex_to_first_monthindex: StorableVec<Yearindex, Monthindex>,
pub yearindex_to_last_monthindex: StorableVec<Yearindex, Monthindex>,
pub yearindex_to_timestamp: StorableVec<Yearindex, Timestamp>,
pub yearindex_to_yearindex: StorableVec<Yearindex, Yearindex>,
pub dateindex_to_date: ComputedVec<Dateindex, Date>,
pub dateindex_to_dateindex: ComputedVec<Dateindex, Dateindex>,
pub dateindex_to_first_height: ComputedVec<Dateindex, Height>,
pub dateindex_to_last_height: ComputedVec<Dateindex, Height>,
pub dateindex_to_monthindex: ComputedVec<Dateindex, Monthindex>,
pub dateindex_to_timestamp: ComputedVec<Dateindex, Timestamp>,
pub dateindex_to_weekindex: ComputedVec<Dateindex, Weekindex>,
pub decadeindex_to_decadeindex: ComputedVec<Decadeindex, Decadeindex>,
pub decadeindex_to_first_yearindex: ComputedVec<Decadeindex, Yearindex>,
pub decadeindex_to_last_yearindex: ComputedVec<Decadeindex, Yearindex>,
pub decadeindex_to_timestamp: ComputedVec<Decadeindex, Timestamp>,
pub difficultyepoch_to_difficultyepoch: ComputedVec<Difficultyepoch, Difficultyepoch>,
pub difficultyepoch_to_first_height: ComputedVec<Difficultyepoch, Height>,
pub difficultyepoch_to_last_height: ComputedVec<Difficultyepoch, Height>,
pub difficultyepoch_to_timestamp: ComputedVec<Difficultyepoch, Timestamp>,
pub halvingepoch_to_first_height: ComputedVec<Halvingepoch, Height>,
pub halvingepoch_to_halvingepoch: ComputedVec<Halvingepoch, Halvingepoch>,
pub halvingepoch_to_last_height: ComputedVec<Halvingepoch, Height>,
pub halvingepoch_to_timestamp: ComputedVec<Halvingepoch, Timestamp>,
pub height_to_dateindex: ComputedVec<Height, Dateindex>,
pub height_to_difficultyepoch: ComputedVec<Height, Difficultyepoch>,
pub height_to_fixed_date: ComputedVec<Height, Date>,
pub height_to_fixed_timestamp: ComputedVec<Height, Timestamp>,
pub height_to_halvingepoch: ComputedVec<Height, Halvingepoch>,
pub height_to_height: ComputedVec<Height, Height>,
pub height_to_last_txindex: ComputedVec<Height, Txindex>,
pub height_to_real_date: ComputedVec<Height, Date>,
pub monthindex_to_first_dateindex: ComputedVec<Monthindex, Dateindex>,
pub monthindex_to_last_dateindex: ComputedVec<Monthindex, Dateindex>,
pub monthindex_to_monthindex: ComputedVec<Monthindex, Monthindex>,
pub monthindex_to_quarterindex: ComputedVec<Monthindex, Quarterindex>,
pub monthindex_to_timestamp: ComputedVec<Monthindex, Timestamp>,
pub monthindex_to_yearindex: ComputedVec<Monthindex, Yearindex>,
pub quarterindex_to_first_monthindex: ComputedVec<Quarterindex, Monthindex>,
pub quarterindex_to_last_monthindex: ComputedVec<Quarterindex, Monthindex>,
pub quarterindex_to_quarterindex: ComputedVec<Quarterindex, Quarterindex>,
pub quarterindex_to_timestamp: ComputedVec<Quarterindex, Timestamp>,
pub txindex_to_last_txinindex: ComputedVec<Txindex, Txinindex>,
pub txindex_to_last_txoutindex: ComputedVec<Txindex, Txoutindex>,
pub weekindex_to_first_dateindex: ComputedVec<Weekindex, Dateindex>,
pub weekindex_to_last_dateindex: ComputedVec<Weekindex, Dateindex>,
pub weekindex_to_timestamp: ComputedVec<Weekindex, Timestamp>,
pub weekindex_to_weekindex: ComputedVec<Weekindex, Weekindex>,
pub yearindex_to_decadeindex: ComputedVec<Yearindex, Decadeindex>,
pub yearindex_to_first_monthindex: ComputedVec<Yearindex, Monthindex>,
pub yearindex_to_last_monthindex: ComputedVec<Yearindex, Monthindex>,
pub yearindex_to_timestamp: ComputedVec<Yearindex, Timestamp>,
pub yearindex_to_yearindex: ComputedVec<Yearindex, Yearindex>,
}
impl Vecs {
@@ -69,244 +69,244 @@ impl Vecs {
fs::create_dir_all(path)?;
Ok(Self {
dateindex_to_date: StorableVec::forced_import(
dateindex_to_date: ComputedVec::forced_import(
&path.join("dateindex_to_date"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_dateindex: StorableVec::forced_import(
dateindex_to_dateindex: ComputedVec::forced_import(
&path.join("dateindex_to_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_first_height: StorableVec::forced_import(
dateindex_to_first_height: ComputedVec::forced_import(
&path.join("dateindex_to_first_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_last_height: StorableVec::forced_import(
dateindex_to_last_height: ComputedVec::forced_import(
&path.join("dateindex_to_last_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_real_date: StorableVec::forced_import(
height_to_real_date: ComputedVec::forced_import(
&path.join("height_to_real_date"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_fixed_date: StorableVec::forced_import(
height_to_fixed_date: ComputedVec::forced_import(
&path.join("height_to_fixed_date"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_dateindex: StorableVec::forced_import(
height_to_dateindex: ComputedVec::forced_import(
&path.join("height_to_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_height: StorableVec::forced_import(
height_to_height: ComputedVec::forced_import(
&path.join("height_to_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_last_txindex: StorableVec::forced_import(
height_to_last_txindex: ComputedVec::forced_import(
&path.join("height_to_last_txindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_last_txinindex: StorableVec::forced_import(
txindex_to_last_txinindex: ComputedVec::forced_import(
&path.join("txindex_to_last_txinindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_last_txoutindex: StorableVec::forced_import(
txindex_to_last_txoutindex: ComputedVec::forced_import(
&path.join("txindex_to_last_txoutindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
difficultyepoch_to_first_height: StorableVec::forced_import(
difficultyepoch_to_first_height: ComputedVec::forced_import(
&path.join("difficultyepoch_to_first_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
difficultyepoch_to_last_height: StorableVec::forced_import(
difficultyepoch_to_last_height: ComputedVec::forced_import(
&path.join("difficultyepoch_to_last_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
halvingepoch_to_first_height: StorableVec::forced_import(
halvingepoch_to_first_height: ComputedVec::forced_import(
&path.join("halvingepoch_to_first_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
halvingepoch_to_last_height: StorableVec::forced_import(
halvingepoch_to_last_height: ComputedVec::forced_import(
&path.join("halvingepoch_to_last_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
weekindex_to_first_dateindex: StorableVec::forced_import(
weekindex_to_first_dateindex: ComputedVec::forced_import(
&path.join("weekindex_to_first_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
weekindex_to_last_dateindex: StorableVec::forced_import(
weekindex_to_last_dateindex: ComputedVec::forced_import(
&path.join("weekindex_to_last_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_first_dateindex: StorableVec::forced_import(
monthindex_to_first_dateindex: ComputedVec::forced_import(
&path.join("monthindex_to_first_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_last_dateindex: StorableVec::forced_import(
monthindex_to_last_dateindex: ComputedVec::forced_import(
&path.join("monthindex_to_last_dateindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_first_monthindex: StorableVec::forced_import(
yearindex_to_first_monthindex: ComputedVec::forced_import(
&path.join("yearindex_to_first_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_last_monthindex: StorableVec::forced_import(
yearindex_to_last_monthindex: ComputedVec::forced_import(
&path.join("yearindex_to_last_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
decadeindex_to_first_yearindex: StorableVec::forced_import(
decadeindex_to_first_yearindex: ComputedVec::forced_import(
&path.join("decadeindex_to_first_yearindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
decadeindex_to_last_yearindex: StorableVec::forced_import(
decadeindex_to_last_yearindex: ComputedVec::forced_import(
&path.join("decadeindex_to_last_yearindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_weekindex: StorableVec::forced_import(
dateindex_to_weekindex: ComputedVec::forced_import(
&path.join("dateindex_to_weekindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_monthindex: StorableVec::forced_import(
dateindex_to_monthindex: ComputedVec::forced_import(
&path.join("dateindex_to_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_yearindex: StorableVec::forced_import(
monthindex_to_yearindex: ComputedVec::forced_import(
&path.join("monthindex_to_yearindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_decadeindex: StorableVec::forced_import(
yearindex_to_decadeindex: ComputedVec::forced_import(
&path.join("yearindex_to_decadeindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_difficultyepoch: StorableVec::forced_import(
height_to_difficultyepoch: ComputedVec::forced_import(
&path.join("height_to_difficultyepoch"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_halvingepoch: StorableVec::forced_import(
height_to_halvingepoch: ComputedVec::forced_import(
&path.join("height_to_halvingepoch"),
Version::from(1),
Version::ONE,
compressed,
)?,
weekindex_to_weekindex: StorableVec::forced_import(
weekindex_to_weekindex: ComputedVec::forced_import(
&path.join("weekindex_to_weekindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_monthindex: StorableVec::forced_import(
monthindex_to_monthindex: ComputedVec::forced_import(
&path.join("monthindex_to_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_yearindex: StorableVec::forced_import(
yearindex_to_yearindex: ComputedVec::forced_import(
&path.join("yearindex_to_yearindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
decadeindex_to_decadeindex: StorableVec::forced_import(
decadeindex_to_decadeindex: ComputedVec::forced_import(
&path.join("decadeindex_to_decadeindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
difficultyepoch_to_difficultyepoch: StorableVec::forced_import(
difficultyepoch_to_difficultyepoch: ComputedVec::forced_import(
&path.join("difficultyepoch_to_difficultyepoch"),
Version::from(1),
Version::ONE,
compressed,
)?,
halvingepoch_to_halvingepoch: StorableVec::forced_import(
halvingepoch_to_halvingepoch: ComputedVec::forced_import(
&path.join("halvingepoch_to_halvingepoch"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_timestamp: StorableVec::forced_import(
dateindex_to_timestamp: ComputedVec::forced_import(
&path.join("dateindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
decadeindex_to_timestamp: StorableVec::forced_import(
decadeindex_to_timestamp: ComputedVec::forced_import(
&path.join("decadeindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
difficultyepoch_to_timestamp: StorableVec::forced_import(
difficultyepoch_to_timestamp: ComputedVec::forced_import(
&path.join("difficultyepoch_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
halvingepoch_to_timestamp: StorableVec::forced_import(
halvingepoch_to_timestamp: ComputedVec::forced_import(
&path.join("halvingepoch_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_timestamp: StorableVec::forced_import(
monthindex_to_timestamp: ComputedVec::forced_import(
&path.join("monthindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
weekindex_to_timestamp: StorableVec::forced_import(
weekindex_to_timestamp: ComputedVec::forced_import(
&path.join("weekindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_timestamp: StorableVec::forced_import(
yearindex_to_timestamp: ComputedVec::forced_import(
&path.join("yearindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_fixed_timestamp: StorableVec::forced_import(
height_to_fixed_timestamp: ComputedVec::forced_import(
&path.join("height_to_fixed_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_quarterindex: StorableVec::forced_import(
monthindex_to_quarterindex: ComputedVec::forced_import(
&path.join("monthindex_to_quarterindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
quarterindex_to_first_monthindex: StorableVec::forced_import(
quarterindex_to_first_monthindex: ComputedVec::forced_import(
&path.join("quarterindex_to_first_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
quarterindex_to_last_monthindex: StorableVec::forced_import(
quarterindex_to_last_monthindex: ComputedVec::forced_import(
&path.join("quarterindex_to_last_monthindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
quarterindex_to_quarterindex: StorableVec::forced_import(
quarterindex_to_quarterindex: ComputedVec::forced_import(
&path.join("quarterindex_to_quarterindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
quarterindex_to_timestamp: StorableVec::forced_import(
quarterindex_to_timestamp: ComputedVec::forced_import(
&path.join("quarterindex_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
})
@@ -10,53 +10,45 @@ use brk_indexer::Indexer;
use brk_vec::{AnyStorableVec, Compressed, Version};
use super::{
Indexes, StorableVec, indexes,
stats::{
StorableVecGeneatorOptions, StorableVecsStatsFromDate, StorableVecsStatsFromHeightStrict,
ComputedVec, Indexes,
grouped::{
ComputedVecsFromDateindex, ComputedVecsFromHeightStrict, StorableVecGeneatorOptions,
},
indexes,
};
#[derive(Clone)]
pub struct Vecs {
pub dateindex_to_close: StorableVec<Dateindex, Close<Dollars>>,
pub dateindex_to_close_in_cents: StorableVec<Dateindex, Close<Cents>>,
pub dateindex_to_high: StorableVec<Dateindex, High<Dollars>>,
pub dateindex_to_high_in_cents: StorableVec<Dateindex, High<Cents>>,
pub dateindex_to_low: StorableVec<Dateindex, Low<Dollars>>,
pub dateindex_to_low_in_cents: StorableVec<Dateindex, Low<Cents>>,
pub dateindex_to_ohlc: StorableVec<Dateindex, OHLCDollars>,
pub dateindex_to_ohlc_in_cents: StorableVec<Dateindex, OHLCCents>,
pub dateindex_to_open: StorableVec<Dateindex, Open<Dollars>>,
pub dateindex_to_open_in_cents: StorableVec<Dateindex, Open<Cents>>,
pub dateindex_to_sats_per_dollar: StorableVec<Dateindex, Close<Sats>>,
pub height_to_close: StorableVec<Height, Close<Dollars>>,
pub height_to_close_in_cents: StorableVec<Height, Close<Cents>>,
pub height_to_high: StorableVec<Height, High<Dollars>>,
pub height_to_high_in_cents: StorableVec<Height, High<Cents>>,
pub height_to_low: StorableVec<Height, Low<Dollars>>,
pub height_to_low_in_cents: StorableVec<Height, Low<Cents>>,
pub height_to_ohlc: StorableVec<Height, OHLCDollars>,
pub height_to_ohlc_in_cents: StorableVec<Height, OHLCCents>,
pub height_to_open: StorableVec<Height, Open<Dollars>>,
pub height_to_open_in_cents: StorableVec<Height, Open<Cents>>,
pub height_to_sats_per_dollar: StorableVec<Height, Close<Sats>>,
pub timeindexes_to_close: StorableVecsStatsFromDate<Close<Dollars>>,
pub timeindexes_to_high: StorableVecsStatsFromDate<High<Dollars>>,
pub timeindexes_to_low: StorableVecsStatsFromDate<Low<Dollars>>,
pub timeindexes_to_open: StorableVecsStatsFromDate<Open<Dollars>>,
pub timeindexes_to_sats_per_dollar: StorableVecsStatsFromDate<Close<Sats>>,
pub chainindexes_to_close: StorableVecsStatsFromHeightStrict<Close<Dollars>>,
pub chainindexes_to_high: StorableVecsStatsFromHeightStrict<High<Dollars>>,
pub chainindexes_to_low: StorableVecsStatsFromHeightStrict<Low<Dollars>>,
pub chainindexes_to_open: StorableVecsStatsFromHeightStrict<Open<Dollars>>,
pub chainindexes_to_sats_per_dollar: StorableVecsStatsFromHeightStrict<Close<Sats>>,
pub weekindex_to_ohlc: StorableVec<Weekindex, OHLCDollars>,
pub difficultyepoch_to_ohlc: StorableVec<Difficultyepoch, OHLCDollars>,
pub monthindex_to_ohlc: StorableVec<Monthindex, OHLCDollars>,
pub quarterindex_to_ohlc: StorableVec<Quarterindex, OHLCDollars>,
pub yearindex_to_ohlc: StorableVec<Yearindex, OHLCDollars>,
// pub dateindex_to_close: ComputedVec<Dateindex, Close<Dollars>>,
pub dateindex_to_close_in_cents: ComputedVec<Dateindex, Close<Cents>>,
pub dateindex_to_high_in_cents: ComputedVec<Dateindex, High<Cents>>,
pub dateindex_to_low_in_cents: ComputedVec<Dateindex, Low<Cents>>,
pub dateindex_to_ohlc: ComputedVec<Dateindex, OHLCDollars>,
pub dateindex_to_ohlc_in_cents: ComputedVec<Dateindex, OHLCCents>,
pub dateindex_to_open_in_cents: ComputedVec<Dateindex, Open<Cents>>,
pub height_to_close_in_cents: ComputedVec<Height, Close<Cents>>,
pub height_to_high_in_cents: ComputedVec<Height, High<Cents>>,
pub height_to_low_in_cents: ComputedVec<Height, Low<Cents>>,
pub height_to_ohlc: ComputedVec<Height, OHLCDollars>,
pub height_to_ohlc_in_cents: ComputedVec<Height, OHLCCents>,
pub height_to_open_in_cents: ComputedVec<Height, Open<Cents>>,
pub timeindexes_to_close: ComputedVecsFromDateindex<Close<Dollars>>,
pub timeindexes_to_high: ComputedVecsFromDateindex<High<Dollars>>,
pub timeindexes_to_low: ComputedVecsFromDateindex<Low<Dollars>>,
pub timeindexes_to_open: ComputedVecsFromDateindex<Open<Dollars>>,
pub timeindexes_to_sats_per_dollar: ComputedVecsFromDateindex<Close<Sats>>,
pub chainindexes_to_close: ComputedVecsFromHeightStrict<Close<Dollars>>,
pub chainindexes_to_high: ComputedVecsFromHeightStrict<High<Dollars>>,
pub chainindexes_to_low: ComputedVecsFromHeightStrict<Low<Dollars>>,
pub chainindexes_to_open: ComputedVecsFromHeightStrict<Open<Dollars>>,
pub chainindexes_to_sats_per_dollar: ComputedVecsFromHeightStrict<Close<Sats>>,
pub weekindex_to_ohlc: ComputedVec<Weekindex, OHLCDollars>,
pub difficultyepoch_to_ohlc: ComputedVec<Difficultyepoch, OHLCDollars>,
pub monthindex_to_ohlc: ComputedVec<Monthindex, OHLCDollars>,
pub quarterindex_to_ohlc: ComputedVec<Quarterindex, OHLCDollars>,
pub yearindex_to_ohlc: ComputedVec<Yearindex, OHLCDollars>,
// pub halvingepoch_to_ohlc: StorableVec<Halvingepoch, OHLCDollars>,
pub decadeindex_to_ohlc: StorableVec<Decadeindex, OHLCDollars>,
pub decadeindex_to_ohlc: ComputedVec<Decadeindex, OHLCDollars>,
}
impl Vecs {
@@ -64,195 +56,165 @@ impl Vecs {
fs::create_dir_all(path)?;
Ok(Self {
dateindex_to_ohlc_in_cents: StorableVec::forced_import(
dateindex_to_ohlc_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_ohlc_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_ohlc: StorableVec::forced_import(
dateindex_to_ohlc: ComputedVec::forced_import(
&path.join("dateindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_close_in_cents: StorableVec::forced_import(
dateindex_to_close_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_close_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_close: StorableVec::forced_import(
&path.join("dateindex_to_close"),
Version::from(1),
compressed,
)?,
dateindex_to_high_in_cents: StorableVec::forced_import(
dateindex_to_high_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_high_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_high: StorableVec::forced_import(
&path.join("dateindex_to_high"),
Version::from(1),
compressed,
)?,
dateindex_to_low_in_cents: StorableVec::forced_import(
dateindex_to_low_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_low_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_low: StorableVec::forced_import(
&path.join("dateindex_to_low"),
Version::from(1),
compressed,
)?,
dateindex_to_open_in_cents: StorableVec::forced_import(
dateindex_to_open_in_cents: ComputedVec::forced_import(
&path.join("dateindex_to_open_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
dateindex_to_open: StorableVec::forced_import(
&path.join("dateindex_to_open"),
Version::from(1),
compressed,
)?,
dateindex_to_sats_per_dollar: StorableVec::forced_import(
&path.join("dateindex_to_sats_per_dollar"),
Version::from(1),
compressed,
)?,
height_to_ohlc_in_cents: StorableVec::forced_import(
height_to_ohlc_in_cents: ComputedVec::forced_import(
&path.join("height_to_ohlc_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_ohlc: StorableVec::forced_import(
height_to_ohlc: ComputedVec::forced_import(
&path.join("height_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_close_in_cents: StorableVec::forced_import(
height_to_close_in_cents: ComputedVec::forced_import(
&path.join("height_to_close_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_close: StorableVec::forced_import(
&path.join("height_to_close"),
Version::from(1),
compressed,
)?,
height_to_high_in_cents: StorableVec::forced_import(
height_to_high_in_cents: ComputedVec::forced_import(
&path.join("height_to_high_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_high: StorableVec::forced_import(
&path.join("height_to_high"),
Version::from(1),
compressed,
)?,
height_to_low_in_cents: StorableVec::forced_import(
height_to_low_in_cents: ComputedVec::forced_import(
&path.join("height_to_low_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_low: StorableVec::forced_import(
&path.join("height_to_low"),
Version::from(1),
compressed,
)?,
height_to_open_in_cents: StorableVec::forced_import(
height_to_open_in_cents: ComputedVec::forced_import(
&path.join("height_to_open_in_cents"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_open: StorableVec::forced_import(
&path.join("height_to_open"),
Version::from(1),
compressed,
)?,
height_to_sats_per_dollar: StorableVec::forced_import(
&path.join("height_to_sats_per_dollar"),
Version::from(1),
compressed,
)?,
timeindexes_to_open: StorableVecsStatsFromDate::forced_import(
&path.join("open"),
timeindexes_to_open: ComputedVecsFromDateindex::forced_import(
path,
"open",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_first(),
)?,
timeindexes_to_high: StorableVecsStatsFromDate::forced_import(
&path.join("high"),
timeindexes_to_high: ComputedVecsFromDateindex::forced_import(
path,
"high",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_max(),
)?,
timeindexes_to_low: StorableVecsStatsFromDate::forced_import(
&path.join("low"),
timeindexes_to_low: ComputedVecsFromDateindex::forced_import(
path,
"low",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_min(),
)?,
timeindexes_to_close: StorableVecsStatsFromDate::forced_import(
&path.join("close"),
timeindexes_to_close: ComputedVecsFromDateindex::forced_import(
path,
"close",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
timeindexes_to_sats_per_dollar: StorableVecsStatsFromDate::forced_import(
&path.join("sats_per_dollar"),
timeindexes_to_sats_per_dollar: ComputedVecsFromDateindex::forced_import(
path,
"sats_per_dollar",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
chainindexes_to_open: StorableVecsStatsFromHeightStrict::forced_import(
&path.join("open"),
chainindexes_to_open: ComputedVecsFromHeightStrict::forced_import(
path,
"open",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_first(),
)?,
chainindexes_to_high: StorableVecsStatsFromHeightStrict::forced_import(
&path.join("high"),
chainindexes_to_high: ComputedVecsFromHeightStrict::forced_import(
path,
"high",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_max(),
)?,
chainindexes_to_low: StorableVecsStatsFromHeightStrict::forced_import(
&path.join("low"),
chainindexes_to_low: ComputedVecsFromHeightStrict::forced_import(
path,
"low",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_min(),
)?,
chainindexes_to_close: StorableVecsStatsFromHeightStrict::forced_import(
&path.join("close"),
chainindexes_to_close: ComputedVecsFromHeightStrict::forced_import(
path,
"close",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
chainindexes_to_sats_per_dollar: StorableVecsStatsFromHeightStrict::forced_import(
&path.join("sats_per_dollar"),
chainindexes_to_sats_per_dollar: ComputedVecsFromHeightStrict::forced_import(
path,
"sats_per_dollar",
Version::ONE,
compressed,
StorableVecGeneatorOptions::default().add_last(),
)?,
weekindex_to_ohlc: StorableVec::forced_import(
weekindex_to_ohlc: ComputedVec::forced_import(
&path.join("weekindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
difficultyepoch_to_ohlc: StorableVec::forced_import(
difficultyepoch_to_ohlc: ComputedVec::forced_import(
&path.join("difficultyepoch_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
monthindex_to_ohlc: StorableVec::forced_import(
monthindex_to_ohlc: ComputedVec::forced_import(
&path.join("monthindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
quarterindex_to_ohlc: StorableVec::forced_import(
quarterindex_to_ohlc: ComputedVec::forced_import(
&path.join("quarterindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
yearindex_to_ohlc: StorableVec::forced_import(
yearindex_to_ohlc: ComputedVec::forced_import(
&path.join("yearindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
// halvingepoch_to_ohlc: StorableVec::forced_import(&path.join("halvingepoch_to_ohlc"), Version::from(1), compressed)?,
decadeindex_to_ohlc: StorableVec::forced_import(
// halvingepoch_to_ohlc: StorableVec::forced_import(&path.join("halvingepoch_to_ohlc"), Version::ONE, compressed)?,
decadeindex_to_ohlc: ComputedVec::forced_import(
&path.join("decadeindex_to_ohlc"),
Version::from(1),
Version::ONE,
compressed,
)?,
})
@@ -320,41 +282,6 @@ impl Vecs {
exit,
)?;
self.height_to_open.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.open),
exit,
)?;
self.height_to_high.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.high),
exit,
)?;
self.height_to_low.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.low),
exit,
)?;
self.height_to_close.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.close),
exit,
)?;
self.height_to_sats_per_dollar.compute_transform(
starting_indexes.height,
self.height_to_close.mut_vec(),
|(di, close, ..)| (di, Close::from(Sats::ONE_BTC / *close)),
exit,
)?;
self.dateindex_to_ohlc_in_cents.compute_transform(
starting_indexes.dateindex,
indexes.dateindex_to_date.mut_vec(),
@@ -400,92 +327,113 @@ impl Vecs {
exit,
)?;
self.dateindex_to_open.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.open),
exit,
)?;
self.dateindex_to_high.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.high),
exit,
)?;
self.dateindex_to_low.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.low),
exit,
)?;
self.dateindex_to_close.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.close),
exit,
)?;
self.dateindex_to_sats_per_dollar.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_close.mut_vec(),
|(di, close, ..)| (di, Close::from(Sats::ONE_BTC / *close)),
exit,
)?;
self.timeindexes_to_close.compute(
&mut self.dateindex_to_close,
|v| {
v.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.close),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.timeindexes_to_high.compute(
&mut self.dateindex_to_high,
|v| {
v.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.high),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.timeindexes_to_low.compute(
&mut self.dateindex_to_low,
|v| {
v.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.low),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.timeindexes_to_open.compute(
&mut self.dateindex_to_open,
|v| {
v.compute_transform(
starting_indexes.dateindex,
self.dateindex_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.open),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.chainindexes_to_close.compute(
&mut self.height_to_close,
|v| {
v.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.close),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.chainindexes_to_high.compute(
&mut self.height_to_high,
|v| {
v.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.high),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.chainindexes_to_low.compute(
&mut self.height_to_low,
|v| {
v.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.low),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.chainindexes_to_open.compute(
&mut self.height_to_open,
|v| {
v.compute_transform(
starting_indexes.height,
self.height_to_ohlc.mut_vec(),
|(di, ohlc, ..)| (di, ohlc.open),
exit,
)
},
indexes,
starting_indexes,
exit,
@@ -771,34 +719,52 @@ impl Vecs {
exit,
)?;
self.chainindexes_to_sats_per_dollar.compute(
|v| {
v.compute_transform(
starting_indexes.height,
self.chainindexes_to_close.height.mut_vec(),
|(i, close, ..)| (i, Close::from(Sats::ONE_BTC / *close)),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
self.timeindexes_to_sats_per_dollar.compute(
|v| {
v.compute_transform(
starting_indexes.dateindex,
self.timeindexes_to_close.dateindex.mut_vec(),
|(i, close, ..)| (i, Close::from(Sats::ONE_BTC / *close)),
exit,
)
},
indexes,
starting_indexes,
exit,
)?;
Ok(())
}
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
vec![
vec![
self.dateindex_to_close.any_vec(),
self.dateindex_to_close_in_cents.any_vec(),
self.dateindex_to_high.any_vec(),
self.dateindex_to_high_in_cents.any_vec(),
self.dateindex_to_low.any_vec(),
self.dateindex_to_low_in_cents.any_vec(),
self.dateindex_to_ohlc.any_vec(),
self.dateindex_to_ohlc_in_cents.any_vec(),
self.dateindex_to_open.any_vec(),
self.dateindex_to_open_in_cents.any_vec(),
self.dateindex_to_sats_per_dollar.any_vec(),
self.height_to_close.any_vec(),
self.height_to_close_in_cents.any_vec(),
self.height_to_high.any_vec(),
self.height_to_high_in_cents.any_vec(),
self.height_to_low.any_vec(),
self.height_to_low_in_cents.any_vec(),
self.height_to_ohlc.any_vec(),
self.height_to_ohlc_in_cents.any_vec(),
self.height_to_open.any_vec(),
self.height_to_open_in_cents.any_vec(),
self.height_to_sats_per_dollar.any_vec(),
self.weekindex_to_ohlc.any_vec(),
self.difficultyepoch_to_ohlc.any_vec(),
self.monthindex_to_ohlc.any_vec(),
@@ -807,14 +773,16 @@ impl Vecs {
// self.halvingepoch_to_ohlc.any_vec(),
self.decadeindex_to_ohlc.any_vec(),
],
self.timeindexes_to_close.as_any_vecs(),
self.timeindexes_to_high.as_any_vecs(),
self.timeindexes_to_low.as_any_vecs(),
self.timeindexes_to_open.as_any_vecs(),
self.chainindexes_to_close.as_any_vecs(),
self.chainindexes_to_high.as_any_vecs(),
self.chainindexes_to_low.as_any_vecs(),
self.chainindexes_to_open.as_any_vecs(),
self.chainindexes_to_sats_per_dollar.any_vecs(),
self.timeindexes_to_sats_per_dollar.any_vecs(),
self.timeindexes_to_close.any_vecs(),
self.timeindexes_to_high.any_vecs(),
self.timeindexes_to_low.any_vecs(),
self.timeindexes_to_open.any_vecs(),
self.chainindexes_to_close.any_vecs(),
self.chainindexes_to_high.any_vecs(),
self.chainindexes_to_low.any_vecs(),
self.chainindexes_to_open.any_vecs(),
]
.concat()
}
+1 -1
View File
@@ -7,9 +7,9 @@ use brk_vec::{AnyStorableVec, Compressed};
mod base;
mod blocks;
mod grouped;
mod indexes;
mod marketprice;
mod stats;
mod transactions;
use base::*;
@@ -1,63 +0,0 @@
use std::path::Path;
use brk_core::{Difficultyepoch, Height};
use brk_exit::Exit;
use brk_vec::{AnyStorableVec, Compressed};
use crate::storage::vecs::{Indexes, base::StorableVec, indexes};
use super::{ComputedType, StorableVecBuilder, StorableVecGeneatorOptions};
#[derive(Clone)]
pub struct StorableVecsStatsFromHeightStrict<T>
where
T: ComputedType + PartialOrd,
{
pub difficultyepoch: StorableVecBuilder<Difficultyepoch, T>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
}
impl<T> StorableVecsStatsFromHeightStrict<T>
where
T: ComputedType + Ord + From<f64>,
f64: From<T>,
{
pub fn forced_import(
path: &Path,
compressed: Compressed,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let options = options.remove_percentiles();
Ok(Self {
difficultyepoch: StorableVecBuilder::forced_import(path, compressed, options)?,
// halvingepoch: StorableVecGeneator::forced_import(path, compressed, options)?,
})
}
pub fn compute(
&mut self,
source: &mut StorableVec<Height, T>,
indexes: &mut indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> color_eyre::Result<()> {
self.difficultyepoch.compute(
starting_indexes.difficultyepoch,
source,
indexes.difficultyepoch_to_first_height.mut_vec(),
indexes.difficultyepoch_to_last_height.mut_vec(),
exit,
)?;
Ok(())
}
pub fn as_any_vecs(&self) -> Vec<&dyn AnyStorableVec> {
[
self.difficultyepoch.as_any_vecs(),
// self.halvingepoch.as_any_vecs(),
]
.concat()
}
}
@@ -5,27 +5,27 @@ use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyStorableVec, Compressed, Version};
use super::{Indexes, StorableVec, indexes};
use super::{ComputedVec, Indexes, indexes};
#[derive(Clone)]
pub struct Vecs {
// pub height_to_fee: StorableVec<Txindex, Sats>,
// pub height_to_inputcount: StorableVec<Height, u32>,
// pub height_to_maxfeerate: StorableVec<Height, Feerate>,
// pub height_to_medianfeerate: StorableVec<Height, Feerate>,
// pub height_to_minfeerate: StorableVec<Height, Feerate>,
// pub height_to_outputcount: StorableVec<Height, u32>,
// pub height_to_subsidy: StorableVec<Height, u32>,
// pub height_to_totalfees: StorableVec<Height, Sats>,
// pub height_to_txcount: StorableVec<Height, u32>,
// pub txindex_to_fee: StorableVec<Txindex, Sats>,
pub txindex_to_is_coinbase: StorableVec<Txindex, bool>,
// pub txindex_to_feerate: StorableVec<Txindex, Feerate>,
pub txindex_to_inputs_count: StorableVec<Txindex, u32>,
// pub txindex_to_inputs_sum: StorableVec<Txindex, Sats>,
pub txindex_to_outputs_count: StorableVec<Txindex, u32>,
// pub txindex_to_outputs_sum: StorableVec<Txindex, Sats>,
// pub txinindex_to_value: StorableVec<Txinindex, Sats>,
// pub height_to_fee: ComputedVec<Txindex, Sats>,
// pub height_to_inputcount: ComputedVec<Height, u32>,
// pub height_to_maxfeerate: ComputedVec<Height, Feerate>,
// pub height_to_medianfeerate: ComputedVec<Height, Feerate>,
// pub height_to_minfeerate: ComputedVec<Height, Feerate>,
// pub height_to_outputcount: ComputedVec<Height, u32>,
// pub height_to_subsidy: ComputedVec<Height, u32>,
// pub height_to_totalfees: ComputedVec<Height, Sats>,
// pub height_to_txcount: ComputedVec<Height, u32>,
// pub txindex_to_fee: ComputedVec<Txindex, Sats>,
pub txindex_to_is_coinbase: ComputedVec<Txindex, bool>,
// pub txindex_to_feerate: ComputedVec<Txindex, Feerate>,
pub txindex_to_inputs_count: ComputedVec<Txindex, u32>,
// pub txindex_to_inputs_sum: ComputedVec<Txindex, Sats>,
pub txindex_to_outputs_count: ComputedVec<Txindex, u32>,
// pub txindex_to_outputs_sum: ComputedVec<Txindex, Sats>,
// pub txinindex_to_value: ComputedVec<Txinindex, Sats>,
}
impl Vecs {
@@ -33,52 +33,52 @@ impl Vecs {
fs::create_dir_all(path)?;
Ok(Self {
// height_to_fee: StorableVec::forced_import(&path.join("height_to_fee"), Version::from(1))?,
// height_to_fee: StorableVec::forced_import(&path.join("height_to_fee"), Version::ONE)?,
// height_to_input_count: StorableVec::forced_import(
// &path.join("height_to_input_count"),
// Version::from(1),
// Version::ONE,
// )?,
// height_to_maxfeerate: StorableVec::forced_import(&path.join("height_to_maxfeerate"), Version::from(1))?,
// height_to_medianfeerate: StorableVec::forced_import(&path.join("height_to_medianfeerate"), Version::from(1))?,
// height_to_minfeerate: StorableVec::forced_import(&path.join("height_to_minfeerate"), Version::from(1))?,
// height_to_maxfeerate: StorableVec::forced_import(&path.join("height_to_maxfeerate"), Version::ONE)?,
// height_to_medianfeerate: StorableVec::forced_import(&path.join("height_to_medianfeerate"), Version::ONE)?,
// height_to_minfeerate: StorableVec::forced_import(&path.join("height_to_minfeerate"), Version::ONE)?,
// height_to_output_count: StorableVec::forced_import(
// &path.join("height_to_output_count"),
// Version::from(1),
// Version::ONE,
// )?,
// height_to_subsidy: StorableVec::forced_import(&path.join("height_to_subsidy"), Version::from(1))?,
// height_to_totalfees: StorableVec::forced_import(&path.join("height_to_totalfees"), Version::from(1))?,
// height_to_txcount: StorableVec::forced_import(&path.join("height_to_txcount"), Version::from(1))?,
// height_to_subsidy: StorableVec::forced_import(&path.join("height_to_subsidy"), Version::ONE)?,
// height_to_totalfees: StorableVec::forced_import(&path.join("height_to_totalfees"), Version::ONE)?,
// height_to_txcount: StorableVec::forced_import(&path.join("height_to_txcount"), Version::ONE)?,
// txindex_to_fee: StorableVec::forced_import(
// &path.join("txindex_to_fee"),
// Version::from(1),
// Version::ONE,
// )?,
txindex_to_is_coinbase: StorableVec::forced_import(
txindex_to_is_coinbase: ComputedVec::forced_import(
&path.join("txindex_to_is_coinbase"),
Version::from(1),
Version::ONE,
compressed,
)?,
// txindex_to_feerate: StorableVec::forced_import(&path.join("txindex_to_feerate"), Version::from(1))?,
txindex_to_inputs_count: StorableVec::forced_import(
// txindex_to_feerate: StorableVec::forced_import(&path.join("txindex_to_feerate"), Version::ONE)?,
txindex_to_inputs_count: ComputedVec::forced_import(
&path.join("txindex_to_inputs_count"),
Version::from(1),
Version::ONE,
compressed,
)?,
// txindex_to_inputs_sum: StorableVec::forced_import(
// &path.join("txindex_to_inputs_sum"),
// Version::from(1),
// Version::ONE,
// )?,
txindex_to_outputs_count: StorableVec::forced_import(
txindex_to_outputs_count: ComputedVec::forced_import(
&path.join("txindex_to_outputs_count"),
Version::from(1),
Version::ONE,
compressed,
)?,
// txindex_to_outputs_sum: StorableVec::forced_import(
// &path.join("txindex_to_outputs_sum"),
// Version::from(1),
// Version::ONE,
// )?,
// txinindex_to_value: StorableVec::forced_import(
// &path.join("txinindex_to_value"),
// Version::from(1),
// Version::ONE,
// compressed,
// )?,
})
+1 -1
View File
@@ -8,5 +8,5 @@ repository.workspace = true
[dependencies]
brk_logger = { workspace = true }
ctrlc = { version = "3.4.5", features = ["termination"] }
ctrlc = { version = "3.4.6", features = ["termination"] }
log = { workspace = true }
+1 -1
View File
@@ -82,7 +82,7 @@ impl Binance {
}
pub fn get_from_1d(&mut self, date: &Date) -> color_eyre::Result<OHLCCents> {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 < date {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date {
self._1d.replace(Self::fetch_1d()?);
}
+1 -1
View File
@@ -81,7 +81,7 @@ impl Kibo {
.last_key_value()
.unwrap()
.0
< date
<= date
{
self.year_to_date_to_ohlc
.insert(year, Self::fetch_date_prices(year)?);
+1 -1
View File
@@ -43,7 +43,7 @@ impl Kraken {
}
pub fn get_from_1d(&mut self, date: &Date) -> color_eyre::Result<OHLCCents> {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 < date {
if self._1d.is_none() || self._1d.as_ref().unwrap().last_key_value().unwrap().0 <= date {
self._1d.replace(Kraken::fetch_1d()?);
}
self._1d
+7
View File
@@ -27,6 +27,7 @@ pub struct Store<Key, Value> {
}
const CHECK_COLLISISONS: bool = true;
const MAJOR_FJALL_VERSION: Version = Version::TWO;
impl<K, V> Store<K, V>
where
@@ -35,6 +36,8 @@ where
<V as TryFrom<ByteView>>::Error: error::Error + Send + Sync + 'static,
{
pub fn import(path: &Path, version: Version) -> color_eyre::Result<Self> {
let version = MAJOR_FJALL_VERSION + version;
let meta = StoreMeta::checked_open(path, version)?;
let keyspace = match Self::open_keyspace(path) {
@@ -89,6 +92,10 @@ where
}
pub fn remove(&mut self, key: K) {
if self.is_empty() {
return;
}
if !self.puts.is_empty() {
unreachable!("Shouldn't reach this");
// self.puts.remove(&key);
+13 -8
View File
@@ -26,14 +26,12 @@ pub struct Stores {
impl Stores {
pub fn import(path: &Path) -> color_eyre::Result<Self> {
thread::scope(|scope| {
let addresshash_to_addressindex = scope.spawn(|| {
Store::import(&path.join("addresshash_to_addressindex"), Version::from(1))
});
let blockhash_prefix_to_height = scope.spawn(|| {
Store::import(&path.join("blockhash_prefix_to_height"), Version::from(1))
});
let txid_prefix_to_txindex = scope
.spawn(|| Store::import(&path.join("txid_prefix_to_txindex"), Version::from(1)));
let addresshash_to_addressindex = scope
.spawn(|| Store::import(&path.join("addresshash_to_addressindex"), Version::ONE));
let blockhash_prefix_to_height = scope
.spawn(|| Store::import(&path.join("blockhash_prefix_to_height"), Version::ONE));
let txid_prefix_to_txindex =
scope.spawn(|| Store::import(&path.join("txid_prefix_to_txindex"), Version::ONE));
Ok(Self {
addresshash_to_addressindex: addresshash_to_addressindex.join().unwrap()?,
@@ -48,6 +46,13 @@ impl Stores {
vecs: &mut Vecs,
starting_indexes: &Indexes,
) -> color_eyre::Result<()> {
if self.addresshash_to_addressindex.is_empty()
&& self.blockhash_prefix_to_height.is_empty()
&& self.txid_prefix_to_txindex.is_empty()
{
return Ok(());
}
vecs.height_to_blockhash
.iter_from(starting_indexes.height, |(_, blockhash, ..)| {
let blockhash_prefix = BlockHashPrefix::from(blockhash);
+7 -7
View File
@@ -6,19 +6,19 @@ use std::{
};
use brk_vec::{
AnyStorableVec, Compressed, Error, MAX_CACHE_SIZE, MAX_PAGE_SIZE, Result, StoredIndex,
StoredType, Value, Version,
AnyStorableVec, Compressed, Error, MAX_CACHE_SIZE, MAX_PAGE_SIZE, Result, StorableVec,
StoredIndex, StoredType, Value, Version,
};
use super::Height;
#[derive(Debug)]
pub struct StorableVec<I, T> {
pub struct IndexedVec<I, T> {
height: Option<Height>,
vec: brk_vec::StorableVec<I, T>,
vec: StorableVec<I, T>,
}
impl<I, T> StorableVec<I, T>
impl<I, T> IndexedVec<I, T>
where
I: StoredIndex,
T: StoredType,
@@ -162,7 +162,7 @@ where
}
}
impl<I, T> Clone for StorableVec<I, T>
impl<I, T> Clone for IndexedVec<I, T>
where
I: StoredIndex,
T: StoredType,
@@ -180,7 +180,7 @@ pub trait AnyIndexedVec: Send + Sync {
fn flush(&mut self, height: Height) -> io::Result<()>;
}
impl<I, T> AnyIndexedVec for StorableVec<I, T>
impl<I, T> AnyIndexedVec for IndexedVec<I, T>
where
I: StoredIndex,
T: StoredType,
+129 -129
View File
@@ -18,50 +18,50 @@ pub use base::*;
#[derive(Clone)]
pub struct Vecs {
pub addressindex_to_addresstype: StorableVec<Addressindex, Addresstype>,
pub addressindex_to_addresstypeindex: StorableVec<Addressindex, Addresstypeindex>,
pub addressindex_to_height: StorableVec<Addressindex, Height>,
pub height_to_blockhash: StorableVec<Height, BlockHash>,
pub height_to_difficulty: StorableVec<Height, f64>,
pub height_to_first_addressindex: StorableVec<Height, Addressindex>,
pub height_to_first_emptyindex: StorableVec<Height, Emptyindex>,
pub height_to_first_multisigindex: StorableVec<Height, Multisigindex>,
pub height_to_first_opreturnindex: StorableVec<Height, Opreturnindex>,
pub height_to_first_pushonlyindex: StorableVec<Height, Pushonlyindex>,
pub height_to_first_txindex: StorableVec<Height, Txindex>,
pub height_to_first_txinindex: StorableVec<Height, Txinindex>,
pub height_to_first_txoutindex: StorableVec<Height, Txoutindex>,
pub height_to_first_unknownindex: StorableVec<Height, Unknownindex>,
pub height_to_first_p2pk33index: StorableVec<Height, P2PK33index>,
pub height_to_first_p2pk65index: StorableVec<Height, P2PK65index>,
pub height_to_first_p2pkhindex: StorableVec<Height, P2PKHindex>,
pub height_to_first_p2shindex: StorableVec<Height, P2SHindex>,
pub height_to_first_p2trindex: StorableVec<Height, P2TRindex>,
pub height_to_first_p2wpkhindex: StorableVec<Height, P2WPKHindex>,
pub height_to_first_p2wshindex: StorableVec<Height, P2WSHindex>,
pub height_to_size: StorableVec<Height, usize>,
pub height_to_timestamp: StorableVec<Height, Timestamp>,
pub height_to_weight: StorableVec<Height, Weight>,
pub p2pk33index_to_p2pk33addressbytes: StorableVec<P2PK33index, P2PK33AddressBytes>,
pub p2pk65index_to_p2pk65addressbytes: StorableVec<P2PK65index, P2PK65AddressBytes>,
pub p2pkhindex_to_p2pkhaddressbytes: StorableVec<P2PKHindex, P2PKHAddressBytes>,
pub p2shindex_to_p2shaddressbytes: StorableVec<P2SHindex, P2SHAddressBytes>,
pub p2trindex_to_p2traddressbytes: StorableVec<P2TRindex, P2TRAddressBytes>,
pub p2wpkhindex_to_p2wpkhaddressbytes: StorableVec<P2WPKHindex, P2WPKHAddressBytes>,
pub p2wshindex_to_p2wshaddressbytes: StorableVec<P2WSHindex, P2WSHAddressBytes>,
pub txindex_to_first_txinindex: StorableVec<Txindex, Txinindex>,
pub txindex_to_first_txoutindex: StorableVec<Txindex, Txoutindex>,
pub txindex_to_height: StorableVec<Txindex, Height>,
pub txindex_to_locktime: StorableVec<Txindex, LockTime>,
pub txindex_to_txid: StorableVec<Txindex, Txid>,
pub txindex_to_base_size: StorableVec<Txindex, usize>,
pub txindex_to_total_size: StorableVec<Txindex, usize>,
pub txindex_to_is_explicitly_rbf: StorableVec<Txindex, bool>,
pub txindex_to_txversion: StorableVec<Txindex, TxVersion>,
pub addressindex_to_addresstype: IndexedVec<Addressindex, Addresstype>,
pub addressindex_to_addresstypeindex: IndexedVec<Addressindex, Addresstypeindex>,
pub addressindex_to_height: IndexedVec<Addressindex, Height>,
pub height_to_blockhash: IndexedVec<Height, BlockHash>,
pub height_to_difficulty: IndexedVec<Height, f64>,
pub height_to_first_addressindex: IndexedVec<Height, Addressindex>,
pub height_to_first_emptyindex: IndexedVec<Height, Emptyindex>,
pub height_to_first_multisigindex: IndexedVec<Height, Multisigindex>,
pub height_to_first_opreturnindex: IndexedVec<Height, Opreturnindex>,
pub height_to_first_pushonlyindex: IndexedVec<Height, Pushonlyindex>,
pub height_to_first_txindex: IndexedVec<Height, Txindex>,
pub height_to_first_txinindex: IndexedVec<Height, Txinindex>,
pub height_to_first_txoutindex: IndexedVec<Height, Txoutindex>,
pub height_to_first_unknownindex: IndexedVec<Height, Unknownindex>,
pub height_to_first_p2pk33index: IndexedVec<Height, P2PK33index>,
pub height_to_first_p2pk65index: IndexedVec<Height, P2PK65index>,
pub height_to_first_p2pkhindex: IndexedVec<Height, P2PKHindex>,
pub height_to_first_p2shindex: IndexedVec<Height, P2SHindex>,
pub height_to_first_p2trindex: IndexedVec<Height, P2TRindex>,
pub height_to_first_p2wpkhindex: IndexedVec<Height, P2WPKHindex>,
pub height_to_first_p2wshindex: IndexedVec<Height, P2WSHindex>,
pub height_to_size: IndexedVec<Height, usize>,
pub height_to_timestamp: IndexedVec<Height, Timestamp>,
pub height_to_weight: IndexedVec<Height, Weight>,
pub p2pk33index_to_p2pk33addressbytes: IndexedVec<P2PK33index, P2PK33AddressBytes>,
pub p2pk65index_to_p2pk65addressbytes: IndexedVec<P2PK65index, P2PK65AddressBytes>,
pub p2pkhindex_to_p2pkhaddressbytes: IndexedVec<P2PKHindex, P2PKHAddressBytes>,
pub p2shindex_to_p2shaddressbytes: IndexedVec<P2SHindex, P2SHAddressBytes>,
pub p2trindex_to_p2traddressbytes: IndexedVec<P2TRindex, P2TRAddressBytes>,
pub p2wpkhindex_to_p2wpkhaddressbytes: IndexedVec<P2WPKHindex, P2WPKHAddressBytes>,
pub p2wshindex_to_p2wshaddressbytes: IndexedVec<P2WSHindex, P2WSHAddressBytes>,
pub txindex_to_first_txinindex: IndexedVec<Txindex, Txinindex>,
pub txindex_to_first_txoutindex: IndexedVec<Txindex, Txoutindex>,
pub txindex_to_height: IndexedVec<Txindex, Height>,
pub txindex_to_locktime: IndexedVec<Txindex, LockTime>,
pub txindex_to_txid: IndexedVec<Txindex, Txid>,
pub txindex_to_base_size: IndexedVec<Txindex, usize>,
pub txindex_to_total_size: IndexedVec<Txindex, usize>,
pub txindex_to_is_explicitly_rbf: IndexedVec<Txindex, bool>,
pub txindex_to_txversion: IndexedVec<Txindex, TxVersion>,
/// If txoutindex == Txoutindex MAX then is it's coinbase
pub txinindex_to_txoutindex: StorableVec<Txinindex, Txoutindex>,
pub txoutindex_to_addressindex: StorableVec<Txoutindex, Addressindex>,
pub txoutindex_to_value: StorableVec<Txoutindex, Sats>,
pub txinindex_to_txoutindex: IndexedVec<Txinindex, Txoutindex>,
pub txoutindex_to_addressindex: IndexedVec<Txoutindex, Addressindex>,
pub txoutindex_to_value: IndexedVec<Txoutindex, Sats>,
}
impl Vecs {
@@ -69,219 +69,219 @@ impl Vecs {
fs::create_dir_all(path)?;
Ok(Self {
addressindex_to_addresstype: StorableVec::forced_import(
addressindex_to_addresstype: IndexedVec::forced_import(
&path.join("addressindex_to_addresstype"),
Version::from(1),
Version::ONE,
compressed,
)?,
addressindex_to_addresstypeindex: StorableVec::forced_import(
addressindex_to_addresstypeindex: IndexedVec::forced_import(
&path.join("addressindex_to_addresstypeindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
addressindex_to_height: StorableVec::forced_import(
addressindex_to_height: IndexedVec::forced_import(
&path.join("addressindex_to_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_blockhash: StorableVec::forced_import(
height_to_blockhash: IndexedVec::forced_import(
&path.join("height_to_blockhash"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
height_to_difficulty: StorableVec::forced_import(
height_to_difficulty: IndexedVec::forced_import(
&path.join("height_to_difficulty"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_addressindex: StorableVec::forced_import(
height_to_first_addressindex: IndexedVec::forced_import(
&path.join("height_to_first_addressindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_emptyindex: StorableVec::forced_import(
height_to_first_emptyindex: IndexedVec::forced_import(
&path.join("height_to_first_emptyindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_multisigindex: StorableVec::forced_import(
height_to_first_multisigindex: IndexedVec::forced_import(
&path.join("height_to_first_multisigindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_opreturnindex: StorableVec::forced_import(
height_to_first_opreturnindex: IndexedVec::forced_import(
&path.join("height_to_first_opreturnindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_pushonlyindex: StorableVec::forced_import(
height_to_first_pushonlyindex: IndexedVec::forced_import(
&path.join("height_to_first_pushonlyindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_txindex: StorableVec::forced_import(
height_to_first_txindex: IndexedVec::forced_import(
&path.join("height_to_first_txindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_txinindex: StorableVec::forced_import(
height_to_first_txinindex: IndexedVec::forced_import(
&path.join("height_to_first_txinindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_txoutindex: StorableVec::forced_import(
height_to_first_txoutindex: IndexedVec::forced_import(
&path.join("height_to_first_txoutindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_unknownindex: StorableVec::forced_import(
height_to_first_unknownindex: IndexedVec::forced_import(
&path.join("height_to_first_unkownindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2pk33index: StorableVec::forced_import(
height_to_first_p2pk33index: IndexedVec::forced_import(
&path.join("height_to_first_p2pk33index"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2pk65index: StorableVec::forced_import(
height_to_first_p2pk65index: IndexedVec::forced_import(
&path.join("height_to_first_p2pk65index"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2pkhindex: StorableVec::forced_import(
height_to_first_p2pkhindex: IndexedVec::forced_import(
&path.join("height_to_first_p2pkhindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2shindex: StorableVec::forced_import(
height_to_first_p2shindex: IndexedVec::forced_import(
&path.join("height_to_first_p2shindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2trindex: StorableVec::forced_import(
height_to_first_p2trindex: IndexedVec::forced_import(
&path.join("height_to_first_p2trindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2wpkhindex: StorableVec::forced_import(
height_to_first_p2wpkhindex: IndexedVec::forced_import(
&path.join("height_to_first_p2wpkhindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_first_p2wshindex: StorableVec::forced_import(
height_to_first_p2wshindex: IndexedVec::forced_import(
&path.join("height_to_first_p2wshindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_size: StorableVec::forced_import(
height_to_size: IndexedVec::forced_import(
&path.join("height_to_size"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_timestamp: StorableVec::forced_import(
height_to_timestamp: IndexedVec::forced_import(
&path.join("height_to_timestamp"),
Version::from(1),
Version::ONE,
compressed,
)?,
height_to_weight: StorableVec::forced_import(
height_to_weight: IndexedVec::forced_import(
&path.join("height_to_weight"),
Version::from(1),
Version::ONE,
compressed,
)?,
p2pk33index_to_p2pk33addressbytes: StorableVec::forced_import(
p2pk33index_to_p2pk33addressbytes: IndexedVec::forced_import(
&path.join("p2pk33index_to_p2pk33addressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2pk65index_to_p2pk65addressbytes: StorableVec::forced_import(
p2pk65index_to_p2pk65addressbytes: IndexedVec::forced_import(
&path.join("p2pk65index_to_p2pk65addressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2pkhindex_to_p2pkhaddressbytes: StorableVec::forced_import(
p2pkhindex_to_p2pkhaddressbytes: IndexedVec::forced_import(
&path.join("p2pkhindex_to_p2pkhaddressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2shindex_to_p2shaddressbytes: StorableVec::forced_import(
p2shindex_to_p2shaddressbytes: IndexedVec::forced_import(
&path.join("p2shindex_to_p2shaddressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2trindex_to_p2traddressbytes: StorableVec::forced_import(
p2trindex_to_p2traddressbytes: IndexedVec::forced_import(
&path.join("p2trindex_to_p2traddressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2wpkhindex_to_p2wpkhaddressbytes: StorableVec::forced_import(
p2wpkhindex_to_p2wpkhaddressbytes: IndexedVec::forced_import(
&path.join("p2wpkhindex_to_p2wpkhaddressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
p2wshindex_to_p2wshaddressbytes: StorableVec::forced_import(
p2wshindex_to_p2wshaddressbytes: IndexedVec::forced_import(
&path.join("p2wshindex_to_p2wshaddressbytes"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
txindex_to_first_txinindex: StorableVec::forced_import(
txindex_to_first_txinindex: IndexedVec::forced_import(
&path.join("txindex_to_first_txinindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_first_txoutindex: StorableVec::forced_import(
txindex_to_first_txoutindex: IndexedVec::forced_import(
&path.join("txindex_to_first_txoutindex"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
txindex_to_height: StorableVec::forced_import(
txindex_to_height: IndexedVec::forced_import(
&path.join("txindex_to_height"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_locktime: StorableVec::forced_import(
txindex_to_locktime: IndexedVec::forced_import(
&path.join("txindex_to_locktime"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_txid: StorableVec::forced_import(
txindex_to_txid: IndexedVec::forced_import(
&path.join("txindex_to_txid"),
Version::from(1),
Version::ONE,
Compressed::NO,
)?,
txindex_to_base_size: StorableVec::forced_import(
txindex_to_base_size: IndexedVec::forced_import(
&path.join("txindex_to_base_size"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_total_size: StorableVec::forced_import(
txindex_to_total_size: IndexedVec::forced_import(
&path.join("txindex_to_total_size"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_is_explicitly_rbf: StorableVec::forced_import(
txindex_to_is_explicitly_rbf: IndexedVec::forced_import(
&path.join("txindex_to_is_explicitly_rbf"),
Version::from(1),
Version::ONE,
compressed,
)?,
txindex_to_txversion: StorableVec::forced_import(
txindex_to_txversion: IndexedVec::forced_import(
&path.join("txindex_to_txversion"),
Version::from(1),
Version::ONE,
compressed,
)?,
txinindex_to_txoutindex: StorableVec::forced_import(
txinindex_to_txoutindex: IndexedVec::forced_import(
&path.join("txinindex_to_txoutindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
txoutindex_to_addressindex: StorableVec::forced_import(
txoutindex_to_addressindex: IndexedVec::forced_import(
&path.join("txoutindex_to_addressindex"),
Version::from(1),
Version::ONE,
compressed,
)?,
txoutindex_to_value: StorableVec::forced_import(
txoutindex_to_value: IndexedVec::forced_import(
&path.join("txoutindex_to_value"),
Version::from(1),
Version::ONE,
compressed,
)?,
})
+3 -2
View File
@@ -24,5 +24,6 @@ minreq = { workspace = true }
oxc = { version = "0.62.0", features = ["codegen", "minifier"] }
serde = { workspace = true }
tokio = { version = "1.44.1", features = ["full"] }
tower-http = { version = "0.6.2", features = ["compression-full"] }
zip = "2.5.0"
tower-http = { version = "0.6.2", features = ["compression-full", "trace"] }
zip = "2.6.1"
tracing = "0.1.41"
+6 -1
View File
@@ -31,7 +31,12 @@ impl DTS for Query<'static> {
let indexes = Index::all();
let mut contents = indexes
let mut contents = "//
// File auto-generated, any modification will be overwritten
//\n\n"
.to_string();
contents += &indexes
.iter()
.enumerate()
.map(|(i_of_i, i)| {
+3 -15
View File
@@ -1,17 +1,12 @@
use std::time::Instant;
use axum::{
Json,
extract::{Query as AxumQuery, State},
http::{HeaderMap, StatusCode, Uri},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use brk_query::{Format, Index, Output, Params};
use crate::{
log_result,
traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
};
use crate::traits::{HeaderMapExtended, ModifiedState, ResponseExtended};
use super::AppState;
@@ -21,21 +16,14 @@ pub use dts::*;
pub async fn handler(
headers: HeaderMap,
uri: Uri,
query: AxumQuery<Params>,
State(app_state): State<AppState>,
) -> Response {
let instant = Instant::now();
match req_to_response_res(headers, query, app_state) {
Ok(response) => {
log_result(response.status(), &uri, instant);
response
}
Ok(response) => response,
Err(error) => {
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
log_result(response.status(), &uri, instant);
response.headers_mut().insert_cors();
response
}
+6 -16
View File
@@ -1,15 +1,15 @@
use std::{fs, path::Path, time::Instant};
use std::{fs, path::Path};
use axum::{
body::Body,
extract::{self, State},
http::{HeaderMap, StatusCode, Uri},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use log::{error, info};
use crate::{
AppState, log_result,
AppState,
traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
};
@@ -18,24 +18,18 @@ use super::minify::minify_js;
pub async fn file_handler(
headers: HeaderMap,
State(app_state): State<AppState>,
uri: Uri,
path: extract::Path<String>,
) -> Response {
any_handler(headers, app_state, uri, Some(path))
any_handler(headers, app_state, Some(path))
}
pub async fn index_handler(
headers: HeaderMap,
State(app_state): State<AppState>,
uri: Uri,
) -> Response {
any_handler(headers, app_state, uri, None)
pub async fn index_handler(headers: HeaderMap, State(app_state): State<AppState>) -> Response {
any_handler(headers, app_state, None)
}
fn any_handler(
headers: HeaderMap,
app_state: AppState,
uri: Uri,
path: Option<extract::Path<String>>,
) -> Response {
let website_path = app_state
@@ -44,8 +38,6 @@ fn any_handler(
.expect("Should never reach here is websites_path is None")
.join(app_state.website.to_folder_name());
let instant = Instant::now();
let response = if let Some(path) = path.as_ref() {
let path = path.0.replace("..", "").replace("\\", "");
@@ -72,8 +64,6 @@ fn any_handler(
path_to_response(&headers, &website_path.join("index.html"))
};
log_result(response.status(), &uri, instant);
response
}
+50 -22
View File
@@ -4,18 +4,18 @@
#![doc = "```"]
use std::{
collections::BTreeMap,
fs,
io::Cursor,
path::{Path, PathBuf},
sync::Arc,
time::Instant,
time::Duration,
};
use api::{ApiRoutes, DTS};
use axum::{
Json, Router,
http::{StatusCode, Uri},
body::Body,
http::{Request, Response, StatusCode, Uri},
middleware::Next,
routing::get,
serve,
};
@@ -28,13 +28,14 @@ use files::FilesRoutes;
use log::{error, info};
pub use tokio;
use tokio::net::TcpListener;
use tower_http::compression::CompressionLayer;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
mod api;
mod files;
mod traits;
pub use files::Website;
use tracing::Span;
#[derive(Clone)]
pub struct AppState {
@@ -48,6 +49,7 @@ pub struct AppState {
const DEV_PATH: &str = "../..";
const DOWNLOADS: &str = "downloads";
const WEBSITES: &str = "websites";
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub struct Server(AppState);
@@ -65,14 +67,15 @@ impl Server {
} else {
let downloads_path = dot_brk_path().join(DOWNLOADS);
let downloaded_websites_path = downloads_path.join("brk-main").join(WEBSITES);
let downloaded_websites_path =
downloads_path.join(format!("brk-{VERSION}")).join(WEBSITES);
if !fs::exists(&downloaded_websites_path)? {
info!("Downloading websites from Github...");
// TODO: Need to download versioned, main is only for testing
let url =
"https://github.com/bitcoinresearchkit/brk/archive/refs/heads/main.zip";
let url = format!(
"https://github.com/bitcoinresearchkit/brk/archive/refs/tags/v{VERSION}.zip",
);
let response = minreq::get(url).send()?;
let bytes = response.as_bytes();
@@ -109,12 +112,48 @@ impl Server {
.gzip(true)
.zstd(true);
let response_uri_layer = axum::middleware::from_fn(
async |request: Request<Body>, next: Next| -> Response<Body> {
let uri = request.uri().clone();
let mut response = next.run(request).await;
response.extensions_mut().insert(uri);
response
},
);
let trace_layer = TraceLayer::new_for_http()
.on_request(())
.on_response(
|response: &Response<Body>, latency: Duration, _span: &Span| {
let latency = latency.bright_black();
let status = response.status();
let uri = response.extensions().get::<Uri>().unwrap();
match status {
StatusCode::INTERNAL_SERVER_ERROR => {
error!("{} {} {:?}", status.as_u16().red(), uri, latency)
}
StatusCode::NOT_MODIFIED => {
info!("{} {} {:?}", status.as_u16().bright_black(), uri, latency)
}
StatusCode::OK => {
info!("{} {} {:?}", status.as_u16().green(), uri, latency)
}
_ => error!("{} {} {:?}", status.as_u16().red(), uri, latency),
}
},
)
.on_body_chunk(())
.on_failure(())
.on_eos(());
let router = Router::new()
.add_api_routes()
.add_website_routes(state.website)
.route("/version", get(Json(env!("CARGO_PKG_VERSION"))))
.route("/version", get(Json(VERSION)))
.with_state(state)
.layer(compression_layer);
.layer(compression_layer)
.layer(response_uri_layer)
.layer(trace_layer);
let mut port = 3110;
@@ -136,14 +175,3 @@ impl Server {
Ok(())
}
}
pub fn log_result(code: StatusCode, uri: &Uri, instant: Instant) {
let time = format!("{}µs", instant.elapsed().as_micros());
let time = time.bright_black();
match code {
StatusCode::INTERNAL_SERVER_ERROR => error!("{} {} {}", code.as_u16().red(), uri, time),
StatusCode::NOT_MODIFIED => info!("{} {} {}", code.as_u16().bright_black(), uri, time),
StatusCode::OK => info!("{} {} {}", code.as_u16().green(), uri, time),
_ => error!("{} {} {}", code.as_u16().red(), uri, time),
}
}
+7 -8
View File
@@ -17,9 +17,9 @@ pub enum ModifiedState {
}
pub trait HeaderMapExtended {
fn get_scheme(&self) -> &str;
fn _get_scheme(&self) -> &str;
fn get_host(&self) -> &str;
fn check_if_host_is_any_local(&self) -> bool;
fn check_if_host_is_local(&self) -> bool;
fn check_if_host_is_0000(&self) -> bool;
fn check_if_host_is_localhost(&self) -> bool;
@@ -30,8 +30,7 @@ pub trait HeaderMapExtended {
-> color_eyre::Result<(ModifiedState, DateTime)>;
fn insert_cache_control_immutable(&mut self);
#[allow(unused)]
fn insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64);
fn _insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64);
fn insert_last_modified(&mut self, date: DateTime);
fn insert_content_disposition_attachment(&mut self);
@@ -53,8 +52,8 @@ pub trait HeaderMapExtended {
}
impl HeaderMapExtended for HeaderMap {
fn get_scheme(&self) -> &str {
if self.check_if_host_is_any_local() {
fn _get_scheme(&self) -> &str {
if self.check_if_host_is_local() {
"http"
} else {
"https"
@@ -65,7 +64,7 @@ impl HeaderMapExtended for HeaderMap {
self[HOST].to_str().unwrap()
}
fn check_if_host_is_any_local(&self) -> bool {
fn check_if_host_is_local(&self) -> bool {
self.check_if_host_is_localhost() || self.check_if_host_is_0000()
}
@@ -95,7 +94,7 @@ impl HeaderMapExtended for HeaderMap {
self.insert(header::CONTENT_DISPOSITION, "attachment".parse().unwrap());
}
fn insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64) {
fn _insert_cache_control_revalidate(&mut self, max_age: u64, stale_while_revalidate: u64) {
self.insert(
header::CACHE_CONTROL,
format!(
+3 -3
View File
@@ -7,7 +7,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
{
let mut vec: StorableVec<usize, u32> =
StorableVec::forced_import(Path::new("./vec"), Version::from(1), Compressed::YES)?;
StorableVec::forced_import(Path::new("./vec"), Version::ONE, Compressed::YES)?;
(0..21_u32).for_each(|v| {
vec.push(v);
@@ -21,7 +21,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
{
let mut vec: StorableVec<usize, u32> =
StorableVec::forced_import(Path::new("./vec"), Version::from(1), Compressed::YES)?;
StorableVec::forced_import(Path::new("./vec"), Version::ONE, Compressed::YES)?;
dbg!(vec.get(0)?);
dbg!(vec.get(0)?);
@@ -43,7 +43,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
{
let mut vec: StorableVec<usize, u32> =
StorableVec::forced_import(Path::new("./vec"), Version::from(1), Compressed::YES)?;
StorableVec::forced_import(Path::new("./vec"), Version::ONE, Compressed::YES)?;
vec.enable_large_cache();
+4
View File
@@ -200,6 +200,10 @@ where
.to_usize()
.unwrap();
if len == 0 {
return Err(Error::IndexTooHigh);
}
let from = from.map_or(0, |from| {
if from >= 0 {
from as usize
+4
View File
@@ -15,6 +15,10 @@ use crate::{Error, Result};
pub struct Version(u32);
impl Version {
pub const ZERO: Self = Self(0);
pub const ONE: Self = Self(1);
pub const TWO: Self = Self(2);
pub fn write(&self, path: &Path) -> Result<(), io::Error> {
fs::write(path, self.as_bytes())
}
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/env bash
cargo clean
cargo build --all-targets
cd crates/brk
cd ../brk_core
cargo publish
cd ../brk_exit
cargo publish
cd ../brk_vec
cargo publish
cd ../brk_logger
cargo publish
cd ../brk_indexer
cargo publish
cd ../brk_parser
cargo publish
cd ../brk_fetcher
cargo publish
cd ../brk_computer
cargo publish
cd ../brk_query
cargo publish
cd ../brk_server
cargo publish
cd ../brk_cli
cargo publish
cd ../brk
cargo publish
cd ../..
+2
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env bash
cargo clean
rustup update
cargo upgrade --incompatible
cargo update
cargo build --all-targets
+61 -23
View File
@@ -268,7 +268,6 @@
--negative-main-padding: calc(-1 * var(--main-padding));
--font-weight-base: 400;
--font-weight-bold: 700;
--transform-scale-active: scaleY(0.9);
@@ -284,7 +283,7 @@
@font-face {
font-family: "Geist mono";
src: url("./assets/fonts/geist_mono_var_1_4_01.woff2") format("woff2");
font-weight: 300 500;
font-weight: 100 900;
font-display: block;
font-style: normal;
}
@@ -371,21 +370,13 @@
margin-bottom: 0rem;
}
header {
small {
font-weight: var(--font-weight-base);
font-size: var(--font-size-base);
line-height: var(--line-height-base);
}
}
body > &[hidden] {
display: flex !important;
}
}
b {
font-weight: var(--font-weight-bold);
font-weight: 700;
}
body {
@@ -447,7 +438,8 @@
}
}
h1 {
h1,
h2 {
text-transform: uppercase;
font-size: var(--font-size-2xl);
line-height: var(--line-height-2xl);
@@ -478,6 +470,7 @@
}
input {
text-transform: inherit;
border: 0;
width: 100%;
text-align: left;
@@ -655,7 +648,7 @@
height: 100%;
display: flex;
flex-direction: column;
gap: 0.5rem;
gap: 2rem;
padding-bottom: var(--bottom-area);
}
@@ -928,16 +921,20 @@
gap: 0.5rem;
&[data-size="sm"] {
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
}
&[data-size="xs"] {
font-size: var(--font-size-xs);
line-height: var(--line-height-xs);
font-weight: 450;
}
> div.field {
text-transform: lowercase;
display: flex;
align-items: center;
/* font-size: var(--font-size-xs);
line-height: var(--line-height-xs); */
gap: 1rem;
> legend,
@@ -947,6 +944,14 @@
> hr {
min-width: 2rem;
fieldset[data-size="sm"] & {
min-width: 1.5rem;
}
fieldset[data-size="xs"] & {
min-width: 1rem;
}
}
label {
@@ -957,6 +962,14 @@
> div {
display: flex;
gap: 1.5rem;
fieldset[data-size="xs"] & {
gap: 1.25rem;
}
fieldset[data-size="xs"] & {
gap: 1rem;
}
}
}
}
@@ -968,7 +981,7 @@
z-index: 20;
flex: 1;
margin-top: 2rem;
margin-bottom: 1.5rem;
margin-bottom: 1rem;
> legend {
text-transform: lowercase;
@@ -980,9 +993,12 @@
margin-right: var(--negative-main-padding);
padding-left: var(--main-padding);
padding-right: var(--main-padding);
padding-bottom: 1.5rem;
padding-bottom: 1rem;
overflow-x: auto;
min-width: 0;
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
height: 2rem;
> div {
flex: 0;
@@ -1031,6 +1047,17 @@
min-height: 0;
height: 100%;
margin-right: var(--negative-main-padding);
margin-left: var(--negative-main-padding);
fieldset {
padding-left: var(--main-padding);
padding-top: 0.5rem;
z-index: 10;
position: absolute;
left: 0;
top: 0;
gap: 0;
}
}
> .panes {
@@ -1492,20 +1519,31 @@
<div class="shadow-bottom"></div>
<div id="resize-bar"></div>
<nav id="nav" hidden></nav>
<nav id="nav" hidden>
<h4 style="margin-top: 0.25rem">
<a href="/"
><span style="font-weight: 500">kibo</span
><span style="color: var(--gray)">.</span
><span style="color: var(--orange)">money</span></a
>
</h4>
</nav>
<search id="search" hidden>
<header>
<div>
<h3 style="display: flex; flex-direction: column">
<h3
style="
display: flex;
flex-direction: column;
text-transform: uppercase;
"
>
<input placeholder="Search..." id="search-input" />
<small id="search-small">
Focus the title or press <strong>/</strong> to search
</small>
</h3>
</div>
</header>
<ul id="search-results"></ul>
<ul id="search-results" style="text-transform: lowercase"></ul>
</search>
<footer>
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
URL + Version:
https://unpkg.com/browse/lightweight-charts@latest/
@@ -1,117 +0,0 @@
import { Signal } from "../solid-signals/types";
import { Accessor } from "../solid-signals/2024-11-02/types/signals";
import {
DeepPartial,
BaselineStyleOptions,
CandlestickStyleOptions,
LineStyleOptions,
SeriesOptionsCommon,
Time,
ISeriesApi,
BaselineData,
} from "./v5.0.5/types";
import { VecId } from "../../scripts/vecid-to-indexes";
interface BaseSeriesBlueprint {
title: string;
defaultActive?: boolean;
}
interface BaselineSeriesBlueprint extends BaseSeriesBlueprint {
type: "Baseline";
color?: Color;
options?: DeepPartial<BaselineStyleOptions & SeriesOptionsCommon>;
data?: Accessor<BaselineData<Time>[]>;
}
interface CandlestickSeriesBlueprint extends BaseSeriesBlueprint {
type: "Candlestick";
color?: Color;
options?: DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon>;
data?: Accessor<CandlestickData[]>;
}
interface LineSeriesBlueprint extends BaseSeriesBlueprint {
type?: "Line";
color: Color;
options?: DeepPartial<LineStyleOptions & SeriesOptionsCommon>;
data?: Accessor<LineData<Time>[]>;
}
type AnySpecificSeriesBlueprint =
| BaselineSeriesBlueprint
| CandlestickSeriesBlueprint
| LineSeriesBlueprint;
type SeriesType = NonNullable<AnySpecificSeriesBlueprint["type"]>;
type PriceSeriesType = "Candlestick" | "Line";
type RemoveSeriesBlueprintFluff<Blueprint extends AnySpecificSeriesBlueprint> =
Omit<Blueprint, "type" | "title">;
type SplitSeriesBlueprint<> = {
key: VecId;
} & AnySpecificSeriesBlueprint;
type SingleSeriesBlueprint = AnySpecificSeriesBlueprint;
interface CreateBaseSeriesParameters extends BaseSeriesBlueprint {
id: string;
disabled?: Accessor<boolean>;
color?: Color;
}
interface BaseSeries {
id: string;
title: string;
color: Color | Color[];
active: Signal<boolean>;
visible: Accessor<boolean>;
}
interface SingleSeries extends BaseSeries {
iseries: ISeriesApi<SeriesType>;
dataset: Accessor<(SingleValueData | CandlestickData)[] | null>;
}
interface SplitSeries extends BaseSeries {
chunks: Array<Accessor<ISeriesApi<SeriesType> | undefined>>;
// dataset: ResourceDataset<number>;
}
type AnySeries = SingleSeries | SplitSeries;
interface CreateSingleSeriesParameters {
blueprint: SingleSeriesBlueprint;
id: string;
}
interface CreateSplitSeriesParameters {
// dataset: ResourceDataset;
blueprint: SplitSeriesBlueprint;
id: string;
index: number;
disabled?: Accessor<boolean>;
}
type ChartPane = IChartApi & {
whitespace: ISeriesApi<"Line">;
hidden: () => boolean;
setHidden: (b: boolean) => void;
setInitialVisibleTimeRange: VoidFunction;
createSingleSeries: (a: CreateSingleSeriesParameters) => SingleSeries;
createSplitSeries: (a: CreateSplitSeriesParameters) => SplitSeries[];
anySeries: AnySeries[];
singleSeries: SingleSeries[];
splitSeries: SplitSeries[];
remove: VoidFunction;
};
interface CreatePaneParameters {
options?: DeepPartial<ChartOptions>;
config?: SingleSeriesBlueprint[];
}
interface Marker {
weight: number;
time: Time;
value: number;
seriesChunk: ISeriesApi<SeriesType>;
}
interface HoveredLegend {
label: HTMLLabelElement;
series: AnySeries;
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -1,8 +1,28 @@
// @ts-check
/** @import {ISeriesApi, SeriesDefinition} from './v5.0.5/types' */
/** @import {IChartApi, ISeriesApi, SeriesDefinition, SingleValueData as _SingleValueData, CandlestickData as _CandlestickData, BaselineData, SeriesType} from './v5.0.5-treeshaked/types' */
export default import("./v5.0.5/script.js").then((lc) => {
/**
* @typedef {[number, number, number, number]} OHLCTuple
*
* @typedef {Object} Valued
* @property {number} value
*
* @typedef {Object} Indexed
* @property {number} index
*/
/**
* @template T
* @typedef {T & Valued & Indexed} ChartData<T>
*/
/**
* @typedef {ChartData<_SingleValueData>} SingleValueData
* @typedef {ChartData<_CandlestickData>} CandlestickData
*/
export default import("./v5.0.5-treeshaked/script.js").then((lc) => {
const oklchToRGBA = createOklchToRGBA();
/**
@@ -27,7 +47,7 @@ export default import("./v5.0.5/script.js").then((lc) => {
autoSize: true,
layout: {
fontFamily: "Geist mono",
fontSize: 14,
fontSize: 13,
background: { color: "transparent" },
attributionLogo: false,
colorSpace: "display-p3",
@@ -51,11 +71,11 @@ export default import("./v5.0.5/script.js").then((lc) => {
const chart = lc.createChart(element, options);
chart.priceScale("right").applyOptions({
scaleMargins: {
top: 0.075,
bottom: 0.05,
},
minimumWidth: 78,
// scaleMargins: {
// top: 0.15,
// bottom: 0.05,
// },
minimumWidth: 80,
});
signals.createEffect(
@@ -110,15 +130,18 @@ export default import("./v5.0.5/script.js").then((lc) => {
* @param {VecsResources} args.vecsResources
* @param {Owner | null} [args.owner]
* @param {true} [args.fitContentOnResize]
* @param {{unit: Unit; blueprints: AnySeriesBlueprint[]}[]} [args.config]
*/
function createChartElement({
parent,
signals,
colors,
utils,
id,
vecsResources,
owner: _owner,
fitContentOnResize,
config,
}) {
let owner = _owner || signals.getOwner();
@@ -150,13 +173,16 @@ export default import("./v5.0.5/script.js").then((lc) => {
let timeResource = /** @type {VecResource| null} */ (null);
let timeScaleSetCallback = /** @type {VoidFunction | null} */ (null);
let timeScaleSetCallback =
/** @type {((unknownTimeScaleCallback: VoidFunction) => void) | null} */ (
null
);
/**
* @param {ISeriesApi<SeriesType>} series
* @param {VecResource} valuesResource
*/
function createSetDataEffect(series, valuesResource) {
function createSetFetchedDataEffect(series, valuesResource) {
signals.runWithOwner(owner, () =>
signals.createEffect(
() => [timeResource?.fetched(), valuesResource.fetched()],
@@ -193,17 +219,18 @@ export default import("./v5.0.5/script.js").then((lc) => {
}
data.length -= offset;
series.setData(data);
timeScaleSetCallback?.();
if (
!timeScaleSet &&
(vecIndex === /** @satisfies {Quarterindex} */ (5) ||
vecIndex === /** @satisfies {Yearindex} */ (6) ||
vecIndex === /** @satisfies {Decadeindex} */ (7))
) {
ichart
.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}
timeScaleSetCallback?.(() => {
if (
!timeScaleSet &&
(vecIndex === /** @satisfies {Quarterindex} */ (5) ||
vecIndex === /** @satisfies {Yearindex} */ (6) ||
vecIndex === /** @satisfies {Decadeindex} */ (7))
) {
ichart
?.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}
});
timeScaleSet = true;
},
),
@@ -220,12 +247,12 @@ export default import("./v5.0.5/script.js").then((lc) => {
}),
);
return {
const chart = {
inner: () => ichart,
/**
* @param {Object} args
* @param {Index} args.index
* @param {VoidFunction} [args.timeScaleSetCallback]
* @param {((unknownTimeScaleCallback: VoidFunction) => void)} [args.timeScaleSetCallback]
*/
create({ index: _index, timeScaleSetCallback: _timeScaleSetCallback }) {
vecIndex = _index;
@@ -248,20 +275,37 @@ export default import("./v5.0.5/script.js").then((lc) => {
colors,
utils,
});
if (fitContentOnResize) {
ichart.applyOptions({
handleScroll: false,
handleScale: false,
timeScale: {
minBarSpacing: 0.001,
},
});
}
},
/**
* @param {Object} args
* @param {VecId} args.vecId
* @param {string} args.name
* @param {number} [args.paneNumber]
* @param {Unit} args.unit
* @param {VecId} [args.vecId]
* @param {Accessor<CandlestickData[]>} [args.data]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
*/
addCandlestickSeries({ vecId, name, paneNumber, defaultActive }) {
if (!ichart || !timeResource) throw Error("Chart not fully set");
addCandlestickSeries({
vecId,
name,
unit,
paneIndex: _paneIndex,
defaultActive,
data,
}) {
const paneIndex = _paneIndex ?? 0;
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
if (!ichart || !timeResource) throw Error("Chart not fully set");
const green = colors.green();
const red = colors.red();
@@ -275,35 +319,75 @@ export default import("./v5.0.5/script.js").then((lc) => {
borderVisible: false,
visible: defaultActive !== false,
},
paneNumber,
paneIndex,
);
let url = /** @type {string | undefined} */ (undefined);
if (vecId) {
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
createSetFetchedDataEffect(series, valuesResource);
url = valuesResource.url;
} else if (data) {
signals.runWithOwner(owner, () =>
signals.createEffect(data, (data) => {
series.setData(data);
}),
);
}
legend.add({
series,
name,
defaultActive,
colors: [colors.green, colors.red],
url: valuesResource.url,
url,
});
createSetDataEffect(series, valuesResource);
createPaneHeightObserver({
ichart,
paneIndex,
signals,
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
paneIndex,
seriesType: "Candlestick",
signals,
id,
unit,
utils,
});
return series;
},
/**
* @param {Object} args
* @param {VecId} args.vecId
* @param {string} args.name
* @param {Unit} args.unit
* @param {Accessor<LineData[]>} [args.data]
* @param {VecId} [args.vecId]
* @param {Color} [args.color]
* @param {number} [args.paneNumber]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
*/
addLineSeries({ vecId, name, color, paneNumber, defaultActive }) {
addLineSeries({
vecId,
name,
unit,
color,
paneIndex: _paneIndex,
defaultActive,
data,
}) {
if (!ichart || !timeResource) throw Error("Chart not fully set");
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
const paneIndex = _paneIndex ?? 0;
color ||= colors.orange;
@@ -315,18 +399,139 @@ export default import("./v5.0.5/script.js").then((lc) => {
priceLineVisible: false,
color: color(),
},
paneNumber,
paneIndex,
);
let url = /** @type {string | undefined} */ (undefined);
if (vecId) {
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
createSetFetchedDataEffect(series, valuesResource);
url = valuesResource.url;
} else if (data) {
signals.runWithOwner(owner, () =>
signals.createEffect(data, (data) => {
series.setData(data);
ichart
?.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}),
);
}
legend.add({
series,
colors: [color],
name,
defaultActive,
url: valuesResource.url,
url,
});
createSetDataEffect(series, valuesResource);
createPaneHeightObserver({
ichart,
paneIndex,
signals,
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
paneIndex,
signals,
seriesType: "Line",
id,
unit,
utils,
});
return series;
},
/**
* @param {Object} args
* @param {string} args.name
* @param {Unit} args.unit
* @param {Accessor<BaselineData[]>} [args.data]
* @param {VecId} [args.vecId]
* @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive]
*/
addBaselineSeries({
vecId,
name,
unit,
paneIndex: _paneIndex,
defaultActive,
data,
}) {
if (!ichart || !timeResource) throw Error("Chart not fully set");
const paneIndex = _paneIndex ?? 0;
const series = ichart.addSeries(
/** @type {SeriesDefinition<'Baseline'>} */ (lc.BaselineSeries),
{
lineWidth: /** @type {any} */ (1.5),
visible: defaultActive !== false,
topLineColor: colors.green(),
bottomLineColor: colors.red(),
baseValue: {
price: 0,
},
baseLineStyle: 0,
baseLineWidth: 1,
baseLineVisible: true,
lineVisible: true,
},
paneIndex,
);
let url = /** @type {string | undefined} */ (undefined);
if (vecId) {
const valuesResource = vecsResources.getOrCreate(vecIndex, vecId);
valuesResource.fetch();
activeResources.push(valuesResource);
createSetFetchedDataEffect(series, valuesResource);
url = valuesResource.url;
} else if (data) {
signals.runWithOwner(owner, () =>
signals.createEffect(data, (data) => {
series.setData(data);
ichart
?.timeScale()
.setVisibleLogicalRange({ from: -1, to: data.length });
}),
);
}
legend.add({
series,
colors: [colors.green, colors.red],
name,
defaultActive,
url,
});
createPaneHeightObserver({
ichart,
paneIndex,
signals,
utils,
});
createPriceScaleSelectorIfNeeded({
ichart,
paneIndex,
signals,
seriesType: "Baseline",
id,
unit,
utils,
});
return series;
},
@@ -344,6 +549,41 @@ export default import("./v5.0.5/script.js").then((lc) => {
legend.reset();
},
};
config?.forEach(({ unit, blueprints }, paneIndex) => {
chart.create({ index: /** @satisfies {Dateindex} */ (1) });
blueprints.forEach((blueprint) => {
if (blueprint.type === "Candlestick") {
chart.addCandlestickSeries({
name: blueprint.title,
unit,
data: blueprint.data,
defaultActive: blueprint.defaultActive,
paneIndex,
});
} else if (blueprint.type === "Baseline") {
chart.addBaselineSeries({
name: blueprint.title,
unit,
data: blueprint.data,
defaultActive: blueprint.defaultActive,
paneIndex,
});
} else {
chart.addLineSeries({
name: blueprint.title,
unit,
data: blueprint.data,
defaultActive: blueprint.defaultActive,
paneIndex,
color: blueprint.color,
});
}
});
});
return chart;
}
return {
@@ -600,3 +840,163 @@ function createOklchToRGBA() {
};
}
}
/**
* @param {Object} args
* @param {IChartApi} args.ichart
* @param {number} args.paneIndex
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
if (!paneIndex) return;
const owner = signals.getOwner();
const one = "1";
const callback = () =>
setTimeout(() => {
try {
const _element = ichart?.panes().at(paneIndex)?.getHTMLElement();
if (!_element) return callback();
const element = _element;
if (element.dataset.observed === one) return;
element.dataset.observed = one;
signals.runWithOwner(owner, () => {
const height = signals.createSignal(null, {
save: {
keyPrefix: "charts",
key: `height-${paneIndex}`,
...utils.serde.optNumber,
},
});
const savedHeight = height();
if (savedHeight !== null) {
ichart.panes().at(paneIndex)?.setHeight(savedHeight);
}
let firstRun = true;
new ResizeObserver(() => {
if (firstRun && savedHeight !== null) {
firstRun = false;
} else {
const h = ichart.panes().at(paneIndex)?.getHeight();
if (h === undefined) return;
height.set(h);
}
}).observe(element);
});
} catch {
callback();
}
}, 5);
callback();
}
/**
* @param {Object} args
* @param {IChartApi} args.ichart
* @param {Unit} args.unit
* @param {string} args.id
* @param {SeriesType} args.seriesType
* @param {number} args.paneIndex
* @param {Signals} args.signals
* @param {Utilities} args.utils
*/
function createPriceScaleSelectorIfNeeded({
ichart,
unit,
paneIndex,
id,
seriesType,
signals,
utils,
}) {
const owner = signals.getOwner();
setTimeout(
() => {
const parent = ichart
?.panes()
.at(paneIndex)
?.getHTMLElement()
.children?.item(1)?.firstChild;
if (!parent) throw Error("Parent should exist");
const tagName = "fieldset";
if (parent.lastChild?.nodeName.toLowerCase() === tagName) {
return;
}
console.log(id);
const choices = /**@type {const} */ (["lin", "log"]);
/** @typedef {(typeof choices)[number]} Choices */
const serializedValue = signals.createSignal(
/** @satisfies {Choices} */ (
unit === "US Dollars" && seriesType !== "Baseline" ? "log" : "lin"
),
{
save: {
...utils.serde.string,
keyPrefix: "",
key: `${id}-price-scale-${paneIndex}`,
},
},
);
const field = utils.dom.createHorizontalChoiceField({
title: unit,
selected: serializedValue(),
choices: choices,
id: `${id}-${unit.replace(" ", "-")}`,
signals,
});
field.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
serializedValue.set(value);
});
const element = window.document.createElement(tagName);
element.dataset.size = "xs";
element.id = `${id}-price-scale-${paneIndex}`;
element.append(field);
const mode = signals.createMemo(() => {
switch (serializedValue()) {
case "lin":
return 0;
case "log":
return 1;
}
});
const pane = ichart?.panes().at(paneIndex);
if (!pane) throw Error("Expect pane");
signals.runWithOwner(owner, () => {
signals.createEffect(mode, (mode) => {
try {
pane.priceScale("right").applyOptions({
mode,
});
} catch {}
});
});
pane.getHTMLElement().children?.item(1)?.firstChild?.appendChild(element);
},
paneIndex ? 10 : 0,
);
}
@@ -1,790 +0,0 @@
// src/core/error.ts
var NotReadyError = class extends Error {
};
var NoOwnerError = class extends Error {
constructor() {
super(
""
);
}
};
var ContextNotFoundError = class extends Error {
constructor() {
super(
""
);
}
};
// src/utils.ts
function isUndefined(value) {
return typeof value === "undefined";
}
// src/core/constants.ts
var STATE_CLEAN = 0;
var STATE_CHECK = 1;
var STATE_DIRTY = 2;
var STATE_DISPOSED = 3;
// src/core/owner.ts
var currentOwner = null;
var defaultContext = {};
function getOwner() {
return currentOwner;
}
function setOwner(owner) {
const out = currentOwner;
currentOwner = owner;
return out;
}
var Owner = class {
// We flatten the owner tree into a linked list so that we don't need a pointer to .firstChild
// However, the children are actually added in reverse creation order
// See comment at the top of the file for an example of the _nextSibling traversal
l = null;
g = null;
j = null;
a = STATE_CLEAN;
e = null;
h = defaultContext;
f = null;
constructor(signal = false) {
if (currentOwner && !signal)
currentOwner.append(this);
}
append(child) {
child.l = this;
child.j = this;
if (this.g)
this.g.j = child;
child.g = this.g;
this.g = child;
if (child.h !== this.h) {
child.h = { ...this.h, ...child.h };
}
if (this.f) {
child.f = !child.f ? this.f : [...child.f, ...this.f];
}
}
dispose(self = true) {
if (this.a === STATE_DISPOSED)
return;
let head = self ? this.j || this.l : this, current = this.g, next = null;
while (current && current.l === this) {
current.dispose(true);
current.n();
next = current.g;
current.g = null;
current = next;
}
if (self)
this.n();
if (current)
current.j = !self ? this : this.j;
if (head)
head.g = current;
}
n() {
if (this.j)
this.j.g = null;
this.l = null;
this.j = null;
this.h = defaultContext;
this.f = null;
this.a = STATE_DISPOSED;
this.emptyDisposal();
}
emptyDisposal() {
if (!this.e)
return;
if (Array.isArray(this.e)) {
for (let i = 0; i < this.e.length; i++) {
const callable = this.e[i];
callable.call(callable);
}
} else {
this.e.call(this.e);
}
this.e = null;
}
handleError(error) {
if (!this.f)
throw error;
let i = 0, len = this.f.length;
for (i = 0; i < len; i++) {
try {
this.f[i](error);
break;
} catch (e) {
error = e;
}
}
if (i === len)
throw error;
}
};
function createContext(defaultValue, description) {
return { id: Symbol(description), defaultValue };
}
function getContext(context, owner = currentOwner) {
if (!owner) {
throw new NoOwnerError();
}
const value = hasContext(context, owner) ? owner.h[context.id] : context.defaultValue;
if (isUndefined(value)) {
throw new ContextNotFoundError();
}
return value;
}
function setContext(context, value, owner = currentOwner) {
if (!owner) {
throw new NoOwnerError();
}
owner.h = {
...owner.h,
[context.id]: isUndefined(value) ? context.defaultValue : value
};
}
function hasContext(context, owner = currentOwner) {
return !isUndefined(owner?.h[context.id]);
}
function onCleanup(disposable) {
if (!currentOwner)
return;
const node = currentOwner;
if (!node.e) {
node.e = disposable;
} else if (Array.isArray(node.e)) {
node.e.push(disposable);
} else {
node.e = [node.e, disposable];
}
}
// src/core/flags.ts
var ERROR_OFFSET = 0;
var ERROR_BIT = 1 << ERROR_OFFSET;
var LOADING_OFFSET = 1;
var LOADING_BIT = 1 << LOADING_OFFSET;
var DEFAULT_FLAGS = ERROR_BIT;
// src/core/scheduler.ts
var scheduled = false;
var runningScheduled = false;
var Computations = [];
var RenderEffects = [];
var Effects = [];
function flushSync() {
if (!runningScheduled)
runScheduled();
}
function flushQueue() {
if (scheduled)
return;
scheduled = true;
queueMicrotask(runScheduled);
}
function runTop(node) {
const ancestors = [];
for (let current = node; current !== null; current = current.l) {
if (current.a !== STATE_CLEAN) {
ancestors.push(current);
}
}
for (let i = ancestors.length - 1; i >= 0; i--) {
if (ancestors[i].a !== STATE_DISPOSED)
ancestors[i].m();
}
}
function runScheduled() {
if (!Effects.length && !RenderEffects.length && !Computations.length) {
scheduled = false;
return;
}
runningScheduled = true;
try {
runPureQueue(Computations);
runPureQueue(RenderEffects);
runPureQueue(Effects);
} finally {
const renderEffects = RenderEffects;
const effects = Effects;
Computations = [];
Effects = [];
RenderEffects = [];
scheduled = false;
runningScheduled = false;
incrementClock();
runEffectQueue(renderEffects);
runEffectQueue(effects);
}
}
function runPureQueue(queue) {
for (let i = 0; i < queue.length; i++) {
if (queue[i].a !== STATE_CLEAN)
runTop(queue[i]);
}
}
function runEffectQueue(queue) {
for (let i = 0; i < queue.length; i++) {
if (queue[i].q && queue[i].a !== STATE_DISPOSED) {
queue[i].r(queue[i].d, queue[i].o);
queue[i].q = false;
queue[i].o = queue[i].d;
}
}
}
// src/core/core.ts
var currentObserver = null;
var currentMask = DEFAULT_FLAGS;
var newSources = null;
var newSourcesIndex = 0;
var newFlags = 0;
var clock = 0;
var syncResolve = false;
var updateCheck = null;
function getObserver() {
return currentObserver;
}
function incrementClock() {
clock++;
}
var UNCHANGED = Symbol(0);
var Computation2 = class extends Owner {
b = null;
c = null;
d;
s;
// Used in __DEV__ mode, hopefully removed in production
B;
// Using false is an optimization as an alternative to _equals: () => false
// which could enable more efficient DIRTY notification
t = isEqual;
x;
/** Whether the computation is an error or has ancestors that are unresolved */
i = 0;
/** Which flags raised by sources are handled, vs. being passed through. */
p = DEFAULT_FLAGS;
u = null;
v = null;
w = -1;
constructor(initialValue, compute2, options) {
super(compute2 === null);
this.s = compute2;
this.a = compute2 ? STATE_DIRTY : STATE_CLEAN;
this.d = initialValue;
if (options?.equals !== void 0)
this.t = options.equals;
if (options?.unobserved)
this.x = options?.unobserved;
}
y() {
if (this.s)
this.m();
if (!this.b || this.b.length)
track(this);
newFlags |= this.i & ~currentMask;
if (this.i & ERROR_BIT) {
throw this.d;
} else {
return this.d;
}
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*/
read() {
return this.y();
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*
* If the computation has any unresolved ancestors, this function waits for the value to resolve
* before continuing
*/
wait() {
if (!syncResolve && this.loading()) {
throw new NotReadyError();
}
return this.y();
}
/**
* Return true if the computation is the value is dependent on an unresolved promise
* Triggers re-execution of the computation when the loading state changes
*
* This is useful especially when effects want to re-execute when a computation's
* loading state changes
*/
loading() {
if (this.v === null) {
this.v = loadingState(this);
}
return this.v.read();
}
/**
* Return true if the computation is the computation threw an error
* Triggers re-execution of the computation when the error state changes
*/
error() {
if (this.u === null) {
this.u = errorState(this);
}
return this.u.read();
}
/** Update the computation with a new value. */
write(value, flags = 0, raw = false) {
const newValue = !raw && typeof value === "function" ? value(this.d) : value;
const valueChanged = newValue !== UNCHANGED && (!!(flags & ERROR_BIT) || this.t === false || !this.t(this.d, newValue));
if (valueChanged)
this.d = newValue;
const changedFlagsMask = this.i ^ flags, changedFlags = changedFlagsMask & flags;
this.i = flags;
this.w = clock + 1;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
if (valueChanged) {
this.c[i].k(STATE_DIRTY);
} else if (changedFlagsMask) {
this.c[i].z(changedFlagsMask, changedFlags);
}
}
}
return this.d;
}
/**
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
k(state) {
if (this.a >= state)
return;
this.a = state;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].k(STATE_CHECK);
}
}
}
/**
* Notify the computation that one of its sources has changed flags.
*
* @param mask A bitmask for which flag(s) were changed.
* @param newFlags The source's new flags, masked to just the changed ones.
*/
z(mask, newFlags2) {
if (this.a >= STATE_DIRTY)
return;
if (mask & this.p) {
this.k(STATE_DIRTY);
return;
}
if (this.a >= STATE_CHECK)
return;
const prevFlags = this.i & mask;
const deltaFlags = prevFlags ^ newFlags2;
if (newFlags2 === prevFlags) ; else if (deltaFlags & prevFlags & mask) {
this.k(STATE_CHECK);
} else {
this.i ^= deltaFlags;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].z(mask, newFlags2);
}
}
}
}
A(error) {
this.write(error, this.i | ERROR_BIT);
}
/**
* This is the core part of the reactivity system, which makes sure that the values are updated
* before they are read. We've also adapted it to return the loading state of the computation,
* so that we can propagate that to the computation's observers.
*
* This function will ensure that the value and states we read from the computation are up to date
*/
m() {
if (this.a === STATE_DISPOSED) {
throw new Error("Tried to read a disposed computation");
}
if (this.a === STATE_CLEAN) {
return;
}
let observerFlags = 0;
if (this.a === STATE_CHECK) {
for (let i = 0; i < this.b.length; i++) {
this.b[i].m();
observerFlags |= this.b[i].i;
if (this.a === STATE_DIRTY) {
break;
}
}
}
if (this.a === STATE_DIRTY) {
update(this);
} else {
this.write(UNCHANGED, observerFlags);
this.a = STATE_CLEAN;
}
}
/**
* Remove ourselves from the owner graph and the computation graph
*/
n() {
if (this.a === STATE_DISPOSED)
return;
if (this.b)
removeSourceObservers(this, 0);
super.n();
}
};
function loadingState(node) {
const prevOwner = setOwner(node.l);
const options = void 0;
const computation = new Computation2(
void 0,
() => {
track(node);
node.m();
return !!(node.i & LOADING_BIT);
},
options
);
computation.p = ERROR_BIT | LOADING_BIT;
setOwner(prevOwner);
return computation;
}
function errorState(node) {
const prevOwner = setOwner(node.l);
const options = void 0;
const computation = new Computation2(
void 0,
() => {
track(node);
node.m();
return !!(node.i & ERROR_BIT);
},
options
);
computation.p = ERROR_BIT;
setOwner(prevOwner);
return computation;
}
function track(computation) {
if (currentObserver) {
if (!newSources && currentObserver.b && currentObserver.b[newSourcesIndex] === computation) {
newSourcesIndex++;
} else if (!newSources)
newSources = [computation];
else if (computation !== newSources[newSources.length - 1]) {
newSources.push(computation);
}
if (updateCheck) {
updateCheck.d = computation.w > currentObserver.w;
}
}
}
function update(node) {
const prevSources = newSources, prevSourcesIndex = newSourcesIndex, prevFlags = newFlags;
newSources = null;
newSourcesIndex = 0;
newFlags = 0;
try {
node.dispose(false);
node.emptyDisposal();
const result = compute(node, node.s, node);
node.write(result, newFlags, true);
} catch (error) {
if (error instanceof NotReadyError) {
node.write(UNCHANGED, newFlags | LOADING_BIT);
} else {
node.A(error);
}
} finally {
if (newSources) {
if (node.b)
removeSourceObservers(node, newSourcesIndex);
if (node.b && newSourcesIndex > 0) {
node.b.length = newSourcesIndex + newSources.length;
for (let i = 0; i < newSources.length; i++) {
node.b[newSourcesIndex + i] = newSources[i];
}
} else {
node.b = newSources;
}
let source;
for (let i = newSourcesIndex; i < node.b.length; i++) {
source = node.b[i];
if (!source.c)
source.c = [node];
else
source.c.push(node);
}
} else if (node.b && newSourcesIndex < node.b.length) {
removeSourceObservers(node, newSourcesIndex);
node.b.length = newSourcesIndex;
}
newSources = prevSources;
newSourcesIndex = prevSourcesIndex;
newFlags = prevFlags;
node.a = STATE_CLEAN;
}
}
function removeSourceObservers(node, index) {
let source;
let swap;
for (let i = index; i < node.b.length; i++) {
source = node.b[i];
if (source.c) {
swap = source.c.indexOf(node);
source.c[swap] = source.c[source.c.length - 1];
source.c.pop();
if (!source.c.length)
source.x?.();
}
}
}
function isEqual(a, b) {
return a === b;
}
function untrack(fn) {
if (currentObserver === null)
return fn();
return compute(getOwner(), fn, null);
}
function hasUpdated(fn) {
const current = updateCheck;
updateCheck = { d: false };
try {
fn();
return updateCheck.d;
} finally {
updateCheck = current;
}
}
function isPending(fn) {
try {
fn();
return false;
} catch (e) {
return e instanceof NotReadyError;
}
}
function latest(fn) {
const prevFlags = newFlags;
syncResolve = true;
try {
return fn();
} catch {
} finally {
newFlags = prevFlags;
syncResolve = false;
}
}
function compute(owner, compute2, observer) {
const prevOwner = setOwner(owner), prevObserver = currentObserver, prevMask = currentMask;
currentObserver = observer;
currentMask = observer?.p ?? DEFAULT_FLAGS;
try {
return compute2(observer ? observer.d : void 0);
} finally {
setOwner(prevOwner);
currentObserver = prevObserver;
currentMask = prevMask;
}
}
var EagerComputation = class extends Computation2 {
constructor(initialValue, compute2, options) {
super(initialValue, compute2, options);
this.m();
Computations.push(this);
}
k(state) {
if (this.a >= state)
return;
if (this.a === STATE_CLEAN) {
Computations.push(this);
flushQueue();
}
super.k(state);
}
};
// src/core/effect.ts
var BaseEffect = class extends Computation2 {
r;
q = false;
o;
constructor(initialValue, compute2, effect, options) {
super(initialValue, compute2, options);
this.r = effect;
this.o = initialValue;
}
write(value) {
if (value === UNCHANGED)
return this.d;
this.d = value;
this.q = true;
return value;
}
A(error) {
this.handleError(error);
}
n() {
this.r = void 0;
this.o = void 0;
super.n();
}
};
var Effect = class extends BaseEffect {
constructor(initialValue, compute2, effect, options) {
super(initialValue, compute2, effect, options);
Effects.push(this);
flushQueue();
}
k(state) {
if (this.a >= state)
return;
if (this.a === STATE_CLEAN) {
Effects.push(this);
flushQueue();
}
this.a = state;
}
};
var RenderEffect = class extends BaseEffect {
constructor(initialValue, compute2, effect, options) {
super(initialValue, compute2, effect, options);
this.m();
RenderEffects.push(this);
}
k(state) {
if (this.a >= state)
return;
if (this.a === STATE_CLEAN) {
RenderEffects.push(this);
flushQueue();
}
this.a = state;
}
};
// src/signals.ts
function createSignal(first, second, third) {
if (typeof first === "function") {
const memo = createMemo((p) => {
const node2 = new Computation2(
first(p ? untrack(p[0]) : second),
null,
third
);
return [node2.read.bind(node2), node2.write.bind(node2)];
});
return [() => memo()[0](), (value) => memo()[1](value)];
}
const node = new Computation2(first, null, second);
return [node.read.bind(node), node.write.bind(node)];
}
function createAsync(fn, initial, options) {
const lhs = new EagerComputation(
{
d: initial
},
(p) => {
const value = p?.d;
const source = fn(value);
const isPromise = source instanceof Promise;
const iterator = source[Symbol.asyncIterator];
if (!isPromise && !iterator) {
return {
wait() {
return source;
},
d: source
};
}
const signal = new Computation2(value, null, options);
signal.write(UNCHANGED, LOADING_BIT);
if (isPromise) {
source.then(
(value2) => {
signal.write(value2, 0);
},
(error) => {
signal.write(error, ERROR_BIT);
}
);
} else {
let abort = false;
onCleanup(() => abort = true);
(async () => {
try {
for await (let value2 of source) {
if (abort)
return;
signal.write(value2, 0);
}
} catch (error) {
signal.write(error, ERROR_BIT);
}
})();
}
return signal;
}
);
return () => lhs.wait().wait();
}
function createMemo(compute2, initialValue, options) {
let node = new Computation2(initialValue, compute2, options);
let value;
return () => {
if (node) {
value = node.wait();
if (!node.b?.length)
node = void 0;
}
return value;
};
}
function createEffect(compute2, effect, initialValue, options) {
void new Effect(
initialValue,
compute2,
effect,
void 0
);
}
function createRenderEffect(compute2, effect, initialValue, options) {
void new RenderEffect(
initialValue,
compute2,
effect,
void 0
);
}
function createRoot(init) {
const owner = new Owner();
return compute(owner, !init.length ? init : () => init(() => owner.dispose()), null);
}
function runWithOwner(owner, run) {
try {
return compute(owner, run, null);
} catch (error) {
owner?.handleError(error);
return void 0;
}
}
function catchError(fn, handler) {
const owner = new Owner();
owner.f = owner.f ? [handler, ...owner.f] : [handler];
try {
compute(owner, fn, null);
} catch (error) {
owner.handleError(error);
}
}
export { Computation2 as Computation, ContextNotFoundError, NoOwnerError, NotReadyError, Owner, catchError, createAsync, createContext, createEffect, createMemo, createRenderEffect, createRoot, createSignal, flushSync, getContext, getObserver, getOwner, hasContext, hasUpdated, isEqual, isPending, latest, onCleanup, runWithOwner, setContext, untrack };
@@ -1,23 +0,0 @@
import { Computation, type SignalOptions } from "./core.js";
/**
* Effects are the leaf nodes of our reactive graph. When their sources change, they are
* automatically added to the queue of effects to re-execute, which will cause them to fetch their
* sources and recompute
*/
export declare class BaseEffect<T = any> extends Computation<T> {
_effect: (val: T, prev: T | undefined) => void;
_modified: boolean;
_prevValue: T | undefined;
constructor(initialValue: T, compute: () => T, effect: (val: T, prev: T | undefined) => void, options?: SignalOptions<T>);
write(value: T): T;
_setError(error: unknown): void;
_disposeNode(): void;
}
export declare class Effect<T = any> extends BaseEffect<T> {
constructor(initialValue: T, compute: () => T, effect: (val: T, prev: T | undefined) => void, options?: SignalOptions<T>);
_notify(state: number): void;
}
export declare class RenderEffect<T = any> extends BaseEffect<T> {
constructor(initialValue: T, compute: () => T, effect: (val: T, prev: T | undefined) => void, options?: SignalOptions<T>);
_notify(state: number): void;
}
@@ -1,6 +0,0 @@
export { ContextNotFoundError, NoOwnerError, NotReadyError, type ErrorHandler } from "./error.js";
export { Owner, createContext, getContext, setContext, hasContext, getOwner, onCleanup, type Context, type ContextRecord, type Disposable } from "./owner.js";
export { Computation, EagerComputation, getObserver, isEqual, untrack, hasUpdated, isPending, latest, UNCHANGED, compute, type SignalOptions } from "./core.js";
export { Effect, RenderEffect } from "./effect.js";
export { flushSync } from "./scheduler.js";
export * from "./flags.js";
@@ -1,9 +0,0 @@
import type { Effect, RenderEffect } from "./effect.js";
import { Computation } from "./core.js";
export declare let Computations: Computation[], RenderEffects: RenderEffect[], Effects: Effect[];
/**
* By default, changes are batched on the microtask queue which is an async process. You can flush
* the queue synchronously to get the latest updates by calling `flushSync()`.
*/
export declare function flushSync(): void;
export declare function flushQueue(): void;
@@ -1,4 +0,0 @@
export { Computation, ContextNotFoundError, NoOwnerError, NotReadyError, Owner, createContext, getContext, setContext, hasContext, getOwner, onCleanup, getObserver, isEqual, untrack, hasUpdated, isPending, latest } from "./core/index.js";
export type { ErrorHandler, SignalOptions, Context, ContextRecord, Disposable } from "./core/index.js";
export { flushSync } from "./core/scheduler.js";
export * from "./signals.js";
@@ -1,12 +0,0 @@
import type { Accessor } from "./signals.js";
export type Maybe<T> = T | void | null | undefined | false;
/**
* Reactive map helper that caches each list item by reference to reduce unnecessary mapping on
* updates.
*
* @see {@link https://github.com/solidjs/x-reactivity#maparray}
*/
export declare function mapArray<Item, MappedItem>(list: Accessor<Maybe<readonly Item[]>>, map: (value: Accessor<Item>, index: Accessor<number>) => MappedItem, options?: {
keyed?: boolean | ((item: Item) => any);
name?: string;
}): Accessor<MappedItem[]>;
@@ -1,56 +0,0 @@
import type { SignalOptions } from "./core/index.js";
import { Owner } from "./core/index.js";
export interface Accessor<T> {
(): T;
}
export interface Setter<T> {
(value: T | SetValue<T>): T;
}
export interface SetValue<T> {
(currentValue: T): T;
}
export type Signal<T> = [read: Accessor<T>, write: Setter<T>];
/**
* Wraps the given value into a signal. The signal will return the current value when invoked
* `fn()`, and provide a simple write API via `write()`. The value can now be observed
* when used inside other computations created with `computed` and `effect`.
*/
export declare function createSignal<T>(initialValue: Exclude<T, Function>, options?: SignalOptions<T>): Signal<T>;
export declare function createSignal<T>(fn: (prev?: T) => T, initialValue?: T, options?: SignalOptions<T>): Signal<T>;
export declare function createAsync<T>(fn: (prev?: T) => Promise<T> | AsyncIterable<T> | T, initial?: T, options?: SignalOptions<T>): Accessor<T>;
/**
* Creates a new computation whose value is computed and returned by the given function. The given
* compute function is _only_ re-run when one of it's dependencies are updated. Dependencies are
* are all signals that are read during execution.
*/
export declare function createMemo<T>(compute: (prev?: T) => T, initialValue?: T, options?: SignalOptions<T>): Accessor<T>;
/**
* Invokes the given function each time any of the signals that are read inside are updated
* (i.e., their value changes). The effect is immediately invoked on initialization.
*/
export declare function createEffect<T>(compute: () => T, effect: (v: T) => (() => void) | void, initialValue?: T, options?: {
name?: string;
}): void;
/**
* Invokes the given function each time any of the signals that are read inside are updated
* (i.e., their value changes). The effect is immediately invoked on initialization.
*/
export declare function createRenderEffect<T>(compute: () => T, effect: (v: T) => (() => void) | void, initialValue?: T, options?: {
name?: string;
}): void;
/**
* Creates a computation root which is given a `dispose()` function to dispose of all inner
* computations.
*/
export declare function createRoot<T>(init: ((dispose: () => void) => T) | (() => T)): T;
/**
* Runs the given function in the given owner so that error handling and cleanups continue to work.
*
* Warning: Usually there are simpler ways of modeling a problem that avoid using this function
*/
export declare function runWithOwner<T>(owner: Owner | null, run: () => T): T | undefined;
/**
* Runs the given function when an error is thrown in a child owner. If the error is thrown again
* inside the error handler, it will trigger the next available parent owner handler.
*/
export declare function catchError<T>(fn: () => T, handler: (error: unknown) => void): void;
@@ -1 +0,0 @@
export * from "./store.js";
@@ -1,5 +0,0 @@
Compiled version of: https://github.com/solidjs/signals/commits/main/
Head:
- SHA: 4d75d3f84ce22b560988f3b27a5065c0fd2e69a8
- Date: Apr 17, 2024
+1 -1
View File
@@ -1,4 +1,4 @@
import { Accessor, Setter } from "./2024-11-02/types/signals";
import { Accessor, Setter } from "./v0.2.4-treeshaked/types/signals";
export type Signal<T> = Accessor<T> & { set: Setter<T>; reset: VoidFunction };
export type Signals = Awaited<typeof import("./wrapper.js").default>;
@@ -0,0 +1,700 @@
// @ts-nocheck
// src/core/error.ts
var NotReadyError = class extends Error {};
var EffectError = class extends Error {
constructor(effect, cause) {
super("");
this.cause = cause;
}
};
// src/core/constants.ts
var STATE_CLEAN = 0;
var STATE_CHECK = 1;
var STATE_DIRTY = 2;
var STATE_DISPOSED = 3;
var EFFECT_PURE = 0;
var EFFECT_RENDER = 1;
var EFFECT_USER = 2;
// src/core/scheduler.ts
var clock = 0;
function getClock() {
return clock;
}
function incrementClock() {
clock++;
}
var scheduled = false;
function schedule() {
if (scheduled) return;
scheduled = true;
if (!globalQueue.w) queueMicrotask(flushSync);
}
var Queue = class {
w = false;
l = [[], [], []];
u = [];
created = clock;
enqueue(type, node) {
this.l[0].push(node);
if (type) this.l[type].push(node);
schedule();
}
run(type) {
if (this.l[type].length) {
if (type === EFFECT_PURE) {
runPureQueue(this.l[type]);
this.l[type] = [];
} else {
const effects = this.l[type];
this.l[type] = [];
runEffectQueue(effects);
}
}
let rerun = false;
for (let i = 0; i < this.u.length; i++) {
rerun = this.u[i].run(type) || rerun;
}
if (type === EFFECT_PURE && this.l[type].length) return true;
}
flush() {
if (this.w) return;
this.w = true;
try {
while (this.run(EFFECT_PURE)) {}
incrementClock();
scheduled = false;
this.run(EFFECT_RENDER);
this.run(EFFECT_USER);
} finally {
this.w = false;
}
}
addChild(child) {
this.u.push(child);
}
removeChild(child) {
const index = this.u.indexOf(child);
if (index >= 0) this.u.splice(index, 1);
}
};
var globalQueue = new Queue();
function flushSync() {
while (scheduled) {
globalQueue.flush();
}
}
function runTop(node) {
const ancestors = [];
for (let current = node; current !== null; current = current.m) {
if (current.a !== STATE_CLEAN) {
ancestors.push(current);
}
}
for (let i = ancestors.length - 1; i >= 0; i--) {
if (ancestors[i].a !== STATE_DISPOSED) ancestors[i].p();
}
}
function runPureQueue(queue) {
for (let i = 0; i < queue.length; i++) {
if (queue[i].a !== STATE_CLEAN) runTop(queue[i]);
}
}
function runEffectQueue(queue) {
for (let i = 0; i < queue.length; i++) queue[i].K();
}
// src/core/owner.ts
var currentOwner = null;
var defaultContext = {};
function getOwner() {
return currentOwner;
}
function setOwner(owner) {
const out = currentOwner;
currentOwner = owner;
return out;
}
var Owner = class {
// We flatten the owner tree into a linked list so that we don't need a pointer to .firstChild
// However, the children are actually added in reverse creation order
// See comment at the top of the file for an example of the _nextSibling traversal
m = null;
h = null;
n = null;
a = STATE_CLEAN;
g = null;
i = defaultContext;
j = null;
f = globalQueue;
constructor(signal = false) {
if (currentOwner && !signal) currentOwner.append(this);
}
append(child) {
child.m = this;
child.n = this;
if (this.h) this.h.n = child;
child.h = this.h;
this.h = child;
if (child.i !== this.i) {
child.i = { ...this.i, ...child.i };
}
if (this.j) {
child.j = !child.j ? this.j : [...child.j, ...this.j];
}
if (this.f) child.f = this.f;
}
dispose(self = true) {
if (this.a === STATE_DISPOSED) return;
let head = self ? this.n || this.m : this,
current = this.h,
next = null;
while (current && current.m === this) {
current.dispose(true);
current.q();
next = current.h;
current.h = null;
current = next;
}
if (self) this.q();
if (current) current.n = !self ? this : this.n;
if (head) head.h = current;
}
q() {
if (this.n) this.n.h = null;
this.m = null;
this.n = null;
this.i = defaultContext;
this.j = null;
this.a = STATE_DISPOSED;
this.emptyDisposal();
}
emptyDisposal() {
if (!this.g) return;
if (Array.isArray(this.g)) {
for (let i = 0; i < this.g.length; i++) {
const callable = this.g[i];
callable.call(callable);
}
} else {
this.g.call(this.g);
}
this.g = null;
}
handleError(error) {
if (!this.j) throw error;
let i = 0,
len = this.j.length;
for (i = 0; i < len; i++) {
try {
this.j[i](error, this);
break;
} catch (e) {
error = e;
}
}
if (i === len) throw error;
}
};
function onCleanup(fn) {
if (!currentOwner) return fn;
const node = currentOwner;
if (!node.g) {
node.g = fn;
} else if (Array.isArray(node.g)) {
node.g.push(fn);
} else {
node.g = [node.g, fn];
}
return fn;
}
// src/core/flags.ts
var ERROR_OFFSET = 0;
var ERROR_BIT = 1 << ERROR_OFFSET;
var LOADING_OFFSET = 1;
var LOADING_BIT = 1 << LOADING_OFFSET;
var UNINITIALIZED_OFFSET = 2;
var UNINITIALIZED_BIT = 1 << UNINITIALIZED_OFFSET;
var DEFAULT_FLAGS = ERROR_BIT;
// src/core/core.ts
var currentObserver = null;
var currentMask = DEFAULT_FLAGS;
var newSources = null;
var newSourcesIndex = 0;
var newFlags = 0;
var notStale = false;
var UNCHANGED = Symbol(0);
var Computation = class extends Owner {
b = null;
c = null;
e;
B;
r;
// Used in __DEV__ mode, hopefully removed in production
O;
// Using false is an optimization as an alternative to _equals: () => false
// which could enable more efficient DIRTY notification
C = isEqual;
L;
/** Whether the computation is an error or has ancestors that are unresolved */
d = 0;
/** Which flags raised by sources are handled, vs. being passed through. */
D = DEFAULT_FLAGS;
E = null;
s = -1;
x = false;
constructor(initialValue, compute2, options) {
super(compute2 === null);
this.r = compute2;
this.a = compute2 ? STATE_DIRTY : STATE_CLEAN;
this.d = compute2 && initialValue === void 0 ? UNINITIALIZED_BIT : 0;
this.e = initialValue;
if (options?.equals !== void 0) this.C = options.equals;
if (options?.unobserved) this.L = options?.unobserved;
}
M() {
if (this.r) {
if (this.d & ERROR_BIT && this.s <= getClock()) update(this);
else this.p();
}
if (!this.r || this.b?.length) track(this);
newFlags |= this.d & ~currentMask;
if (this.d & ERROR_BIT) {
throw this.B;
} else {
return this.e;
}
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*/
read() {
return this.M();
}
/**
* Return the current value of this computation
* Automatically re-executes the surrounding computation when the value changes
*
* If the computation has any unresolved ancestors, this function waits for the value to resolve
* before continuing
*/
wait() {
if (this.r && this.d & ERROR_BIT && this.s <= getClock()) {
update(this);
}
if ((notStale || this.d & UNINITIALIZED_BIT) && this.loading()) {
throw new NotReadyError();
}
return this.M();
}
/**
* Return true if the computation is the value is dependent on an unresolved promise
* Triggers re-execution of the computation when the loading state changes
*
* This is useful especially when effects want to re-execute when a computation's
* loading state changes
*/
loading() {
if (this.E === null) {
this.E = loadingState(this);
}
return this.E.read();
}
/** Update the computation with a new value. */
write(value, flags = 0, raw = false) {
const newValue =
!raw && typeof value === "function" ? value(this.e) : value;
const valueChanged =
newValue !== UNCHANGED &&
(!!(this.d & UNINITIALIZED_BIT) ||
this.C === false ||
!this.C(this.e, newValue));
if (valueChanged) {
this.e = newValue;
this.B = void 0;
}
const changedFlagsMask = this.d ^ flags,
changedFlags = changedFlagsMask & flags;
this.d = flags;
this.s = getClock() + 1;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
if (valueChanged) {
this.c[i].k(STATE_DIRTY);
} else if (changedFlagsMask) {
this.c[i].N(changedFlagsMask, changedFlags);
}
}
}
return this.e;
}
/**
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
k(state, skipQueue) {
if (this.a >= state && !this.x) return;
this.x = !!skipQueue;
this.a = state;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].k(STATE_CHECK, skipQueue);
}
}
}
/**
* Notify the computation that one of its sources has changed flags.
*
* @param mask A bitmask for which flag(s) were changed.
* @param newFlags The source's new flags, masked to just the changed ones.
*/
N(mask, newFlags2) {
if (this.a >= STATE_DIRTY) return;
if (mask & this.D) {
this.k(STATE_DIRTY);
return;
}
if (this.a >= STATE_CHECK) return;
const prevFlags = this.d & mask;
const deltaFlags = prevFlags ^ newFlags2;
if (newFlags2 === prevFlags);
else if (deltaFlags & prevFlags & mask) {
this.k(STATE_CHECK);
} else {
this.d ^= deltaFlags;
if (this.c) {
for (let i = 0; i < this.c.length; i++) {
this.c[i].N(mask, newFlags2);
}
}
}
}
F(error) {
this.B = error;
this.write(
UNCHANGED,
(this.d & ~LOADING_BIT) | ERROR_BIT | UNINITIALIZED_BIT,
);
}
/**
* This is the core part of the reactivity system, which makes sure that the values are updated
* before they are read. We've also adapted it to return the loading state of the computation,
* so that we can propagate that to the computation's observers.
*
* This function will ensure that the value and states we read from the computation are up to date
*/
p() {
if (this.a === STATE_DISPOSED) {
throw new Error("Tried to read a disposed computation");
}
if (this.a === STATE_CLEAN) {
return;
}
let observerFlags = 0;
if (this.a === STATE_CHECK) {
for (let i = 0; i < this.b.length; i++) {
this.b[i].p();
observerFlags |= this.b[i].d;
if (this.a === STATE_DIRTY) {
break;
}
}
}
if (this.a === STATE_DIRTY) {
update(this);
} else {
this.write(UNCHANGED, observerFlags);
this.a = STATE_CLEAN;
}
}
/**
* Remove ourselves from the owner graph and the computation graph
*/
q() {
if (this.a === STATE_DISPOSED) return;
if (this.b) removeSourceObservers(this, 0);
super.q();
}
};
function loadingState(node) {
const prevOwner = setOwner(node.m);
const options = void 0;
const computation = new Computation(
void 0,
() => {
track(node);
node.p();
return !!(node.d & LOADING_BIT);
},
options,
);
computation.D = ERROR_BIT | LOADING_BIT;
setOwner(prevOwner);
return computation;
}
function track(computation) {
if (currentObserver) {
if (
!newSources &&
currentObserver.b &&
currentObserver.b[newSourcesIndex] === computation
) {
newSourcesIndex++;
} else if (!newSources) newSources = [computation];
else if (computation !== newSources[newSources.length - 1]) {
newSources.push(computation);
}
}
}
function update(node) {
const prevSources = newSources,
prevSourcesIndex = newSourcesIndex,
prevFlags = newFlags;
newSources = null;
newSourcesIndex = 0;
newFlags = 0;
try {
node.dispose(false);
node.emptyDisposal();
const result = compute(node, node.r, node);
node.write(result, newFlags, true);
} catch (error) {
if (error instanceof NotReadyError) {
node.write(
UNCHANGED,
newFlags | LOADING_BIT | (node.d & UNINITIALIZED_BIT),
);
} else {
node.F(error);
}
} finally {
if (newSources) {
if (node.b) removeSourceObservers(node, newSourcesIndex);
if (node.b && newSourcesIndex > 0) {
node.b.length = newSourcesIndex + newSources.length;
for (let i = 0; i < newSources.length; i++) {
node.b[newSourcesIndex + i] = newSources[i];
}
} else {
node.b = newSources;
}
let source;
for (let i = newSourcesIndex; i < node.b.length; i++) {
source = node.b[i];
if (!source.c) source.c = [node];
else source.c.push(node);
}
} else if (node.b && newSourcesIndex < node.b.length) {
removeSourceObservers(node, newSourcesIndex);
node.b.length = newSourcesIndex;
}
newSources = prevSources;
newSourcesIndex = prevSourcesIndex;
newFlags = prevFlags;
node.s = getClock() + 1;
node.a = STATE_CLEAN;
}
}
function removeSourceObservers(node, index) {
let source;
let swap;
for (let i = index; i < node.b.length; i++) {
source = node.b[i];
if (source.c) {
swap = source.c.indexOf(node);
source.c[swap] = source.c[source.c.length - 1];
source.c.pop();
if (!source.c.length) source.L?.();
}
}
}
function isEqual(a, b) {
return a === b;
}
function untrack(fn) {
if (currentObserver === null) return fn();
return compute(getOwner(), fn, null);
}
function latest(fn, fallback) {
const argLength = arguments.length;
const prevFlags = newFlags;
const prevNotStale = notStale;
notStale = false;
try {
return fn();
} catch (err) {
if (argLength > 1 && err instanceof NotReadyError) return fallback;
throw err;
} finally {
newFlags = prevFlags;
notStale = prevNotStale;
}
}
function compute(owner, fn, observer) {
const prevOwner = setOwner(owner),
prevObserver = currentObserver,
prevMask = currentMask,
prevNotStale = notStale;
currentObserver = observer;
currentMask = observer?.D ?? DEFAULT_FLAGS;
notStale = true;
try {
return fn(observer ? observer.e : void 0);
} finally {
setOwner(prevOwner);
currentObserver = prevObserver;
currentMask = prevMask;
notStale = prevNotStale;
}
}
// src/core/effect.ts
var Effect = class extends Computation {
y;
z;
t;
G = false;
A;
o;
constructor(initialValue, compute2, effect, error, options) {
super(initialValue, compute2, options);
this.y = effect;
this.z = error;
this.A = initialValue;
this.o = options?.render ? EFFECT_RENDER : EFFECT_USER;
if (this.o === EFFECT_RENDER) {
this.r = (p) =>
getClock() > this.f.created && !(this.d & ERROR_BIT)
? latest(() => compute2(p))
: compute2(p);
}
this.p();
!options?.defer &&
(this.o === EFFECT_USER ? this.f.enqueue(this.o, this) : this.K());
}
write(value, flags = 0) {
if (this.a == STATE_DIRTY) {
const currentFlags = this.d;
this.d = flags;
if (
this.o === EFFECT_RENDER &&
(flags & LOADING_BIT) !== (currentFlags & LOADING_BIT)
) {
this.f.H?.(this);
}
}
if (value === UNCHANGED) return this.e;
this.e = value;
this.G = true;
return value;
}
k(state, skipQueue) {
if (this.a >= state || skipQueue) return;
if (this.a === STATE_CLEAN) this.f.enqueue(this.o, this);
this.a = state;
}
F(error) {
this.t?.();
if (this.d & LOADING_BIT) {
this.f.H?.(this);
}
this.d = ERROR_BIT;
if (this.o === EFFECT_USER) {
try {
return this.z
? (this.t = this.z(error))
: console.error(new EffectError(this.y, error));
} catch (e) {
error = e;
}
}
this.handleError(error);
}
q() {
if (this.a === STATE_DISPOSED) return;
this.y = void 0;
this.A = void 0;
this.z = void 0;
this.t?.();
this.t = void 0;
super.q();
}
K() {
if (this.G && this.a !== STATE_DISPOSED) {
this.t?.();
try {
this.t = this.y(this.e, this.A);
} catch (e) {
this.handleError(e);
} finally {
this.A = this.e;
this.G = false;
}
}
}
};
// src/signals.ts
function createSignal(first, second, third) {
if (typeof first === "function") {
const memo = createMemo((p) => {
const node2 = new Computation(
first(p ? untrack(p[0]) : second),
null,
third,
);
return [node2.read.bind(node2), node2.write.bind(node2)];
});
return [() => memo()[0](), (value) => memo()[1](value)];
}
const node = new Computation(first, null, second);
return [node.read.bind(node), node.write.bind(node)];
}
function createMemo(compute2, value, options) {
let node = new Computation(value, compute2, options);
let resolvedValue;
return () => {
if (node) {
resolvedValue = node.wait();
if (!node.b?.length && node.h?.m !== node) {
node.dispose();
node = void 0;
} else if (!node.m && !node.c?.length) {
node.dispose();
node.a = STATE_DIRTY;
}
}
return resolvedValue;
};
}
function createEffect(compute2, effect, error, value, options) {
void new Effect(value, compute2, effect, error, options);
}
function createRoot(init) {
const owner = new Owner();
return compute(
owner,
!init.length ? init : () => init(() => owner.dispose()),
null,
);
}
function runWithOwner(owner, run) {
return compute(owner, run, null);
}
export {
Owner,
createEffect,
createMemo,
createRoot,
createSignal,
flushSync,
getOwner,
onCleanup,
runWithOwner,
untrack,
};
@@ -8,3 +8,7 @@ export declare const STATE_CLEAN = 0;
export declare const STATE_CHECK = 1;
export declare const STATE_DIRTY = 2;
export declare const STATE_DISPOSED = 3;
export declare const EFFECT_PURE = 0;
export declare const EFFECT_RENDER = 1;
export declare const EFFECT_USER = 2;
export declare const SUPPORTS_PROXY: boolean;
@@ -28,6 +28,7 @@
*/
import { type Flags } from "./flags.js";
import { Owner } from "./owner.js";
import { type IQueue } from "./scheduler.js";
export interface SignalOptions<T> {
name?: string;
equals?: ((prev: T, next: T) => boolean) | false;
@@ -42,7 +43,7 @@ interface SourceType {
}
interface ObserverType {
_sources: SourceType[] | null;
_notify: (state: number) => void;
_notify: (state: number, skipQueue?: boolean) => void;
_handlerMask: Flags;
_notifyFlags: (mask: Flags, newFlags: Flags) => void;
_time: number;
@@ -50,14 +51,14 @@ interface ObserverType {
/**
* Returns the current observer.
*/
export declare function getObserver(): ObserverType | null;
export declare function incrementClock(): void;
export declare function getObserver(): Computation | null;
export declare const UNCHANGED: unique symbol;
export type UNCHANGED = typeof UNCHANGED;
export declare class Computation<T = any> extends Owner implements SourceType, ObserverType {
_sources: SourceType[] | null;
_observers: ObserverType[] | null;
_value: T | undefined;
_error: unknown;
_compute: null | ((p?: T) => T);
_name: string | undefined;
_equals: false | ((a: T, b: T) => boolean);
@@ -66,9 +67,9 @@ export declare class Computation<T = any> extends Owner implements SourceType, O
_stateFlags: number;
/** Which flags raised by sources are handled, vs. being passed through. */
_handlerMask: number;
_error: Computation<boolean> | null;
_loading: Computation<boolean> | null;
_time: number;
_forceNotify: boolean;
constructor(initialValue: T | undefined, compute: null | ((p?: T) => T), options?: SignalOptions<T>);
_read(): T;
/**
@@ -92,17 +93,12 @@ export declare class Computation<T = any> extends Owner implements SourceType, O
* loading state changes
*/
loading(): boolean;
/**
* Return true if the computation is the computation threw an error
* Triggers re-execution of the computation when the error state changes
*/
error(): boolean;
/** Update the computation with a new value. */
write(value: T | ((currentValue: T) => T) | UNCHANGED, flags?: number, raw?: boolean): T;
/**
* Set the current node's state, and recursively mark all of this node's observers as STATE_CHECK
*/
_notify(state: number): void;
_notify(state: number, skipQueue?: boolean): void;
/**
* Notify the computation that one of its sources has changed flags.
*
@@ -144,18 +140,31 @@ export declare function untrack<T>(fn: () => T): T;
*/
export declare function hasUpdated(fn: () => any): boolean;
/**
* Returns true if the given function contains async signals that are not ready yet.
* Returns true if the given function contains async signals are out of date.
*/
export declare function isPending(fn: () => any): boolean;
export declare function latest<T>(fn: () => T): T | undefined;
export declare function isPending(fn: () => any, loadingValue: boolean): boolean;
/**
* Attempts to resolve value of expression synchronously returning the last resolved value for any async computation.
*/
export declare function latest<T>(fn: () => T): T;
export declare function latest<T, U>(fn: () => T, fallback: U): T | U;
export declare function catchError(fn: () => void): unknown | undefined;
/**
* Runs the given function in the given observer.
*
* Warning: Usually there are simpler ways of modeling a problem that avoid using this function
*/
export declare function runWithObserver<T>(observer: Computation, run: () => T): T | undefined;
/**
* A convenient wrapper that calls `compute` with the `owner` and `observer` and is guaranteed
* to reset the global context after the computation is finished even if an error is thrown.
*/
export declare function compute<T>(owner: Owner | null, compute: (val: T) => T, observer: Computation<T>): T;
export declare function compute<T>(owner: Owner | null, compute: (val: undefined) => T, observer: null): T;
export declare class EagerComputation<T = any> extends Computation<T> {
constructor(initialValue: T, compute: () => T, options?: SignalOptions<T>);
_notify(state: number): void;
}
export declare function compute<T>(owner: Owner | null, fn: (val: T) => T, observer: Computation<T>): T;
export declare function compute<T>(owner: Owner | null, fn: (val: undefined) => T, observer: null): T;
export declare function flatten(children: any, options?: {
skipNonRendered?: boolean;
doNotUnwrap?: boolean;
}): any;
export declare function createBoundary<T>(fn: () => T, queue: IQueue): T;
export {};
@@ -0,0 +1,34 @@
import { EFFECT_RENDER, EFFECT_USER } from "./constants.js";
import { Computation, type SignalOptions } from "./core.js";
/**
* Effects are the leaf nodes of our reactive graph. When their sources change, they are
* automatically added to the queue of effects to re-execute, which will cause them to fetch their
* sources and recompute
*/
export declare class Effect<T = any> extends Computation<T> {
_effect: (val: T, prev: T | undefined) => void | (() => void);
_onerror: ((err: unknown) => void | (() => void)) | undefined;
_cleanup: (() => void) | undefined;
_modified: boolean;
_prevValue: T | undefined;
_type: typeof EFFECT_RENDER | typeof EFFECT_USER;
constructor(initialValue: T, compute: (val?: T) => T, effect: (val: T, prev: T | undefined) => void | (() => void), error?: (err: unknown) => void | (() => void), options?: SignalOptions<T> & {
render?: boolean;
defer?: boolean;
});
write(value: T, flags?: number): T;
_notify(state: number, skipQueue?: boolean): void;
_setError(error: unknown): void;
_disposeNode(): void;
_runEffect(): void;
}
export declare class EagerComputation<T = any> extends Computation<T> {
constructor(initialValue: T, compute: () => T, options?: SignalOptions<T> & {
defer?: boolean;
});
_notify(state: number, skipQueue?: boolean): void;
}
export declare class ProjectionComputation extends Computation {
constructor(compute: () => void);
_notify(state: number, skipQueue?: boolean): void;
}
@@ -1,3 +1,4 @@
import type { Owner } from "./owner.js";
export declare class NotReadyError extends Error {
}
export declare class NoOwnerError extends Error {
@@ -6,6 +7,9 @@ export declare class NoOwnerError extends Error {
export declare class ContextNotFoundError extends Error {
constructor();
}
export interface ErrorHandler {
(error: unknown): void;
export declare class EffectError extends Error {
constructor(effect: Function, cause: unknown);
}
export interface ErrorHandler {
(error: unknown, node: Owner): void;
}
@@ -5,4 +5,7 @@ export declare const ERROR: unique symbol;
export declare const LOADING_OFFSET = 1;
export declare const LOADING_BIT: number;
export declare const LOADING: unique symbol;
export declare const UNINITIALIZED_OFFSET = 2;
export declare const UNINITIALIZED_BIT: number;
export declare const UNINITIALIZED: unique symbol;
export declare const DEFAULT_FLAGS: number;
@@ -0,0 +1,8 @@
export { ContextNotFoundError, NoOwnerError, NotReadyError, type ErrorHandler } from "./error.js";
export { Owner, createContext, getContext, setContext, hasContext, getOwner, onCleanup, type Context, type ContextRecord, type Disposable } from "./owner.js";
export { Computation, createBoundary, getObserver, isEqual, untrack, hasUpdated, isPending, latest, flatten, catchError, UNCHANGED, compute, runWithObserver, type SignalOptions } from "./core.js";
export { Effect, EagerComputation } from "./effect.js";
export { flushSync, getClock, incrementClock, type IQueue, Queue } from "./scheduler.js";
export { createSuspense } from "./suspense.js";
export { SUPPORTS_PROXY } from "./constants.js";
export * from "./flags.js";
@@ -28,6 +28,7 @@
* Note that the owner tree is largely orthogonal to the reactivity tree, and is much closer to the component tree.
*/
import { type ErrorHandler } from "./error.js";
import { type IQueue } from "./scheduler.js";
export type ContextRecord = Record<string | symbol, unknown>;
export interface Disposable {
(): void;
@@ -45,6 +46,7 @@ export declare class Owner {
_disposal: Disposable | Disposable[] | null;
_context: ContextRecord;
_handlers: ErrorHandler[] | null;
_queue: IQueue;
constructor(signal?: boolean);
append(child: Owner): void;
dispose(this: Owner, self?: boolean): void;
@@ -83,6 +85,11 @@ export declare function setContext<T>(context: Context<T>, value?: T, owner?: Ow
*/
export declare function hasContext(context: Context<any>, owner?: Owner | null): boolean;
/**
* Runs the given function when the parent owner computation is being disposed.
* Runs an effect once before the reactive scope is disposed
* @param fn an effect that should run only once on cleanup
*
* @returns the same {@link fn} function that was passed in
*
* @description https://docs.solidjs.com/reference/lifecycle/on-cleanup
*/
export declare function onCleanup(disposable: Disposable): void;
export declare function onCleanup(fn: Disposable): Disposable;
@@ -0,0 +1,29 @@
import type { Computation } from "./core.js";
import type { Effect } from "./effect.js";
export declare function getClock(): number;
export declare function incrementClock(): void;
export interface IQueue {
enqueue<T extends Computation | Effect>(type: number, node: T): void;
run(type: number): boolean | void;
flush(): void;
addChild(child: IQueue): void;
removeChild(child: IQueue): void;
created: number;
}
export declare class Queue implements IQueue {
_running: boolean;
_queues: [Computation[], Effect[], Effect[]];
_children: IQueue[];
created: number;
enqueue<T extends Computation | Effect>(type: number, node: T): void;
run(type: number): true | undefined;
flush(): void;
addChild(child: IQueue): void;
removeChild(child: IQueue): void;
}
export declare const globalQueue: Queue;
/**
* By default, changes are batched on the microtask queue which is an async process. You can flush
* the queue synchronously to get the latest updates by calling `flushSync()`.
*/
export declare function flushSync(): void;
@@ -0,0 +1,11 @@
import { Computation } from "./core.js";
import { type Effect } from "./effect.js";
import { Queue } from "./scheduler.js";
export declare class SuspenseQueue extends Queue {
_nodes: Set<Effect>;
_fallback: boolean;
_signal: Computation<boolean>;
run(type: number): true | undefined;
_update(node: Effect): void;
}
export declare function createSuspense(fn: () => any, fallback: () => any): () => any;
@@ -0,0 +1,2 @@
export { Owner, flushSync, getOwner, onCleanup, untrack } from "./core/index.js";
export * from "./signals.js";
@@ -0,0 +1,22 @@
import type { Accessor } from "./signals.js";
export type Maybe<T> = T | void | null | undefined | false;
/**
* Reactively transforms an array with a callback function - underlying helper for the `<For>` control flow
*
* similar to `Array.prototype.map`, but gets the value and index as accessors, transforms only values that changed and returns an accessor and reactively tracks changes to the list.
*
* @description https://docs.solidjs.com/reference/reactive-utilities/map-array
*/
export declare function mapArray<Item, MappedItem>(list: Accessor<Maybe<readonly Item[]>>, map: (value: Accessor<Item>, index: Accessor<number>) => MappedItem, options?: {
keyed?: boolean | ((item: Item) => any);
fallback?: Accessor<any>;
}): Accessor<MappedItem[]>;
/**
* Reactively repeats a callback function the count provided - underlying helper for the `<Repeat>` control flow
*
* @description https://docs.solidjs.com/reference/reactive-utilities/repeat
*/
export declare function repeat(count: Accessor<number>, map: (index: number) => any, options?: {
from?: Accessor<number | undefined>;
fallback?: Accessor<any>;
}): Accessor<any[]>;
@@ -0,0 +1,105 @@
import type { SignalOptions } from "./core/index.js";
import { Owner } from "./core/index.js";
export type Accessor<T> = () => T;
export type Setter<in out T> = {
<U extends T>(...args: undefined extends T ? [] : [value: Exclude<U, Function> | ((prev: T) => U)]): undefined extends T ? undefined : U;
<U extends T>(value: (prev: T) => U): U;
<U extends T>(value: Exclude<U, Function>): U;
<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)): U;
};
export type Signal<T> = [get: Accessor<T>, set: Setter<T>];
export type ComputeFunction<Prev, Next extends Prev = Prev> = (v: Prev) => Next;
export type EffectFunction<Prev, Next extends Prev = Prev> = (v: Next, p?: Prev) => (() => void) | void;
export interface EffectOptions {
name?: string;
defer?: boolean;
}
export interface MemoOptions<T> {
name?: string;
equals?: false | ((prev: T, next: T) => boolean);
}
export type NoInfer<T extends any> = [T][T extends any ? 0 : never];
/**
* Creates a simple reactive state with a getter and setter
* ```typescript
* const [state: Accessor<T>, setState: Setter<T>] = createSignal<T>(
* value: T,
* options?: { name?: string, equals?: false | ((prev: T, next: T) => boolean) }
* )
* ```
* @param value initial value of the state; if empty, the state's type will automatically extended with undefined; otherwise you need to extend the type manually if you want setting to undefined not be an error
* @param options optional object with a name for debugging purposes and equals, a comparator function for the previous and next value to allow fine-grained control over the reactivity
*
* @returns ```typescript
* [state: Accessor<T>, setState: Setter<T>]
* ```
* * the Accessor is a function that returns the current value and registers each call to the reactive root
* * the Setter is a function that allows directly setting or mutating the value:
* ```typescript
* const [count, setCount] = createSignal(0);
* setCount(count => count + 1);
* ```
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-signal
*/
export declare function createSignal<T>(): Signal<T | undefined>;
export declare function createSignal<T>(value: Exclude<T, Function>, options?: SignalOptions<T>): Signal<T>;
export declare function createSignal<T>(fn: ComputeFunction<T>, initialValue?: T, options?: SignalOptions<T>): Signal<T>;
/**
* Creates a readonly derived reactive memoized signal
* ```typescript
* export function createMemo<T>(
* compute: (v: T) => T,
* value?: T,
* options?: { name?: string, equals?: false | ((prev: T, next: T) => boolean) }
* ): () => T;
* ```
* @param compute a function that receives its previous or the initial value, if set, and returns a new value used to react on a computation
* @param value an optional initial value for the computation; if set, fn will never receive undefined as first argument
* @param options allows to set a name in dev mode for debugging purposes and use a custom comparison function in equals
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-memo
*/
export declare function createMemo<Next extends Prev, Prev = Next>(compute: ComputeFunction<undefined | NoInfer<Prev>, Next>): Accessor<Next>;
export declare function createMemo<Next extends Prev, Init = Next, Prev = Next>(compute: ComputeFunction<Init | Prev, Next>, value: Init, options?: MemoOptions<Next>): Accessor<Next>;
/**
* Creates a reactive effect that runs after the render phase
* ```typescript
* export function createEffect<T>(
* compute: (prev: T) => T,
* effect: (v: T, prev: T) => (() => void) | void,
* value?: T,
* options?: { name?: string }
* ): void;
* ```
* @param compute a function that receives its previous or the initial value, if set, and returns a new value used to react on a computation
* @param effect a function that receives the new value and is used to perform side effects, return a cleanup function to run on disposal
* @param error an optional function that receives an error if thrown during the computation
* @param value an optional initial value for the computation; if set, fn will never receive undefined as first argument
* @param options allows to set a name in dev mode for debugging purposes
*
* @description https://docs.solidjs.com/reference/basic-reactivity/create-effect
*/
export declare function createEffect<Next>(compute: ComputeFunction<undefined | NoInfer<Next>, Next>, effect: EffectFunction<NoInfer<Next>, Next>, error?: (err: unknown) => void): void;
export declare function createEffect<Next, Init = Next>(compute: ComputeFunction<Init | Next, Next>, effect: EffectFunction<Next, Next>, error: ((err: unknown) => void) | undefined, value: Init, options?: EffectOptions): void;
/**
* Creates a new non-tracked reactive context with manual disposal
*
* @param fn a function in which the reactive state is scoped
* @returns the output of `fn`.
*
* @description https://docs.solidjs.com/reference/reactive-utilities/create-root
*/
export declare function createRoot<T>(init: ((dispose: () => void) => T) | (() => T)): T;
/**
* Runs the given function in the given owner to move ownership of nested primitives and cleanups.
* This method untracks the current scope.
*
* Warning: Usually there are simpler ways of modeling a problem that avoid using this function
*/
export declare function runWithOwner<T>(owner: Owner | null, run: () => T): T;
/**
* Returns a promise of the resolved value of a reactive expression
* @param fn a reactive expression to resolve
*/
export declare function resolve<T>(fn: () => T): Promise<T>;
@@ -0,0 +1,6 @@
export type { Store, StoreSetter, StoreNode, NotWrappable, SolidStore } from "./store.js";
export type { Merge, Omit } from "./utils.js";
export { unwrap, isWrappable, createStore, $RAW, $TRACK, $PROXY, $TARGET } from "./store.js";
export { createProjection } from "./projection.js";
export { reconcile } from "./reconcile.js";
export { merge, omit } from "./utils.js";
@@ -0,0 +1,8 @@
import { type Store, type StoreSetter } from "./store.js";
/**
* Creates a mutable derived value
*
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
*/
export declare function createProjection<T extends Object>(fn: (draft: T) => void, initialValue?: T): Store<T>;
export declare function wrapProjection<T>(fn: (draft: T) => void, store: Store<T>, setStore: StoreSetter<T>): [Store<T>, StoreSetter<T>];
@@ -0,0 +1 @@
export declare function reconcile<T extends U, U>(value: T, key: string | ((item: NonNullable<any>) => any)): (state: U) => T;
@@ -1,13 +1,22 @@
import { Computation } from "../core/index.js";
export type Store<T> = Readonly<T>;
export type StoreSetter<T> = (fn: (state: T) => void) => void;
declare const $TRACK: unique symbol, $PROXY: unique symbol;
export { $PROXY, $TRACK };
export type StoreNode = Record<PropertyKey, any>;
type DataNode = Computation<any>;
type DataNodes = Record<PropertyKey, DataNode>;
declare const $RAW: unique symbol, $TRACK: unique symbol, $TARGET: unique symbol, $PROXY: unique symbol;
export declare const STORE_VALUE = "v", STORE_NODE = "n", STORE_HAS = "h";
export { $PROXY, $TRACK, $RAW, $TARGET };
export type StoreNode = {
[STORE_VALUE]: Record<PropertyKey, any>;
[STORE_NODE]?: DataNodes;
[STORE_HAS]?: DataNodes;
};
export declare namespace SolidStore {
interface Unwrappable {
}
}
export type NotWrappable = string | number | bigint | symbol | boolean | Function | null | undefined | SolidStore.Unwrappable[keyof SolidStore.Unwrappable];
export declare function wrap<T extends Record<PropertyKey, any>>(value: T): T;
export declare function isWrappable<T>(obj: T | NotWrappable): obj is T;
/**
* Returns the underlying data in the store without a proxy.
@@ -20,12 +29,6 @@ export declare function isWrappable<T>(obj: T | NotWrappable): obj is T;
* initial === unwrap(state); // => true
* ```
*/
export declare function unwrap<T>(item: T, set?: Set<unknown>): T;
export declare function unwrap<T>(item: T, deep?: boolean, set?: Set<unknown>): T;
export declare function createStore<T extends object = {}>(store: T | Store<T>): [get: Store<T>, set: StoreSetter<T>];
export declare function createStore<T extends object = {}>(fn: (store: T) => void, store: T | Store<T>): [get: Store<T>, set: StoreSetter<T>];
/**
* Creates a mutable derived value
*
* @see {@link https://github.com/solidjs/x-reactivity#createprojection}
*/
export declare function createProjection<T extends Object>(fn: (draft: T) => void, initialValue: T): Readonly<T>;
@@ -0,0 +1,29 @@
type DistributeOverride<T, F> = T extends undefined ? F : T;
type Override<T, U> = T extends any ? U extends any ? {
[K in keyof T]: K extends keyof U ? DistributeOverride<U[K], T[K]> : T[K];
} & {
[K in keyof U]: K extends keyof T ? DistributeOverride<U[K], T[K]> : U[K];
} : T & U : T & U;
type OverrideSpread<T, U> = T extends any ? {
[K in keyof ({
[K in keyof T]: any;
} & {
[K in keyof U]?: any;
} & {
[K in U extends any ? keyof U : keyof U]?: any;
})]: K extends keyof T ? Exclude<U extends any ? U[K & keyof U] : never, undefined> | T[K] : U extends any ? U[K & keyof U] : never;
} : T & U;
type Simplify<T> = T extends any ? {
[K in keyof T]: T[K];
} : T;
type _Merge<T extends unknown[], Curr = {}> = T extends [
infer Next | (() => infer Next),
...infer Rest
] ? _Merge<Rest, Override<Curr, Next>> : T extends [...infer Rest, infer Next | (() => infer Next)] ? Override<_Merge<Rest, Curr>, Next> : T extends [] ? Curr : T extends (infer I | (() => infer I))[] ? OverrideSpread<Curr, I> : Curr;
export type Merge<T extends unknown[]> = Simplify<_Merge<T>>;
export declare function merge<T extends unknown[]>(...sources: T): Merge<T>;
export type Omit<T, K extends readonly (keyof T)[]> = {
[P in keyof T as Exclude<P, K[number]>]: T[P];
};
export declare function omit<T extends Record<any, any>, K extends readonly (keyof T)[]>(props: T, ...keys: K): Omit<T, K>;
export {};
@@ -1,78 +1,99 @@
// @ts-check
/**
* @import { SignalOptions } from "./2024-11-02/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "./2024-11-02/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "./2024-11-02/types/signals";
* @import { SignalOptions } from "./v0.2.4-treeshaked/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "./v0.2.4-treeshaked/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "./v0.2.4-treeshaked/types/signals";
* @import { Signal } from "./types";
*/
const importSignals = import("./2024-11-02/script.js").then((_signals) => {
const signals = {
createSolidSignal: /** @type {CreateSignal} */ (_signals.createSignal),
createSolidEffect: /** @type {CreateEffect} */ (_signals.createEffect),
createEffect: /** @type {CreateEffect} */ (compute, effect) => {
let dispose = /** @type {VoidFunction | null} */ (null);
// @ts-ignore
_signals.createEffect(compute, (v) => {
dispose?.();
signals.createRoot((_dispose) => {
dispose = _dispose;
effect(v);
});
signals.onCleanup(() => dispose?.());
});
signals.onCleanup(() => dispose?.());
},
createMemo: /** @type {CreateMemo} */ (_signals.createMemo),
createRoot: /** @type {CreateRoot} */ (_signals.createRoot),
getOwner: /** @type {GetOwner} */ (_signals.getOwner),
runWithOwner: /** @type {RunWithOwner} */ (_signals.runWithOwner),
onCleanup: /** @type {OnCleanup} */ (_signals.onCleanup),
flushSync: _signals.flushSync,
/**
* @template T
* @param {T} initialValue
* @param {SignalOptions<T> & {save?: {keyPrefix: string; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
* @returns {Signal<T>}
*/
createSignal(initialValue, options) {
const [get, set] = this.createSolidSignal(
/** @type {any} */ (initialValue),
options,
);
// @ts-ignore
get.set = set;
// @ts-ignore
get.reset = () => set(initialValue);
if (options?.save) {
const save = options.save;
const paramKey = save.key;
const storageKey = `${save.keyPrefix}-${paramKey}`;
let serialized = /** @type {string | null} */ (null);
if (options.save.serializeParam !== false) {
serialized = new URLSearchParams(window.location.search).get(
paramKey,
);
const importSignals = import("./v0.2.4-treeshaked/script.js").then(
(_signals) => {
const signals = {
createSolidSignal: /** @type {typeof CreateSignal} */ (
_signals.createSignal
),
createSolidEffect: /** @type {typeof CreateEffect} */ (
_signals.createEffect
),
createEffect: /** @type {typeof CreateEffect} */ (
// @ts-ignore
(compute, effect) => {
let dispose = /** @type {VoidFunction | null} */ (null);
// @ts-ignore
_signals.createEffect(compute, (v) => {
dispose?.();
signals.createRoot((_dispose) => {
dispose = _dispose;
effect(v);
});
signals.onCleanup(() => dispose?.());
});
signals.onCleanup(() => dispose?.());
}
),
createMemo: /** @type {typeof CreateMemo} */ (_signals.createMemo),
createRoot: /** @type {typeof CreateRoot} */ (_signals.createRoot),
getOwner: /** @type {typeof GetOwner} */ (_signals.getOwner),
runWithOwner: /** @type {typeof RunWithOwner} */ (_signals.runWithOwner),
onCleanup: /** @type {typeof OnCleanup} */ (_signals.onCleanup),
flushSync: _signals.flushSync,
/**
* @template T
* @param {T} initialValue
* @param {SignalOptions<T> & {save?: {keyPrefix: string; key: string; serialize: (v: T) => string; deserialize: (v: string) => T; serializeParam?: boolean}}} [options]
* @returns {Signal<T>}
*/
createSignal(initialValue, options) {
const [get, set] = this.createSolidSignal(
/** @type {any} */ (initialValue),
options,
);
if (serialized === null) {
serialized = localStorage.getItem(storageKey);
}
if (serialized) {
set(save.deserialize(serialized));
}
// @ts-ignore
get.set = set;
let firstEffect = true;
this.createEffect(get, (value) => {
if (!save) return;
// @ts-ignore
get.reset = () => set(initialValue);
if (options?.save) {
const save = options.save;
const paramKey = save.key;
const storageKey = `${save.keyPrefix}-${paramKey}`;
let serialized = /** @type {string | null} */ (null);
if (options.save.serializeParam !== false) {
serialized = new URLSearchParams(window.location.search).get(
paramKey,
);
}
if (serialized === null) {
serialized = localStorage.getItem(storageKey);
}
if (serialized) {
set(() => save.deserialize(serialized));
}
let firstEffect = true;
this.createEffect(get, (value) => {
if (!save) return;
if (!firstEffect) {
if (
value !== undefined &&
value !== null &&
(initialValue === undefined ||
initialValue === null ||
save.serialize(value) !== save.serialize(initialValue))
) {
localStorage.setItem(storageKey, save.serialize(value));
} else {
localStorage.removeItem(storageKey);
}
}
if (!firstEffect) {
if (
value !== undefined &&
value !== null &&
@@ -80,35 +101,23 @@ const importSignals = import("./2024-11-02/script.js").then((_signals) => {
initialValue === null ||
save.serialize(value) !== save.serialize(initialValue))
) {
localStorage.setItem(storageKey, save.serialize(value));
writeParam(paramKey, save.serialize(value));
} else {
localStorage.removeItem(storageKey);
removeParam(paramKey);
}
}
if (
value !== undefined &&
value !== null &&
(initialValue === undefined ||
initialValue === null ||
save.serialize(value) !== save.serialize(initialValue))
) {
writeParam(paramKey, save.serialize(value));
} else {
removeParam(paramKey);
}
firstEffect = false;
});
}
firstEffect = false;
});
}
// @ts-ignore
return get;
},
};
// @ts-ignore
return get;
},
};
return signals;
});
return signals;
},
);
/**
* @param {string} key
+21 -14
View File
@@ -28,14 +28,14 @@ export function init({
elements.charts.append(utils.dom.createShadow("left"));
elements.charts.append(utils.dom.createShadow("right"));
const { headerElement, titleElement } = utils.dom.createHeader({});
const { headerElement, headingElement } = utils.dom.createHeader({});
elements.charts.append(headerElement);
const chart = lightweightCharts.createChartElement({
parent: elements.charts,
signals,
colors,
id: "chart",
id: "charts",
utils,
vecsResources,
});
@@ -45,10 +45,8 @@ export function init({
let firstRun = true;
signals.createEffect(selected, (option) => {
titleElement.innerHTML = option.title;
headingElement.innerHTML = option.title;
signals.createEffect(index_, (index) => {
utils.url.writeParam("index", String(index));
chart.reset({ owner: signals.getOwner() });
const TIMERANGE_LS_KEY = `chart-timerange-${index}`;
@@ -72,7 +70,7 @@ export function init({
chart.create({
index,
timeScaleSetCallback: () => {
timeScaleSetCallback: (unknownTimeScaleCallback) => {
const from_ = from();
const to_ = to();
if (from_ !== null && to_ !== null) {
@@ -80,6 +78,8 @@ export function init({
from: from_,
to: to_,
});
} else {
unknownTimeScaleCallback();
}
},
});
@@ -87,6 +87,7 @@ export function init({
const candles = chart.addCandlestickSeries({
vecId: "ohlc",
name: "Price",
unit: "US Dollars",
});
signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => {
if (!latest) return;
@@ -98,17 +99,18 @@ export function init({
});
[
{ blueprints: option.top, paneNumber: 0 },
{ blueprints: option.bottom, paneNumber: 1 },
].forEach(({ blueprints, paneNumber }) => {
{ blueprints: option.top, paneIndex: 0 },
{ blueprints: option.bottom, paneIndex: 1 },
].forEach(({ blueprints, paneIndex }) => {
blueprints?.forEach((blueprint) => {
if (vecIdToIndexes[blueprint.key].includes(index)) {
chart.addLineSeries({
vecId: blueprint.key,
color: blueprint.color,
name: blueprint.title,
unit: option.unit,
defaultActive: blueprint.defaultActive,
paneNumber,
paneIndex,
});
}
});
@@ -136,7 +138,6 @@ export function init({
* @param {Utilities} args.utils
*/
function createIndexSelector({ elements, signals, utils }) {
const indexLSKey = "chart-index";
const indexChoices = /**@type {const} */ ([
"timestamp",
"date",
@@ -149,8 +150,14 @@ function createIndexSelector({ elements, signals, utils }) {
"decade",
]);
/** @typedef {(typeof indexChoices)[number]} SerializedIndex */
const serializedIndex = signals.createSignal(
/** @type {SerializedIndex} */ (localStorage.getItem(indexLSKey) || "date"),
const serializedIndex = /** @type {Signal<SerializedIndex>} */ (
signals.createSignal("date", {
save: {
keyPrefix: "charts",
key: "index",
...utils.serde.string,
},
})
);
const indexesField = utils.dom.createHorizontalChoiceField({
title: "Index",
@@ -162,12 +169,12 @@ function createIndexSelector({ elements, signals, utils }) {
indexesField.addEventListener("change", (event) => {
// @ts-ignore
const value = event.target.value;
localStorage.setItem(indexLSKey, value);
serializedIndex.set(value);
});
const fieldset = window.document.createElement("fieldset");
fieldset.append(indexesField);
fieldset.dataset.size = "sm";
elements.charts.append(fieldset);
const index = signals.createMemo(
+53 -42
View File
@@ -1,13 +1,13 @@
// @ts-check
/**
* @import { Option, Weighted, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple } from "./types/self"
* @import { Marker, CreatePaneParameters, HoveredLegend, ChartPane, SplitSeries, SingleSeries, CreateSplitSeriesParameters, LineSeriesBlueprint, CandlestickSeriesBlueprint, BaselineSeriesBlueprint, CreateBaseSeriesParameters, BaseSeries, RemoveSeriesBlueprintFluff, SplitSeriesBlueprint, AnySeries, PriceSeriesType } from "../packages/lightweight-charts/types";
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Unit, AnySeriesBlueprint } from "./options"
* @import {Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple} from "../packages/lightweight-charts/wrapper"
* @import * as _ from "../packages/ufuzzy/v1.0.14/types"
* @import { createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesMarker, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v5.0.5/types"
* @import { SignalOptions } from "../packages/solid-signals/2024-11-02/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/2024-11-02/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "../packages/solid-signals/2024-11-02/types/signals";
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesType, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.5-treeshaked/types"
* @import { SignalOptions } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/core"
* @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/v0.2.4-treeshaked/types/core/owner"
* @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo } from "../packages/solid-signals/v0.2.4-treeshaked/types/signals";
* @import {Signal, Signals} from "../packages/solid-signals/types";
* @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex, VecIdToIndexes, Quarterindex} from "./vecid-to-indexes"
*/
@@ -490,30 +490,21 @@ function createUtils() {
return { input, signal };
},
/**
* @param {Object} param0
* @param {string} [param0.title]
* @param {Object} args
* @param {1 | 2 | 3} [args.level]
* @param {string} [args.title]
*/
createHeader({ title }) {
createHeader({ title, level = 1 }) {
const headerElement = window.document.createElement("header");
const div = window.document.createElement("div");
headerElement.append(div);
const h1 = window.document.createElement("h1");
div.append(h1);
h1.style.display = "flex";
h1.style.flexDirection = "column";
const titleElement = window.document.createElement("span");
if (title) {
titleElement.append(title);
}
h1.append(titleElement);
titleElement.style.display = "block";
const headingElement = window.document.createElement(`h${level}`);
headingElement.innerHTML = title || "";
headerElement.append(headingElement);
headingElement.style.display = "block";
return {
headerElement,
titleElement,
headingElement,
};
},
/**
@@ -550,11 +541,11 @@ function createUtils() {
select.append(optGroup);
list.forEach((option) => {
optGroup.append(this.createOption(option));
setters[option.value] = () => signal.set(option);
setters[option.value] = () => signal.set(() => option);
});
} else {
select.append(this.createOption(anyOption));
setters[anyOption.value] = () => signal.set(anyOption);
setters[anyOption.value] = () => signal.set(() => anyOption);
}
if (index !== list.length - 1) {
select.append(window.document.createElement("hr"));
@@ -852,6 +843,20 @@ function createUtils() {
};
const serde = {
string: {
/**
* @param {string} v
*/
serialize(v) {
return v;
},
/**
* @param {string} v
*/
deserialize(v) {
return v;
},
},
number: {
/**
* @param {number} v
@@ -880,12 +885,12 @@ function createUtils() {
return v ? Number(v) : null;
},
},
date: {
optDate: {
/**
* @param {Date} v
* @param {Date | null} date
*/
serialize(v) {
return date.toString(v);
serialize(date) {
return date !== null ? date.toString() : "";
},
/**
* @param {string} v
@@ -947,11 +952,22 @@ function createUtils() {
},
/**
* @param {Date} date
* @returns {string}
*/
toString(date) {
return date.toJSON().split("T")[0];
},
/**
* @param {Date} date
*/
toDateIndex(date) {
if (
date.getUTCFullYear() === 2009 &&
date.getUTCMonth() === 0 &&
date.getUTCDate() === 3
)
return 0;
return this.differenceBetween(date, new Date("2009-01-09"));
},
/**
* @param {Time} time
*/
@@ -983,7 +999,6 @@ function createUtils() {
/**
* @param {Date} date1
* @param {Date} date2
* @returns
*/
differenceBetween(date1, date2) {
return Math.abs(date1.valueOf() - date2.valueOf()) / this.ONE_DAY_IN_MS;
@@ -1376,7 +1391,6 @@ function getElements() {
searchInput: /** @type {HTMLInputElement} */ (
getElementById("search-input")
),
searchSmall: getElementById("search-small"),
searchResults: getElementById("search-results"),
selectors: getElementById("frame-selectors"),
style: getComputedStyle(window.document.documentElement),
@@ -1605,6 +1619,8 @@ function createColors(dark, elements) {
}
/**
* @typedef {ReturnType<typeof createColors>} Colors
* @typedef {Colors["orange"]} Color
* @typedef {keyof Colors} ColorName
*/
/**
@@ -1672,7 +1688,7 @@ function initWebSockets(signals, utils) {
}
/**
* @param {(candle: DatasetCandlestickData) => void} callback
* @param {(candle: CandlestickData) => void} callback
* @param {number} interval
*/
function krakenCandleWebSocketCreator(callback, interval) {
@@ -1702,7 +1718,7 @@ function initWebSockets(signals, utils) {
const dateStr = utils.date.toString(date);
/** @type {DatasetCandlestickData} */
/** @type {CandlestickData} */
const candle = {
index: -1,
time: dateStr,
@@ -1985,7 +2001,7 @@ function main() {
utils,
webSockets,
vecsResources,
vecIdToIndexes: vecIdToIndexes,
vecIdToIndexes,
}),
),
),
@@ -2017,6 +2033,7 @@ function main() {
lightweightCharts,
signals,
utils,
vecsResources,
}),
),
),
@@ -2108,8 +2125,6 @@ function main() {
const haystack = options.list.map((option) => option.title);
const searchSmallOgInnerHTML = elements.searchSmall.innerHTML;
const RESULTS_PER_PAGE = 100;
packages.ufuzzy().then((ufuzzy) => {
@@ -2193,7 +2208,6 @@ function main() {
});
if (!needle) {
elements.searchSmall.innerHTML = searchSmallOgInnerHTML;
elements.searchResults.innerHTML = "";
return;
}
@@ -2253,9 +2267,6 @@ function main() {
);
}
elements.searchSmall.innerHTML = `Found <strong>${
result?.[0]?.length || 0
}</strong> result(s)`;
elements.searchResults.innerHTML = "";
const list = computeResultPage(result, 0);
File diff suppressed because it is too large Load Diff
@@ -20,8 +20,8 @@ self.addEventListener("install", (_event) => {
"/scripts/simulation.js",
"/styles/simulation.css",
"/packages/lean-qr/v2.3.4/script.js",
"/packages/lightweight-charts/v5.0.4/script.js",
"/packages/solid-signals/2024-11-02/script.js",
"/packages/lightweight-charts/v5.0.5-treeshaked/script.js",
"/packages/solid-signals/v0.2.4-treeshaked/script.js",
"/packages/ufuzzy/v1.0.14/script.js",
]);
}),
+341 -328
View File
@@ -7,11 +7,17 @@
* @param {Signals} args.signals
* @param {Utilities} args.utils
* @param {Elements} args.elements
* @param {VecsResources} args.vecsResources
*/
export function init({ colors, elements, lightweightCharts, signals, utils }) {
export function init({
colors,
elements,
lightweightCharts,
signals,
utils,
vecsResources,
}) {
/**
* @import { ColorName } from './types/self';
*
* @typedef {Object} Frequency
* @property {string} name
* @property {string} value
@@ -151,7 +157,7 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
initial: {
amount: signals.createSignal(/** @type {number | null} */ (1000), {
save: {
...utils.serde.number,
...utils.serde.optNumber,
keyPrefix,
key: "initial-amount",
},
@@ -160,7 +166,7 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
topUp: {
amount: signals.createSignal(/** @type {number | null} */ (150), {
save: {
...utils.serde.number,
...utils.serde.optNumber,
keyPrefix,
key: "top-up-amount",
},
@@ -181,14 +187,14 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
investment: {
initial: signals.createSignal(/** @type {number | null} */ (1000), {
save: {
...utils.serde.number,
...utils.serde.optNumber,
keyPrefix,
key: "initial-swap",
},
}),
recurrent: signals.createSignal(/** @type {number | null} */ (5), {
save: {
...utils.serde.number,
...utils.serde.optNumber,
keyPrefix,
key: "recurrent-swap",
},
@@ -210,7 +216,7 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
/** @type {Date | null} */ (new Date("2021-04-15")),
{
save: {
...utils.serde.date,
...utils.serde.optDate,
keyPrefix,
key: "interval-start",
},
@@ -218,7 +224,7 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
),
end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
save: {
...utils.serde.date,
...utils.serde.optDate,
keyPrefix,
key: "interval-end",
},
@@ -227,7 +233,7 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
fees: {
percentage: signals.createSignal(/** @type {number | null} */ (0.25), {
save: {
...utils.serde.number,
...utils.serde.optNumber,
keyPrefix,
key: "percentage",
},
@@ -257,7 +263,8 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
* @param {string} param0.text
*/
function createColoredSpan({ color, text }) {
return `<span style="color: ${colors[color]()}; font-weight: var(--font-weight-bold)">${text}</span>`;
return `<span style="color: ${colors[color]()}; font-weight: 500; text-transform: uppercase;
font-size: var(--font-size-sm);">${text}</span>`;
}
parametersElement.append(
@@ -544,25 +551,19 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
parent: resultsElement,
signals,
colors,
id: `simulation-0`,
kind: "static",
scale: "date",
id: `result`,
fitContentOnResize: true,
vecsResources,
utils,
config: [
{
unit: "US Dollars",
config: [
blueprints: [
{
title: "Fees Paid",
title: "Bitcoin Value",
type: "Line",
color: colors.rose,
data: totalFeesPaidData,
},
{
title: "Dollars Left",
type: "Line",
color: colors.offDollars,
data: dollarsLeftData,
color: colors.amber,
data: bitcoinValueData,
},
{
title: "Dollars Converted",
@@ -571,10 +572,18 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
data: totalInvestedAmountData,
},
{
title: "Bitcoin Value",
title: "Dollars Left",
type: "Line",
color: colors.amber,
data: bitcoinValueData,
color: colors.offDollars,
data: dollarsLeftData,
defaultActive: false,
},
{
title: "Fees Paid",
type: "Line",
color: colors.rose,
data: totalFeesPaidData,
defaultActive: false,
},
],
},
@@ -585,14 +594,14 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
parent: resultsElement,
signals,
colors,
id: `simulation-1`,
scale: "date",
kind: "static",
id: `bitcoin`,
fitContentOnResize: true,
vecsResources,
utils,
config: [
{
unit: "US Dollars",
config: [
unit: "Bitcoin",
blueprints: [
{
title: "Bitcoin Stack",
type: "Line",
@@ -608,14 +617,14 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
parent: resultsElement,
signals,
colors,
id: `simulation-average-price`,
scale: "date",
kind: "static",
id: `average-price`,
fitContentOnResize: true,
vecsResources,
utils,
config: [
{
unit: "US Dollars",
config: [
blueprints: [
{
title: "Bitcoin Price",
type: "Line",
@@ -637,28 +646,18 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
parent: resultsElement,
signals,
colors,
id: `simulation-return-ratio`,
scale: "date",
kind: "static",
vecsResources,
id: `return-ratio`,
fitContentOnResize: true,
utils,
config: [
{
unit: "US Dollars",
config: [
blueprints: [
{
title: "Return Of Investment",
type: "Baseline",
data: resultData,
// TODO: Doesn't work for some reason
// options: {
// baseLineColor: "#888",
// baseLineVisible: true,
// baseLineWidth: 1,
// baseValue: {
// price: 0,
// type: "price",
// },
// },
},
],
},
@@ -670,323 +669,337 @@ export function init({ colors, elements, lightweightCharts, signals, utils }) {
signals,
colors,
id: `simulation-profitability-ratios`,
kind: "static",
scale: "date",
fitContentOnResize: true,
vecsResources,
utils,
owner,
config: [
{
unit: "Percentage",
config: [
{
title: "Unprofitable Days Ratio",
type: "Line",
color: colors.red,
data: unprofitableDaysRatioData,
},
blueprints: [
{
title: "Profitable Days Ratio",
type: "Line",
color: colors.green,
data: profitableDaysRatioData,
},
{
title: "Unprofitable Days Ratio",
type: "Line",
color: colors.red,
data: unprofitableDaysRatioData,
},
],
},
],
});
const closes = datasets.getOrCreate("date", "date-to-close");
closes.fetchRange(2009, new Date().getUTCFullYear()).then(() => {
signals.runWithOwner(owner, () => {
signals.createEffect(
() => ({
initialDollarAmount: settings.dollars.initial.amount() || 0,
topUpAmount: settings.dollars.topUp.amount() || 0,
topUpFrequency: settings.dollars.topUp.frenquency(),
initialSwap: settings.bitcoin.investment.initial() || 0,
recurrentSwap: settings.bitcoin.investment.recurrent() || 0,
swapFrequency: settings.bitcoin.investment.frequency(),
start: settings.interval.start(),
end: settings.interval.end(),
fees: settings.fees.percentage(),
}),
({
initialDollarAmount,
topUpAmount,
topUpFrequency,
initialSwap,
recurrentSwap,
swapFrequency,
start,
end,
fees,
}) => {
if (!start || !end || start > end) return;
vecsResources
.getOrCreate(/** @satisfies {Dateindex} */ (1), "close")
.fetch()
.then((_closes) => {
if (!_closes) return;
const closes = /** @type {number[]} */ (_closes);
const range = utils.date.getRange(start, end);
signals.runWithOwner(owner, () => {
signals.createEffect(
() => ({
initialDollarAmount: settings.dollars.initial.amount() || 0,
topUpAmount: settings.dollars.topUp.amount() || 0,
topUpFrequency: settings.dollars.topUp.frenquency(),
initialSwap: settings.bitcoin.investment.initial() || 0,
recurrentSwap: settings.bitcoin.investment.recurrent() || 0,
swapFrequency: settings.bitcoin.investment.frequency(),
start: settings.interval.start(),
end: settings.interval.end(),
fees: settings.fees.percentage(),
}),
({
initialDollarAmount,
topUpAmount,
topUpFrequency,
initialSwap,
recurrentSwap,
swapFrequency,
start,
end,
fees,
}) => {
if (!start || !end || start > end) return;
totalInvestedAmountData().length = 0;
bitcoinValueData().length = 0;
bitcoinData().length = 0;
resultData().length = 0;
dollarsLeftData().length = 0;
totalValueData().length = 0;
investmentData().length = 0;
bitcoinAddedData().length = 0;
averagePricePaidData().length = 0;
bitcoinPriceData().length = 0;
buyCountData().length = 0;
totalFeesPaidData().length = 0;
daysCountData().length = 0;
profitableDaysRatioData().length = 0;
unprofitableDaysRatioData().length = 0;
const range = utils.date.getRange(start, end);
let bitcoin = 0;
let sats = 0;
let dollars = initialDollarAmount;
let investedAmount = 0;
let postFeesInvestedAmount = 0;
let buyCount = 0;
let averagePricePaid = 0;
let bitcoinValue = 0;
let roi = 0;
let totalValue = 0;
let totalFeesPaid = 0;
let daysCount = range.length;
let profitableDays = 0;
let unprofitableDays = 0;
let profitableDaysRatio = 0;
let unprofitableDaysRatio = 0;
let lastInvestDay = range[0];
let dailyInvestment = 0;
let bitcoinAdded = 0;
let satsAdded = 0;
let lastSatsAdded = 0;
totalInvestedAmountData().length = 0;
bitcoinValueData().length = 0;
bitcoinData().length = 0;
resultData().length = 0;
dollarsLeftData().length = 0;
totalValueData().length = 0;
investmentData().length = 0;
bitcoinAddedData().length = 0;
averagePricePaidData().length = 0;
bitcoinPriceData().length = 0;
buyCountData().length = 0;
totalFeesPaidData().length = 0;
daysCountData().length = 0;
profitableDaysRatioData().length = 0;
unprofitableDaysRatioData().length = 0;
range.forEach((date, index) => {
const year = date.getUTCFullYear();
const time = utils.date.toString(date);
let bitcoin = 0;
let sats = 0;
let dollars = initialDollarAmount;
let investedAmount = 0;
let postFeesInvestedAmount = 0;
let buyCount = 0;
let averagePricePaid = 0;
let bitcoinValue = 0;
let roi = 0;
let totalValue = 0;
let totalFeesPaid = 0;
let daysCount = range.length;
let profitableDays = 0;
let unprofitableDays = 0;
let profitableDaysRatio = 0;
let unprofitableDaysRatio = 0;
let lastInvestDay = range[0];
let dailyInvestment = 0;
let bitcoinAdded = 0;
let satsAdded = 0;
let lastSatsAdded = 0;
if (topUpFrequency.isTriggerDay(date)) {
dollars += topUpAmount;
}
range.forEach((date, index) => {
const year = date.getUTCFullYear();
const time = utils.date.toString(date);
const close = closes.ranges
.at(utils.chunkIdToIndex("date", year))
?.json()?.dataset.map[utils.date.toString(date)];
if (topUpFrequency.isTriggerDay(date)) {
dollars += topUpAmount;
}
if (!close) return;
const close = closes[utils.date.toDateIndex(date)];
dailyInvestment = 0;
/** @param {number} value */
function invest(value) {
value = Math.min(dollars, value);
dailyInvestment += value;
dollars -= value;
buyCount += 1;
lastInvestDay = date;
}
if (!index) {
invest(initialSwap);
}
if (swapFrequency.isTriggerDay(date) && dollars > 0) {
invest(recurrentSwap);
}
if (!close) return;
investedAmount += dailyInvestment;
dailyInvestment = 0;
/** @param {number} value */
function invest(value) {
value = Math.min(dollars, value);
dailyInvestment += value;
dollars -= value;
buyCount += 1;
lastInvestDay = date;
}
if (!index) {
invest(initialSwap);
}
if (swapFrequency.isTriggerDay(date) && dollars > 0) {
invest(recurrentSwap);
}
let dailyInvestmentPostFees =
dailyInvestment * (1 - (fees || 0) / 100);
investedAmount += dailyInvestment;
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
let dailyInvestmentPostFees =
dailyInvestment * (1 - (fees || 0) / 100);
bitcoinAdded = dailyInvestmentPostFees / close;
bitcoin += bitcoinAdded;
satsAdded = Math.floor(bitcoinAdded * 100_000_000);
if (satsAdded > 0) {
lastSatsAdded = satsAdded;
}
sats += satsAdded;
totalFeesPaid += dailyInvestment - dailyInvestmentPostFees;
postFeesInvestedAmount += dailyInvestmentPostFees;
bitcoinAdded = dailyInvestmentPostFees / close;
bitcoin += bitcoinAdded;
satsAdded = Math.floor(bitcoinAdded * 100_000_000);
if (satsAdded > 0) {
lastSatsAdded = satsAdded;
}
sats += satsAdded;
bitcoinValue = close * bitcoin;
postFeesInvestedAmount += dailyInvestmentPostFees;
totalValue = dollars + bitcoinValue;
bitcoinValue = close * bitcoin;
averagePricePaid = postFeesInvestedAmount / bitcoin;
totalValue = dollars + bitcoinValue;
roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100;
averagePricePaid = postFeesInvestedAmount / bitcoin;
const daysCount = index + 1;
profitableDaysRatio = profitableDays / daysCount;
unprofitableDaysRatio = unprofitableDays / daysCount;
roi = (bitcoinValue / postFeesInvestedAmount - 1) * 100;
if (roi >= 0) {
profitableDays += 1;
} else {
unprofitableDays += 1;
}
const daysCount = index + 1;
profitableDaysRatio = profitableDays / daysCount;
unprofitableDaysRatio = unprofitableDays / daysCount;
bitcoinPriceData().push({
time,
value: close,
if (roi >= 0) {
profitableDays += 1;
} else {
unprofitableDays += 1;
}
bitcoinPriceData().push({
time,
value: close,
});
bitcoinData().push({
time,
value: bitcoin,
});
totalInvestedAmountData().push({
time,
value: investedAmount,
});
bitcoinValueData().push({
time,
value: bitcoinValue,
});
resultData().push({
time,
value: roi,
});
dollarsLeftData().push({
time,
value: dollars,
});
totalValueData().push({
time,
value: totalValue,
});
investmentData().push({
time,
value: dailyInvestment,
});
bitcoinAddedData().push({
time,
value: bitcoinAdded,
});
averagePricePaidData().push({
time,
value: averagePricePaid,
});
buyCountData().push({
time,
value: buyCount,
});
totalFeesPaidData().push({
time,
value: totalFeesPaid,
});
daysCountData().push({
time,
value: daysCount,
});
profitableDaysRatioData().push({
time,
value: profitableDaysRatio * 100,
});
unprofitableDaysRatioData().push({
time,
value: unprofitableDaysRatio * 100,
});
});
bitcoinData().push({
time,
value: bitcoin,
});
totalInvestedAmountData().push({
time,
value: investedAmount,
});
bitcoinValueData().push({
time,
value: bitcoinValue,
});
resultData().push({
time,
value: roi,
});
dollarsLeftData().push({
time,
value: dollars,
});
totalValueData().push({
time,
value: totalValue,
});
investmentData().push({
time,
value: dailyInvestment,
});
bitcoinAddedData().push({
time,
value: bitcoinAdded,
});
averagePricePaidData().push({
time,
value: averagePricePaid,
});
buyCountData().push({
time,
value: buyCount,
});
totalFeesPaidData().push({
time,
value: totalFeesPaid,
});
daysCountData().push({
time,
value: daysCount,
});
profitableDaysRatioData().push({
time,
value: profitableDaysRatio * 100,
});
unprofitableDaysRatioData().push({
time,
value: unprofitableDaysRatio * 100,
});
});
const f = utils.locale.numberToUSFormat;
/** @param {number} v */
const fd = (v) => utils.formatters.dollars.format(v);
/** @param {number} v */
const fp = (v) => utils.formatters.percentage.format(v);
/**
* @param {ColorName} c
* @param {string} t
*/
const c = (c, t) => createColoredSpan({ color: c, text: t });
const serInvestedAmount = c("dollars", fd(investedAmount));
const serDaysCount = c("sky", f(daysCount));
const serSats = c("orange", f(sats));
const serBitcoin = c("orange", `~${f(bitcoin)}`);
const serBitcoinValue = c("amber", fd(bitcoinValue));
const serAveragePricePaid = c("lightDollars", fd(averagePricePaid));
const serRoi = c("yellow", fp(roi / 100));
const serDollars = c("offDollars", fd(dollars));
const serTotalFeesPaid = c("rose", fd(totalFeesPaid));
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
const dayDiff = Math.floor(
utils.date.differenceBetween(new Date(), lastInvestDay),
);
const serDailyInvestment = c("offDollars", fd(dailyInvestment));
const setLastSatsAdded = c("bitcoin", f(lastSatsAdded));
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
const serUnprofitableDaysRatio = c("red", fp(unprofitableDaysRatio));
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
signals.createEffect(lastValues, (lastValues) => {
const lowestAnnual4YReturn = 0.2368;
// const lowestAnnual4YReturn = lastValues?.["price-4y-compound-return"] || 0
const serLowestAnnual4YReturn = c(
"cyan",
`${fp(lowestAnnual4YReturn)}`,
);
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
/**
* @param {number} power
*/
function bitcoinValueReturn(power) {
return (
bitcoinValue * Math.pow(lowestAnnual4YReturnPercentage, power)
);
}
const bitcoinValueAfter4y = bitcoinValueReturn(4);
const serBitcoinValueAfter4y = c("purple", fd(bitcoinValueAfter4y));
const bitcoinValueAfter10y = bitcoinValueReturn(10);
const serBitcoinValueAfter10y = c(
"fuchsia",
fd(bitcoinValueAfter10y),
);
const bitcoinValueAfter21y = bitcoinValueReturn(21);
const serBitcoinValueAfter21y = c("pink", fd(bitcoinValueAfter21y));
const f = utils.locale.numberToUSFormat;
/** @param {number} v */
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
});
const fd = (v) => utils.formatters.dollars.format(v);
/** @param {number} v */
const fp = (v) => utils.formatters.percentage.format(v);
/**
* @param {ColorName} c
* @param {string} t
*/
const c = (c, t) => createColoredSpan({ color: c, text: t });
totalInvestedAmountData.set((a) => a);
bitcoinValueData.set((a) => a);
bitcoinData.set((a) => a);
resultData.set((a) => a);
dollarsLeftData.set((a) => a);
totalValueData.set((a) => a);
investmentData.set((a) => a);
bitcoinAddedData.set((a) => a);
averagePricePaidData.set((a) => a);
bitcoinPriceData.set((a) => a);
buyCountData.set((a) => a);
totalFeesPaidData.set((a) => a);
daysCountData.set((a) => a);
profitableDaysRatioData.set((a) => a);
unprofitableDaysRatioData.set((a) => a);
},
);
const serInvestedAmount = c("dollars", fd(investedAmount));
const serDaysCount = c("sky", f(daysCount));
const serSats = c("orange", f(sats));
const serBitcoin = c("orange", `~${f(bitcoin)}`);
const serBitcoinValue = c("amber", fd(bitcoinValue));
const serAveragePricePaid = c("lightDollars", fd(averagePricePaid));
const serRoi = c("yellow", fp(roi / 100));
const serDollars = c("offDollars", fd(dollars));
const serTotalFeesPaid = c("rose", fd(totalFeesPaid));
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
const dayDiff = Math.floor(
utils.date.differenceBetween(new Date(), lastInvestDay),
);
const serDailyInvestment = c("offDollars", fd(dailyInvestment));
const setLastSatsAdded = c("bitcoin", f(lastSatsAdded));
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
const serUnprofitableDaysRatio = c(
"red",
fp(unprofitableDaysRatio),
);
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
signals.createEffect(
() => 0.073,
(lowestAnnual4YReturn) => {
const serLowestAnnual4YReturn = c(
"cyan",
`${fp(lowestAnnual4YReturn)}`,
);
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
/**
* @param {number} power
*/
function bitcoinValueReturn(power) {
return (
bitcoinValue *
Math.pow(lowestAnnual4YReturnPercentage, power)
);
}
const bitcoinValueAfter4y = bitcoinValueReturn(4);
const serBitcoinValueAfter4y = c(
"purple",
fd(bitcoinValueAfter4y),
);
const bitcoinValueAfter10y = bitcoinValueReturn(10);
const serBitcoinValueAfter10y = c(
"fuchsia",
fd(bitcoinValueAfter10y),
);
const bitcoinValueAfter21y = bitcoinValueReturn(21);
const serBitcoinValueAfter21y = c(
"pink",
fd(bitcoinValueAfter21y),
);
/** @param {number} v */
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
},
);
totalInvestedAmountData.set((a) => a);
bitcoinValueData.set((a) => a);
bitcoinData.set((a) => a);
resultData.set((a) => a);
dollarsLeftData.set((a) => a);
totalValueData.set((a) => a);
investmentData.set((a) => a);
bitcoinAddedData.set((a) => a);
averagePricePaidData.set((a) => a);
bitcoinPriceData.set((a) => a);
buyCountData.set((a) => a);
totalFeesPaidData.set((a) => a);
daysCountData.set((a) => a);
profitableDaysRatioData.set((a) => a);
unprofitableDaysRatioData.set((a) => a);
},
);
});
});
});
}
-168
View File
@@ -1,168 +0,0 @@
import {
Accessor,
Setter,
} from "../../packages/solid-signals/2024-11-02/types/signals";
import {
DeepPartial,
BaselineStyleOptions,
CandlestickStyleOptions,
LineStyleOptions,
SeriesOptionsCommon,
IRange,
Time,
SingleValueData as _SingleValueData,
CandlestickData as _CandlestickData,
SeriesType,
ISeriesApi,
BaselineData,
} from "../../packages/lightweight-charts/v5.0.5/types";
import { AnyPossibleCohortId, Groups } from "../options";
type Color = () => string;
type ColorName = keyof Colors;
// TODO: Compute from VecId when displaying the Unit
// And write a checker when localhost, similar to the dup one
type Unit =
| ""
| "Bitcoin"
| "Coinblocks"
| "Count"
| "Date"
| "Dollars / (PetaHash / Second)"
| "ExaHash / Second"
| "Height"
| "Gigabytes"
| "Megabytes"
| "Percentage"
| "Ratio"
| "Satoshis"
| "Seconds"
| "Transactions"
| "US Dollars"
| "Virtual Bytes"
| "Weight";
interface PartialOption {
name: string;
}
interface PartialChartOption extends PartialOption {
title?: string;
unit?: string;
top?: SplitSeriesBlueprint[];
bottom?: SplitSeriesBlueprint[];
}
interface PartialSimulationOption extends PartialOption {
kind: "simulation";
title: string;
name: string;
}
interface PartialUrlOption extends PartialOption {
qrcode?: true;
url: () => string;
}
interface PartialOptionsGroup {
name: string;
tree: PartialOptionsTree;
}
type AnyPartialOption =
| PartialChartOption
| PartialSimulationOption
| PartialUrlOption;
type PartialOptionsTree = (AnyPartialOption | PartialOptionsGroup)[];
interface ProcessedOptionAddons {
id: string;
path: string[];
title: string;
}
type SimulationOption = PartialSimulationOption & ProcessedOptionAddons;
interface UrlOption extends PartialUrlOption, ProcessedOptionAddons {
kind: "url";
}
interface ChartOption
extends Omit<PartialChartOption, "title">,
ProcessedOptionAddons {
kind: "chart";
unit: string;
}
type Option = UrlOption | ChartOption | SimulationOption;
type OptionsTree = (Option | OptionsGroup)[];
interface OptionsGroup extends PartialOptionsGroup {
id: string;
tree: OptionsTree;
}
type OHLCTuple = [number, number, number, number];
interface Valued {
value: number;
}
interface Indexed {
index: number;
}
type ChartData<T> = T & Valued & Indexed;
type SingleValueData = ChartData<_SingleValueData>;
type CandlestickData = ChartData<_CandlestickData>;
type FetchedSource = string;
interface FetchedChunk {
id: number;
previous: string | null;
next: string | null;
}
interface Weighted {
weight: number;
}
type DatasetCandlestickData = ChartData<CandlestickData>;
// type NotFunction<T> = T extends Function ? never : T;
type DefaultCohortOption = CohortOption<AnyPossibleCohortId>;
interface CohortOption<Id extends AnyPossibleCohortId> {
name: string;
title: string;
datasetId: Id;
color: Color;
filenameAddon?: string;
}
type DefaultCohortOptions = CohortOptions<AnyPossibleCohortId>;
interface CohortOptions<Id extends AnyPossibleCohortId> {
name: string;
title: string;
list: CohortOption<Id>[];
}
interface RatioOption {
color: Color;
// valueDatasetPath: AnyDatasetPath;
// ratioDatasetPath: AnyDatasetPath;
title: string;
}
interface RatioOptions {
title: string;
list: RatioOption[];
}
// TODO: Remove
// Fetch last of each individually when in viewport
// type LastValues = Record<LastPath, number> | null;
@@ -1,3 +1,7 @@
//
// File auto-generated, any modification will be overwritten
//
/** @typedef {0} Height */
/** @typedef {1} Dateindex */
/** @typedef {2} Weekindex */
@@ -72,7 +76,6 @@ export function createVecIdToIndexes() {
"first-height": [Dateindex, Difficultyepoch, Halvingepoch],
"first-monthindex": [Quarterindex, Yearindex],
"first-multisigindex": [Height],
"first-open": [Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"first-opreturnindex": [Height],
"first-p2pk33index": [Height],
"first-p2pk65index": [Height],
@@ -91,9 +94,8 @@ export function createVecIdToIndexes() {
"fixed-timestamp": [Height],
halvingepoch: [Height, Halvingepoch],
height: [Addressindex, Height, Txindex],
high: [Dateindex, Height],
high: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"high-in-cents": [Dateindex, Height],
"high-max": [Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"inputs-count": [Txindex],
"is-coinbase": [Txindex],
"is-explicitly-rbf": [Txindex],
@@ -105,13 +107,12 @@ export function createVecIdToIndexes() {
"last-txoutindex": [Txindex],
"last-yearindex": [Decadeindex],
locktime: [Txindex],
low: [Dateindex, Height],
low: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"low-in-cents": [Dateindex, Height],
"low-min": [Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
monthindex: [Dateindex, Monthindex],
ohlc: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"ohlc-in-cents": [Dateindex, Height],
open: [Dateindex, Height],
open: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
"open-in-cents": [Dateindex, Height],
"outputs-count": [Txindex],
p2pk33addressbytes: [P2PK33index],
@@ -123,7 +124,7 @@ export function createVecIdToIndexes() {
p2wshaddressbytes: [P2WSHindex],
quarterindex: [Monthindex, Quarterindex],
"real-date": [Height],
"sats-per-dollar": [Dateindex, Height],
"sats-per-dollar": [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch],
size: [Height],
timestamp: [Dateindex, Height, Weekindex, Monthindex, Quarterindex, Yearindex, Decadeindex, Difficultyepoch, Halvingepoch],
"total-block-count": [Dateindex],
-4
View File
@@ -26,9 +26,5 @@
.chart {
flex: 1;
.lightweight-chart {
margin-left: var(--negative-main-padding);
}
}
}
+11 -9
View File
@@ -5,13 +5,8 @@
> div {
display: flex;
flex-direction: column;
gap: 1.5rem;
gap: 2rem;
padding: var(--main-padding);
> div {
display: flex;
flex-direction: column;
}
}
@media (max-width: 767px) {
@@ -60,16 +55,23 @@
display: block;
}
small {
font-size: var(--font-size-base);
font-size: var(--font-size-sm);
line-height: var(--line-height-sm);
display: block;
}
}
.chart {
flex-shrink: 0;
flex: none;
height: 400px;
.lightweight-chart {
margin-left: calc(var(--negative-main-padding) / 2);
margin-left: calc(var(--negative-main-padding) * 0.75);
fieldset {
margin-left: -0.5rem;
}
}
}
}
}