gloomberb 0.18.4 → 0.19.0
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 +1 -1
- package/README.zh-CN.md +1 -1
- package/package.json +1 -1
- package/patches/electrobun@1.18.1.patch +169 -5
- package/src/account/auth-model.ts +7 -3
- package/src/api-client/auth.ts +9 -4
- package/src/api-client/catalysts.ts +2 -1
- package/src/api-client/cot.ts +6 -2
- package/src/api-client/credit-documents.ts +6 -0
- package/src/api-client/data.ts +31 -6
- package/src/api-client/equity-screener.ts +24 -0
- package/src/api-client/fund-facts.ts +45 -4
- package/src/api-client/index.ts +171 -11
- package/src/api-client/live-scan.ts +91 -0
- package/src/api-client/market-discovery.ts +10 -0
- package/src/api-client/market-movers.ts +7 -0
- package/src/api-client/members.ts +13 -1
- package/src/api-client/perps.ts +43 -0
- package/src/api-client/request.ts +222 -41
- package/src/api-client/research-activity.ts +6 -12
- package/src/api-client/types.ts +74 -0
- package/src/app/keybindings/index.ts +3 -0
- package/src/app/keybindings/labels.ts +18 -1
- package/src/app/keybindings/resolve.ts +14 -0
- package/src/app/pane-runtime/plugin-bindings.ts +9 -5
- package/src/app/runtime/broker-import.ts +42 -27
- package/src/app/runtime/plugin-bindings.ts +73 -56
- package/src/architecture/core-builtin-allowlist.json +1 -1
- package/src/architecture/pane-tabs-allowlist.json +3 -0
- package/src/architecture/ttl-caches-allowlist.json +19 -0
- package/src/brokers/sync-broker-instance.ts +2 -8
- package/src/capabilities/chart-series.ts +1 -1
- package/src/cli/commands/crypto-hints.ts +40 -24
- package/src/cli/commands/fred-series.ts +5 -0
- package/src/cli/commands/keybindings.ts +23 -0
- package/src/cli/commands/market.ts +138 -46
- package/src/cli/commands/options-chain.ts +107 -10
- package/src/cli/commands/options-leaps.ts +9 -3
- package/src/cli/commands/overview.ts +176 -39
- package/src/cli/commands/search.ts +39 -3
- package/src/cli/commands/themes.ts +11 -2
- package/src/cli/commands/ticker.ts +57 -35
- package/src/cli/company-name-hint.ts +41 -0
- package/src/cli/earnings-calendar.ts +1 -1
- package/src/cli/errors.ts +10 -2
- package/src/cli/export-source.ts +29 -1
- package/src/cli/glossary.ts +73 -1
- package/src/cli/history-rows.ts +87 -16
- package/src/cli/index.ts +2 -0
- package/src/cli/known-gaps.ts +5 -2
- package/src/cli/listing-arg.ts +195 -56
- package/src/cli/output-file.ts +68 -0
- package/src/cli/pane-functions/capabilities.ts +3 -0
- package/src/cli/pane-functions/catalog.ts +80 -7
- package/src/cli/pane-functions/dom.ts +13 -3
- package/src/cli/pane-functions/freshness.ts +111 -16
- package/src/cli/pane-functions/function-help.ts +23 -13
- package/src/cli/pane-functions/headless.ts +56 -12
- package/src/cli/pane-functions/index.ts +58 -16
- package/src/cli/pane-functions/market-session.ts +81 -9
- package/src/cli/pane-functions/options.ts +19 -1
- package/src/cli/pane-functions/report.ts +5 -1
- package/src/cli/pane-functions/resolver.ts +35 -8
- package/src/cli/pane-functions/screenshot-assessment.ts +6 -4
- package/src/cli/pane-functions/screenshot-types.ts +5 -0
- package/src/cli/quote-answer.ts +30 -13
- package/src/cli/report-tables.ts +18 -7
- package/src/cli/restore-plugins.ts +13 -1
- package/src/cli/result.ts +57 -5
- package/src/cli/ticker-resolution.ts +3 -2
- package/src/components/chart/composite/composite-chart.tsx +26 -4
- package/src/components/chart/composite/format.ts +27 -0
- package/src/components/chart/composite/interactions.ts +6 -0
- package/src/components/chart/composite/legend.tsx +45 -7
- package/src/components/chart/composite/levels.ts +2 -0
- package/src/components/chart/composite/panel-gestures.ts +33 -0
- package/src/components/chart/composite/panel-series.ts +1 -0
- package/src/components/chart/composite/panel-surface.tsx +61 -5
- package/src/components/chart/composite/range-statistics.ts +62 -0
- package/src/components/chart/composite/rasterizer.ts +24 -13
- package/src/components/chart/composite/scene.ts +46 -7
- package/src/components/chart/composite/series-axis-tags.tsx +16 -9
- package/src/components/chart/composite/series-cues.ts +14 -0
- package/src/components/chart/composite/text-renderer.ts +9 -2
- package/src/components/chart/composite/tool-catalog.ts +36 -0
- package/src/components/chart/composite/tools.ts +203 -44
- package/src/components/chart/composite/types.ts +4 -0
- package/src/components/chart/composite/use-composite-chart-keyboard.ts +11 -4
- package/src/components/command-bar/assist/inventory.ts +2 -1
- package/src/components/command-bar/commands/direct/index.ts +28 -13
- package/src/components/command-bar/pane-templates/items.ts +3 -2
- package/src/components/command-bar/panel/prompt-binding.ts +0 -8
- package/src/components/command-bar/workflow/alert.ts +1 -1
- package/src/components/command-bar/workflow/collection-actions.ts +3 -3
- package/src/components/layout/header.tsx +26 -40
- package/src/components/layout/pane/footer/index.tsx +1 -0
- package/src/components/layout/pane/footer/model.ts +27 -0
- package/src/components/onboarding/onboarding-frame.tsx +1 -1
- package/src/components/onboarding/onboarding-wizard.tsx +4 -1
- package/src/components/onboarding/portfolio-step/positions-panel.tsx +14 -9
- package/src/components/onboarding/wizard-broker-sync.ts +0 -1
- package/src/components/onboarding/wizard-positions.ts +3 -3
- package/src/components/ticker/list-table-view.tsx +4 -1
- package/src/components/ui/choice-dialog.tsx +23 -18
- package/src/components/ui/choice-groups.ts +18 -0
- package/src/components/ui/data-table/opentui/index.tsx +9 -7
- package/src/components/ui/data-table/opentui/model.ts +26 -1
- package/src/components/ui/data-table/types.ts +2 -0
- package/src/components/ui/display.tsx +5 -1
- package/src/components/ui/list-view.tsx +8 -4
- package/src/components/ui/query-bar-keys.tsx +2 -0
- package/src/components/ui/query-bar.tsx +1 -0
- package/src/components/ui/select-button.tsx +5 -1
- package/src/components/ui/select-field.tsx +1 -0
- package/src/components/ui/table-layout.ts +8 -5
- package/src/core/app-runtime.ts +2 -0
- package/src/core/app-services.ts +2 -2
- package/src/core/state/app/layout.ts +2 -0
- package/src/core/state/app/state.ts +9 -0
- package/src/core/state/app/types.ts +3 -0
- package/src/data/config/chart-settings.ts +1 -1
- package/src/data/json-ticker-repository.ts +17 -8
- package/src/data/saved-copies.ts +55 -16
- package/src/data/ticker-repository.ts +16 -9
- package/src/data/ticker-writes.ts +81 -0
- package/src/data/ttl-cache.ts +165 -0
- package/src/docs/snapshot.json +427 -216
- package/src/i18n/es.ts +19 -3
- package/src/i18n/ja.ts +19 -3
- package/src/i18n/ko.ts +19 -3
- package/src/i18n/zh-cn.ts +19 -3
- package/src/i18n/zh-tw.ts +19 -3
- package/src/market-data/coordinator/financials.ts +10 -2
- package/src/market-data/coordinator/index.ts +3 -1
- package/src/market-data/coordinator/quotes.ts +4 -2
- package/src/market-data/depositary-pair.ts +2 -2
- package/src/market-data/fund-facts.ts +50 -0
- package/src/market-data/market/change-basis.ts +48 -7
- package/src/market-data/market/dated-previous-close.ts +203 -0
- package/src/market-data/market/format.ts +25 -0
- package/src/market-data/market/freshness.ts +2 -1
- package/src/market-data/market/futures-week.ts +68 -0
- package/src/market-data/market/status.ts +16 -4
- package/src/market-data/options-alternatives.ts +141 -4
- package/src/market-data/quotes/contributions.ts +20 -2
- package/src/market-data/quotes/resolution.ts +3 -0
- package/src/market-data/selectors.ts +3 -0
- package/src/news/aggregator.ts +113 -9
- package/src/news/cloud-session.ts +21 -0
- package/src/news/hooks.ts +1 -0
- package/src/news/news-model.ts +1 -0
- package/src/news/types.ts +11 -0
- package/src/notifications/notification-log.ts +6 -4
- package/src/plugins/builtin/alerts/alert-engine.ts +25 -2
- package/src/plugins/builtin/alerts/alert-log.ts +33 -0
- package/src/plugins/builtin/alerts/calendar-events.ts +57 -6
- package/src/plugins/builtin/alerts/cli-forms.ts +216 -0
- package/src/plugins/builtin/alerts/cli.ts +380 -82
- package/src/plugins/builtin/alerts/command.ts +31 -0
- package/src/plugins/builtin/alerts/constants.ts +1 -1
- package/src/plugins/builtin/alerts/events-pane.tsx +19 -4
- package/src/plugins/builtin/alerts/fired-log.ts +113 -8
- package/src/plugins/builtin/alerts/format.ts +80 -4
- package/src/plugins/builtin/alerts/headless.ts +30 -7
- package/src/plugins/builtin/alerts/history-rows.ts +7 -3
- package/src/plugins/builtin/alerts/history.ts +2 -0
- package/src/plugins/builtin/alerts/index.tsx +135 -22
- package/src/plugins/builtin/alerts/live.ts +3 -3
- package/src/plugins/builtin/alerts/metric-command.ts +134 -0
- package/src/plugins/builtin/alerts/metric-data.ts +111 -0
- package/src/plugins/builtin/alerts/metric-model.ts +324 -0
- package/src/plugins/builtin/alerts/metric-parity-fixture.json +725 -0
- package/src/plugins/builtin/alerts/pane.tsx +90 -39
- package/src/plugins/builtin/alerts/quotes.ts +6 -0
- package/src/plugins/builtin/alerts/storage.ts +37 -10
- package/src/plugins/builtin/alerts/types.ts +7 -0
- package/src/plugins/builtin/analytics/index.tsx +2 -1
- package/src/plugins/builtin/analytics/risk-client.ts +16 -5
- package/src/plugins/builtin/analytics/risk-fx.ts +70 -15
- package/src/plugins/builtin/analytics/risk-headless.ts +27 -0
- package/src/plugins/builtin/analytics/risk-model.ts +55 -9
- package/src/plugins/builtin/analytics/risk-pane.tsx +8 -2
- package/src/plugins/builtin/attention/index.tsx +2 -1
- package/src/plugins/builtin/attention/listing.ts +14 -0
- package/src/plugins/builtin/backtest/headless.ts +10 -5
- package/src/plugins/builtin/backtest/pane.tsx +9 -4
- package/src/plugins/builtin/backtest/presets.ts +29 -3
- package/src/plugins/builtin/broker-manager/index.tsx +10 -5
- package/src/plugins/builtin/broker-manager/model.ts +6 -1
- package/src/plugins/builtin/broker-trading/controller.ts +22 -12
- package/src/plugins/builtin/broker-trading/index.tsx +13 -1
- package/src/plugins/builtin/broker-trading/order-feed.ts +9 -4
- package/src/plugins/builtin/broker-trading/pane.tsx +1 -1
- package/src/plugins/builtin/buildout/cells.tsx +12 -4
- package/src/plugins/builtin/buildout/format.ts +38 -0
- package/src/plugins/builtin/buildout/model/normalizers.ts +36 -1
- package/src/plugins/builtin/buildout/pane/index.tsx +4 -1
- package/src/plugins/builtin/buildout/table-model.ts +23 -2
- package/src/plugins/builtin/builtin-plugin-meta.ts +7 -3
- package/src/plugins/builtin/catalysts/model.ts +2 -1
- package/src/plugins/builtin/changelog/index.tsx +9 -6
- package/src/plugins/builtin/chart-composer/catalog-inventory.ts +6 -1
- package/src/plugins/builtin/chart-composer/chart-spec-edit.ts +1 -1
- package/src/plugins/builtin/chart-composer/cli-options.ts +9 -5
- package/src/plugins/builtin/chart-composer/data-catalog-pane.tsx +12 -2
- package/src/plugins/builtin/chart-composer/editor/controller.ts +4 -2
- package/src/plugins/builtin/chart-composer/editor/model.ts +2 -0
- package/src/plugins/builtin/chart-composer/editor.tsx +7 -5
- package/src/plugins/builtin/chart-composer/formula.ts +71 -18
- package/src/plugins/builtin/chart-composer/headless-schema.ts +21 -4
- package/src/plugins/builtin/chart-composer/headless.ts +19 -3
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chart-composer/pane.tsx +53 -9
- package/src/plugins/builtin/chart-composer/presets.ts +50 -15
- package/src/plugins/builtin/chart-composer/quick-add.tsx +40 -25
- package/src/plugins/builtin/chart-composer/session-history.ts +18 -9
- package/src/plugins/builtin/chart-composer/settings.ts +27 -0
- package/src/plugins/builtin/chart-composer/use-series-catalog.ts +40 -26
- package/src/plugins/builtin/chat/content/command-suggestions.ts +93 -0
- package/src/plugins/builtin/chat/content/composer.tsx +13 -6
- package/src/plugins/builtin/chat/content/footer.tsx +6 -3
- package/src/plugins/builtin/chat/content/index.tsx +50 -21
- package/src/plugins/builtin/chat/content/open-targets.ts +14 -3
- package/src/plugins/builtin/chat/controller/session-runtime.ts +4 -2
- package/src/plugins/builtin/chat/index.tsx +23 -0
- package/src/plugins/builtin/chat/message/desktop.tsx +1 -0
- package/src/plugins/builtin/chat/message/inline-tokens.tsx +23 -0
- package/src/plugins/builtin/chat/message/pane-chip.tsx +50 -20
- package/src/plugins/builtin/chat/pane-links/commands.ts +271 -0
- package/src/plugins/builtin/chat/pane-links/model.ts +12 -3
- package/src/plugins/builtin/chat/pane-links/open.ts +8 -1
- package/src/plugins/builtin/chat/unread-inbox-pane.tsx +10 -1
- package/src/plugins/builtin/clinical-trials/model.ts +0 -9
- package/src/plugins/builtin/clinical-trials/pane.tsx +15 -22
- package/src/plugins/builtin/clinical-trials/sponsor-tickers.ts +8 -6
- package/src/plugins/builtin/cloud/askg/manifest.ts +2 -0
- package/src/plugins/builtin/cloud/plugin.tsx +1 -1
- package/src/plugins/builtin/company-attention/building.tsx +5 -1
- package/src/plugins/builtin/company-attention/client.ts +8 -9
- package/src/plugins/builtin/company-attention/evidence.ts +4 -1
- package/src/plugins/builtin/company-attention/moves.tsx +5 -1
- package/src/plugins/builtin/company-attention/pane.tsx +9 -2
- package/src/plugins/builtin/company-attention/signals.tsx +5 -1
- package/src/plugins/builtin/correlation/headless.ts +13 -5
- package/src/plugins/builtin/correlation/history.ts +22 -0
- package/src/plugins/builtin/cot/client.ts +22 -18
- package/src/plugins/builtin/cot/headless.ts +15 -10
- package/src/plugins/builtin/cot/model.ts +14 -1
- package/src/plugins/builtin/cot/pane.tsx +22 -14
- package/src/plugins/builtin/credit-boards/client.ts +6 -10
- package/src/plugins/builtin/credit-conditions/headless.ts +8 -2
- package/src/plugins/builtin/credit-conditions/index.tsx +5 -1
- package/src/plugins/builtin/credit-conditions/model.ts +4 -0
- package/src/plugins/builtin/credit-conditions/series.ts +3 -1
- package/src/plugins/builtin/credit-documents/detail.tsx +2 -2
- package/src/plugins/builtin/credit-documents/export.ts +173 -0
- package/src/plugins/builtin/credit-documents/headless.ts +7 -7
- package/src/plugins/builtin/credit-documents/model.ts +104 -14
- package/src/plugins/builtin/credit-documents/pane.tsx +12 -5
- package/src/plugins/builtin/custom-view/sources.ts +2 -0
- package/src/plugins/builtin/dividend-yield/client.ts +131 -23
- package/src/plugins/builtin/dividend-yield/headless.ts +44 -16
- package/src/plugins/builtin/dividend-yield/model.ts +8 -5
- package/src/plugins/builtin/dividend-yield/pane.tsx +38 -13
- package/src/plugins/builtin/dividend-yield/ranking.ts +37 -14
- package/src/plugins/builtin/dividend-yield/types.ts +20 -2
- package/src/plugins/builtin/dividend-yield/view.ts +30 -2
- package/src/plugins/builtin/docs/targets.ts +5 -1
- package/src/plugins/builtin/earnings/board-model.ts +16 -15
- package/src/plugins/builtin/earnings/board.tsx +62 -71
- package/src/plugins/builtin/earnings/headless.ts +40 -4
- package/src/plugins/builtin/earnings/scope.ts +31 -43
- package/src/plugins/builtin/earnings-calls/calendar.tsx +2 -2
- package/src/plugins/builtin/earnings-calls/format.ts +31 -2
- package/src/plugins/builtin/earnings-calls/headless.ts +7 -2
- package/src/plugins/builtin/earnings-calls/model.ts +6 -1
- package/src/plugins/builtin/earnings-calls/pane.tsx +5 -4
- package/src/plugins/builtin/econ/calendar-model.ts +14 -9
- package/src/plugins/builtin/econ/figures.ts +72 -0
- package/src/plugins/builtin/econ/index.tsx +33 -3
- package/src/plugins/builtin/econ/types.ts +4 -0
- package/src/plugins/builtin/econ-statistics/defs.ts +6 -0
- package/src/plugins/builtin/econ-statistics/headless.ts +85 -35
- package/src/plugins/builtin/econ-statistics/stats.ts +37 -9
- package/src/plugins/builtin/econ-statistics/view.ts +3 -0
- package/src/plugins/builtin/equity-screener/categories.ts +179 -0
- package/src/plugins/builtin/equity-screener/client.ts +4 -2
- package/src/plugins/builtin/equity-screener/headless.ts +144 -13
- package/src/plugins/builtin/equity-screener/ideas.ts +49 -0
- package/src/plugins/builtin/equity-screener/model.ts +99 -10
- package/src/plugins/builtin/equity-screener/pane.tsx +70 -15
- package/src/plugins/builtin/exposure/charts.tsx +2 -2
- package/src/plugins/builtin/exposure/model.ts +13 -1
- package/src/plugins/builtin/exposure/pane.tsx +4 -4
- package/src/plugins/builtin/fixed-income/pane.tsx +2 -1
- package/src/plugins/builtin/futures/index.tsx +1 -0
- package/src/plugins/builtin/futures/model.ts +2 -1
- package/src/plugins/builtin/futures/table.tsx +2 -2
- package/src/plugins/builtin/futures/use-board-terms.ts +42 -28
- package/src/plugins/builtin/futures/use-front-returns.ts +45 -28
- package/src/plugins/builtin/fx-matrix/client.ts +11 -3
- package/src/plugins/builtin/fx-matrix/headless.ts +6 -3
- package/src/plugins/builtin/fx-matrix/index.tsx +16 -4
- package/src/plugins/builtin/fx-matrix/live-legs.ts +54 -3
- package/src/plugins/builtin/gpu/headless.ts +8 -2
- package/src/plugins/builtin/help/function-index.ts +4 -1
- package/src/plugins/builtin/help/index.tsx +1 -0
- package/src/plugins/builtin/help/keybindings-editor.tsx +18 -14
- package/src/plugins/builtin/help/shortcut-model.ts +1 -1
- package/src/plugins/builtin/holders/fund-holders.ts +51 -0
- package/src/plugins/builtin/holders/headless.ts +18 -1
- package/src/plugins/builtin/iv-history/client.ts +52 -11
- package/src/plugins/builtin/iv-history/format.ts +36 -23
- package/src/plugins/builtin/iv-history/headless.ts +36 -18
- package/src/plugins/builtin/iv-history/index.tsx +2 -2
- package/src/plugins/builtin/iv-history/model.ts +163 -27
- package/src/plugins/builtin/iv-history/pane.tsx +10 -6
- package/src/plugins/builtin/iv-history/screen-pane.tsx +22 -15
- package/src/plugins/builtin/iv-history/universe.ts +5 -1
- package/src/plugins/builtin/jobs/client.ts +25 -29
- package/src/plugins/builtin/kelly-sizer/model.ts +12 -8
- package/src/plugins/builtin/kelly-sizer/pane.tsx +9 -9
- package/src/plugins/builtin/kelly-sizer/sections.tsx +19 -10
- package/src/plugins/builtin/kelly-sizer/types.ts +10 -4
- package/src/plugins/builtin/macro-day/pane.tsx +7 -5
- package/src/plugins/builtin/macro-day/release-table.ts +2 -2
- package/src/plugins/builtin/market-heatmap/data.ts +8 -33
- package/src/plugins/builtin/market-movers/client.ts +25 -7
- package/src/plugins/builtin/market-movers/footer.ts +2 -2
- package/src/plugins/builtin/market-movers/headless.ts +32 -12
- package/src/plugins/builtin/market-movers/index.tsx +26 -14
- package/src/plugins/builtin/market-movers/min-float.ts +74 -0
- package/src/plugins/builtin/market-movers/model.ts +4 -1
- package/src/plugins/builtin/market-movers/screener.ts +14 -0
- package/src/plugins/builtin/market-movers/session-body.tsx +13 -8
- package/src/plugins/builtin/market-movers/session-table.ts +4 -3
- package/src/plugins/builtin/market-movers/table.ts +17 -5
- package/src/plugins/builtin/members/breadth-pane.tsx +82 -0
- package/src/plugins/builtin/members/client.ts +19 -4
- package/src/plugins/builtin/members/headless.ts +20 -7
- package/src/plugins/builtin/members/index.tsx +9 -9
- package/src/plugins/builtin/members/model.ts +23 -4
- package/src/plugins/builtin/members/pane.tsx +25 -15
- package/src/plugins/builtin/news/index.tsx +1 -0
- package/src/plugins/builtin/news/wire/categories.ts +19 -3
- package/src/plugins/builtin/news/wire/industry-pane.tsx +1 -0
- package/src/plugins/builtin/news/wire/news/footer.ts +31 -5
- package/src/plugins/builtin/news/wire/news/preset-pane.tsx +6 -1
- package/src/plugins/builtin/news/wire/news/table.tsx +2 -2
- package/src/plugins/builtin/openfda/client.ts +14 -2
- package/src/plugins/builtin/openfda/index.tsx +3 -2
- package/src/plugins/builtin/openfda/model.ts +9 -0
- package/src/plugins/builtin/openfda/pane.tsx +42 -6
- package/src/plugins/builtin/options/analytics.ts +54 -6
- package/src/plugins/builtin/options/enrichment.ts +17 -5
- package/src/plugins/builtin/options/model-spot.ts +5 -2
- package/src/plugins/builtin/options/strike-window.ts +2 -1
- package/src/plugins/builtin/options/table.ts +6 -9
- package/src/plugins/builtin/options/types.ts +2 -0
- package/src/plugins/builtin/options/view.tsx +42 -12
- package/src/plugins/builtin/options-positioning/client.ts +30 -0
- package/src/plugins/builtin/options-positioning/headless.ts +74 -21
- package/src/plugins/builtin/options-positioning/index.tsx +4 -4
- package/src/plugins/builtin/options-positioning/model.ts +122 -14
- package/src/plugins/builtin/options-positioning/pane.tsx +87 -42
- package/src/plugins/builtin/options-scenario/client.ts +116 -47
- package/src/plugins/builtin/options-scenario/csp-screen-headless.ts +142 -10
- package/src/plugins/builtin/options-scenario/csp-screen-pane.tsx +206 -55
- package/src/plugins/builtin/options-scenario/csp-screen.ts +63 -23
- package/src/plugins/builtin/options-scenario/csp.ts +8 -2
- package/src/plugins/builtin/options-scenario/index.tsx +9 -7
- package/src/plugins/builtin/options-scenario/strategy-screen.ts +532 -0
- package/src/plugins/builtin/pe-band/model.ts +12 -49
- package/src/plugins/builtin/perps/cascade-tabs.tsx +161 -0
- package/src/plugins/builtin/perps/cascade.ts +236 -0
- package/src/plugins/builtin/perps/client.ts +56 -2
- package/src/plugins/builtin/perps/freshness.ts +12 -10
- package/src/plugins/builtin/perps/headless.ts +65 -9
- package/src/plugins/builtin/perps/index.tsx +7 -5
- package/src/plugins/builtin/perps/live-rows.ts +10 -3
- package/src/plugins/builtin/perps/model.ts +18 -4
- package/src/plugins/builtin/perps/pane.tsx +32 -31
- package/src/plugins/builtin/perps/status-line.ts +25 -0
- package/src/plugins/builtin/plugin-module.ts +5 -1
- package/src/plugins/builtin/portfolio-list/cached-account.ts +3 -1
- package/src/plugins/builtin/portfolio-list/cli/bulk-symbols.ts +2 -8
- package/src/plugins/builtin/portfolio-list/cli/portfolio-command.ts +4 -4
- package/src/plugins/builtin/portfolio-list/cli/render.ts +12 -7
- package/src/plugins/builtin/portfolio-list/headless.ts +1 -1
- package/src/plugins/builtin/portfolio-list/pane/futures-front.ts +40 -0
- package/src/plugins/builtin/portfolio-list/pane/index.tsx +4 -1
- package/src/plugins/builtin/portfolio-list/pane/streaming.ts +5 -2
- package/src/plugins/builtin/portfolio-list/pane/supplemental.ts +3 -2
- package/src/plugins/builtin/portfolio-list/quick-add/resolution.ts +3 -1
- package/src/plugins/builtin/portfolio-list/summary/index.tsx +11 -8
- package/src/plugins/builtin/portfolio-list/table.tsx +11 -3
- package/src/plugins/builtin/primary-dealers/client.ts +13 -38
- package/src/plugins/builtin/rate-path/pane.tsx +2 -2
- package/src/plugins/builtin/realized-vol/index.tsx +2 -2
- package/src/plugins/builtin/realized-vol/pane.tsx +3 -3
- package/src/plugins/builtin/relative-rotation/client.ts +18 -4
- package/src/plugins/builtin/relative-rotation/headless.ts +3 -3
- package/src/plugins/builtin/relative-rotation/index.tsx +2 -1
- package/src/plugins/builtin/relative-rotation/model.ts +16 -40
- package/src/plugins/builtin/relative-rotation/pane.tsx +3 -2
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -2
- package/src/plugins/builtin/research/event-model.ts +10 -8
- package/src/plugins/builtin/research/headless.ts +1 -1
- package/src/plugins/builtin/research/relative-valuation-headless.ts +5 -1
- package/src/plugins/builtin/research/relative-valuation-model.ts +31 -3
- package/src/plugins/builtin/research/relative-valuation-pane.tsx +11 -9
- package/src/plugins/builtin/reverse-dcf/headless.ts +5 -3
- package/src/plugins/builtin/reverse-dcf/model.ts +61 -6
- package/src/plugins/builtin/reverse-dcf/pane.tsx +7 -4
- package/src/plugins/builtin/scanner/feed.ts +2 -2
- package/src/plugins/builtin/scanner/flow-model.ts +50 -4
- package/src/plugins/builtin/scanner/flow-pane.tsx +367 -65
- package/src/plugins/builtin/scanner/flow-session.ts +146 -0
- package/src/plugins/builtin/scanner/headless.ts +204 -35
- package/src/plugins/builtin/scanner/index.ts +40 -6
- package/src/plugins/builtin/scanner/live-condition-editor.tsx +222 -0
- package/src/plugins/builtin/scanner/live-feed.ts +168 -0
- package/src/plugins/builtin/scanner/live-headless.ts +138 -0
- package/src/plugins/builtin/scanner/live-model.ts +362 -0
- package/src/plugins/builtin/scanner/live-pane.tsx +677 -0
- package/src/plugins/builtin/scanner/live-track.ts +186 -0
- package/src/plugins/builtin/scanner/lock-note.ts +3 -2
- package/src/plugins/builtin/seasonality/headless.ts +10 -1
- package/src/plugins/builtin/sec/fund-facts-view.tsx +5 -2
- package/src/plugins/builtin/sec/fund-facts.ts +116 -34
- package/src/plugins/builtin/sec/headless.ts +2 -1
- package/src/plugins/builtin/series-inventory.ts +3 -0
- package/src/plugins/builtin/shared/cloud-resource.ts +3 -2
- package/src/plugins/builtin/shared/cloud-upgrade.ts +4 -3
- package/src/plugins/builtin/shared/earnings-calendar.ts +6 -2
- package/src/plugins/builtin/shared/earnings-report.ts +2 -2
- package/src/plugins/builtin/shared/job-posting.ts +1 -0
- package/src/plugins/builtin/shared/macro-releases.ts +65 -0
- package/src/plugins/builtin/shared/members-menu.tsx +3 -2
- package/src/plugins/builtin/shared/pro-lock-note.ts +12 -12
- package/src/plugins/builtin/shared/report-freshness.ts +3 -2
- package/src/plugins/builtin/shared/scanner-capture.ts +3 -0
- package/src/plugins/builtin/shared/ticker-choice.tsx +2 -1
- package/src/plugins/builtin/shared/ticker-company.ts +77 -0
- package/src/plugins/builtin/shared/use-quote-board.ts +10 -3
- package/src/plugins/builtin/shared/volatility/live-session.ts +34 -8
- package/src/plugins/builtin/shared/volatility/pricing.ts +26 -1
- package/src/plugins/builtin/shared/wall-view-lifecycle.ts +3 -1
- package/src/plugins/builtin/themes/headless.ts +6 -4
- package/src/plugins/builtin/themes/model.ts +17 -8
- package/src/plugins/builtin/themes/pane.tsx +7 -7
- package/src/plugins/builtin/ticker-detail/data-panes/historical-prices.tsx +43 -18
- package/src/plugins/builtin/ticker-detail/data-panes/returns-history.ts +3 -2
- package/src/plugins/builtin/ticker-detail/data-panes/returns-model.ts +62 -0
- package/src/plugins/builtin/ticker-detail/data-panes/returns-pane.tsx +83 -46
- package/src/plugins/builtin/ticker-detail/headless.ts +31 -16
- package/src/plugins/builtin/ticker-detail/overview/model.ts +1 -1
- package/src/plugins/builtin/ticker-detail/pane.tsx +3 -2
- package/src/plugins/builtin/ticker-detail/quote-footer.ts +6 -1
- package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +2 -1
- package/src/plugins/builtin/ticker-detail/settings.ts +1 -0
- package/src/plugins/builtin/time-sales/pane.tsx +31 -19
- package/src/plugins/builtin/treasury-daily/client.ts +41 -5
- package/src/plugins/builtin/treasury-daily/headless.ts +2 -2
- package/src/plugins/builtin/treasury-daily/index.tsx +1 -1
- package/src/plugins/builtin/treasury-daily/model.ts +50 -3
- package/src/plugins/builtin/treasury-daily/pane.tsx +81 -2
- package/src/plugins/builtin/vol-surface/charts.tsx +9 -4
- package/src/plugins/builtin/vol-surface/client.ts +9 -17
- package/src/plugins/builtin/vol-surface/headless.ts +63 -14
- package/src/plugins/builtin/vol-surface/index.tsx +2 -2
- package/src/plugins/builtin/vol-surface/model.ts +22 -7
- package/src/plugins/builtin/vol-surface/pane.tsx +48 -9
- package/src/plugins/builtin/vol-surface/tables.ts +31 -3
- package/src/plugins/builtin/volatility/dvol.ts +46 -0
- package/src/plugins/builtin/volatility/index.tsx +2 -2
- package/src/plugins/builtin/volatility/pane.tsx +4 -4
- package/src/plugins/builtin/volatility/series.ts +26 -0
- package/src/plugins/builtin/world-indices/footer.ts +1 -1
- package/src/plugins/builtin/world-venue-map/headless.ts +3 -3
- package/src/plugins/catalog-browser.ts +13 -8
- package/src/plugins/catalog-manifest.ts +2 -1
- package/src/plugins/marketplace/model.ts +18 -5
- package/src/portfolio/column-values.ts +5 -5
- package/src/portfolio/futures-positions.ts +6 -3
- package/src/portfolio/position-quantity.ts +15 -9
- package/src/public/__snapshots__/public-api.test.ts.snap +1 -0
- package/src/remote/resources.ts +2 -1
- package/src/renderers/browser/app-services.ts +1 -7
- package/src/renderers/browser/config-host.ts +2 -55
- package/src/renderers/browser/main.tsx +7 -57
- package/src/renderers/dom/data-table/row.tsx +26 -0
- package/src/renderers/dom/desktop/list-view.tsx +13 -10
- package/src/renderers/dom/desktop/query-bar.tsx +4 -2
- package/src/renderers/dom/desktop/select-field.tsx +4 -2
- package/src/renderers/dom/host/chart-surface.tsx +21 -0
- package/src/renderers/electrobun/bun/desktop/app-name.ts +222 -0
- package/src/renderers/electrobun/bun/desktop/backend.ts +29 -7
- package/src/renderers/electrobun/bun/desktop/host-requests.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/initialization.ts +2 -0
- package/src/renderers/electrobun/bun/desktop/relaunch.ts +74 -25
- package/src/renderers/electrobun/bun/desktop/state-broadcaster.ts +1 -0
- package/src/renderers/electrobun/bun/desktop/update.ts +173 -5
- package/src/renderers/electrobun/bun/desktop/view-ids.ts +52 -0
- package/src/renderers/electrobun/bun/desktop/workspace/index.ts +2 -0
- package/src/renderers/electrobun/bun/index.ts +25 -1
- package/src/renderers/electrobun/shared/app-name.ts +30 -0
- package/src/renderers/electrobun/shared/protocol.ts +10 -0
- package/src/renderers/electrobun/view/backend-rpc.ts +6 -0
- package/src/renderers/electrobun/view/cli-pane-shot-frame.tsx +3 -3
- package/src/renderers/electrobun/view/desktop/window/snapshot.ts +1 -0
- package/src/renderers/electrobun/view/main.tsx +24 -1
- package/src/renderers/electrobun/view/news-source.ts +3 -1
- package/src/renderers/electrobun/view/remote/ticker-repository.ts +14 -7
- package/src/renderers/electrobun/view/view-ids.ts +85 -0
- package/src/renderers/opentui/start.tsx +5 -0
- package/src/renderers/opentui/toast-host.tsx +71 -43
- package/src/series/registry.ts +1 -0
- package/src/series/types.ts +6 -0
- package/src/series/validate.ts +1 -0
- package/src/sources/gloomberb-cloud/index.ts +1 -0
- package/src/sources/gloomberb-cloud/news.ts +47 -7
- package/src/sources/gloomberb-cloud/normalizers.ts +2 -0
- package/src/sources/provider-router/batches.ts +10 -2
- package/src/sources/provider-router/financial-routes.ts +3 -3
- package/src/sources/provider-router/financials.ts +15 -3
- package/src/sources/provider-router/history.ts +28 -7
- package/src/sources/provider-router/index.ts +149 -10
- package/src/sources/provider-router/search.ts +13 -2
- package/src/state/app/context/index.tsx +3 -0
- package/src/state/ticker-cleanup.ts +50 -0
- package/src/sync/auth-transition.ts +19 -9
- package/src/sync/baseline.ts +10 -4
- package/src/sync/controller.ts +70 -19
- package/src/sync/core-contributors.ts +90 -31
- package/src/sync/react.ts +4 -1
- package/src/sync/types.ts +3 -0
- package/src/telemetry/stall-reports-dom.ts +38 -0
- package/src/telemetry/stall-reports-node.ts +75 -0
- package/src/telemetry/stall-reports.ts +214 -0
- package/src/telemetry/usage-counts.ts +0 -18
- package/src/test-support/preload.ts +14 -0
- package/src/theme/colors.ts +8 -0
- package/src/theme/themes.ts +10 -0
- package/src/tickers/company-name.ts +103 -3
- package/src/tickers/listing-venues.ts +38 -3
- package/src/tickers/search/bare-symbol.ts +36 -0
- package/src/tickers/search/index.ts +36 -7
- package/src/tickers/search/ranking.ts +10 -1
- package/src/tickers/search/types.ts +5 -0
- package/src/tickers/terminal-codes.ts +19 -0
- package/src/time-series/alignment.ts +1 -1
- package/src/time-series/base-series.ts +13 -5
- package/src/time-series/daily-valuation.ts +62 -0
- package/src/time-series/economic.ts +18 -4
- package/src/time-series/field-catalog.ts +2 -4
- package/src/time-series/filed-trailing-eps.ts +197 -0
- package/src/time-series/forward-valuation.ts +32 -0
- package/src/time-series/fundamentals.ts +198 -7
- package/src/time-series/hooks.ts +1 -0
- package/src/time-series/ice-credit.ts +3 -0
- package/src/time-series/price-comparison.ts +30 -2
- package/src/time-series/price-units.ts +72 -2
- package/src/time-series/resolve-context.ts +7 -1
- package/src/time-series/resolve.ts +116 -15
- package/src/time-series/series-presentation.ts +40 -6
- package/src/time-series/session-bar.ts +11 -0
- package/src/time-series/spec.ts +5 -3
- package/src/time-series/studies.ts +278 -74
- package/src/time-series/total-return.ts +160 -0
- package/src/time-series/transforms.ts +3 -3
- package/src/time-series/types.ts +27 -1
- package/src/time-series/valuation-releases.ts +39 -0
- package/src/types/data-provider.ts +9 -1
- package/src/types/desktop-window.ts +2 -0
- package/src/types/financials.ts +73 -2
- package/src/types/headless.ts +61 -5
- package/src/types/instrument.ts +3 -0
- package/src/types/news-source.ts +1 -0
- package/src/types/plugin.ts +10 -1
- package/src/ui/host.tsx +3 -0
- package/src/updater/command-names.ts +12 -7
- package/src/updater/failure.ts +4 -1
- package/src/utils/cli-output.ts +42 -1
- package/src/utils/crypto-pair.ts +44 -0
- package/src/utils/financial-statements.ts +6 -1
- package/src/utils/forward-dividend.ts +2 -0
- package/src/utils/fundamentals.ts +24 -3
- package/src/utils/fx-market-hours.ts +26 -3
- package/src/utils/main-thread-stall.ts +35 -2
- package/src/utils/market-capitalization.ts +24 -1
- package/src/utils/network-failure.ts +9 -6
- package/src/utils/option-expiry.ts +48 -1
- package/src/utils/options.ts +10 -0
- package/src/utils/plugin-proxy-hosts.json +1 -0
- package/src/utils/price-history.ts +12 -1
- package/src/utils/shell-word.ts +8 -0
- package/src/utils/trailing-dividend.ts +53 -4
- package/src/plugins/builtin/clinical-trials/ticker-sponsor.ts +0 -17
- package/src/renderers/browser/first-screen.ts +0 -193
|
@@ -18,6 +18,8 @@ export interface SelectControl {
|
|
|
18
18
|
export interface SelectButtonOption<T extends string = string> {
|
|
19
19
|
value: T;
|
|
20
20
|
label: string;
|
|
21
|
+
/** Heading for a contiguous group of choices; never selectable itself. */
|
|
22
|
+
group?: string;
|
|
21
23
|
/** Trigger wording when the full label is too wide for a pane header. */
|
|
22
24
|
short?: string;
|
|
23
25
|
description?: string;
|
|
@@ -130,6 +132,7 @@ export function SelectButton<T extends string = string>({
|
|
|
130
132
|
choices={options.map((option) => ({
|
|
131
133
|
id: option.value,
|
|
132
134
|
label: option.label,
|
|
135
|
+
group: option.group,
|
|
133
136
|
description: option.description,
|
|
134
137
|
disabled: option.disabled,
|
|
135
138
|
color: option.color,
|
|
@@ -158,7 +161,7 @@ export function SelectButton<T extends string = string>({
|
|
|
158
161
|
selectRef={(element) => { selectFieldRef.current = element; }}
|
|
159
162
|
onFocus={onFocus}
|
|
160
163
|
value={value}
|
|
161
|
-
options={options.map((option) => ({ value: option.value, label: option.label, disabled: option.disabled }))}
|
|
164
|
+
options={options.map((option) => ({ value: option.value, label: option.label, group: option.group, disabled: option.disabled }))}
|
|
162
165
|
disabled={disabled}
|
|
163
166
|
onChange={selectValue}
|
|
164
167
|
/>
|
|
@@ -180,6 +183,7 @@ export function SelectButton<T extends string = string>({
|
|
|
180
183
|
options={options.map((option) => ({
|
|
181
184
|
value: option.value,
|
|
182
185
|
label: option.label,
|
|
186
|
+
group: option.group,
|
|
183
187
|
disabled: option.disabled,
|
|
184
188
|
}))}
|
|
185
189
|
disabled={disabled}
|
|
@@ -106,18 +106,21 @@ export function getTableWidth(
|
|
|
106
106
|
* The columns that fit in `width` cells: whole columns in `dropOrder` (least
|
|
107
107
|
* needed first) give way until the rest fit, rather than the table's right
|
|
108
108
|
* edge cutting a value, so a narrow pane drops a name that repeats its ticker
|
|
109
|
-
* before it cuts a date.
|
|
110
|
-
*
|
|
109
|
+
* before it cuts a date. An entry that lists several ids drops them together,
|
|
110
|
+
* for columns that mean little apart (a bid's venue and an ask's). Columns not
|
|
111
|
+
* listed stay at every width. A table that scrolls vertically passes its width
|
|
112
|
+
* less the scrollbar's cell.
|
|
111
113
|
*/
|
|
112
114
|
export function dropColumnsToFit<C extends TableWidthColumn & { id: string }>(
|
|
113
115
|
columns: readonly C[],
|
|
114
116
|
width: number,
|
|
115
|
-
dropOrder: readonly string[],
|
|
117
|
+
dropOrder: readonly (string | readonly string[])[],
|
|
116
118
|
): C[] {
|
|
117
119
|
let shown = [...columns];
|
|
118
|
-
for (const
|
|
120
|
+
for (const step of dropOrder) {
|
|
119
121
|
if (getTableWidth(shown) <= width) break;
|
|
120
|
-
|
|
122
|
+
const ids = typeof step === "string" ? [step] : step;
|
|
123
|
+
shown = shown.filter((column) => !ids.includes(column.id));
|
|
121
124
|
}
|
|
122
125
|
return shown;
|
|
123
126
|
}
|
package/src/core/app-runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { MarketDataCoordinator, getSharedMarketDataCoordinator, setSharedMarketDataCoordinator } from "../market-data/coordinator";
|
|
2
2
|
import { NewsService, type NewsServiceOptions } from "../news/aggregator";
|
|
3
|
+
import { cloudNewsSession } from "../news/cloud-session";
|
|
3
4
|
import { getSharedNewsService, setSharedNewsService } from "../news/hooks";
|
|
4
5
|
import { PluginRegistry } from "../plugins/registry";
|
|
5
6
|
import { releaseSharedRegistry } from "../plugins/registry/shared";
|
|
@@ -61,6 +62,7 @@ export function createAppRuntime({
|
|
|
61
62
|
const newsService = new NewsService({
|
|
62
63
|
connectionHealth: pluginRegistry.connectionHealth,
|
|
63
64
|
pollIntervalMs: () => Math.max(1, config.refreshIntervalMinutes) * 60_000,
|
|
65
|
+
session: cloudNewsSession,
|
|
64
66
|
...newsOptions,
|
|
65
67
|
});
|
|
66
68
|
const modules = { pluginRegistry, marketData, newsService };
|
package/src/core/app-services.ts
CHANGED
|
@@ -27,8 +27,8 @@ export function createAppServices({ config, plugins, externalPlugins }: AppServi
|
|
|
27
27
|
provider: {
|
|
28
28
|
fetchNews: async (query) => (await newsService.load(query)).articles,
|
|
29
29
|
fetchNewsPage: async (query) => {
|
|
30
|
-
const { articles, error, nextCursor } = await newsService.load(query);
|
|
31
|
-
return { articles, error, nextCursor };
|
|
30
|
+
const { articles, error, nextCursor, access } = await newsService.load(query);
|
|
31
|
+
return { articles, error, nextCursor, access };
|
|
32
32
|
},
|
|
33
33
|
getCachedNews: (query) => newsService.getQueryState(query).articles,
|
|
34
34
|
},
|
|
@@ -594,6 +594,7 @@ export function withFocusedPane(
|
|
|
594
594
|
config: syncedConfig,
|
|
595
595
|
paneState: nextPaneState,
|
|
596
596
|
brokerAccounts: reconcileBrokerAccounts(syncedConfig, state.brokerAccounts),
|
|
597
|
+
brokerSyncErrors: Object.fromEntries(Object.entries(state.brokerSyncErrors).filter(([id]) => syncedConfig.brokerInstances.some((instance) => instance.id === id))),
|
|
597
598
|
focusedPaneId,
|
|
598
599
|
previousFocusedPaneId: state.focusedPaneId && state.focusedPaneId !== focusedPaneId
|
|
599
600
|
? state.focusedPaneId
|
|
@@ -605,6 +606,7 @@ export function hydrateDesktopSnapshot(state: AppState, snapshot: DesktopSharedS
|
|
|
605
606
|
return withFocusedPane({
|
|
606
607
|
...state,
|
|
607
608
|
paneState: snapshot.paneState,
|
|
609
|
+
brokerSyncErrors: snapshot.brokerSyncErrors ?? {},
|
|
608
610
|
focusedPaneId: snapshot.focusedPaneId,
|
|
609
611
|
statusBarVisible: snapshot.statusBarVisible,
|
|
610
612
|
}, snapshot.config);
|
|
@@ -203,6 +203,14 @@ export function appReducer(state: AppState, action: AppAction): AppState {
|
|
|
203
203
|
},
|
|
204
204
|
};
|
|
205
205
|
|
|
206
|
+
case "SET_BROKER_SYNC_ERROR": {
|
|
207
|
+
if (!state.config.brokerInstances.some((instance) => instance.id === action.instanceId)) return state;
|
|
208
|
+
const brokerSyncErrors = { ...state.brokerSyncErrors };
|
|
209
|
+
if (action.error) brokerSyncErrors[action.instanceId] = action.error;
|
|
210
|
+
else delete brokerSyncErrors[action.instanceId];
|
|
211
|
+
return { ...state, brokerSyncErrors };
|
|
212
|
+
}
|
|
213
|
+
|
|
206
214
|
case "SET_INITIALIZED":
|
|
207
215
|
return { ...state, initialized: true };
|
|
208
216
|
|
|
@@ -371,6 +379,7 @@ export function createInitialState(config: AppConfig, sessionSnapshot: AppSessio
|
|
|
371
379
|
financials: new Map(),
|
|
372
380
|
exchangeRates: new Map([["USD", 1]]),
|
|
373
381
|
brokerAccounts: {},
|
|
382
|
+
brokerSyncErrors: {},
|
|
374
383
|
focusedPaneId,
|
|
375
384
|
previousFocusedPaneId: null,
|
|
376
385
|
paneState,
|
|
@@ -68,6 +68,8 @@ export interface AppState {
|
|
|
68
68
|
*/
|
|
69
69
|
exchangeRates: Map<string, number>;
|
|
70
70
|
brokerAccounts: Record<string, BrokerAccount[]>;
|
|
71
|
+
/** A failed position import stays visible until this profile syncs successfully. */
|
|
72
|
+
brokerSyncErrors: Record<string, string>;
|
|
71
73
|
/**
|
|
72
74
|
* @deprecated Never set. The app has no left or right panel state; read
|
|
73
75
|
* `focusedPaneId` for the focused pane.
|
|
@@ -131,6 +133,7 @@ export type AppAction =
|
|
|
131
133
|
| { type: "RECORD_COMMAND"; id: string; label: string; arg?: string }
|
|
132
134
|
| { type: "SET_REFRESHING"; symbol: string; refreshing: boolean }
|
|
133
135
|
| { type: "SET_BROKER_ACCOUNTS"; instanceId: string; accounts: BrokerAccount[] }
|
|
136
|
+
| { type: "SET_BROKER_SYNC_ERROR"; instanceId: string; error: string | null }
|
|
134
137
|
| { type: "SET_INITIALIZED" }
|
|
135
138
|
| { type: "TOGGLE_STATUS_BAR" }
|
|
136
139
|
| { type: "SET_THEME"; theme: string }
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
import { recordOrNull } from "../../utils/guards";
|
|
21
21
|
|
|
22
22
|
const CHART_RANGES = new Set<TimeRange>(TIME_RANGES);
|
|
23
|
-
const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "ohlc", "hlc"]);
|
|
23
|
+
const PRICE_RENDER_MODES = new Set<SeriesStyle>(["area", "line", "candles", "hollow", "heikin-ashi", "ohlc", "hlc"]);
|
|
24
24
|
const LEGACY_INDICATOR_IDS = new Set<LegacyChartIndicatorId>([
|
|
25
25
|
"volume",
|
|
26
26
|
"sma20",
|
|
@@ -4,6 +4,7 @@ import { hydrateTickerMetadata } from "../tickers/metadata";
|
|
|
4
4
|
import type { TickerMetadata, TickerRecord } from "../types/ticker";
|
|
5
5
|
import { BROWSER_STORAGE_KEYS, SafeJsonStorage, type StorageLike } from "./json-storage";
|
|
6
6
|
import { isRecord } from "../utils/guards";
|
|
7
|
+
import { runTickerWrite } from "./ticker-writes";
|
|
7
8
|
|
|
8
9
|
type StoredTickers = Record<string, TickerMetadata>;
|
|
9
10
|
|
|
@@ -39,20 +40,28 @@ export class JsonTickerRepository implements AppTickerRepositoryPort {
|
|
|
39
40
|
return metadata ? hydrate(metadata) : null;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
private writeTicker(ticker: TickerRecord): void {
|
|
43
44
|
const symbol = normalizeSymbol(ticker.metadata.ticker);
|
|
44
45
|
this.data.set({ ...this.data.get(), [symbol]: ticker.metadata });
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
saveTicker(ticker: TickerRecord): Promise<void> {
|
|
49
|
+
return runTickerWrite(this, ticker.metadata.ticker, () => this.writeTicker(ticker));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
|
|
48
53
|
const ticker = { metadata };
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
return runTickerWrite(this, metadata.ticker, () => {
|
|
55
|
+
this.writeTicker(ticker);
|
|
56
|
+
return ticker;
|
|
57
|
+
});
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
deleteTicker(symbol: string): Promise<void> {
|
|
61
|
+
return runTickerWrite(this, symbol, () => {
|
|
62
|
+
const tickers = { ...this.data.get() };
|
|
63
|
+
delete tickers[normalizeSymbol(symbol)];
|
|
64
|
+
this.data.set(tickers);
|
|
65
|
+
});
|
|
57
66
|
}
|
|
58
67
|
}
|
package/src/data/saved-copies.ts
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Answers
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
2
|
+
* Answers given from a copy saved earlier because refreshing it failed:
|
|
3
|
+
* offline, a hung or refused proxy, a service error.
|
|
4
|
+
*
|
|
5
|
+
* A CLI command answers once, so the line it ends with says "couldn't
|
|
6
|
+
* refresh; saved copy from <time>" instead of reading as live or delayed, but
|
|
7
|
+
* only when what it shows came from such a copy: a saved copy of something
|
|
8
|
+
* else it loaded on the way (a fallback for a pane it does not print) says
|
|
9
|
+
* nothing about the data on screen. The app says the same of a quote, in the
|
|
10
|
+
* footer of a pane showing one (`savedCopyAt`), until a refresh replaces it.
|
|
8
11
|
*
|
|
9
12
|
* The copy served is marked, by a symbol property that object spreads carry
|
|
10
13
|
* along and JSON never writes, and the command's output is searched for the
|
|
11
|
-
* marks.
|
|
12
|
-
*
|
|
13
|
-
* footers.
|
|
14
|
+
* marks. Outside a command only quotes are marked (`quoteServedFromSavedCopy`):
|
|
15
|
+
* the app keeps running for hours, and its other panes say how current their
|
|
16
|
+
* data is in their own footers. A quote built from a newer answer must not
|
|
17
|
+
* inherit a mark through a spread: `withSavedCopyMarkOf` gives it the mark of
|
|
18
|
+
* what was served instead.
|
|
14
19
|
*/
|
|
15
20
|
const SAVED_AT = Symbol("gloomberb.savedCopyAt");
|
|
16
21
|
|
|
17
22
|
let collecting = 0;
|
|
18
|
-
|
|
23
|
+
/** Whether anything was ever marked, so output is not searched for marks that cannot be there. */
|
|
24
|
+
let anyMarked = false;
|
|
19
25
|
|
|
20
26
|
type Marked = { [SAVED_AT]?: number };
|
|
21
27
|
|
|
22
28
|
function mark<T>(value: T, savedAt: number): T {
|
|
23
29
|
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
24
|
-
|
|
30
|
+
anyMarked = true;
|
|
25
31
|
return { ...value, [SAVED_AT]: savedAt };
|
|
26
32
|
}
|
|
27
33
|
|
|
@@ -36,13 +42,48 @@ export function servedFromSavedCopy<T>(value: T, savedAt: number): T {
|
|
|
36
42
|
return (Array.isArray(value) ? value.map((item) => mark(item, savedAt)) : mark(value, savedAt)) as T;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
/** A quote answered from a copy saved at `savedAt` (ms) after its refresh failed: marked, in a command and in the app. */
|
|
46
|
+
export function quoteServedFromSavedCopy<T extends object>(quote: T, savedAt: number): T {
|
|
47
|
+
return Number.isFinite(savedAt) ? mark(quote, savedAt) : quote;
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
/**
|
|
40
51
|
* The saved-copy mark of `value`, to spread into what is drawn from it (a
|
|
41
52
|
* report row built from a quote's fields), or nothing when it has none.
|
|
42
53
|
*/
|
|
43
54
|
export function savedCopyMark(value: unknown): object {
|
|
55
|
+
const savedAt = savedCopyAt(value);
|
|
56
|
+
return savedAt != null ? { [SAVED_AT]: savedAt } : {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** When `value` itself was saved, if it is a saved copy served after a failed refresh; null otherwise. */
|
|
60
|
+
export function savedCopyAt(value: unknown): number | null {
|
|
44
61
|
const savedAt = value && typeof value === "object" ? (value as Marked)[SAVED_AT] : undefined;
|
|
45
|
-
return typeof savedAt === "number" ?
|
|
62
|
+
return typeof savedAt === "number" ? savedAt : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The oldest saved copy among `values` (a pane's quotes), by when it was saved; null when none is one. */
|
|
66
|
+
export function oldestSavedCopyAt(values: Iterable<unknown>): number | null {
|
|
67
|
+
let oldest: number | null = null;
|
|
68
|
+
for (const value of values) {
|
|
69
|
+
const savedAt = savedCopyAt(value);
|
|
70
|
+
if (savedAt != null) oldest = Math.min(oldest ?? savedAt, savedAt);
|
|
71
|
+
}
|
|
72
|
+
return oldest;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* `value` carrying the saved-copy mark of `served` and no other: what is
|
|
77
|
+
* built from an answer (resolved, merged onto an older quote) is a saved copy
|
|
78
|
+
* exactly when that answer was.
|
|
79
|
+
*/
|
|
80
|
+
export function withSavedCopyMarkOf<T extends object>(value: T, served: unknown): T {
|
|
81
|
+
const savedAt = savedCopyAt(served);
|
|
82
|
+
const own = savedCopyAt(value);
|
|
83
|
+
if (own === savedAt) return value;
|
|
84
|
+
if (savedAt != null) return mark(value, savedAt);
|
|
85
|
+
const { [SAVED_AT]: _dropped, ...rest } = value as T & Marked;
|
|
86
|
+
return rest as T;
|
|
46
87
|
}
|
|
47
88
|
|
|
48
89
|
/** How far into a command's output the marks are looked for, so a large report is not walked whole. */
|
|
@@ -55,7 +96,7 @@ const MAX_NODES = 50_000;
|
|
|
55
96
|
* came from one. The oldest, since that is how old what is shown can be.
|
|
56
97
|
*/
|
|
57
98
|
export function savedCopyShownAt(shown: unknown): number | null {
|
|
58
|
-
if (collecting === 0 ||
|
|
99
|
+
if (collecting === 0 || !anyMarked) return null;
|
|
59
100
|
let oldest: number | null = null;
|
|
60
101
|
let budget = MAX_NODES;
|
|
61
102
|
const seen = new Set<object>();
|
|
@@ -71,14 +112,12 @@ export function savedCopyShownAt(shown: unknown): number | null {
|
|
|
71
112
|
return oldest;
|
|
72
113
|
}
|
|
73
114
|
|
|
74
|
-
/** Runs one command,
|
|
115
|
+
/** Runs one command, whose output `savedCopyShownAt` then searches for saved copies. */
|
|
75
116
|
export async function collectSavedCopies<T>(run: () => Promise<T>): Promise<T> {
|
|
76
|
-
if (collecting === 0) marked = 0;
|
|
77
117
|
collecting += 1;
|
|
78
118
|
try {
|
|
79
119
|
return await run();
|
|
80
120
|
} finally {
|
|
81
121
|
collecting -= 1;
|
|
82
|
-
if (collecting === 0) marked = 0;
|
|
83
122
|
}
|
|
84
123
|
}
|
|
@@ -2,6 +2,7 @@ import { logFailure } from "../utils/log-failure";
|
|
|
2
2
|
import type { TickerRecord, TickerMetadata } from "../types/ticker";
|
|
3
3
|
import type { TickerStore } from "./ticker-store";
|
|
4
4
|
import { hydrateTickerMetadata } from "../tickers/metadata";
|
|
5
|
+
import { runTickerWrite } from "./ticker-writes";
|
|
5
6
|
|
|
6
7
|
function parseTickerMetadata(json: string): TickerMetadata {
|
|
7
8
|
return hydrateTickerMetadata(JSON.parse(json) as Record<string, unknown>);
|
|
@@ -32,22 +33,28 @@ export class TickerRepository {
|
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
this
|
|
36
|
+
saveTicker(ticker: TickerRecord): Promise<void> {
|
|
37
|
+
return runTickerWrite(this, ticker.metadata.ticker, () => {
|
|
38
|
+
this.tickers.save(ticker.metadata.ticker, ticker.metadata);
|
|
39
|
+
});
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
/** Saves several tickers in one write. */
|
|
40
|
-
|
|
41
|
-
this
|
|
43
|
+
saveTickers(tickers: readonly TickerRecord[]): Promise<void> {
|
|
44
|
+
return runTickerWrite(this, tickers.map((ticker) => ticker.metadata.ticker), () => {
|
|
45
|
+
this.tickers.saveMany(tickers.map((ticker) => ({ symbol: ticker.metadata.ticker, metadata: ticker.metadata })));
|
|
46
|
+
});
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
createTicker(metadata: TickerMetadata): Promise<TickerRecord> {
|
|
45
50
|
const ticker: TickerRecord = { metadata };
|
|
46
|
-
|
|
47
|
-
|
|
51
|
+
return runTickerWrite(this, metadata.ticker, () => {
|
|
52
|
+
this.tickers.save(metadata.ticker, metadata);
|
|
53
|
+
return ticker;
|
|
54
|
+
});
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
|
|
51
|
-
this.tickers.delete(symbol);
|
|
57
|
+
deleteTicker(symbol: string): Promise<void> {
|
|
58
|
+
return runTickerWrite(this, symbol, () => this.tickers.delete(symbol));
|
|
52
59
|
}
|
|
53
60
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
interface TickerWrites {
|
|
2
|
+
revisions: Map<string, number>;
|
|
3
|
+
tails: Map<string, Promise<unknown>>;
|
|
4
|
+
pending: Set<Promise<unknown>>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const repositories = new WeakMap<object, TickerWrites>();
|
|
8
|
+
|
|
9
|
+
function writesFor(repository: object): TickerWrites {
|
|
10
|
+
let writes = repositories.get(repository);
|
|
11
|
+
if (!writes) {
|
|
12
|
+
writes = { revisions: new Map(), tails: new Map(), pending: new Set() };
|
|
13
|
+
repositories.set(repository, writes);
|
|
14
|
+
}
|
|
15
|
+
return writes;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function tickerKey(symbol: string): string {
|
|
19
|
+
return symbol.trim().toUpperCase();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Reserve writes when requested, including ones waiting for an earlier acknowledgement. */
|
|
23
|
+
export function runTickerWrite<Result>(
|
|
24
|
+
repository: object,
|
|
25
|
+
symbols: string | readonly string[],
|
|
26
|
+
write: () => Result | PromiseLike<Result>,
|
|
27
|
+
): Promise<Result> {
|
|
28
|
+
const writes = writesFor(repository);
|
|
29
|
+
const keys = [...new Set((typeof symbols === "string" ? [symbols] : symbols).map(tickerKey))];
|
|
30
|
+
const previous = [...new Set(keys.flatMap((key) => {
|
|
31
|
+
const tail = writes.tails.get(key);
|
|
32
|
+
return tail ? [tail] : [];
|
|
33
|
+
}))];
|
|
34
|
+
let resolve!: (value: Result | PromiseLike<Result>) => void;
|
|
35
|
+
let reject!: (error: unknown) => void;
|
|
36
|
+
const result = new Promise<Result>((accept, fail) => {
|
|
37
|
+
resolve = accept;
|
|
38
|
+
reject = fail;
|
|
39
|
+
});
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
writes.revisions.set(key, (writes.revisions.get(key) ?? 0) + 1);
|
|
42
|
+
writes.tails.set(key, result);
|
|
43
|
+
}
|
|
44
|
+
writes.pending.add(result);
|
|
45
|
+
const finished = () => {
|
|
46
|
+
writes.pending.delete(result);
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (writes.tails.get(key) === result) writes.tails.delete(key);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
void result.then(finished, finished);
|
|
52
|
+
const start = () => {
|
|
53
|
+
try {
|
|
54
|
+
resolve(write());
|
|
55
|
+
} catch (error) {
|
|
56
|
+
reject(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
if (previous.length) void Promise.allSettled(previous).then(start);
|
|
60
|
+
else start();
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Changes immediately when a save or deletion is requested, before its acknowledgement. */
|
|
65
|
+
export function tickerWriteRevision(repository: object, symbol: string): number {
|
|
66
|
+
return repositories.get(repository)?.revisions.get(tickerKey(symbol)) ?? 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Whether a write arrived after a caller last waited for acknowledgements. */
|
|
70
|
+
export function hasPendingTickerWrites(repository: object): boolean {
|
|
71
|
+
return (repositories.get(repository)?.pending.size ?? 0) > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Let save-await callers publish their edits before cleanup reads the current rows. */
|
|
75
|
+
export async function settleTickerWrites(repository: object): Promise<void> {
|
|
76
|
+
const writes = writesFor(repository);
|
|
77
|
+
do {
|
|
78
|
+
// Each writer handles its own failure; cleanup still needs the last published row.
|
|
79
|
+
await Promise.allSettled([...writes.pending]);
|
|
80
|
+
} while (writes.pending.size);
|
|
81
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A keyed in-memory cache that serves a loaded value for a while and shares a
|
|
3
|
+
* load that is still out. It is what a plugin reaches for instead of keeping
|
|
4
|
+
* its own `Map<key, { at, value }>` with a TTL constant and `Date.now() - at`
|
|
5
|
+
* checks, each copy with its own rule for a concurrent call, a forced reload
|
|
6
|
+
* and a failed load.
|
|
7
|
+
*
|
|
8
|
+
* It holds nothing across a restart, and `load` never answers with an expired
|
|
9
|
+
* value. A pane whose data must outlive a restart, or fall back to the saved
|
|
10
|
+
* copy when a refresh fails, wants `createPluginCache` (src/data/plugin-cache.ts).
|
|
11
|
+
*
|
|
12
|
+
* The rules, for every key:
|
|
13
|
+
* - A fresh value is served as it is; a load that is still out is shared, so
|
|
14
|
+
* two callers never start two requests for one key. A load is shared for
|
|
15
|
+
* no longer than `ttlMs` (a number), so one that hangs cannot hold the key.
|
|
16
|
+
* - `force` skips a fresh value and starts a load, except that it joins one
|
|
17
|
+
* that is already out when that load began after the value it would
|
|
18
|
+
* replace: panes that refresh together make one request, not one each.
|
|
19
|
+
* - The last good value stays until a load brings a newer one. A forced
|
|
20
|
+
* load that fails, or brings a value `keep` refuses, leaves it in place,
|
|
21
|
+
* and `peek(key, { stale: true })` reads it past its TTL while a reload is
|
|
22
|
+
* out, for a view that keeps drawing the last copy.
|
|
23
|
+
* - A load that rejects reaches every caller waiting on it. By default
|
|
24
|
+
* nothing is kept, so the next call asks again; `rejectionMs` keeps the
|
|
25
|
+
* rejection that long, for a load a poll would otherwise repeat each tick.
|
|
26
|
+
* - A load runs as soon as `load` is called, not on a later tick.
|
|
27
|
+
*/
|
|
28
|
+
export interface TtlCacheOptions<V> {
|
|
29
|
+
/** How long a loaded value is served, from when it arrives; a function sets it per value. */
|
|
30
|
+
ttlMs: number | ((value: V) => number);
|
|
31
|
+
/**
|
|
32
|
+
* Whether a loaded value is kept. One that is not still answers the callers
|
|
33
|
+
* waiting on it, and the next call loads again. Default: every value.
|
|
34
|
+
*/
|
|
35
|
+
keep?: (value: V) => boolean;
|
|
36
|
+
/** How long a rejected load is kept and served again. Default 0: it is dropped. */
|
|
37
|
+
rejectionMs?: number;
|
|
38
|
+
/** The oldest keys go first past this many. Default: unbounded. */
|
|
39
|
+
maxEntries?: number;
|
|
40
|
+
/** The clock, for tests. */
|
|
41
|
+
now?: () => number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TtlCache<V> {
|
|
45
|
+
/** The key's fresh value or the load already out for it; otherwise starts `load`. */
|
|
46
|
+
load(key: string, load: () => V | PromiseLike<V>, options?: { force?: boolean }): Promise<V>;
|
|
47
|
+
/**
|
|
48
|
+
* The key's fresh value, `undefined` when it has none. `stale` also reads
|
|
49
|
+
* the last good value past its TTL, for a view that keeps drawing it until
|
|
50
|
+
* the next load lands.
|
|
51
|
+
*/
|
|
52
|
+
peek(key: string, options?: { stale?: boolean }): V | undefined;
|
|
53
|
+
/** Whether the key's last load was rejected and `rejectionMs` still keeps that rejection. */
|
|
54
|
+
failed(key: string): boolean;
|
|
55
|
+
/** Keeps a value that arrived by another road, for the TTL. */
|
|
56
|
+
set(key: string, value: V): void;
|
|
57
|
+
/**
|
|
58
|
+
* Forgets the key. With `only`, a promise `load` returned, forgets just the
|
|
59
|
+
* load or kept rejection it stands for, and only while it is still the
|
|
60
|
+
* key's: a caller that gives up on a load cannot drop a newer one.
|
|
61
|
+
*/
|
|
62
|
+
delete(key: string, only?: Promise<V>): void;
|
|
63
|
+
clear(): void;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface Slot<V> {
|
|
67
|
+
/** The last good value: fresh until `expiresAt`, readable as stale after. */
|
|
68
|
+
held?: { value: V; arrivedAt: number; expiresAt: number };
|
|
69
|
+
/** The load that is out. */
|
|
70
|
+
loading?: { promise: Promise<V>; startedAt: number };
|
|
71
|
+
/** A rejected load kept for `rejectionMs`. */
|
|
72
|
+
failed?: { promise: Promise<V>; expiresAt: number };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function createTtlCache<V>(options: TtlCacheOptions<V>): TtlCache<V> {
|
|
76
|
+
const { keep = () => true, rejectionMs = 0, maxEntries = Infinity, now: clock = () => Date.now() } = options;
|
|
77
|
+
const ttlFor = (value: V) => typeof options.ttlMs === "function" ? options.ttlMs(value) : options.ttlMs;
|
|
78
|
+
const shareMs = typeof options.ttlMs === "number" ? options.ttlMs : Infinity;
|
|
79
|
+
const slots = new Map<string, Slot<V>>();
|
|
80
|
+
|
|
81
|
+
const store = (key: string, slot: Slot<V>) => {
|
|
82
|
+
slots.delete(key);
|
|
83
|
+
slots.set(key, slot);
|
|
84
|
+
while (slots.size > maxEntries) slots.delete(slots.keys().next().value!);
|
|
85
|
+
};
|
|
86
|
+
/** A slot with nothing in it is not kept. */
|
|
87
|
+
const tidy = (key: string, slot: Slot<V>) => {
|
|
88
|
+
if (!slot.held && !slot.loading && !slot.failed && slots.get(key) === slot) slots.delete(key);
|
|
89
|
+
};
|
|
90
|
+
const hold = (slot: Slot<V>, value: V) => {
|
|
91
|
+
const arrivedAt = clock();
|
|
92
|
+
slot.held = { value, arrivedAt, expiresAt: arrivedAt + ttlFor(value) };
|
|
93
|
+
slot.failed = undefined;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
load(key, load, { force = false } = {}) {
|
|
98
|
+
const at = clock();
|
|
99
|
+
const slot = slots.get(key) ?? {};
|
|
100
|
+
const { held, loading, failed } = slot;
|
|
101
|
+
if (!force) {
|
|
102
|
+
if (held && held.expiresAt > at) return Promise.resolve(held.value);
|
|
103
|
+
if (failed && failed.expiresAt > at) return failed.promise;
|
|
104
|
+
}
|
|
105
|
+
if (loading && at - loading.startedAt < shareMs && (!force || !held || loading.startedAt >= held.arrivedAt)) return loading.promise;
|
|
106
|
+
|
|
107
|
+
const run: NonNullable<Slot<V>["loading"]> = { promise: undefined as unknown as Promise<V>, startedAt: at };
|
|
108
|
+
const current = () => slots.get(key)?.loading === run;
|
|
109
|
+
run.promise = new Promise<V>((resolve) => resolve(load())).then((value) => {
|
|
110
|
+
if (current()) {
|
|
111
|
+
slot.loading = undefined;
|
|
112
|
+
try {
|
|
113
|
+
// A `keep` or TTL function that throws rejects for the callers, like the load.
|
|
114
|
+
if (keep(value)) hold(slot, value);
|
|
115
|
+
} finally {
|
|
116
|
+
tidy(key, slot);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}, (error: unknown) => {
|
|
121
|
+
if (current()) {
|
|
122
|
+
slot.loading = undefined;
|
|
123
|
+
const fresh = slot.held && slot.held.expiresAt > clock();
|
|
124
|
+
if (rejectionMs > 0 && !fresh) slot.failed = { promise: run.promise, expiresAt: clock() + rejectionMs };
|
|
125
|
+
tidy(key, slot);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
});
|
|
129
|
+
// A caller that drops the promise must not raise an unhandled rejection.
|
|
130
|
+
run.promise.then(undefined, () => { /* Intentional ignore: every caller holds this promise and sees the rejection. */ });
|
|
131
|
+
slot.loading = run;
|
|
132
|
+
slot.failed = undefined;
|
|
133
|
+
store(key, slot);
|
|
134
|
+
return run.promise;
|
|
135
|
+
},
|
|
136
|
+
peek(key, { stale = false } = {}) {
|
|
137
|
+
const held = slots.get(key)?.held;
|
|
138
|
+
return held && (stale || held.expiresAt > clock()) ? held.value : undefined;
|
|
139
|
+
},
|
|
140
|
+
failed(key) {
|
|
141
|
+
const failed = slots.get(key)?.failed;
|
|
142
|
+
return !!failed && failed.expiresAt > clock();
|
|
143
|
+
},
|
|
144
|
+
set(key, value) {
|
|
145
|
+
if (!keep(value)) return;
|
|
146
|
+
const slot = slots.get(key) ?? {};
|
|
147
|
+
hold(slot, value);
|
|
148
|
+
store(key, slot);
|
|
149
|
+
},
|
|
150
|
+
delete(key, only) {
|
|
151
|
+
const slot = slots.get(key);
|
|
152
|
+
if (!slot) return;
|
|
153
|
+
if (!only) {
|
|
154
|
+
slots.delete(key);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (slot.loading?.promise === only) slot.loading = undefined;
|
|
158
|
+
if (slot.failed?.promise === only) slot.failed = undefined;
|
|
159
|
+
tidy(key, slot);
|
|
160
|
+
},
|
|
161
|
+
clear() {
|
|
162
|
+
slots.clear();
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|