@gloom-sh/terminal 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
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
2
|
import type { CachePolicy, PersistedResourceValue } from "../types/persistence";
|
|
3
3
|
import type { JsonValue } from "./sqlite/json";
|
|
4
|
+
import { createSqliteAccess, type SqliteAccess } from "./sqlite/access";
|
|
4
5
|
import { safeParseJson, serializeJson } from "./sqlite/json";
|
|
5
|
-
import {
|
|
6
|
+
import { logFailure } from "../utils/log-failure";
|
|
6
7
|
|
|
7
8
|
const RESOURCE_CACHE_SOFT_ROW_LIMIT = 25_000;
|
|
8
9
|
const RESOURCE_CACHE_SOFT_SIZE_LIMIT = 100 * 1024 * 1024;
|
|
@@ -13,6 +14,13 @@ const RESOURCE_CACHE_MAINTENANCE_BYTE_INTERVAL = Math.floor(
|
|
|
13
14
|
const RESOURCE_CACHE_MAINTENANCE_TIME_INTERVAL_MS = 5 * 60_000;
|
|
14
15
|
/** Rows read per eviction pass, so a full cache is evicted in slices. */
|
|
15
16
|
const RESOURCE_CACHE_PRUNE_BATCH_SIZE = 512;
|
|
17
|
+
/**
|
|
18
|
+
* A read moves its row up the eviction order in memory, and the rows read
|
|
19
|
+
* since are written in one transaction this often, before an eviction and
|
|
20
|
+
* at close: a read writes nothing. A crash loses at most this much of the
|
|
21
|
+
* order, which only decides what is evicted first.
|
|
22
|
+
*/
|
|
23
|
+
const RESOURCE_CACHE_TOUCH_FLUSH_MS = 30_000;
|
|
16
24
|
const DEFAULT_RESOURCE_SCHEMA_VERSION = 1;
|
|
17
25
|
const EMPTY_VARIANT_KEY = "";
|
|
18
26
|
const EMPTY_SOURCE_KEY = "";
|
|
@@ -93,6 +101,10 @@ function isStale(staleAt: number): boolean {
|
|
|
93
101
|
return staleAt < Date.now();
|
|
94
102
|
}
|
|
95
103
|
|
|
104
|
+
function rowMapKey(key: ResourceCacheKey): string {
|
|
105
|
+
return [key.namespace, key.kind, key.entityKey, normalizeVariantKey(key.variantKey), normalizeSourceKey(key.sourceKey)].join("\u0000");
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
function sortRows(rows: ResourceCacheRow[]): ResourceCacheRow[] {
|
|
97
109
|
return [...rows].sort((a, b) => {
|
|
98
110
|
if (a.expires_at !== b.expires_at) return b.expires_at - a.expires_at;
|
|
@@ -106,8 +118,18 @@ export class ResourceStore {
|
|
|
106
118
|
private writesSinceMaintenance = 0;
|
|
107
119
|
private bytesWrittenSinceMaintenance = 0;
|
|
108
120
|
private lastMaintenanceAt = 0;
|
|
121
|
+
/** Reads not yet written to the eviction order, by row. */
|
|
122
|
+
private readonly pendingTouches = new Map<string, { key: ResourceCacheKey; touchedAt: number }>();
|
|
123
|
+
private touchTimer: ReturnType<typeof setTimeout> | null = null;
|
|
109
124
|
|
|
110
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Everything here can be fetched again, so no statement waits for another
|
|
127
|
+
* program's lock: a locked read is a miss, and a locked write is skipped.
|
|
128
|
+
*/
|
|
129
|
+
constructor(
|
|
130
|
+
private readonly db: Database,
|
|
131
|
+
private readonly access: SqliteAccess = createSqliteAccess(db),
|
|
132
|
+
) {}
|
|
111
133
|
|
|
112
134
|
private toCachedResource<T>(row: ResourceCacheRow): CachedResourceRecord<T> | null {
|
|
113
135
|
const value = safeParseJson<T>(row.payload);
|
|
@@ -132,7 +154,8 @@ export class ResourceStore {
|
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
private deleteExact(key: ResourceCacheKey): void {
|
|
135
|
-
|
|
157
|
+
this.pendingTouches.delete(rowMapKey(key));
|
|
158
|
+
this.access.writeCache("delete cached resource", () => {
|
|
136
159
|
this.db
|
|
137
160
|
.query(
|
|
138
161
|
`DELETE FROM resource_cache
|
|
@@ -148,74 +171,124 @@ export class ResourceStore {
|
|
|
148
171
|
});
|
|
149
172
|
}
|
|
150
173
|
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
/** Moves a read row up the eviction order, unless it moved within `touchIntervalMs`. */
|
|
175
|
+
private touchRead(record: CachedResourceRecord, options: GetResourceOptions): void {
|
|
176
|
+
const mapKey = rowMapKey(record);
|
|
177
|
+
record.lastAccessedAt = Math.max(record.lastAccessedAt, this.pendingTouches.get(mapKey)?.touchedAt ?? 0);
|
|
178
|
+
if (options.touch === false) return;
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
if (options.touchIntervalMs != null && now - record.lastAccessedAt < options.touchIntervalMs) return;
|
|
181
|
+
const { namespace, kind, entityKey, variantKey, sourceKey } = record;
|
|
182
|
+
this.pendingTouches.set(mapKey, { key: { namespace, kind, entityKey, variantKey, sourceKey }, touchedAt: now });
|
|
183
|
+
record.lastAccessedAt = now;
|
|
184
|
+
if (this.touchTimer) return;
|
|
185
|
+
this.touchTimer = setTimeout(() => {
|
|
186
|
+
this.touchTimer = null;
|
|
187
|
+
try {
|
|
188
|
+
this.flushTouches();
|
|
189
|
+
} catch (error) {
|
|
190
|
+
logFailure("data.resource-store", "Saving the cache's eviction order failed")(error);
|
|
191
|
+
}
|
|
192
|
+
}, RESOURCE_CACHE_TOUCH_FLUSH_MS);
|
|
193
|
+
(this.touchTimer as { unref?: () => void }).unref?.();
|
|
153
194
|
}
|
|
154
195
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Writes the reads since the last flush into the eviction order, in one
|
|
198
|
+
* transaction. False when another program had the cache locked; they are
|
|
199
|
+
* kept for the next one.
|
|
200
|
+
*/
|
|
201
|
+
flushTouches(): boolean {
|
|
202
|
+
if (this.touchTimer) {
|
|
203
|
+
clearTimeout(this.touchTimer);
|
|
204
|
+
this.touchTimer = null;
|
|
205
|
+
}
|
|
206
|
+
if (this.pendingTouches.size === 0) return true;
|
|
207
|
+
const touches = [...this.pendingTouches.values()];
|
|
208
|
+
const written = this.access.writeCache("touch cached resources", () => {
|
|
209
|
+
const update = this.db.query(
|
|
210
|
+
`UPDATE resource_cache
|
|
211
|
+
SET last_accessed_at = MAX(last_accessed_at, ?)
|
|
212
|
+
WHERE namespace = ? AND kind = ? AND entity_key = ? AND variant_key = ? AND source_key = ?`,
|
|
213
|
+
);
|
|
214
|
+
this.db.transaction(() => {
|
|
215
|
+
for (const { key, touchedAt } of touches) {
|
|
216
|
+
update.run(
|
|
217
|
+
touchedAt,
|
|
218
|
+
key.namespace,
|
|
219
|
+
key.kind,
|
|
220
|
+
key.entityKey,
|
|
221
|
+
normalizeVariantKey(key.variantKey),
|
|
222
|
+
normalizeSourceKey(key.sourceKey),
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
173
226
|
});
|
|
174
|
-
|
|
227
|
+
if (written) this.pendingTouches.clear();
|
|
228
|
+
return written;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Writes the reads still held in memory; the connection closes after. A
|
|
233
|
+
* failure loses only that order: the queued user writes and the
|
|
234
|
+
* connection still have to close.
|
|
235
|
+
*/
|
|
236
|
+
close(): void {
|
|
237
|
+
try {
|
|
238
|
+
this.flushTouches();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
logFailure("data.resource-store", "Saving the cache's eviction order at close failed")(error);
|
|
241
|
+
}
|
|
242
|
+
if (this.touchTimer) clearTimeout(this.touchTimer);
|
|
243
|
+
this.touchTimer = null;
|
|
175
244
|
}
|
|
176
245
|
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
/** Null when another program has the cache locked; maintenance waits for the next write. */
|
|
247
|
+
private readCacheFootprint(operationName: string): { count: number; totalSize: number } | null {
|
|
248
|
+
const stats = this.access.readCache(operationName, () => (
|
|
179
249
|
this.db
|
|
180
250
|
.query<{ count: number; total_size: number }, []>(
|
|
181
251
|
"SELECT COUNT(*) as count, COALESCE(SUM(size_bytes), 0) as total_size FROM resource_cache",
|
|
182
252
|
)
|
|
183
253
|
.get()
|
|
184
254
|
));
|
|
185
|
-
return { count: stats
|
|
255
|
+
return stats ? { count: stats.count ?? 0, totalSize: stats.total_size ?? 0 } : null;
|
|
186
256
|
}
|
|
187
257
|
|
|
188
258
|
private withinSoftLimits(count: number, totalSize: number): boolean {
|
|
189
259
|
return count <= RESOURCE_CACHE_SOFT_ROW_LIMIT && totalSize <= RESOURCE_CACHE_SOFT_SIZE_LIMIT;
|
|
190
260
|
}
|
|
191
261
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
262
|
+
/** False when another program had the cache locked and pruning has to try again later. */
|
|
263
|
+
private pruneIfNeeded(): boolean {
|
|
264
|
+
const footprint = this.readCacheFootprint("read resource cache size");
|
|
265
|
+
if (!footprint) return false;
|
|
266
|
+
if (this.withinSoftLimits(footprint.count, footprint.totalSize)) return true;
|
|
195
267
|
|
|
196
|
-
|
|
268
|
+
const prunedExpired = this.access.writeCache("prune expired cached resources", () => {
|
|
197
269
|
this.db.query("DELETE FROM resource_cache WHERE expires_at < ?").run(Date.now());
|
|
198
270
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
271
|
+
if (!prunedExpired) return false;
|
|
272
|
+
|
|
273
|
+
const remaining = this.readCacheFootprint("read pruned resource cache size");
|
|
274
|
+
if (!remaining) return false;
|
|
275
|
+
let { count: remainingCount, totalSize: remainingSize } = remaining;
|
|
276
|
+
if (this.withinSoftLimits(remainingCount, remainingSize)) return true;
|
|
277
|
+
// Eviction goes by the order on disk, which has to hold the latest reads.
|
|
278
|
+
if (!this.flushTouches()) return false;
|
|
279
|
+
|
|
280
|
+
// Only the row ids, which the recency index holds: a row's size sits
|
|
281
|
+
// after its payload, and reading it walks the whole payload from disk.
|
|
282
|
+
// Asking it of the oldest 512 rows read 10 MB to pick what to evict.
|
|
283
|
+
const candidates = this.db.query<{ rowid: number }, [number]>(
|
|
284
|
+
`SELECT rowid
|
|
212
285
|
FROM resource_cache
|
|
213
286
|
ORDER BY last_accessed_at ASC, fetched_at ASC
|
|
214
287
|
LIMIT ?`,
|
|
215
288
|
);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
289
|
+
// Deleting a row reads its payload anyway, to free it: the size comes along.
|
|
290
|
+
const remove = this.db.query<{ size_bytes: number }, [number]>(
|
|
291
|
+
"DELETE FROM resource_cache WHERE rowid = ? RETURNING size_bytes",
|
|
219
292
|
);
|
|
220
293
|
|
|
221
294
|
// A cache at its limit holds tens of thousands of rows, so eviction reads
|
|
@@ -223,29 +296,31 @@ export class ResourceStore {
|
|
|
223
296
|
// for more only if it is still over. Reading every row first turned the
|
|
224
297
|
// moment the cache filled up into a multi-second pause.
|
|
225
298
|
while (!this.withinSoftLimits(remainingCount, remainingSize)) {
|
|
226
|
-
const batch =
|
|
299
|
+
const batch = this.access.readCache("select cached resources to prune", () => (
|
|
227
300
|
candidates.all(RESOURCE_CACHE_PRUNE_BATCH_SIZE)
|
|
228
301
|
));
|
|
229
|
-
if (batch
|
|
230
|
-
|
|
231
|
-
const rowsToDelete = [] as typeof batch;
|
|
232
|
-
for (const row of batch) {
|
|
233
|
-
if (this.withinSoftLimits(remainingCount, remainingSize)) break;
|
|
234
|
-
rowsToDelete.push(row);
|
|
235
|
-
remainingCount -= 1;
|
|
236
|
-
remainingSize -= row.size_bytes;
|
|
237
|
-
}
|
|
238
|
-
if (rowsToDelete.length === 0) return;
|
|
302
|
+
if (!batch) return false;
|
|
303
|
+
if (batch.length === 0) return true;
|
|
239
304
|
|
|
240
|
-
|
|
305
|
+
let left = { count: remainingCount, size: remainingSize };
|
|
306
|
+
const pruned = this.access.writeCache("prune least recently used cached resources", () => {
|
|
307
|
+
left = { count: remainingCount, size: remainingSize };
|
|
241
308
|
const tx = this.db.transaction(() => {
|
|
242
|
-
for (const
|
|
243
|
-
|
|
309
|
+
for (const { rowid } of batch) {
|
|
310
|
+
if (this.withinSoftLimits(left.count, left.size)) break;
|
|
311
|
+
const removed = remove.get(rowid);
|
|
312
|
+
if (!removed) continue;
|
|
313
|
+
left.count -= 1;
|
|
314
|
+
left.size -= removed.size_bytes;
|
|
244
315
|
}
|
|
245
316
|
});
|
|
246
317
|
tx();
|
|
247
318
|
});
|
|
319
|
+
if (!pruned) return false;
|
|
320
|
+
remainingCount = left.count;
|
|
321
|
+
remainingSize = left.size;
|
|
248
322
|
}
|
|
323
|
+
return true;
|
|
249
324
|
}
|
|
250
325
|
|
|
251
326
|
private maintainCacheAfterWrite(now: number, sizeBytes: number): void {
|
|
@@ -257,14 +332,14 @@ export class ResourceStore {
|
|
|
257
332
|
|| now - this.lastMaintenanceAt >= RESOURCE_CACHE_MAINTENANCE_TIME_INTERVAL_MS;
|
|
258
333
|
if (!maintenanceDue) return;
|
|
259
334
|
|
|
260
|
-
this.pruneIfNeeded();
|
|
335
|
+
if (!this.pruneIfNeeded()) return;
|
|
261
336
|
this.writesSinceMaintenance = 0;
|
|
262
337
|
this.bytesWrittenSinceMaintenance = 0;
|
|
263
338
|
this.lastMaintenanceAt = now;
|
|
264
339
|
}
|
|
265
340
|
|
|
266
341
|
get<T>(key: ResourceCacheKey, options: GetResourceOptions = {}): CachedResourceRecord<T> | null {
|
|
267
|
-
const row =
|
|
342
|
+
const row = this.access.readCache("load cached resource", () => (
|
|
268
343
|
this.db
|
|
269
344
|
.query<ResourceCacheRow, [string, string, string, string, string]>(
|
|
270
345
|
`SELECT namespace, kind, entity_key, variant_key, source_key, schema_version, payload, provenance,
|
|
@@ -294,12 +369,7 @@ export class ResourceStore {
|
|
|
294
369
|
return null;
|
|
295
370
|
}
|
|
296
371
|
|
|
297
|
-
|
|
298
|
-
const touchedAt = this.touch(key);
|
|
299
|
-
if (touchedAt != null) {
|
|
300
|
-
record.lastAccessedAt = touchedAt;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
372
|
+
this.touchRead(record, options);
|
|
303
373
|
return record;
|
|
304
374
|
}
|
|
305
375
|
|
|
@@ -307,25 +377,33 @@ export class ResourceStore {
|
|
|
307
377
|
key: Pick<ResourceCacheKey, "namespace" | "kind" | "entityKey">,
|
|
308
378
|
options: ListResourceOptions = {},
|
|
309
379
|
): CachedResourceRecord<T>[] {
|
|
310
|
-
const
|
|
380
|
+
const variantKeys = options.variantKeys ? [...new Set(options.variantKeys.map(normalizeVariantKey))] : null;
|
|
381
|
+
const sourceKeys = options.sourceKeys ? [...new Set(options.sourceKeys.map(normalizeSourceKey))] : null;
|
|
382
|
+
if (variantKeys?.length === 0 || sourceKeys?.length === 0) return [];
|
|
383
|
+
// The rows asked for only: an entity can hold dozens of variants (every
|
|
384
|
+
// range of a price history, from every source), megabytes of payload
|
|
385
|
+
// that a filter after the read threw away. The keys come from the
|
|
386
|
+
// primary key's index. Expiry stays below: the column sits after the
|
|
387
|
+
// payload, and checking it here walked a 10 MB payload twice.
|
|
388
|
+
const filters = [
|
|
389
|
+
...(variantKeys ? [`variant_key IN (${variantKeys.map(() => "?").join(", ")})`] : []),
|
|
390
|
+
...(sourceKeys ? [`source_key IN (${sourceKeys.map(() => "?").join(", ")})`] : []),
|
|
391
|
+
];
|
|
392
|
+
const params: string[] = [key.namespace, key.kind, key.entityKey, ...(variantKeys ?? []), ...(sourceKeys ?? [])];
|
|
393
|
+
const rows = this.access.readCache("list cached resources", () => (
|
|
311
394
|
this.db
|
|
312
|
-
.query<ResourceCacheRow, [
|
|
395
|
+
.query<ResourceCacheRow, string[]>(
|
|
313
396
|
`SELECT namespace, kind, entity_key, variant_key, source_key, schema_version, payload, provenance,
|
|
314
397
|
fetched_at, stale_at, expires_at, last_accessed_at, size_bytes
|
|
315
398
|
FROM resource_cache
|
|
316
|
-
WHERE namespace = ?
|
|
399
|
+
WHERE ${["namespace = ?", "kind = ?", "entity_key = ?", ...filters].join(" AND ")}`,
|
|
317
400
|
)
|
|
318
|
-
.all(
|
|
319
|
-
));
|
|
320
|
-
|
|
321
|
-
const allowedVariantKeys = options.variantKeys ? new Set(options.variantKeys.map(normalizeVariantKey)) : null;
|
|
322
|
-
const allowedSourceKeys = options.sourceKeys ? new Set(options.sourceKeys.map(normalizeSourceKey)) : null;
|
|
401
|
+
.all(...params)
|
|
402
|
+
)) ?? [];
|
|
323
403
|
|
|
324
404
|
const records: CachedResourceRecord<T>[] = [];
|
|
325
405
|
for (const row of sortRows(rows)) {
|
|
326
406
|
if (!options.allowExpired && isExpired(row.expires_at)) continue;
|
|
327
|
-
if (allowedVariantKeys && !allowedVariantKeys.has(row.variant_key)) continue;
|
|
328
|
-
if (allowedSourceKeys && !allowedSourceKeys.has(row.source_key)) continue;
|
|
329
407
|
if (options.schemaVersion != null && row.schema_version !== options.schemaVersion) {
|
|
330
408
|
this.deleteExact({
|
|
331
409
|
namespace: row.namespace,
|
|
@@ -349,13 +427,7 @@ export class ResourceStore {
|
|
|
349
427
|
}
|
|
350
428
|
records.push(record);
|
|
351
429
|
}
|
|
352
|
-
|
|
353
|
-
for (const record of records) {
|
|
354
|
-
if (!this.touchDue(record, options)) continue;
|
|
355
|
-
const touchedAt = this.touch(record);
|
|
356
|
-
if (touchedAt != null) record.lastAccessedAt = touchedAt;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
430
|
+
for (const record of records) this.touchRead(record, options);
|
|
359
431
|
return records;
|
|
360
432
|
}
|
|
361
433
|
|
|
@@ -375,7 +447,8 @@ export class ResourceStore {
|
|
|
375
447
|
sourceKey: normalizeSourceKey(key.sourceKey),
|
|
376
448
|
};
|
|
377
449
|
|
|
378
|
-
|
|
450
|
+
this.pendingTouches.delete(rowMapKey(rowKey));
|
|
451
|
+
const saved = this.access.writeCache("save cached resource", () => {
|
|
379
452
|
this.db
|
|
380
453
|
.query(
|
|
381
454
|
`INSERT OR REPLACE INTO resource_cache (
|
|
@@ -400,7 +473,7 @@ export class ResourceStore {
|
|
|
400
473
|
);
|
|
401
474
|
});
|
|
402
475
|
|
|
403
|
-
this.maintainCacheAfterWrite(Date.now(), sizeBytes);
|
|
476
|
+
if (saved) this.maintainCacheAfterWrite(Date.now(), sizeBytes);
|
|
404
477
|
return {
|
|
405
478
|
...rowKey,
|
|
406
479
|
value: JSON.parse(payload) as T,
|
|
@@ -420,14 +493,16 @@ export class ResourceStore {
|
|
|
420
493
|
this.deleteExact(key);
|
|
421
494
|
}
|
|
422
495
|
|
|
496
|
+
/** Asked for by the user, so it waits for another program's lock, and throws when it cannot get it. */
|
|
423
497
|
clear(namespace?: string): void {
|
|
424
498
|
if (!namespace) {
|
|
425
|
-
|
|
499
|
+
this.pendingTouches.clear();
|
|
500
|
+
this.access.run("clear cached resources", () => {
|
|
426
501
|
this.db.query("DELETE FROM resource_cache").run();
|
|
427
502
|
});
|
|
428
503
|
return;
|
|
429
504
|
}
|
|
430
|
-
|
|
505
|
+
this.access.run("clear namespaced cached resources", () => {
|
|
431
506
|
this.db.query("DELETE FROM resource_cache WHERE namespace = ?").run(namespace);
|
|
432
507
|
});
|
|
433
508
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite";
|
|
2
|
+
import { createSqliteAccess, type SqliteAccess } from "./sqlite/access";
|
|
2
3
|
import { safeParseJson, serializeJson } from "./sqlite/json";
|
|
3
|
-
import { withSqliteBusyRetry } from "./sqlite/retry";
|
|
4
4
|
|
|
5
5
|
const DEFAULT_SESSION_SCHEMA_VERSION = 1;
|
|
6
6
|
|
|
@@ -11,11 +11,23 @@ export interface SessionSnapshotRecord<T = unknown> {
|
|
|
11
11
|
updatedAt: number;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** A queued write's row (null for a delete), one object per write. */
|
|
15
|
+
interface PendingSession {
|
|
16
|
+
row: { schema_version: number; value: string; updated_at: number } | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
export class SessionStore {
|
|
15
|
-
|
|
20
|
+
/** Writes queued behind another program's lock, read back until they land. */
|
|
21
|
+
private readonly pending = new Map<string, PendingSession>();
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private readonly db: Database,
|
|
25
|
+
private readonly access: SqliteAccess = createSqliteAccess(db),
|
|
26
|
+
) {}
|
|
16
27
|
|
|
17
28
|
get<T>(sessionId = "app", schemaVersion = DEFAULT_SESSION_SCHEMA_VERSION): SessionSnapshotRecord<T> | null {
|
|
18
|
-
const
|
|
29
|
+
const pending = this.pending.get(sessionId);
|
|
30
|
+
const row = pending ? pending.row : this.access.run("load session snapshot", () => (
|
|
19
31
|
this.db
|
|
20
32
|
.query<{ schema_version: number; value: string; updated_at: number }, [string]>(
|
|
21
33
|
"SELECT schema_version, value, updated_at FROM session_snapshots WHERE session_id = ?",
|
|
@@ -24,12 +36,12 @@ export class SessionStore {
|
|
|
24
36
|
));
|
|
25
37
|
if (!row) return null;
|
|
26
38
|
if (row.schema_version !== schemaVersion) {
|
|
27
|
-
this.delete(sessionId);
|
|
39
|
+
if (!pending) this.delete(sessionId);
|
|
28
40
|
return null;
|
|
29
41
|
}
|
|
30
42
|
const value = safeParseJson<T>(row.value);
|
|
31
43
|
if (value == null) {
|
|
32
|
-
this.delete(sessionId);
|
|
44
|
+
if (!pending) this.delete(sessionId);
|
|
33
45
|
return null;
|
|
34
46
|
}
|
|
35
47
|
return {
|
|
@@ -41,19 +53,30 @@ export class SessionStore {
|
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
set(sessionId: string, value: unknown, schemaVersion = DEFAULT_SESSION_SCHEMA_VERSION): void {
|
|
44
|
-
|
|
56
|
+
const row = { schema_version: schemaVersion, value: serializeJson(value), updated_at: Date.now() };
|
|
57
|
+
this.write("save session snapshot", sessionId, { row }, () => {
|
|
45
58
|
this.db
|
|
46
59
|
.query(
|
|
47
60
|
`INSERT OR REPLACE INTO session_snapshots (session_id, schema_version, value, updated_at)
|
|
48
61
|
VALUES (?, ?, ?, ?)`,
|
|
49
62
|
)
|
|
50
|
-
.run(sessionId,
|
|
63
|
+
.run(sessionId, row.schema_version, row.value, row.updated_at);
|
|
51
64
|
});
|
|
52
65
|
}
|
|
53
66
|
|
|
54
67
|
delete(sessionId: string): void {
|
|
55
|
-
|
|
68
|
+
this.write("delete session snapshot", sessionId, { row: null }, () => {
|
|
56
69
|
this.db.query("DELETE FROM session_snapshots WHERE session_id = ?").run(sessionId);
|
|
57
70
|
});
|
|
58
71
|
}
|
|
72
|
+
|
|
73
|
+
private write(operationName: string, sessionId: string, entry: PendingSession, statement: () => void): void {
|
|
74
|
+
this.pending.set(sessionId, entry);
|
|
75
|
+
this.access.write(operationName, statement, {
|
|
76
|
+
key: `session_snapshots\u0000${sessionId}`,
|
|
77
|
+
settled: () => {
|
|
78
|
+
if (this.pending.get(sessionId) === entry) this.pending.delete(sessionId);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
59
82
|
}
|