feat: persist bet-store with Prisma so bets survive restarts (#519) - #577
Open
Richardkingz2019 wants to merge 2 commits into
Open
feat: persist bet-store with Prisma so bets survive restarts (#519)#577Richardkingz2019 wants to merge 2 commits into
Richardkingz2019 wants to merge 2 commits into
Conversation
…s#519) Back the process-local bet store with a durable BetRecord table so demo bets keep their audit trail across process restarts and multi-instance deployments when DATA_STORE=postgres (the DATA_MODE=live default). - Define BetStoreBackend interface with memory and postgres impls; the facade resolves the backend per call so mock mode stays process-local. - Add BetRecord Prisma model + migration; money columns use Decimal(20,8). - Make every bet-store method async and migrate BetService, bets routes, and in-memory repositories to await them. - Resume the bet-{n} id sequence from the highest existing row on boot. - Add restart-continuity tests (bet-store-persistence.spec.ts) covering both backends; pin DB-free suites to the memory backend. - Fail fast on unsupported Node versions before any dependency loads (node-version.ts) and build before unit tests in CI so the version-gate spec has a dist to boot. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
jest.isolateModules does not await async callbacks: the module registry is torn down before the awaited dynamic imports resolve, which surfaced as flaky "createHackathonApp is not a function" failures when the full unit suite ran. isolateModulesAsync keeps the isolation scope alive for the whole callback. Generated with Codebuff 🤖 Co-Authored-By: Codebuff <noreply@codebuff.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #519 — Persist bet-store beyond process memory.
The bet store lived entirely in process-local
Maps (src/data/bet-store.ts), so the bet audit trail was lost on every deploy/restart and was invisible to other instances sharing the database. This PR backs the store with a durable PrismaBetRecordtable so demo bets survive restarts and multi-instance demos, while keeping the pure in-memory backend for true mock mode.What changed
Persistence model
BetRecordPrisma model (prisma/schema.prisma) + migration20260827000000_add_bet_record:amount,predictedPrice) useDecimal(20, 8)/Decimal(18, 8)per the repo's monetary-precision rule.address,roundId,status,timestampfor the admin/audit query paths.src/lib/prisma.tsgains an in-memorybetRecordstub so unit tests exercise the same Prisma-shaped API as production without a live DB.Bet-store refactor (
src/data/bet-store.ts)BetStoreBackendinterface with two implementations:InMemoryBetStore— process-local Maps; used whenDATA_STORE=memory(auto-derived fromDATA_MODE=mock). Nothing survives a restart.PrismaBetStore— bets persisted inBetRecord; used forDATA_STORE=postgres(theDATA_MODE=livedefault). Bets survive restarts and are shared across instances.BetStorefacade resolves the backend lazily per call (resolveBetStoreKind()), so:DATA_STOREbetween cases without re-importing modules.Promise-based.bet-{n}ids resume from the highest existing row on first write, so a restarted process continues numbering where its predecessor stopped (single-writer contract documented in code; DB-generated ids noted as the next step for true multi-writer deployments).getTotalBetsCount()reads from the DB so a restarted process reports the full audit trail instead of a reset counter.Call-site migration
BetService(src/services/bet.service.ts) — all bet-store calls awaited, including the stub → live reconcile path and failure handling.GET /api/bets/reconciliationandGET /api/bets/:id(src/routes/bets.routes.ts) — nowasync, summary computed in parallel with the bet list.InMemoryRoundRepository/InMemoryStatsRepository(src/repositories/in-memory.repositories.ts) — awaited.Operational hardening (found while making CI green)
src/config/node-version.ts— fail-fast Node ≥ 22 gate imported as the very first side effect ofsrc/index.ts, so an unsupported runtime dies with a clear message instead of an opaqueERR_REQUIRE_ESMfrom@stellar/stellar-sdk. Mirrors the existing preflight check and the version-gate spec..github/workflows/ci.yml— the unit job now builds first sonode-version-check.spec.ts(which bootsdist/index.jsunder Node 18) has an artifact to test.jest.config.ts—bet-store-persistence.spec.tsregistered as an integration test.Tests
New:
src/tests/bet-store-persistence.spec.ts(integration, covers both acceptance criteria)bet-Nshape preserved, no id reuse).Updated suites (await-ified / pinned to the memory backend where they assert audit-event emission, not durability):
bet-reconciliation.spec.ts,bet-audit.spec.ts(pinsDATA_STORE=memory),bet-store-decimal-precision.spec.ts,performance.spec.ts(pinsDATA_STORE=memory— DB-free load suite),admin-bet-audit.spec.ts.Pre-existing breakage fixed so the CI unit job is green again (all of these suites were red on
main):hackathon-auth.smoke.spec.ts,http-logger-unified.spec.ts,api-contract.spec.ts— incompleterateLimiter.middlewaremocks (Route.post() requires a callback functionat import).http-logger-unified.spec.ts—jest.isolateModules(async ...)doesn't await the callback; switched toisolateModulesAsyncto stop the flaky "createHackathonApp is not a function" / teardown races.hackathon-logger.spec.ts— logger mock TDZ (temporal-dead-zone) crash at import; mock is now self-contained.hackathon-rounds.spec.ts/api-contract.spec.ts— round envelope contract updated to the shared{ success, data: { source, rounds } }shape.admin-bet-audit.spec.ts— JWT secret set before app build (16+ char preflight requirement), role-awareuser.findUniquemock.retention.service.spec.ts— prisma stub gainsauthChallenge/message/auditLogmodel stubs.CI status (local verification, CI env)
tsc --noEmit)docs:verify)main(upstream CI is red on this job for the same tests — incomplete prisma mocks, fake Soroban secrets injest.setup.js). This PR addsbet-store-persistence.spec.ts(6/6 passing) and introduces zero new failures.main.How to verify
To see durability end-to-end: run with
DATA_STORE=postgres, place a bet, restart the process, andGET /api/bets/reconciliation— the bet and its status survive.closes #519