Skip to content

Watched document handling and misc fixes#122

Merged
peachbits merged 3 commits into
masterfrom
matthew/missing-coinrank
Apr 17, 2026
Merged

Watched document handling and misc fixes#122
peachbits merged 3 commits into
masterfrom
matthew/missing-coinrank

Conversation

@peachbits
Copy link
Copy Markdown
Contributor

@peachbits peachbits commented Feb 4, 2026

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

none

Note

Medium Risk
Touches v3 synced-document initialization and introduces a DB watcher, which can affect runtime config loading and update propagation. Also changes rate-provider behavior (coinrank filtering and historical cutoff), which may alter returned datasets for some requests.

Overview
Centralizes v3 rates_settings CouchDB synced-document handling by introducing a one-time bootstrapV3SyncedDocs() that performs an initial sync() across all v3 docs, starts 30-minute fallback re-sync intervals, and begins a watchDatabase changes-feed watcher; both src/v3/index.ts and src/v3/indexEngines.ts now call this before serving/starting engines.

Adds shared synced-document options (cleanFailStrategy: 'preserve' + Slack reporting on cleaner failures) and refactors providers/router to export their synced-doc lists and use these options when constructing syncedDocument(...) instances.

Tightens data handling: Coinrank now treats Coingecko market_cap_rank as optional and filters unranked assets from the cached market list, and CoinMarketCap historical lookups now short-circuit for dates older than a configurable maxHistoricalMonths (new env/config setting). Also bumps edge-server-tools to ^0.2.24 and makes a few error typings explicit (unknown).

Reviewed by Cursor Bugbot for commit dd35df3. Bugbot is set up for automated code reviews on this repo. Configure here.


@peachbits peachbits force-pushed the matthew/missing-coinrank branch from 2b50944 to 196daae Compare February 4, 2026 16:53
Comment thread src/coinrankEngine.ts Outdated
const marketsPage = asCoingeckoMarkets(reply)
markets = [...markets, ...marketsPage]
// Filter out assets without a rank (newly listed assets may lack rankings)
markets = [...markets, ...marketsPage.filter(m => m.rank != null)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filtering rank reduces cached market coverage

Medium Severity

Filtering marketsPage by m.rank != null while still stopping at a fixed NUM_PAGES can store fewer markets than before and omit ranked assets that appear on later pages. This can make the cached dataset incomplete relative to the expected “top N” coverage and lead to unexpectedly short result sets downstream.

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine. There is no expectation that there will be exactly 2000 ranked assets

Copy link
Copy Markdown
Member

@paullinator paullinator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean, correct change that handles newly listed/unranked assets gracefully. One suggestion to tighten the TypeScript type after filtering — see inline comment.

Comment thread src/coinrankEngine.ts Outdated
const marketsPage = asCoingeckoMarkets(reply)
markets = [...markets, ...marketsPage]
// Filter out assets without a rank (newly listed assets may lack rankings)
markets = [...markets, ...marketsPage.filter(m => m.rank != null)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this filter, all entries in markets are guaranteed to have a numeric rank. However, the TypeScript type still reflects rank: number | undefined (since asOptional(asNumber) returns number | undefined). Any downstream consumer of CoinrankMarkets will see rank as potentially undefined even though it never is after filtering.

Suggestion: Use a type predicate in the filter to narrow the type:

markets = [
  ...markets,
  ...marketsPage.filter((m): m is typeof m & { rank: number } => m.rank != null)
]

Comment thread src/v3/providers/constantRates.ts Outdated
Comment thread src/coinrankEngine.ts Outdated
@peachbits peachbits force-pushed the matthew/missing-coinrank branch from 332d361 to 5c9c7e6 Compare April 13, 2026 18:52
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Engine process never bootstraps synced documents
    • The engines entrypoint now awaits bootstrapV3SyncedDocs before starting loops so synced docs are initially loaded and watched in the ratesEngines process too.

Create PR

Or push these changes by commenting:

@cursor push 03341bc33b
Preview (03341bc33b)
diff --git a/src/v3/indexEngines.ts b/src/v3/indexEngines.ts
--- a/src/v3/indexEngines.ts
+++ b/src/v3/indexEngines.ts
@@ -5,6 +5,7 @@
   dbProviders,
   memoryProviders
 } from './providers/allProviders'
+import { bootstrapV3SyncedDocs } from './syncedDocs'
 import type { Frequency, FrequencySeconds, RateEngine } from './types'
 
 const frequencyToMs: Record<Frequency, number> = {
@@ -56,4 +57,13 @@
     }
   }
 }
-startEngines()
+
+async function main(): Promise<void> {
+  await bootstrapV3SyncedDocs()
+  startEngines()
+}
+
+main().catch((error: unknown) => {
+  console.error(error)
+  process.exit(1)
+})

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

Comment thread src/v3/syncedDocs.ts
@peachbits peachbits force-pushed the matthew/missing-coinrank branch from ad9fbfe to f84b851 Compare April 13, 2026 21:03
@peachbits peachbits changed the title Ignore assets with null market_cap_rank Watched document handling and misc fixes Apr 13, 2026
Comment thread src/v3/syncedDocs.ts
bootstrapPromise ??= (async () => {
console.log('Starting v3 synced doc bootstrap')

const syncResults = await Promise.allSettled(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

watchDatabase() already starts the Couch changes feed before doing its initial sync() calls. Doing a separate Promise.allSettled(...sync()) first creates a race window where a rates_settings update can land after this preload but before the watcher starts with since: "now", so this process misses that change until the 30-minute interval fires. I would let watchDatabase own the initial sync (or start watching before the preload) to avoid serving stale mappings during startup.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took me a minute to understand the comment. The issue is the onChange wouldn't fire if the doc was edited between the initial sync and when watchDatabase is called. I don't think this actually matters because this bootstrap function blocks the engine from starting and therefore those docs aren't being written to. There are human-managed docs in the settings db that could technically be written to during those milliseconds but the likelihood is small (it's rare for humans to touch the docs) and trivial (only a 30 minute delay in the doc updating)

@j0ntz
Copy link
Copy Markdown

j0ntz commented Apr 16, 2026

#122 (comment) is still unaddressed?

Brand new assets added to coingecko are shoved into the middle of the list without a rank and should be ignored
@peachbits peachbits force-pushed the matthew/missing-coinrank branch from ec66fb5 to fc4cb62 Compare April 17, 2026 19:13
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fc4cb62. Configure here.

Comment thread src/config.ts Outdated
Start the shared settings docs before serving traffic, watch CouchDB changes in real time, and preserve the last good document while alerting on cleaner failures.
@peachbits peachbits force-pushed the matthew/missing-coinrank branch from fc4cb62 to dd35df3 Compare April 17, 2026 19:27
@peachbits peachbits merged commit f677a7f into master Apr 17, 2026
2 checks passed
@peachbits peachbits deleted the matthew/missing-coinrank branch April 17, 2026 19:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants