gloomberb 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/package.json +2 -1
- package/patches/electrobun@1.18.1.patch +108 -1
- package/src/account/upgrade-dialog.tsx +4 -23
- package/src/api-client/coalesce.ts +118 -0
- package/src/api-client/credit-boards.ts +13 -0
- package/src/api-client/crypto-etf-flows.ts +6 -0
- package/src/api-client/crypto-markets.ts +40 -0
- package/src/api-client/data.ts +201 -14
- package/src/api-client/equity-screener.ts +10 -0
- package/src/api-client/errors.ts +58 -0
- package/src/api-client/gpu.ts +29 -0
- package/src/api-client/index.ts +5 -2
- package/src/api-client/plan-access.ts +10 -1
- package/src/api-client/plan-rules.ts +35 -0
- package/src/api-client/research-activity.ts +1 -32
- package/src/api-client/types.ts +21 -1
- package/src/app/attention-notice.tsx +94 -0
- package/src/app/runtime/startup.ts +3 -1
- package/src/app.tsx +23 -1
- package/src/architecture/core-builtin-allowlist.json +2 -1
- package/src/capabilities/chart-series.ts +2 -1
- package/src/capabilities/factories.ts +1 -0
- package/src/cli/ascii-output.ts +42 -0
- package/src/cli/base-converter.ts +10 -2
- package/src/cli/command-options.ts +16 -1
- package/src/cli/commands/crypto-hints.ts +23 -4
- package/src/cli/commands/market.ts +101 -27
- package/src/cli/commands/news-search.ts +24 -5
- package/src/cli/commands/overview.ts +14 -10
- package/src/cli/commands/search.ts +84 -25
- package/src/cli/commands/system.ts +22 -30
- package/src/cli/commands/ticker.ts +49 -9
- package/src/cli/commands/version.ts +33 -0
- package/src/cli/context.ts +6 -9
- package/src/cli/data-dir.ts +7 -0
- package/src/cli/desktop-pane-shot-api-policy.ts +85 -0
- package/src/cli/desktop-pane-shot-browser.ts +320 -0
- package/src/cli/desktop-pane-shot-routes.ts +26 -1
- package/src/cli/desktop-pane-shot.ts +196 -256
- package/src/cli/early-command.ts +42 -0
- package/src/cli/earnings-calendar.ts +9 -0
- package/src/cli/entry.ts +19 -13
- package/src/cli/errors.ts +12 -2
- package/src/cli/glossary.ts +2 -1
- package/src/cli/help.ts +1 -1
- package/src/cli/history-export.ts +22 -2
- package/src/cli/history-rows.ts +189 -11
- package/src/cli/index.ts +18 -1
- package/src/cli/known-gaps.ts +51 -5
- package/src/cli/listing-arg.ts +63 -10
- package/src/cli/options.ts +1 -1
- package/src/cli/output-file.ts +60 -13
- package/src/cli/pane-functions/catalog.ts +14 -3
- package/src/cli/pane-functions/data.ts +1 -1
- package/src/cli/pane-functions/dom.ts +45 -0
- package/src/cli/pane-functions/freshness.ts +46 -9
- package/src/cli/pane-functions/function-help.ts +17 -9
- package/src/cli/pane-functions/headless.ts +22 -14
- package/src/cli/pane-functions/index.ts +6 -2
- package/src/cli/pane-functions/options.ts +6 -2
- package/src/cli/pane-functions/resolver.ts +27 -5
- package/src/cli/pane-functions/screenshot-assessment.ts +33 -0
- package/src/cli/pane-functions/screenshot-bridge.ts +94 -13
- package/src/cli/pane-functions/screenshot-http.ts +70 -0
- package/src/cli/pane-functions/screenshot-render.ts +19 -5
- package/src/cli/quote-answer.ts +49 -6
- package/src/cli/registry.ts +41 -13
- package/src/cli/result.ts +12 -6
- package/src/cli/ticker-resolution.ts +2 -1
- package/src/cli/types.ts +2 -0
- package/src/components/chart/composite/format.ts +3 -0
- package/src/components/chart-table/header.tsx +1 -1
- package/src/components/chart-table/layout.ts +28 -11
- package/src/components/command-bar/action-runtime.ts +4 -0
- package/src/components/command-bar/commands/direct/index.ts +18 -33
- package/src/components/command-bar/commands/direct/runtime.ts +5 -0
- package/src/components/command-bar/commands/registry.ts +10 -1
- package/src/components/command-bar/routes/root/command-items.ts +9 -4
- package/src/components/command-bar/routes/root/runtime.ts +51 -6
- package/src/components/command-bar/selection-runtime.ts +12 -29
- package/src/components/command-bar/surface/index.tsx +9 -3
- package/src/components/company-logo.tsx +2 -1
- package/src/components/layout/detached-pane-shell.tsx +2 -2
- package/src/components/layout/header.tsx +10 -5
- package/src/components/layout/market-summary.ts +25 -7
- package/src/components/layout/pane/footer/index.tsx +8 -2
- package/src/components/layout/pane/footer/model.ts +73 -6
- package/src/components/layout/pane/header.tsx +2 -2
- package/src/components/layout/pane/status-footer.ts +2 -1
- package/src/components/market-board/index.tsx +3 -2
- package/src/components/onboarding/desks-step.tsx +12 -4
- package/src/components/onboarding/first-run-workspace.ts +81 -4
- package/src/components/onboarding/onboarding-frame.tsx +4 -2
- package/src/components/onboarding/onboarding-keyboard.ts +8 -5
- package/src/components/onboarding/onboarding-wizard.tsx +60 -26
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +7 -2
- package/src/components/onboarding/run-setup-again.ts +24 -0
- package/src/components/onboarding/use-onboarding-progress.ts +33 -15
- package/src/components/onboarding/use-onboarding-upgrade.ts +2 -6
- package/src/components/onboarding/wizard-model.ts +57 -3
- package/src/components/onboarding/wizard-positions.ts +1 -1
- package/src/components/ui/delayed-mark.tsx +56 -0
- package/src/components/ui/display.tsx +16 -5
- package/src/components/ui/external-link.tsx +3 -6
- package/src/components/ui/icon.tsx +16 -3
- package/src/components/ui/logo-mark.tsx +8 -5
- package/src/components/ui/stat-grid.tsx +263 -19
- package/src/core/app-services.ts +11 -2
- package/src/data/app-persistence.ts +9 -7
- package/src/data/config/store/node.ts +11 -5
- package/src/data/config/store/normalize.ts +16 -3
- package/src/data/plugin-cache.ts +15 -0
- package/src/data/plugin-state-store.ts +81 -24
- package/src/data/profile-permissions.ts +47 -0
- package/src/data/resource-store.ts +165 -90
- package/src/data/session-store.ts +31 -8
- package/src/data/sqlite/access.ts +324 -0
- package/src/data/sqlite/checkpointer.ts +285 -0
- package/src/data/sqlite/database.ts +80 -4
- package/src/data/sqlite/retry.ts +50 -9
- package/src/data/ticker-store.ts +57 -23
- package/src/docs/snapshot.json +191 -78
- package/src/i18n/es.ts +18 -2
- package/src/i18n/ja.ts +18 -2
- package/src/i18n/ko.ts +18 -2
- package/src/i18n/zh-cn.ts +18 -2
- package/src/i18n/zh-tw.ts +18 -2
- package/src/layout/desk-keys.ts +4 -1
- package/src/layout/desks.ts +38 -5
- package/src/layout-marketplace/gallery-desktop.tsx +45 -16
- package/src/market-data/batched-reads.ts +16 -0
- package/src/market-data/coordinator/auxiliary.ts +4 -2
- package/src/market-data/coordinator/index.ts +11 -2
- package/src/market-data/futures-board.ts +1 -1
- package/src/market-data/futures-contract.ts +3 -1
- package/src/market-data/market/index-level.ts +6 -4
- package/src/market-data/published-apac-sessions.ts +6 -0
- package/src/market-data/realized-volatility.ts +4 -2
- package/src/news/aggregator.ts +7 -4
- package/src/news/news-model.ts +11 -0
- package/src/news/story-id.ts +22 -0
- package/src/news/ticker-symbols.ts +17 -1
- package/src/notifications/app-notifier.ts +35 -14
- package/src/plugins/auto-update-node.ts +10 -6
- package/src/plugins/builtin/account-management/form-components.tsx +1 -1
- package/src/plugins/builtin/account-management/index.tsx +2 -3
- package/src/plugins/builtin/account-management/model.ts +1 -1
- package/src/plugins/builtin/alerts/cli-forms.ts +15 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +7 -5
- package/src/plugins/builtin/alerts/events.ts +10 -2
- package/src/plugins/builtin/alerts/index.tsx +5 -5
- package/src/plugins/builtin/alerts/research-builder.ts +5 -1
- package/src/plugins/builtin/alerts/research-rules.ts +18 -0
- package/src/plugins/builtin/alerts/targets.ts +4 -1
- package/src/plugins/builtin/attention/headless.ts +1 -1
- package/src/plugins/builtin/attention/index.tsx +2 -4
- package/src/plugins/builtin/attention/listing.ts +1 -1
- package/src/plugins/builtin/attention/series.ts +1 -1
- package/src/plugins/builtin/awards/index.tsx +1 -2
- package/src/plugins/builtin/backtest/client.ts +1 -0
- package/src/plugins/builtin/backtest/index.tsx +1 -2
- package/src/plugins/builtin/backtest/rules.ts +3 -1
- package/src/plugins/builtin/backtest/window.ts +8 -7
- package/src/plugins/builtin/broker-manager/detail.tsx +16 -3
- package/src/plugins/builtin/broker-manager/index.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +5 -0
- package/src/plugins/builtin/buildout/company/flow.tsx +367 -0
- package/src/plugins/builtin/buildout/company/id.ts +2 -0
- package/src/plugins/builtin/buildout/company/model.ts +247 -0
- package/src/plugins/builtin/buildout/company/pane.tsx +317 -0
- package/src/plugins/builtin/buildout/data-runtime.ts +31 -10
- package/src/plugins/builtin/buildout/facilities.ts +309 -0
- package/src/plugins/builtin/buildout/format.ts +9 -0
- package/src/plugins/builtin/buildout/index.tsx +51 -5
- package/src/plugins/builtin/buildout/model/index.ts +62 -10
- package/src/plugins/builtin/buildout/model/normalizers.ts +5 -0
- package/src/plugins/builtin/buildout/model/types.ts +11 -2
- package/src/plugins/builtin/buildout/pane/index.tsx +91 -20
- package/src/plugins/builtin/buildout/pane/status.tsx +1 -1
- package/src/plugins/builtin/buildout/pane/themes.tsx +110 -0
- package/src/plugins/builtin/buildout/table-model.ts +26 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +16 -0
- package/src/plugins/builtin/cds/headless.ts +10 -2
- package/src/plugins/builtin/cds/model.ts +7 -3
- package/src/plugins/builtin/central-bank-rates/index.tsx +1 -2
- package/src/plugins/builtin/central-bank-rates/pane.tsx +2 -1
- package/src/plugins/builtin/central-bank-rates/series.ts +2 -3
- package/src/plugins/builtin/changelog/index.tsx +3 -2
- package/src/plugins/builtin/chart-composer/cli-options.ts +16 -0
- package/src/plugins/builtin/chart-composer/headless-schema.ts +10 -0
- package/src/plugins/builtin/chart-composer/headless.ts +88 -21
- package/src/plugins/builtin/chart-composer/index.tsx +2 -3
- package/src/plugins/builtin/chart-composer/pane.tsx +30 -2
- package/src/plugins/builtin/chart-composer/series-expression.ts +3 -2
- package/src/plugins/builtin/chart-composer/session-range.ts +5 -2
- package/src/plugins/builtin/chart-composer/studies.ts +1 -1
- package/src/plugins/builtin/chat/controller/index.ts +1 -1
- package/src/plugins/builtin/chat/controller/messages.ts +15 -4
- package/src/plugins/builtin/chat/ghost-user.ts +8 -0
- package/src/plugins/builtin/chat/index.tsx +5 -5
- package/src/plugins/builtin/clinical-trials/index.tsx +1 -2
- package/src/plugins/builtin/cloud/company-picker.tsx +1 -1
- package/src/plugins/builtin/cloud/index.tsx +1 -2
- package/src/plugins/builtin/cloud/plugin.tsx +6 -4
- package/src/plugins/builtin/cloud/team/module.tsx +1 -1
- package/src/plugins/builtin/cloud/thesis/module.tsx +1 -1
- package/src/plugins/builtin/cloud/trial-offer-status-widget.tsx +24 -1
- package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +39 -15
- package/src/plugins/builtin/comment-letters/index.tsx +1 -2
- package/src/plugins/builtin/commodity-balances/client.ts +4 -1
- package/src/plugins/builtin/commodity-balances/index.tsx +3 -3
- package/src/plugins/builtin/commodity-balances/pane.tsx +2 -2
- package/src/plugins/builtin/congress-trades/aggregates.ts +13 -2
- package/src/plugins/builtin/congress-trades/detail.tsx +20 -4
- package/src/plugins/builtin/congress-trades/headless.ts +2 -0
- package/src/plugins/builtin/congress-trades/model.ts +92 -64
- package/src/plugins/builtin/congress-trades/table.tsx +14 -2
- package/src/plugins/builtin/connections/index.ts +2 -3
- package/src/plugins/builtin/cot/client.ts +28 -2
- package/src/plugins/builtin/cot/headless.ts +4 -1
- package/src/plugins/builtin/cot/index.tsx +5 -4
- package/src/plugins/builtin/cot/model.ts +58 -7
- package/src/plugins/builtin/cot/pane.tsx +6 -2
- package/src/plugins/builtin/cpi/client.ts +6 -5
- package/src/plugins/builtin/cpi/index.tsx +1 -2
- package/src/plugins/builtin/credit-boards/cdx-pane.tsx +2 -1
- package/src/plugins/builtin/credit-boards/client.ts +7 -2
- package/src/plugins/builtin/credit-boards/headless.ts +25 -12
- package/src/plugins/builtin/credit-boards/model.ts +29 -0
- package/src/plugins/builtin/credit-boards/series.ts +3 -4
- package/src/plugins/builtin/credit-boards/sovr-pane.tsx +9 -4
- package/src/plugins/builtin/credit-conditions/client.ts +22 -14
- package/src/plugins/builtin/crypto-board/bitcoin-network.ts +143 -0
- package/src/plugins/builtin/crypto-board/client.ts +5 -5
- package/src/plugins/builtin/crypto-board/headless.ts +4 -3
- package/src/plugins/builtin/crypto-board/index.tsx +4 -2
- package/src/plugins/builtin/crypto-board/series.ts +6 -4
- package/src/plugins/builtin/crypto-etf-flows/client.ts +9 -2
- package/src/plugins/builtin/crypto-etf-flows/headless.ts +61 -6
- package/src/plugins/builtin/crypto-etf-flows/index.tsx +1 -2
- package/src/plugins/builtin/crypto-etf-flows/model.ts +14 -1
- package/src/plugins/builtin/crypto-etf-flows/pane.tsx +8 -5
- package/src/plugins/builtin/debt-maturities/headless.ts +4 -3
- package/src/plugins/builtin/debt-maturities/model.ts +17 -1
- package/src/plugins/builtin/debt-maturities/pane.tsx +18 -4
- package/src/plugins/builtin/debt-maturities/series.ts +3 -3
- package/src/plugins/builtin/debug/index.tsx +1 -1
- package/src/plugins/builtin/dividend-yield/client.ts +32 -2
- package/src/plugins/builtin/dividend-yield/headless.ts +58 -33
- package/src/plugins/builtin/dividend-yield/index.tsx +37 -2
- package/src/plugins/builtin/dividend-yield/pane.tsx +32 -13
- package/src/plugins/builtin/dividend-yield/ranking-reads.ts +73 -0
- package/src/plugins/builtin/dividend-yield/ranking-view.tsx +163 -0
- package/src/plugins/builtin/dividend-yield/ranking.ts +1 -1
- package/src/plugins/builtin/dividend-yield/view.ts +21 -0
- package/src/plugins/builtin/docs/index.tsx +2 -3
- package/src/plugins/builtin/doe/client.ts +6 -5
- package/src/plugins/builtin/doe/headless.ts +16 -3
- package/src/plugins/builtin/doe/index.tsx +5 -4
- package/src/plugins/builtin/doe/prices.ts +2 -3
- package/src/plugins/builtin/doe/series.ts +4 -4
- package/src/plugins/builtin/earnings/board.tsx +2 -1
- package/src/plugins/builtin/earnings/history.tsx +2 -1
- package/src/plugins/builtin/earnings-calls/calendar.tsx +10 -2
- package/src/plugins/builtin/earnings-calls/data.ts +19 -11
- package/src/plugins/builtin/earnings-calls/pane.tsx +27 -14
- package/src/plugins/builtin/earnings-ripple/client.ts +32 -7
- package/src/plugins/builtin/earnings-ripple/index.tsx +1 -2
- package/src/plugins/builtin/econ/calendar-model.ts +29 -6
- package/src/plugins/builtin/econ/detail-view.tsx +16 -2
- package/src/plugins/builtin/econ-statistics/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/client.ts +1 -1
- package/src/plugins/builtin/equity-screener/headless.ts +1 -0
- package/src/plugins/builtin/equity-screener/ideas.ts +30 -1
- package/src/plugins/builtin/equity-screener/index.tsx +1 -2
- package/src/plugins/builtin/equity-screener/model.ts +28 -4
- package/src/plugins/builtin/equity-screener/pane.tsx +5 -0
- package/src/plugins/builtin/equity-screener/screen-check.ts +23 -1
- package/src/plugins/builtin/estimate-revisions/index.tsx +1 -2
- package/src/plugins/builtin/executives/data.ts +6 -1
- package/src/plugins/builtin/executives/pane.tsx +9 -21
- package/src/plugins/builtin/exposure/holdings.ts +35 -13
- package/src/plugins/builtin/exposure/index.tsx +1 -2
- package/src/plugins/builtin/fear-greed/cache.ts +4 -0
- package/src/plugins/builtin/fear-greed/cli.ts +44 -0
- package/src/plugins/builtin/fear-greed/index.tsx +5 -53
- package/src/plugins/builtin/fear-greed/pane.tsx +2 -1
- package/src/plugins/builtin/fear-greed/series.ts +6 -4
- package/src/plugins/builtin/fixed-income/index.tsx +1 -2
- package/src/plugins/builtin/futures/board-reads.ts +25 -0
- package/src/plugins/builtin/futures/coalesced-bump.ts +44 -0
- package/src/plugins/builtin/futures/index.tsx +15 -0
- package/src/plugins/builtin/futures/model.ts +14 -0
- package/src/plugins/builtin/futures/use-board-terms.ts +22 -15
- package/src/plugins/builtin/futures/use-front-returns.ts +20 -14
- package/src/plugins/builtin/futures-curve/headless.ts +6 -1
- package/src/plugins/builtin/futures-curve/index.tsx +1 -2
- package/src/plugins/builtin/fx-matrix/index.tsx +2 -2
- package/src/plugins/builtin/gpu/board.tsx +51 -12
- package/src/plugins/builtin/gpu/client.ts +19 -3
- package/src/plugins/builtin/gpu/equities.tsx +5 -2
- package/src/plugins/builtin/gpu/headless.ts +14 -2
- package/src/plugins/builtin/gpu/index.tsx +1 -2
- package/src/plugins/builtin/gpu/model.ts +20 -2
- package/src/plugins/builtin/gpu/pane.tsx +1 -1
- package/src/plugins/builtin/gpu/series.ts +2 -3
- package/src/plugins/builtin/help/index.tsx +1 -1
- package/src/plugins/builtin/holders/beneficial-view.tsx +1 -2
- package/src/plugins/builtin/holders/index-holders.ts +24 -12
- package/src/plugins/builtin/holders/index.tsx +2 -3
- package/src/plugins/builtin/ipo-calendar/index.tsx +1 -2
- package/src/plugins/builtin/iv-history/index.tsx +1 -2
- package/src/plugins/builtin/jodi/client.ts +4 -1
- package/src/plugins/builtin/jodi/index.tsx +1 -2
- package/src/plugins/builtin/kelly-sizer/index.tsx +1 -2
- package/src/plugins/builtin/layout-manager/index.tsx +9 -8
- package/src/plugins/builtin/macro-day/pane.tsx +4 -2
- package/src/plugins/builtin/market-halts/index.tsx +1 -2
- package/src/plugins/builtin/market-heatmap/index.tsx +11 -2
- package/src/plugins/builtin/market-movers/client.ts +1 -0
- package/src/plugins/builtin/market-movers/index.tsx +16 -5
- package/src/plugins/builtin/market-movers/screener.ts +23 -6
- package/src/plugins/builtin/market-movers/session-body.tsx +6 -2
- package/src/plugins/builtin/market-valuation/index.tsx +1 -2
- package/src/plugins/builtin/members/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/index.tsx +1 -2
- package/src/plugins/builtin/money-markets/pane.tsx +2 -1
- package/src/plugins/builtin/money-markets/series.ts +3 -5
- package/src/plugins/builtin/news/headless.ts +7 -0
- package/src/plugins/builtin/news/wire/index.tsx +1 -1
- package/src/plugins/builtin/news/wire/news/pop-out.tsx +9 -3
- package/src/plugins/builtin/notes/index.tsx +3 -6
- package/src/plugins/builtin/openfda/index.tsx +1 -2
- package/src/plugins/builtin/options/view.tsx +6 -5
- package/src/plugins/builtin/options-calculator/index.tsx +1 -2
- package/src/plugins/builtin/options-positioning/index.tsx +1 -2
- package/src/plugins/builtin/options-scenario/index.tsx +2 -4
- package/src/plugins/builtin/perps/index.tsx +1 -2
- package/src/plugins/builtin/plugin-marketplace/index.tsx +2 -3
- package/src/plugins/builtin/plugin-marketplace/pane.tsx +2 -1
- package/src/plugins/builtin/plugin-module.ts +16 -2
- package/src/plugins/builtin/portfolio-list/cli/render.ts +53 -3
- package/src/plugins/builtin/portfolio-list/index.tsx +1 -2
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +11 -0
- package/src/plugins/builtin/portfolio-list/quick-add/index.tsx +2 -2
- package/src/plugins/builtin/power/gas-client.ts +2 -1
- package/src/plugins/builtin/power/index.tsx +1 -2
- package/src/plugins/builtin/power/prices-client.ts +3 -2
- package/src/plugins/builtin/rate-path/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/headless.ts +2 -0
- package/src/plugins/builtin/realized-vol/index.tsx +1 -2
- package/src/plugins/builtin/realized-vol/model.ts +16 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +1 -2
- package/src/plugins/builtin/research/analyst-headless.ts +33 -6
- package/src/plugins/builtin/research/analyst-model.ts +37 -2
- package/src/plugins/builtin/research/analyst-pane.tsx +25 -5
- package/src/plugins/builtin/research/index.tsx +5 -8
- package/src/plugins/builtin/research-search/index.tsx +1 -2
- package/src/plugins/builtin/revenue-breakdown/headless.ts +22 -8
- package/src/plugins/builtin/revenue-breakdown/model.ts +23 -0
- package/src/plugins/builtin/reverse-dcf/pane.tsx +4 -2
- package/src/plugins/builtin/risk-factors/data.ts +8 -4
- package/src/plugins/builtin/risk-factors/index.tsx +3 -1
- package/src/plugins/builtin/risk-factors/pane.tsx +5 -8
- package/src/plugins/builtin/roadmap/index.tsx +2 -3
- package/src/plugins/builtin/roadmap/pane.tsx +49 -2
- package/src/plugins/builtin/scanner/hilo-model.ts +18 -1
- package/src/plugins/builtin/scanner/hilo-pane.tsx +12 -2
- package/src/plugins/builtin/scanner/live-pane.tsx +7 -4
- package/src/plugins/builtin/scanner/lock-note.ts +26 -3
- package/src/plugins/builtin/sec/filing-content.ts +38 -6
- package/src/plugins/builtin/sec/headless.ts +5 -1
- package/src/plugins/builtin/sec/index.tsx +4 -2
- package/src/plugins/builtin/sec/model.ts +2 -0
- package/src/plugins/builtin/sectors/index.tsx +11 -5
- package/src/plugins/builtin/series-inventory.ts +2 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +73 -8
- package/src/plugins/builtin/shared/cloud-upgrade.ts +24 -1
- package/src/plugins/builtin/shared/forming-bars.ts +29 -0
- package/src/plugins/builtin/shared/fred-points.ts +6 -1
- package/src/plugins/builtin/shared/fx-rate-freshness.ts +17 -0
- package/src/plugins/builtin/shared/pro-lock-note.ts +23 -8
- package/src/plugins/builtin/shared/series-detail-chart.tsx +4 -2
- package/src/plugins/builtin/shared/series-warning.ts +13 -4
- package/src/plugins/builtin/shared/use-quote-board.ts +5 -0
- package/src/plugins/builtin/short-interest/publications-pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/pane.tsx +2 -1
- package/src/plugins/builtin/short-volume/series.ts +2 -3
- package/src/plugins/builtin/social-mentions/client.ts +15 -10
- package/src/plugins/builtin/social-mentions/index.tsx +1 -2
- package/src/plugins/builtin/social-mentions/pane.tsx +2 -1
- package/src/plugins/builtin/social-mentions/series.ts +2 -3
- package/src/plugins/builtin/supply-chain/client.ts +22 -1
- package/src/plugins/builtin/supply-chain/graph-client.ts +22 -1
- package/src/plugins/builtin/supply-chain/index.tsx +1 -2
- package/src/plugins/builtin/thirteenf/api.ts +127 -29
- package/src/plugins/builtin/thirteenf/data.ts +165 -35
- package/src/plugins/builtin/thirteenf/index.tsx +2 -4
- package/src/plugins/builtin/thirteenf/model.ts +2 -0
- package/src/plugins/builtin/thirteenf/overlap-pane.tsx +5 -1
- package/src/plugins/builtin/thirteenf/pane.tsx +41 -16
- package/src/plugins/builtin/thirteenf/types.ts +14 -0
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +22 -6
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +81 -11
- package/src/plugins/builtin/ticker-detail/financials/model.ts +35 -2
- package/src/plugins/builtin/ticker-detail/financials/ratios.ts +53 -18
- package/src/plugins/builtin/ticker-detail/headless.ts +70 -11
- package/src/plugins/builtin/ticker-detail/index.tsx +5 -10
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +16 -6
- package/src/plugins/builtin/time-sales/index.tsx +1 -2
- package/src/plugins/builtin/time-sales/pane.tsx +3 -1
- package/src/plugins/builtin/trace-bonds/headless.ts +11 -2
- package/src/plugins/builtin/trace-bonds/model.ts +44 -10
- package/src/plugins/builtin/trace-bonds/pane.tsx +12 -7
- package/src/plugins/builtin/treasury-auctions/cache.ts +20 -8
- package/src/plugins/builtin/treasury-auctions/client.ts +3 -3
- package/src/plugins/builtin/treasury-auctions/headless.ts +3 -1
- package/src/plugins/builtin/treasury-auctions/pane.tsx +10 -4
- package/src/plugins/builtin/vol-surface/client.ts +18 -3
- package/src/plugins/builtin/vol-surface/index.tsx +1 -2
- package/src/plugins/builtin/volatility/client.ts +43 -16
- package/src/plugins/builtin/volatility/headless.ts +5 -2
- package/src/plugins/builtin/volatility/index.tsx +1 -2
- package/src/plugins/builtin/volatility/model.ts +17 -6
- package/src/plugins/builtin/volatility/pane.tsx +6 -2
- package/src/plugins/builtin/volatility/series.ts +2 -1
- package/src/plugins/builtin/world-indices/footer.ts +6 -1
- package/src/plugins/builtin/world-venue-map/client.ts +10 -1
- package/src/plugins/builtin/world-venue-map/index.ts +6 -2
- package/src/plugins/builtin/world-venue-map/layered-pane.tsx +48 -7
- package/src/plugins/builtin/world-venue-map/layers.ts +17 -0
- package/src/plugins/builtin/world-venue-map/local-layers.ts +141 -0
- package/src/plugins/builtin/world-venue-map/map-symbols.ts +18 -0
- package/src/plugins/builtin/world-venue-map/map.tsx +4 -2
- package/src/plugins/builtin/world-venue-map/scope.ts +63 -0
- package/src/plugins/builtin/yield-curve/client.ts +4 -6
- package/src/plugins/builtin/yield-curve/curves.ts +16 -2
- package/src/plugins/builtin/yield-curve/headless.ts +3 -3
- package/src/plugins/bundle.ts +52 -2
- package/src/plugins/catalog-browser.ts +2 -2
- package/src/plugins/catalog-cli.ts +20 -0
- package/src/plugins/host-link.ts +13 -1
- package/src/plugins/installer.ts +64 -42
- package/src/plugins/loader.ts +43 -11
- package/src/plugins/peer-imports.ts +201 -0
- package/src/plugins/peer-retry.ts +26 -29
- package/src/plugins/plugin-process.ts +128 -0
- package/src/plugins/redirection-guard.ts +45 -0
- package/src/remote/endpoint.ts +16 -3
- package/src/renderers/browser/main.tsx +3 -2
- package/src/renderers/cloudflare/http-proxy.ts +7 -123
- package/src/renderers/dom/desktop/icons.tsx +5 -2
- package/src/renderers/dom/host/image-surface.tsx +46 -21
- package/src/renderers/dom/styles.css +7 -0
- package/src/renderers/dom/toast-host.tsx +2 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +7 -1
- package/src/renderers/electrobun/bun/desktop/detached-windows.ts +23 -1
- package/src/renderers/electrobun/bun/desktop/initialization.ts +12 -3
- package/src/renderers/electrobun/bun/desktop/rpc-timing.ts +88 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +20 -4
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +8 -4
- package/src/renderers/electrobun/bun/external-plugins.ts +4 -0
- package/src/renderers/electrobun/bun/window/attention.ts +54 -0
- package/src/renderers/electrobun/bun/window/create.ts +6 -2
- package/src/renderers/electrobun/bun/window/native-timing.ts +48 -0
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +82 -4
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +13 -5
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +3 -0
- package/src/renderers/electrobun/view/main.tsx +7 -2
- package/src/renderers/electrobun/view/remote/asset-data-client.ts +1 -0
- package/src/renderers/electrobun/view/ui-host.tsx +9 -2
- package/src/renderers/opentui/browser-open.ts +10 -4
- package/src/renderers/opentui/image/surface.tsx +1 -1
- package/src/renderers/opentui/start.tsx +12 -6
- package/src/renderers/opentui/toast-host.tsx +2 -1
- package/src/series/registry.ts +18 -0
- package/src/series/resolve.ts +2 -1
- package/src/series/standalone.ts +10 -1
- package/src/series/types.ts +12 -0
- package/src/series/validate.ts +11 -2
- package/src/sources/gloomberb-cloud/fred-series.ts +5 -3
- package/src/sources/gloomberb-cloud/index.ts +28 -5
- package/src/sources/gloomberb-cloud/news.ts +24 -3
- package/src/sources/gloomberb-cloud/normalizers.ts +17 -2
- package/src/sources/provider-errors.ts +26 -3
- package/src/sources/provider-router/history.ts +5 -0
- package/src/sources/provider-router/index.ts +13 -0
- package/src/sync/core-contributors.ts +32 -0
- package/src/telemetry/attention-counts.ts +64 -6
- package/src/telemetry/attention-setting.ts +33 -0
- package/src/telemetry/crash-reports-node.ts +3 -1
- package/src/telemetry/stall-clocks.ts +93 -0
- package/src/telemetry/stall-reports-node.ts +25 -9
- package/src/telemetry/stall-reports.ts +218 -13
- package/src/test-support/outage.ts +8 -0
- package/src/test-support/preload.ts +9 -0
- package/src/tickers/company-name-listing.ts +59 -29
- package/src/tickers/company-name.ts +84 -1
- package/src/tickers/listing-fate.ts +14 -3
- package/src/tickers/saved-instrument.ts +17 -0
- package/src/tickers/search/home-listing.ts +104 -8
- package/src/tickers/search/index.ts +5 -2
- package/src/tickers/search/types.ts +6 -1
- package/src/time-series/bar-aggregation.ts +7 -1
- package/src/time-series/base-series.ts +3 -3
- package/src/time-series/chart-bounds.ts +1 -1
- package/src/time-series/chart-data.ts +5 -2
- package/src/time-series/chart-seed.ts +1 -1
- package/src/time-series/coin-session-close.ts +211 -0
- package/src/time-series/live-bars.ts +13 -2
- package/src/time-series/live-quotes.ts +1 -1
- package/src/time-series/market.ts +5 -3
- package/src/time-series/official-close.ts +159 -0
- package/src/time-series/price-comparison.ts +75 -8
- package/src/time-series/resolve.ts +32 -4
- package/src/time-series/series-presentation.ts +1 -1
- package/src/time-series/studies/formula.ts +205 -0
- package/src/time-series/studies/indicators.ts +299 -0
- package/src/time-series/studies/leverage.ts +75 -0
- package/src/time-series/studies/pair.ts +138 -0
- package/src/time-series/studies/resolve.ts +333 -0
- package/src/time-series/studies/shared.ts +141 -0
- package/src/time-series/studies/units.ts +243 -0
- package/src/time-series/studies/vwap.ts +274 -0
- package/src/time-series/types.ts +7 -0
- package/src/types/config.ts +18 -2
- package/src/types/data-provider.ts +23 -0
- package/src/types/financials.ts +40 -3
- package/src/types/headless.ts +42 -3
- package/src/types/plugin.ts +32 -3
- package/src/ui/host.tsx +17 -1
- package/src/ui/toast.tsx +26 -1
- package/src/utils/ascii-text.ts +140 -0
- package/src/utils/cli-output.ts +91 -18
- package/src/utils/concurrency.ts +24 -0
- package/src/utils/exchanges.ts +6 -0
- package/src/utils/external-url.ts +20 -7
- package/src/utils/financial-statements.ts +73 -16
- package/src/utils/fundamentals.ts +17 -5
- package/src/utils/futures-generic.ts +4 -4
- package/src/utils/http-proxy-upstream.ts +124 -0
- package/src/utils/lease-liabilities.ts +54 -0
- package/src/utils/main-thread-stall.ts +122 -5
- package/src/utils/perf-marks.ts +58 -12
- package/src/utils/price-history-integrity.ts +47 -1
- package/src/utils/private-files.ts +141 -0
- package/src/utils/public-address.ts +99 -0
- package/src/account/upgrade-personalization.ts +0 -57
- package/src/account/upgrade-tickers.ts +0 -124
- package/src/api-client/upgrade-experiment.ts +0 -91
- package/src/plugins/builtin/cloud/browser.tsx +0 -3
- package/src/time-series/studies.ts +0 -1628
package/src/api-client/data.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { PerpBoardPayload, PerpEquitiesPayload, PerpEquityListing, PerpHist
|
|
|
10
10
|
PerpMarketPayload, PerpLiquidationsPayload, PerpDepthPayload } from "./perps";
|
|
11
11
|
import type { ExposureRequest, ExposurePayload, ExposureScenario } from "./exposure";
|
|
12
12
|
import type { SupplyChainPayload, SupplyOptions } from "./supply-chain";
|
|
13
|
+
import { createReadCoalescer } from "./coalesce";
|
|
13
14
|
import { supplyGraphQuery, type GraphOptions, type GraphPayload } from "./supply-chain-graph";
|
|
14
15
|
import type { AwardDetailPayload, AwardFilter, AwardsPayload } from "./awards";
|
|
15
16
|
import type { EarningsEvent } from "../types/data-provider";
|
|
@@ -30,7 +31,7 @@ import type { IpoCalendarParams, IpoCalendarPayload } from "./ipo";
|
|
|
30
31
|
import type { TickerChanges } from "../types/ticker-changes";
|
|
31
32
|
import type { IssuerListings } from "../types/issuer-listings";
|
|
32
33
|
import type { OrganizationTickersResponse } from "./organization-tickers";
|
|
33
|
-
import type { CryptoMarketsPayload, DefiTvlPayload, StablecoinSupplyHistoryPayload, StablecoinSupplyPayload } from "./crypto-markets";
|
|
34
|
+
import type { BitcoinNetworkPayload, CryptoMarketsPayload, DefiTvlPayload, StablecoinSupplyHistoryPayload, StablecoinSupplyPayload } from "./crypto-markets";
|
|
34
35
|
import type { CryptoEtfFlowAsset, CryptoEtfFlowsPayload } from "./crypto-etf-flows";
|
|
35
36
|
import type { CentralBankRatesPayload } from "./central-bank-rates";
|
|
36
37
|
import type { EstimateRevisionsPayload } from "./estimate-revisions";
|
|
@@ -53,7 +54,7 @@ import type { CpiBoardPayload } from "./cpi";
|
|
|
53
54
|
import type { TapeRange, TapeSnapshot } from "./tape";
|
|
54
55
|
import { LIVE_SCAN_PATH, type LiveScanPayload, type LiveScanWireDefinition } from "./live-scan";
|
|
55
56
|
import { signalWithTimeout } from "../utils/async-deadline";
|
|
56
|
-
import { ApiRequestError } from "./errors";
|
|
57
|
+
import { ApiRequestError, isRetryableApiError, markAskedTwice } from "./errors";
|
|
57
58
|
import type { ExchangeRateSnapshot } from "../types/exchange-rate";
|
|
58
59
|
import type { RatePathPayload } from "./rates";
|
|
59
60
|
import type { EarningsCalendarPayload, EarningsCalendarQuery, EarningsHistoryPayload } from "./earnings";
|
|
@@ -204,8 +205,161 @@ function batchAnswers<K extends string, T>(response: CloudMarketResponse<CloudBa
|
|
|
204
205
|
return answers;
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
/** How long a listing read waits before asking again after the service said its source failed. */
|
|
209
|
+
const LISTING_RETRY_DELAY_MS = 400;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Whether a failed listing read is asked again: the service answered that its
|
|
213
|
+
* own source failed (`retryable_error`, or the catch-all `fatal_error`, with no
|
|
214
|
+
* data), or failed with a 5xx or a timeout. A rate limit is never asked again,
|
|
215
|
+
* whatever wait it names: asking again is more of what it refused. An answer of
|
|
216
|
+
* no data, a refusal, and a request that never reached the service
|
|
217
|
+
* (`DataServiceUnreachableError`) are not asked again either.
|
|
218
|
+
*/
|
|
219
|
+
function isListingReadRetried(outcome: { response: CloudMarketResponse<unknown> } | { error: unknown }): boolean {
|
|
220
|
+
if ("response" in outcome) {
|
|
221
|
+
const { status, data } = outcome.response ?? {};
|
|
222
|
+
return (status === "retryable_error" || status === "fatal_error") && data == null;
|
|
223
|
+
}
|
|
224
|
+
return isRetryableApiError(outcome.error) && (outcome.error as ApiRequestError).status !== 429;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* `batch`: the caller asks many reads like this one in the same moment (a
|
|
229
|
+
* board's rows), so the read may wait a few milliseconds to go out in a batch
|
|
230
|
+
* request with them. Any other read goes out on its own at once.
|
|
231
|
+
*/
|
|
232
|
+
export interface BatchableRead {
|
|
233
|
+
batch?: boolean;
|
|
234
|
+
}
|
|
235
|
+
|
|
207
236
|
export class CloudDataApi {
|
|
208
|
-
constructor(
|
|
237
|
+
constructor(
|
|
238
|
+
private readonly request: CloudApiRequest,
|
|
239
|
+
/** The wait before a failed listing read is asked again (`readListing`); tests set 0. */
|
|
240
|
+
private readonly listingRetryDelayMs = LISTING_RETRY_DELAY_MS,
|
|
241
|
+
) {}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Histories asked with others (`batch`) in the same moment with the same
|
|
245
|
+
* bars (a futures board's contracts, SOVR's currencies) go out as
|
|
246
|
+
* `/market/history/batch` requests, each answered as its own
|
|
247
|
+
* `/market/history` would be. Sixteen a request, sent side by side: a
|
|
248
|
+
* board's seventy-two rows are five requests, and the first rows land as
|
|
249
|
+
* soon as sixteen reads have.
|
|
250
|
+
*/
|
|
251
|
+
private readonly histories = createReadCoalescer<
|
|
252
|
+
{ symbol: string; exchange: string; params: Pick<CloudHistoryParams, "interval" | "outputsize" | "rangeKey"> },
|
|
253
|
+
CloudMarketResponse<CloudPricePointPayload[]>
|
|
254
|
+
>({
|
|
255
|
+
max: 16,
|
|
256
|
+
// Reads asked alone after a refused batch stay within what the panes kept in flight before: SOVR four, the board twelve with its terms.
|
|
257
|
+
fallbackConcurrency: 4,
|
|
258
|
+
group: ({ params }) => `${params.interval ?? ""}|${params.outputsize ?? ""}|${params.rangeKey ?? ""}`,
|
|
259
|
+
single: ({ symbol, exchange, params }) => this.request(cloudHistoryPath(symbol, exchange, params)),
|
|
260
|
+
batch: async (keys) => {
|
|
261
|
+
const { interval, outputsize, rangeKey } = keys[0]!.params;
|
|
262
|
+
// The server answers each target in the order asked, so a listing asked twice is asked once.
|
|
263
|
+
const targetKey = ({ symbol, exchange }: { symbol: string; exchange: string }) => `${symbol}\u001f${exchange}`;
|
|
264
|
+
const targets = [...new Map(keys.map((key) => [targetKey(key), { symbol: key.symbol, exchange: key.exchange }])).values()];
|
|
265
|
+
const response = await this.request<CloudMarketResponse<{ items: Array<CloudMarketResponse<CloudPricePointPayload[]>> }>>("/market/history/batch", {
|
|
266
|
+
method: "POST",
|
|
267
|
+
body: JSON.stringify({
|
|
268
|
+
targets,
|
|
269
|
+
interval: interval ?? "1day",
|
|
270
|
+
...(outputsize != null ? { outputsize: String(outputsize) } : {}),
|
|
271
|
+
...(rangeKey ? { rangeKey } : {}),
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
const items = response.data?.items;
|
|
275
|
+
// An answer that does not line up with the targets asked is not matched by place: each read is asked on its own.
|
|
276
|
+
if (!Array.isArray(items) || items.length !== targets.length) return keys.map(() => undefined);
|
|
277
|
+
const byTarget = new Map(targets.map((target, index) => [targetKey(target), items[index]]));
|
|
278
|
+
return keys.map((key) => byTarget.get(targetKey(key)));
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Futures terms asked with others in the same moment (a futures board's rows) go out as
|
|
284
|
+
* `/cloud/futures/contracts` requests, sixteen a request like the histories.
|
|
285
|
+
* A symbol without known terms answers null there, which reads as the 404
|
|
286
|
+
* its own request answers.
|
|
287
|
+
*/
|
|
288
|
+
private readonly futuresTerms = createReadCoalescer<string, FuturesContractTermsPayload | null>({
|
|
289
|
+
max: 16,
|
|
290
|
+
// With the board's histories' four, the twelve reads it kept in flight before.
|
|
291
|
+
fallbackConcurrency: 8,
|
|
292
|
+
group: () => "",
|
|
293
|
+
single: (symbol) => this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) }),
|
|
294
|
+
batch: async (symbols) => {
|
|
295
|
+
// The server answers each symbol once, by the symbol asked.
|
|
296
|
+
const { items } = await this.getCloudFuturesContracts([...new Set(symbols)]);
|
|
297
|
+
const bySymbol = new Map(items.map((item) => [item.symbol, item]));
|
|
298
|
+
// A failed read is asked again on its own.
|
|
299
|
+
return symbols.map((symbol) => bySymbol.get(symbol)?.error ? undefined : bySymbol.get(symbol)?.terms);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Expiries of one listing's chain asked with others in the same moment (a
|
|
305
|
+
* volatility surface's) go out as `/market/options/batch` requests, each answered as
|
|
306
|
+
* its own `/market/options` would be. Six a request, the server's one round,
|
|
307
|
+
* sent side by side: a surface's eighteen expiries are three requests.
|
|
308
|
+
*/
|
|
309
|
+
private readonly optionsChains = createReadCoalescer<
|
|
310
|
+
{ symbol: string; exchange?: string; expirationDate: number },
|
|
311
|
+
CloudMarketResponse<CloudOptionsChainPayload>
|
|
312
|
+
>({
|
|
313
|
+
max: 6,
|
|
314
|
+
fallbackConcurrency: 4,
|
|
315
|
+
group: ({ symbol, exchange }) => `${symbol}|${exchange ?? ""}`,
|
|
316
|
+
single: ({ symbol, exchange, expirationDate }) => this.request(cloudOptionsChainPath(symbol, exchange, expirationDate)),
|
|
317
|
+
batch: async (keys) => {
|
|
318
|
+
const { symbol, exchange } = keys[0]!;
|
|
319
|
+
// The server answers each expiry once, naming it.
|
|
320
|
+
const expiries = [...new Set(keys.map((key) => key.expirationDate))];
|
|
321
|
+
const search = new URLSearchParams({ symbol, expirationDates: expiries.join(",") });
|
|
322
|
+
if (exchange) search.set("exchange", exchange);
|
|
323
|
+
const response = await this.request<CloudMarketResponse<{ items: Array<CloudMarketResponse<CloudOptionsChainPayload> & { expirationDate?: number }> }>>(
|
|
324
|
+
`/market/options/batch?${search}`,
|
|
325
|
+
);
|
|
326
|
+
const byExpiry = new Map((response.data?.items ?? []).map(({ expirationDate, ...item }) => [expirationDate, item]));
|
|
327
|
+
return keys.map((key) => byExpiry.get(key.expirationDate));
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Whether reads of `kind` asked together go out in batch requests now:
|
|
333
|
+
* false while the service answers that batch route 404, until it is asked
|
|
334
|
+
* again. A caller limiting its reads in flight asks them all at once only
|
|
335
|
+
* when they do.
|
|
336
|
+
*/
|
|
337
|
+
batchesReads(kind: "history" | "options" | "futures-terms"): boolean {
|
|
338
|
+
const coalescer = kind === "history" ? this.histories : kind === "options" ? this.optionsChains : this.futuresTerms;
|
|
339
|
+
return coalescer.batching();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* A listing's quote, history or financials, asked again once when the
|
|
344
|
+
* service failed to answer it (`isListingReadRetried`): its source blinks for a
|
|
345
|
+
* few minutes at a time, and a known listing then read as one no source
|
|
346
|
+
* covers. A caller's abort stops the second ask.
|
|
347
|
+
*/
|
|
348
|
+
private async readListing<T>(read: () => Promise<CloudMarketResponse<T>>, signal?: AbortSignal): Promise<CloudMarketResponse<T>> {
|
|
349
|
+
const first = await read().then((response) => ({ response }), (error: unknown) => ({ error }));
|
|
350
|
+
const settle = () => {
|
|
351
|
+
if ("error" in first) throw first.error;
|
|
352
|
+
return first.response;
|
|
353
|
+
};
|
|
354
|
+
if (!isListingReadRetried(first) || signal?.aborted) return settle();
|
|
355
|
+
if (this.listingRetryDelayMs > 0) await new Promise((resolve) => setTimeout(resolve, this.listingRetryDelayMs));
|
|
356
|
+
if (signal?.aborted) return settle();
|
|
357
|
+
const second = await read().then((response) => ({ response }), (error: unknown) => ({ error }));
|
|
358
|
+
// Failing the same way twice, the service did not answer it (`markAskedTwice`).
|
|
359
|
+
if (isListingReadRetried(second)) markAskedTwice("error" in second ? second.error : second.response);
|
|
360
|
+
if ("error" in second) throw second.error;
|
|
361
|
+
return second.response;
|
|
362
|
+
}
|
|
209
363
|
|
|
210
364
|
private requestMarketSymbol<T>(
|
|
211
365
|
path: string,
|
|
@@ -315,7 +469,7 @@ export class CloudDataApi {
|
|
|
315
469
|
symbol: string,
|
|
316
470
|
exchange?: string,
|
|
317
471
|
): Promise<CloudMarketResponse<CloudQuotePayload>> {
|
|
318
|
-
return this.requestMarketSymbol("/market/quote", symbol, exchange);
|
|
472
|
+
return this.readListing(() => this.requestMarketSymbol("/market/quote", symbol, exchange));
|
|
319
473
|
}
|
|
320
474
|
|
|
321
475
|
async getCloudQuotesBatch(
|
|
@@ -323,7 +477,7 @@ export class CloudDataApi {
|
|
|
323
477
|
mode: "cache-first" | "refresh" = "cache-first",
|
|
324
478
|
options: { signal?: AbortSignal } = {},
|
|
325
479
|
): Promise<CloudMarketResponse<CloudMarketBatchPayload<CloudQuotePayload>>> {
|
|
326
|
-
return this.postMarketBatch("/market/quotes/batch", targets, mode, options.signal);
|
|
480
|
+
return this.readListing(() => this.postMarketBatch("/market/quotes/batch", targets, mode, options.signal), options.signal);
|
|
327
481
|
}
|
|
328
482
|
|
|
329
483
|
async getCloudWorldVenues(): Promise<
|
|
@@ -388,7 +542,10 @@ export class CloudDataApi {
|
|
|
388
542
|
symbol: string,
|
|
389
543
|
exchange?: string,
|
|
390
544
|
expirationDate?: number,
|
|
545
|
+
options: BatchableRead = {},
|
|
391
546
|
): Promise<CloudMarketResponse<CloudOptionsChainPayload>> {
|
|
547
|
+
// A dated expiry asked with others may go out with the listing's other expiries asked in the same moment.
|
|
548
|
+
if (expirationDate != null && options.batch) return this.optionsChains.read({ symbol, exchange, expirationDate });
|
|
392
549
|
return this.request<CloudMarketResponse<CloudOptionsChainPayload>>(
|
|
393
550
|
cloudOptionsChainPath(symbol, exchange, expirationDate),
|
|
394
551
|
);
|
|
@@ -400,14 +557,14 @@ export class CloudDataApi {
|
|
|
400
557
|
statementHistory?: "extended",
|
|
401
558
|
): Promise<CloudMarketResponse<CloudFinancialsPayload>> {
|
|
402
559
|
const path = cloudMarketSymbolPath("/market/financials", symbol, exchange);
|
|
403
|
-
return this.request(statementHistory === "extended" ? `${path}&statementHistory=extended` : path);
|
|
560
|
+
return this.readListing(() => this.request(statementHistory === "extended" ? `${path}&statementHistory=extended` : path));
|
|
404
561
|
}
|
|
405
562
|
|
|
406
563
|
async getCloudFinancialsBatch(
|
|
407
564
|
targets: CloudMarketBatchTarget[],
|
|
408
565
|
mode: "cache-first" | "refresh" = "cache-first",
|
|
409
566
|
): Promise<CloudMarketResponse<CloudMarketBatchPayload<CloudFinancialsPayload>>> {
|
|
410
|
-
return this.postMarketBatch("/market/financials/batch", targets, mode);
|
|
567
|
+
return this.readListing(() => this.postMarketBatch("/market/financials/batch", targets, mode));
|
|
411
568
|
}
|
|
412
569
|
|
|
413
570
|
async getCloudHolders(
|
|
@@ -518,12 +675,16 @@ export class CloudDataApi {
|
|
|
518
675
|
symbol: string,
|
|
519
676
|
exchange: string,
|
|
520
677
|
params: CloudHistoryParams = {},
|
|
521
|
-
options: { signal?: AbortSignal } = {},
|
|
678
|
+
options: { signal?: AbortSignal } & BatchableRead = {},
|
|
522
679
|
): Promise<CloudMarketResponse<CloudPricePointPayload[]>> {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
680
|
+
// Trailing bars asked with others, not a session, window or recovery of their own, may go out with the others asked in the same moment.
|
|
681
|
+
const shared = options.batch && !options.signal && !params.startDate && !params.endDate && !params.historyRecovery && !params.session;
|
|
682
|
+
return this.readListing(() => shared
|
|
683
|
+
? this.histories.read({ symbol, exchange, params: { interval: params.interval, outputsize: params.outputsize, rangeKey: params.rangeKey } })
|
|
684
|
+
: this.request<CloudMarketResponse<CloudPricePointPayload[]>>(
|
|
685
|
+
cloudHistoryPath(symbol, exchange, params),
|
|
686
|
+
options.signal ? { signal: options.signal } : undefined,
|
|
687
|
+
), options.signal);
|
|
527
688
|
}
|
|
528
689
|
|
|
529
690
|
async getCloudExchangeRate(
|
|
@@ -732,6 +893,19 @@ export class CloudDataApi {
|
|
|
732
893
|
return this.request<AwardDetailPayload>(`/cloud/awards/detail/${encodeURIComponent(id)}${query}`, { signal });
|
|
733
894
|
}
|
|
734
895
|
|
|
896
|
+
/** Many symbols' disclosures, each as getCloudSupplyChain reads it; an item that failed has the status its own read would have had. */
|
|
897
|
+
async getCloudSupplyChains(symbols: string[], options: SupplyOptions = {}): Promise<{ items: Array<{ symbol: string; chain?: SupplyChainPayload; status?: number }> }> {
|
|
898
|
+
const query = new URLSearchParams({ symbols: symbols.join(",") });
|
|
899
|
+
if (options.tiers?.length) query.set("tiers", options.tiers.join(","));
|
|
900
|
+
if (options.includeLeads) query.set("includeLeads", "1");
|
|
901
|
+
return this.request(`/cloud/supply-chain/batch?${query}`);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
/** Many symbols' graphs on the same options, each as getCloudSupplyGraph reads it. */
|
|
905
|
+
async getCloudSupplyGraphs(symbols: string[], options: Partial<GraphOptions> = {}): Promise<{ items: Array<{ symbol: string; graph?: GraphPayload; status?: number }> }> {
|
|
906
|
+
return this.request(`/cloud/supply-chain/graphs?symbols=${encodeURIComponent(symbols.join(","))}&${supplyGraphQuery(options)}`);
|
|
907
|
+
}
|
|
908
|
+
|
|
735
909
|
async getCloudSupplyGraph(symbol: string, options: Partial<GraphOptions> = {}): Promise<GraphPayload> {
|
|
736
910
|
return this.request<GraphPayload>(`/cloud/supply-chain/${encodeURIComponent(symbol)}/graph?${supplyGraphQuery(options)}`);
|
|
737
911
|
}
|
|
@@ -896,6 +1070,11 @@ export class CloudDataApi {
|
|
|
896
1070
|
return this.request<DefiTvlPayload>("/cloud/crypto/defi/tvl", { signal: AbortSignal.timeout(30_000) });
|
|
897
1071
|
}
|
|
898
1072
|
|
|
1073
|
+
/** Bitcoin's daily hash rate, the difficulty at each retarget, the next retarget's estimate and derived hashprice. */
|
|
1074
|
+
async getCloudBitcoinNetwork(): Promise<BitcoinNetworkPayload> {
|
|
1075
|
+
return this.request<BitcoinNetworkPayload>("/cloud/crypto/bitcoin/network", { signal: AbortSignal.timeout(30_000) });
|
|
1076
|
+
}
|
|
1077
|
+
|
|
899
1078
|
/** Daily net flows of the US spot bitcoin or ether ETFs, per fund, in US$ millions. */
|
|
900
1079
|
async getCloudCryptoEtfFlows(asset: CryptoEtfFlowAsset): Promise<CryptoEtfFlowsPayload> {
|
|
901
1080
|
return this.request<CryptoEtfFlowsPayload>(
|
|
@@ -1023,8 +1202,16 @@ export class CloudDataApi {
|
|
|
1023
1202
|
}
|
|
1024
1203
|
|
|
1025
1204
|
/** What a futures symbol (GC=F, GCZ26, CL1) trades: its contract, terms and latest confirmed settlement. */
|
|
1026
|
-
async getCloudFuturesContract(symbol: string): Promise<FuturesContractTermsPayload> {
|
|
1027
|
-
return this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) });
|
|
1205
|
+
async getCloudFuturesContract(symbol: string, options: BatchableRead = {}): Promise<FuturesContractTermsPayload> {
|
|
1206
|
+
if (!options.batch) return this.request<FuturesContractTermsPayload>(`/cloud/futures/contract/${encodeURIComponent(symbol)}`, { signal: AbortSignal.timeout(20_000) });
|
|
1207
|
+
const terms = await this.futuresTerms.read(symbol);
|
|
1208
|
+
if (!terms) throw new ApiRequestError("Not a futures symbol with known contract terms", 404);
|
|
1209
|
+
return terms;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/** Many symbols' terms, each as getCloudFuturesContract reads it: null for one without known terms, an error for a failed read. */
|
|
1213
|
+
async getCloudFuturesContracts(symbols: string[]): Promise<{ items: Array<{ symbol: string; terms: FuturesContractTermsPayload | null; error?: string }> }> {
|
|
1214
|
+
return this.request(`/cloud/futures/contracts?${new URLSearchParams({ symbols: symbols.join(",") })}`, { signal: AbortSignal.timeout(30_000) });
|
|
1028
1215
|
}
|
|
1029
1216
|
|
|
1030
1217
|
async getCloudFuturesCurveAsOf(root: string, date: string): Promise<FuturesCurveAsOfPayload> {
|
|
@@ -23,6 +23,15 @@ export const OPTION_VOLATILITY_FIELDS = ["iv30Percent", "ivPercentile", "iv30Cha
|
|
|
23
23
|
* listed only when asked (`include=ownership`), and omitted by a server from before them.
|
|
24
24
|
*/
|
|
25
25
|
export const OWNERSHIP_FIELDS = ["sharesOutstanding", "floatShares", "shortPercentOfFloat", "institutionalOwnershipPercent"] as const;
|
|
26
|
+
/**
|
|
27
|
+
* ROIC, FCF yield, leverage, revenue and EPS CAGRs and consensus growth, on the bases `FA` and `EE` use; listed only
|
|
28
|
+
* when asked (`include=quality`), and omitted by a server from before them.
|
|
29
|
+
*/
|
|
30
|
+
export const QUALITY_FIELDS = [
|
|
31
|
+
"roicPercent", "fcfYieldPercent", "netDebtToEbitda", "debtToEquity",
|
|
32
|
+
"revenueCagr3YPercent", "revenueCagr5YPercent", "epsCagr3YPercent", "epsCagr5YPercent",
|
|
33
|
+
"forwardRevenueGrowthPercent", "forwardEpsGrowthPercent",
|
|
34
|
+
] as const;
|
|
26
35
|
export const NUMERIC_FIELDS = [
|
|
27
36
|
...KPI_FIELDS,
|
|
28
37
|
"price",
|
|
@@ -73,6 +82,7 @@ export const NUMERIC_FIELDS = [
|
|
|
73
82
|
"ivToHv",
|
|
74
83
|
...OPTION_VOLATILITY_FIELDS,
|
|
75
84
|
...OWNERSHIP_FIELDS,
|
|
85
|
+
...QUALITY_FIELDS,
|
|
76
86
|
"hiringOpenRoles", "hiringNetAddsWeek", "hiringRemotePercent", "hiringZScore",
|
|
77
87
|
"appAttentionScore", "appRankVelocity7d", "appRatingDrift7d", "appRatingCountGrowth7d",
|
|
78
88
|
] as const;
|
package/src/api-client/errors.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isNetworkFailure } from "../utils/network-failure";
|
|
2
|
+
|
|
1
3
|
const HARD_SESSION_INVALID_PATTERNS = [
|
|
2
4
|
/\b(user|account)\b.*\b(not found|deleted|removed|disabled|deactivated|suspended)\b/i,
|
|
3
5
|
/\b(user|account)\b.*\bdoes(?:\s+not|n't)\s+exist\b/i,
|
|
@@ -61,6 +63,62 @@ export function isRetryableApiError(error: unknown): boolean {
|
|
|
61
63
|
return error.code === "retryable_error" || status === 408 || status === 429 || (status !== undefined && status >= 500);
|
|
62
64
|
}
|
|
63
65
|
|
|
66
|
+
/**
|
|
67
|
+
* A 200 reply saying its data is unavailable this time (a board's `unavailable`
|
|
68
|
+
* status, a market answer's `retryable_error`): the server's own source did
|
|
69
|
+
* not answer. Like a 5xx it may pass, so a saved copy answers it with its note.
|
|
70
|
+
* A permanent reason (a route not served yet, an unsupported symbol or series,
|
|
71
|
+
* sign-in or entitlement) comes as its status, or as a plain error, and stays
|
|
72
|
+
* the error.
|
|
73
|
+
*/
|
|
74
|
+
export function sourceDidNotAnswer(message: string): ApiRequestError {
|
|
75
|
+
return new ApiRequestError(message, undefined, undefined, "retryable_error");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Registered, so a mark set by another copy of this module is still read.
|
|
79
|
+
const ASKED_TWICE = Symbol.for("gloomberb.listingReadAskedTwice");
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Marks a listing read's failure, or its failed answer, as the second of two
|
|
83
|
+
* asks (`CloudDataApi.readListing`). Only such a failure is said to be the data
|
|
84
|
+
* service not answering; any other 5xx keeps the server's own words.
|
|
85
|
+
*/
|
|
86
|
+
export function markAskedTwice<T>(value: T): T {
|
|
87
|
+
if (value && typeof value === "object" && Object.isExtensible(value) && !wasAskedTwice(value)) {
|
|
88
|
+
Object.defineProperty(value, ASKED_TWICE, { value: true });
|
|
89
|
+
}
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Whether a failure, or a failed answer, came from a listing read asked twice (`markAskedTwice`). */
|
|
94
|
+
export function wasAskedTwice(value: unknown): boolean {
|
|
95
|
+
return !!value && typeof value === "object" && (value as Record<symbol, unknown>)[ASKED_TWICE] === true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Codes of a connection that dropped or stalled after it opened: Node's and the system's, then Bun's. */
|
|
99
|
+
const INTERRUPTED_CODES = new Set(["ECONNRESET", "ECONNABORTED", "EPIPE", "ETIMEDOUT", "ConnectionClosed"]);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* A failure asking again later may get past, which a copy saved earlier may
|
|
103
|
+
* answer for: no connection, a connection that dropped, a timeout, or a server
|
|
104
|
+
* that was rate limited or overloaded (408, 429, 5xx). Anything else the
|
|
105
|
+
* server answered (another 4xx) and an answer that failed validation are
|
|
106
|
+
* refusals to show as errors, saved copy or not. A bare throw with nothing to
|
|
107
|
+
* classify is not taken for a refusal.
|
|
108
|
+
*/
|
|
109
|
+
export function isTransientFailure(error: unknown): boolean {
|
|
110
|
+
if (!error || typeof error !== "object") return typeof error !== "string" || isNetworkFailure(error);
|
|
111
|
+
if (isNetworkFailure(error) || isRetryableApiError(error)) return true;
|
|
112
|
+
for (let current: unknown = error, depth = 0; current && typeof current === "object" && depth < 4; depth += 1) {
|
|
113
|
+
if (current instanceof ApiRequestError) return false;
|
|
114
|
+
const { name, code } = current as { name?: unknown; code?: unknown };
|
|
115
|
+
if (name === "AbortError" || name === "TimeoutError") return true;
|
|
116
|
+
if (typeof code === "string" && INTERRUPTED_CODES.has(code)) return true;
|
|
117
|
+
current = (current as { cause?: unknown }).cause;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
64
122
|
/** Reads `Retry-After` as milliseconds, accepting seconds or an HTTP date. */
|
|
65
123
|
export function parseRetryAfterMs(
|
|
66
124
|
header: string | null,
|
package/src/api-client/gpu.ts
CHANGED
|
@@ -45,6 +45,33 @@ export interface GpuBoardRow extends GpuObservation {
|
|
|
45
45
|
lastError: string | null;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Gloom's daily settle: the official price of one GPU and form factor for a
|
|
50
|
+
* day, set at 20:00 UTC and never revised. A day with too few prices is
|
|
51
|
+
* "no-settle" with no price. A correction is a new entry naming the one it
|
|
52
|
+
* replaces.
|
|
53
|
+
*/
|
|
54
|
+
export interface GpuSettle {
|
|
55
|
+
id: string;
|
|
56
|
+
/** The settle day, YYYY-MM-DD. */
|
|
57
|
+
date: string;
|
|
58
|
+
/** The moment the price stands for: the day at 20:00 UTC. */
|
|
59
|
+
settleAt: string;
|
|
60
|
+
gpuModel: string;
|
|
61
|
+
formFactor: string;
|
|
62
|
+
memoryGb: number | null;
|
|
63
|
+
label: string;
|
|
64
|
+
status: "settled" | "no-settle";
|
|
65
|
+
pricePerGpuHr: number | null;
|
|
66
|
+
currency: "USD";
|
|
67
|
+
kind: "settle" | "correction";
|
|
68
|
+
corrects: string | null;
|
|
69
|
+
publishedAt: string;
|
|
70
|
+
change1d: number | null;
|
|
71
|
+
change7d: number | null;
|
|
72
|
+
change30d: number | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
48
75
|
export interface GpuBoardPayload {
|
|
49
76
|
access?: GpuAccess;
|
|
50
77
|
generatedAt: string;
|
|
@@ -53,6 +80,8 @@ export interface GpuBoardPayload {
|
|
|
53
80
|
stale: boolean;
|
|
54
81
|
gaps: string[];
|
|
55
82
|
rows: GpuBoardRow[];
|
|
83
|
+
/** Each GPU's latest daily settle, the same on every plan. Older servers send none. */
|
|
84
|
+
settles?: GpuSettle[];
|
|
56
85
|
}
|
|
57
86
|
|
|
58
87
|
export interface GpuHistoryPayload {
|
package/src/api-client/index.ts
CHANGED
|
@@ -601,8 +601,6 @@ class GloomApiClient {
|
|
|
601
601
|
async recordExperimentExposure(payload: {
|
|
602
602
|
eventId: string; surface: "web" | "desktop" | "tui" | "cli"; anonymousId?: string;
|
|
603
603
|
attribution?: Record<string, string>; experiment: string; variant?: string;
|
|
604
|
-
/** Where the experiment showed, by a content-free id like an upgrade placement. */
|
|
605
|
-
placement?: string;
|
|
606
604
|
}, signal?: AbortSignal): Promise<import("./web-experiments").ExperimentAnswer> {
|
|
607
605
|
return this.request("/activity/research", {
|
|
608
606
|
method: "POST",
|
|
@@ -955,6 +953,7 @@ class GloomApiClient {
|
|
|
955
953
|
getCloudStablecoinSupply = this.data.getCloudStablecoinSupply.bind(this.data);
|
|
956
954
|
getCloudStablecoinSupplyHistory = this.data.getCloudStablecoinSupplyHistory.bind(this.data);
|
|
957
955
|
getCloudDefiTvl = this.data.getCloudDefiTvl.bind(this.data);
|
|
956
|
+
getCloudBitcoinNetwork = this.data.getCloudBitcoinNetwork.bind(this.data);
|
|
958
957
|
getCloudCentralBankRates = this.data.getCloudCentralBankRates.bind(this.data);
|
|
959
958
|
getMobileAlertHistory = this.data.getMobileAlertHistory.bind(this.data);
|
|
960
959
|
getScannerFlowHistory = this.data.getScannerFlowHistory.bind(this.data);
|
|
@@ -993,6 +992,8 @@ class GloomApiClient {
|
|
|
993
992
|
getCloudFuturesCurve = this.data.getCloudFuturesCurve.bind(this.data);
|
|
994
993
|
getCloudFuturesCurveAsOf = this.data.getCloudFuturesCurveAsOf.bind(this.data);
|
|
995
994
|
getCloudFuturesContract = this.data.getCloudFuturesContract.bind(this.data);
|
|
995
|
+
getCloudFuturesContracts = this.data.getCloudFuturesContracts.bind(this.data);
|
|
996
|
+
batchesReads = this.data.batchesReads.bind(this.data);
|
|
996
997
|
getCloudRatePath = this.data.getCloudRatePath.bind(this.data);
|
|
997
998
|
getCloudShiller = this.data.getCloudShiller.bind(this.data);
|
|
998
999
|
getCloudCotBoard = this.data.getCloudCotBoard.bind(this.data);
|
|
@@ -1025,6 +1026,8 @@ class GloomApiClient {
|
|
|
1025
1026
|
analyzeCloudExposure = this.data.analyzeCloudExposure.bind(this.data);
|
|
1026
1027
|
getCloudExposureScenarios = this.data.getCloudExposureScenarios.bind(this.data);
|
|
1027
1028
|
getCloudSupplyGraph = this.data.getCloudSupplyGraph.bind(this.data);
|
|
1029
|
+
getCloudSupplyChains = this.data.getCloudSupplyChains.bind(this.data);
|
|
1030
|
+
getCloudSupplyGraphs = this.data.getCloudSupplyGraphs.bind(this.data);
|
|
1028
1031
|
getCloudSupplyPaths = this.data.getCloudSupplyPaths.bind(this.data);
|
|
1029
1032
|
getCloudSupplyChain = this.data.getCloudSupplyChain.bind(this.data);
|
|
1030
1033
|
getCloudAwards = this.data.getCloudAwards.bind(this.data);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useSyncExternalStore } from "react";
|
|
2
2
|
import { apiClient } from "./index";
|
|
3
|
-
import { resolvePlanAccess, type PlanAccess } from "./plan-rules";
|
|
3
|
+
import { freeQuoteDelayMinutes, resolvePlanAccess, type PlanAccess, type QuoteDelayFacts } from "./plan-rules";
|
|
4
4
|
|
|
5
5
|
export * from "./plan-rules";
|
|
6
6
|
|
|
@@ -25,3 +25,12 @@ export function usePlanAccess(): PlanAccess {
|
|
|
25
25
|
);
|
|
26
26
|
return resolvePlanAccess(apiClient.getCurrentUser());
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The delay this account sees on a price (see {@link freeQuoteDelayMinutes}):
|
|
31
|
+
* the free tier's on a price that is not live, null with Pro or a trial.
|
|
32
|
+
* Follows sign-in and plan changes.
|
|
33
|
+
*/
|
|
34
|
+
export function useFreeQuoteDelay(quote: QuoteDelayFacts | null | undefined): number | null {
|
|
35
|
+
return freeQuoteDelayMinutes(usePlanAccess(), quote);
|
|
36
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// The plan rules alone, free of React and the API client, so the provider
|
|
2
2
|
// router can gate real-time routing on the same entitlement the panes show.
|
|
3
3
|
|
|
4
|
+
import type { Quote } from "../types/financials";
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Minimum shape the plan helpers need. Both `AuthUser` (from `/auth/get-session`)
|
|
6
8
|
* and `AccountProfile` (from `/account/profile`) satisfy it.
|
|
@@ -70,6 +72,39 @@ export function hasProAccess(user: PlanAccessUser | null | undefined, now = Date
|
|
|
70
72
|
return user.effectivePlan === "pro" && !user.trialEndsAt;
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/** What of a price decides whether a free account sees it late. */
|
|
76
|
+
export type QuoteDelayFacts = Pick<Quote, "dataSource" | "delisting" | "priceObservation">;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The delay in minutes an account sees on a price: the free tier's quote
|
|
80
|
+
* delay when the account has no real-time access (signed out, free, a trial
|
|
81
|
+
* that ended) and the price is not live, null otherwise. A live price (a
|
|
82
|
+
* coin, a broker's own feed) has none, and neither does a price with no delay
|
|
83
|
+
* to speak of (a delisted line's last trade, a fund's NAV). Pro and trial
|
|
84
|
+
* accounts never see one.
|
|
85
|
+
*/
|
|
86
|
+
export function freeQuoteDelayMinutes(
|
|
87
|
+
access: Pick<PlanAccess, "hasProAccess">,
|
|
88
|
+
quote: QuoteDelayFacts | null | undefined,
|
|
89
|
+
): number | null {
|
|
90
|
+
if (access.hasProAccess || !quote) return null;
|
|
91
|
+
if (quote.dataSource === "live" || quote.delisting || quote.priceObservation === "nav") return null;
|
|
92
|
+
return CLOUD_QUOTE_DELAY_MINUTES;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The delay of the first of `quotes` an account sees late (see {@link freeQuoteDelayMinutes}), or null when none is. */
|
|
96
|
+
export function freeQuotesDelayMinutes(
|
|
97
|
+
access: Pick<PlanAccess, "hasProAccess">,
|
|
98
|
+
quotes: Iterable<QuoteDelayFacts | null | undefined>,
|
|
99
|
+
): number | null {
|
|
100
|
+
if (access.hasProAccess) return null;
|
|
101
|
+
for (const quote of quotes) {
|
|
102
|
+
const minutes = freeQuoteDelayMinutes(access, quote);
|
|
103
|
+
if (minutes != null) return minutes;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
73
108
|
export function resolvePlanAccess(
|
|
74
109
|
user: PlanAccessUser | null | undefined,
|
|
75
110
|
now = Date.now(),
|
|
@@ -4,7 +4,6 @@ import { getCurrentPluginTarget } from "../plugins/current-target";
|
|
|
4
4
|
import type { DesktopDeepLinkBridge } from "../types/desktop-deeplink";
|
|
5
5
|
import { exposeExperiment, storedExperimentAssignments } from "./web-experiments";
|
|
6
6
|
import { createWallExperimentSession, type WallExperiment, type WallExperimentContext, type WallTeaserVariant } from "./wall-experiments";
|
|
7
|
-
import { createUpgradeExperimentSession, UPGRADE_PERSONALIZED, type UpgradeExperimentContext, type UpgradePersonalizedVariant } from "./upgrade-experiment";
|
|
8
7
|
import { hasProAccess } from "./plan-rules";
|
|
9
8
|
import { withDeadline } from "../utils/async-deadline";
|
|
10
9
|
import { browserDoNotTrack } from "../telemetry/crash-reports-dom";
|
|
@@ -43,7 +42,6 @@ let anonymousId: string | undefined;
|
|
|
43
42
|
let attribution: Record<string, string> = {};
|
|
44
43
|
const wallExperiment = createWallExperimentSession();
|
|
45
44
|
const signinWallExperiment = createWallExperimentSession("wall_teaser_signin");
|
|
46
|
-
const upgradeExperiment = createUpgradeExperimentSession();
|
|
47
45
|
const WALL_VIEWS_SESSION_KEY = "gloomberb.wall-teaser.views";
|
|
48
46
|
|
|
49
47
|
// The desktop app also keeps a copy of these two on disk (electrobun/view/view-ids.ts).
|
|
@@ -350,7 +348,7 @@ function attributionPayload(): Record<string, string> | undefined {
|
|
|
350
348
|
}
|
|
351
349
|
|
|
352
350
|
export interface ResearchActivityDetails {
|
|
353
|
-
/** The desks picked at "What do you trade?", on the milestone that ends the first run. */
|
|
351
|
+
/** The desks picked at "What do you trade most?", on the milestone that ends the first run. */
|
|
354
352
|
desks?: readonly string[];
|
|
355
353
|
/**
|
|
356
354
|
* Which prompt raised an `upgrade_intent` (a pane footer, a wall, the
|
|
@@ -538,35 +536,6 @@ export function exposeWallTeaser(experiment: WallExperiment = "wall_teaser"): Pr
|
|
|
538
536
|
});
|
|
539
537
|
}
|
|
540
538
|
|
|
541
|
-
function upgradeExperimentContext(): UpgradeExperimentContext {
|
|
542
|
-
const { accountId, pro, optedOut, automated, session } = wallExperimentContext();
|
|
543
|
-
return { accountId, pro, optedOut, automated, session };
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
/** The `upgrade_personalized` arm this session already has, without asking; see upgrade-experiment.ts. */
|
|
547
|
-
export function knownUpgradePersonalized(): UpgradePersonalizedVariant | null | undefined {
|
|
548
|
-
return upgradeExperiment.known(upgradeExperimentContext());
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Asks for the `upgrade_personalized` arm when an upgrade surface is shown to
|
|
553
|
-
* a free account that has tickers to name, once per account and session. The
|
|
554
|
-
* request says where it showed and nothing about the holdings.
|
|
555
|
-
*/
|
|
556
|
-
export function exposeUpgradePersonalized(placement: string): Promise<UpgradePersonalizedVariant | null> {
|
|
557
|
-
const context = upgradeExperimentContext();
|
|
558
|
-
const surface = getCurrentPluginTarget();
|
|
559
|
-
return upgradeExperiment.expose(context, () => {
|
|
560
|
-
const controller = new AbortController();
|
|
561
|
-
return withDeadline(apiClient.recordExperimentExposure({
|
|
562
|
-
eventId: crypto.randomUUID(),
|
|
563
|
-
surface,
|
|
564
|
-
experiment: UPGRADE_PERSONALIZED,
|
|
565
|
-
...(ACTIVITY_ID.test(placement) ? { placement } : {}),
|
|
566
|
-
}, controller.signal), 3_000, "Upgrade experiment request timed out", (error) => controller.abort(error));
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
|
|
570
539
|
/** Records the visible wall once per placement, including visitors outside the experiment. */
|
|
571
540
|
export function recordWallViewed(placement: string, teaserKind?: "summary" | "sample" | "none"): void {
|
|
572
541
|
recordResearchActivity("wall_viewed", undefined, undefined, { placement, teaser_kind: teaserKind });
|