Skip to content

Preserve decimal precision in GraphQL prices - #389

Open
Jossy-ossy wants to merge 3 commits into
Divinity-Labs:mainfrom
Jossy-ossy:fix/issue-371
Open

Jossy-ossy wants to merge 3 commits into
Divinity-Labs:mainfrom
Jossy-ossy:fix/issue-371

Conversation

@Jossy-ossy

Copy link
Copy Markdown
Contributor

Summary

  • Add a string-serialized GraphQL Decimal scalar and exact companion fields for monetary values.
  • Keep current Float fields available but deprecated, with a migration guide for clients.
  • Remove SQL float casts from price and history paths, and persist exact numeric text through Prisma Decimal values.
  • Add precision regression coverage for values that cannot survive a JavaScript double round-trip.

The trade-off and migration path are documented in docs/GRAPHQL_DECIMAL_MIGRATION.md.

Closes #371

@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

@Jossy-ossy Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Jossy-ossy

Copy link
Copy Markdown
Contributor Author

@Miracle656 I resolved the merge conflicts and updated this branch. Please take another look and merge when ready.

Miracle656 added a commit that referenced this pull request Oct 22, 2026
  tsc --noEmit   clean
  vitest         93 files, 1068 passing (up from 91 / 1052)
  dropped-tests  none

  #433  from/to range args on getPriceHistory
  #456  make duplicate price points impossible at the database

#456 is the better of the two: a uniqueness constraint in the schema rather
than a check in the ingest path, so a duplicate cannot be written even by a
code path that forgets to look. It carries a migration and adds 14 tests.

Two others from this group were NOT merged -- both fail on their own, so this
is not a merge-order problem:

  #389 (preserve decimal precision in GraphQL prices)
       2 tsc errors, 31 test failures in isolation:
         src/aggregator/vwap.ts(250): Cannot find name 'isNonZeroDecimal'
       The function is called twice and never defined.

  #462 (read per-pool AMM fees from the venue)
       4 tsc errors, 2 failures in isolation:
         src/api/rest.ts(281): Expected 4 arguments, but got 3
         src/api/rest.ts(286): 'pairKey' is specified more than once
       The duplicate key is the shape a repeated base merge produces.

Claude-Session: https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This does not compile on its own branch — it is not a merge-order problem, so please do not rebase and re-request without fixing it first.

src/aggregator/vwap.ts(250,17): error TS2304: Cannot find name 'isNonZeroDecimal'
src/aggregator/vwap.ts(250,63): error TS2304: Cannot find name 'isNonZeroDecimal'

The function is called twice on line 250 and never defined or imported. Merged alone against current main, that is 2 type errors and 31 failing tests.

The goal is right and it is the same one #542 is chasing: preserving exact decimals from Postgres instead of round-tripping through a double. If isNonZeroDecimal exists on another branch of yours, it needs to come with this one; if it was meant to be new, it needs writing.

Worth coordinating with #542 before you go further — it covers the same ground for the REST boundary and has a specific unresolved gap (volume24h and priceChange24h are still summed as doubles). Two independent decimal fixes landing on the same module will conflict, and the one that lands second will look like it broke the first.

@Jossy-ossy

Copy link
Copy Markdown
Contributor Author

Note for maintainers: the compile error on this branch (isNonZeroDecimal undefined in vwap.ts) is fixed. The Float fields are deprecated, not removed, and the repo has no Money type. Please coordinate with #542 on volume24h and priceChange24h.

Merge origin/main into the PR branch and resolve the conflicts:

- vwap.ts: take main's string-format aggregate (decimalFormat: 'string')
  instead of a parallel copy. Add calculateVWAPExact and
  calculateOHLCVExact as string-returning wrappers, so aggregateRefresh
  keeps persisting exact Prisma.Decimal values.
- priceFields.ts: withExactPriceFields maps the string aggregate to the
  deprecated Float fields and the *Exact Decimal fields.
- graphql.ts: the getPrice resolver and the Decimal scalar, with the Asset
  and Pair types from main kept alongside.
- rest.ts, routes/screener.ts: take main's versions, which already negotiate
  decimal format and delegate the screener to services/screener.ts.

This also fixes the compile error reported in review, where
isNonZeroDecimal was called but never defined.

Regenerate schema.graphql, add a changeset, and update the tests that
assumed the superseded aggregate shape.

@Miracle656 Miracle656 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Scanned clean. Two things to know here, and the second one changes what this PR should contain.

1. The branch is 79 commits behind main. Against current main it removes around 6,000 lines from src/ that are not yours — src/reconciliation/dispersion.ts (312, #448), src/__tests__/venueReconciliation.test.ts (385) and src/__tests__/observationAndSourceTime.test.ts (335, #240) among them. That is what a 79-commit-old base does, not a judgement on your code.

2. Most of this landed an hour ago in #470 (4b5ced6), so a rebase will tell you a lot. What is now on main:

  • No ::float cast remains inside any aggregate expression in src/ — SUM(x::float) became SUM(x)::float in src/api/history.ts, src/api/graphql.ts, src/routes/candles.ts and src/routes/movers.ts. That was the real bug: casting per row made the total depend on the order rows were summed in.
  • src/pricing/precision.ts narrows once at the serialization edge, with formatPrice and formatVolume used by the GraphQL resolvers.

So please rebase and then look at what is actually left. If your approach differs from #470's, say how and I will take that seriously — there is a real question still open here that #470 did not solve:

A JavaScript number cannot carry a Decimal(36, 18) price. It is an IEEE-754 double with 15–17 significant digits, so anything returning a Float from GraphQL is lossy no matter how carefully it is rounded. #470's helpers are honest about that now rather than claiming otherwise, but the underlying limitation stands. If "preserve decimal precision in GraphQL prices" means genuinely preserving it, that needs a String scalar on those fields — which src/utils/decimals.ts already does for REST, negotiated per request.

That would be a more interesting PR than the cast fix, and it is still unclaimed. Your call whether to take it or close this one.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

@Miracle656

Copy link
Copy Markdown
Collaborator

Correction to my earlier review — I got the main point wrong.

I said this branch was far behind main and that merging it would delete thousands of lines of shipped work. That came from git diff origin/main <branch>, which compares the two tips: everything main gained since you branched appears as a "deletion" on your side. That is not what a merge does — a merge uses the merge base and keeps files main added that your branch never touched.

Measured properly against current main:

conflicts: 3
files a merge would delete: 0

Nothing would be reverted. I apologise; that framing was both alarming and inaccurate.

The points about the code itself are unchanged. Most of the ::float-inside-aggregate work landed in #470 since, so rebasing will shrink this considerably. The open question — that a GraphQL Float cannot carry a Decimal(36,18) — is still unsolved and still interesting.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

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.

Money is a Float throughout the GraphQL schema

2 participants