// Compile-time contract between the WIRE row and the PUBLIC market types. // // `toMarket` builds a per-variant object, taking required type-specific columns // with `?? unreachable(...)` because GraphQL cannot express that `marketType` // correlates with which nullable columns are populated. That assertion is // necessary — but on its own it also erases a failure this change exists to catch: // a field that still EXISTS in the schema but changed TYPE (say `asset` String → // Int) regenerates cleanly and typechecks, silently making the public type a lie. // // So the correlation stays asserted, while every field's NAME and WIRE TYPE is // pinned here. These are type-level only — no runtime cost, nothing exported from // the package. A mismatch is a compile error naming the field. // // On failure: the schema changed. Update the public type in markets.ts to match the // new wire type (or fix the fragment if the field was renamed), then adjust the // mapping if the change is more than cosmetic. import type { PerpPortfolioMarket } from "./perp/portfolio.js"; import type { SpotPortfolioMarket } from "./spot/portfolio.js"; import type { PortfolioMarket } from "./binary/portfolio.js"; import type { OrderMarket } from "./orders.js"; import type { RawMarketRow, BinaryMarket, SpotMarket, PerpMarket } from "./markets.js"; /** * Is `Wire` the wire spelling of the public 0x type `Pub`? * * Hasura has no address/hash scalar — every one arrives as `String` — so a public * `Address` / `Hex` backed by a wire `string` is the INTENDED narrowing (done at * the `toMarket` seam by `asAddress`/`asHex`), not drift. This is deliberately * narrow: it accepts `string → 0x${string}` and nothing else, so a column that * changes to `number` or `boolean` under a public `Address` still fails the check. */ type IsWireOfHex = [Wire] extends [string] ? ([Pub] extends [`0x${string}`] ? true : false) : false; /** * Every key whose wire type is not assignable to the same key on the public type * `Pub`. Four things are deliberately NOT flagged: * * - keys `Pub` does not declare (a variant omits the other variants' columns); * - nullability (`NonNullable` on both sides), since `toMarket`'s * `?? unreachable(...)` on required variant columns is precisely the claim * that a variant's own columns are populated; * - `marketType`, whose whole purpose is to be narrowed from the wire's full * union to one literal per variant; * - a wire `string` under a public `Address`/`Hex` — see {@link IsWireOfHex}. */ type MismatchedKeys = { [K in Exclude]: NonNullable extends NonNullable ? never : IsWireOfHex, NonNullable> extends true ? never : K; }[Exclude]; /** * Assert no field of the public type disagrees with the wire row's type for it. * Resolves to `true`, or to the offending key names — so the compile error reads * `Type '"asset"' is not assignable to type 'true'`, naming the drifted field. */ type NoMismatch = [MismatchedKeys] extends [never] ? true : MismatchedKeys; // One check per public variant. Each reads: "every field this variant declares // that the wire also returns agrees on type." const _binaryMatchesWire: NoMismatch = true; const _spotMatchesWire: NoMismatch = true; const _perpMatchesWire: NoMismatch = true; /** * Fields the SDK COMPUTES rather than selects, so the wire row legitimately has no * column for them. Exempt by name — a short, explicit list — because the alternative * (loosening the check) would also stop catching genuinely-dropped columns. * * - `interval`: the human timeframe label (`"15m"` / `"1h"` / …) that `toMarket` * derives from `intervalSec`, falling back to `expiry − tradingStart`. See * `marketIntervalLabel`. * - `mode`: `"reference"` / `"fixed"`, which `toMarket` reads off `strike` (0 is * the protocol's sentinel for "resolves against another question"). See * `binaryResolutionMode`. * * Adding a name here is a claim that something in the mapping layer populates it. */ type DerivedFields = "interval" | "mode"; /** * Assert the public types do not promise fields the wire row never returns — * catches a REMOVED/renamed column that the fragment stopped selecting, which * would otherwise leave the public type advertising a field that is now * permanently `undefined`. {@link DerivedFields} are excluded: they are computed, * not selected. */ type MissingFromWire = Exclude, DerivedFields>; type NothingMissing = [MissingFromWire] extends [never] ? true : MissingFromWire; const _binaryFieldsExist: NothingMissing = true; const _spotFieldsExist: NothingMissing = true; const _perpFieldsExist: NothingMissing = true; // ============================================================================ // The ASSERTED set — which fields `toMarket` claims the indexer always populates. // // The two checks above deliberately strip nullability, so neither notices the // class of drift that actually reaches users: a column the indexer STOPS always // writing. `toMarket` takes each required variant column with `?? unreachable()`, // and that assertion is only as good as the handler that upholds it — if a field // becomes conditional upstream, nothing here would have complained and the throw // would surface as a dropped row (list reads) or a failed fetch (single reads). // // So the set is pinned by NAME below. A field that is required on the public // variant but nullable on the wire IS an asserted field, by construction — that // is exactly the gap `?? unreachable()` bridges — so the compiler can derive the // set and diff it against the expectation. The list cannot drift silently: // // - make a public field optional / add a nullable one → it leaves the set // - make a public field required (new assertion) → it enters the set // // Either way the diff fails to compile and names the field. Editing the list is // then a deliberate act: the claim is that `indexer/src/handlers/{binary,spot, // perp}.ts` writes that column UNCONDITIONALLY at row creation. Verify against the // handler before adding a name, and prefer making the public field optional over // asserting a field the indexer only usually writes. // ============================================================================ /** * Keys whose wire column is nullable but whose PUBLIC type admits neither `null` * nor `undefined` — i.e. exactly the fields `toMarket` bridges with * `?? unreachable(...)`. * * Both public spellings of "may be absent" are therefore excluded, and the * distinction matters: `lastPrice: string | null` (a required key holding a * nullable value — null until the first fill) is pass-through, while * `nonce?: string | null` (optional AND nullable) is too. Neither is asserted. * `-?` strips optionality before the test so `undefined` is only present when the * declared type genuinely includes it. */ type AssertedKeys = { [K in Exclude]-?: null extends Pub[K] ? never : undefined extends Pub[K] ? never : null extends Row[K] ? K : never; }[Exclude]; /** Set equality, reported as whichever side has the unexpected member. */ type SameKeys = [Exclude] extends [never] ? [Exclude] extends [never] ? true : { missingFromCode: Exclude } : { unexpectedlyAsserted: Exclude }; /** * BINARY's asserted columns. Each is written unconditionally by * `BinaryMarketsModule.MarketCreated` — see `indexer/src/handlers/binary.ts`. * `status` (wire `clobStatus`) is derived there from a total three-branch * expression, so it is never absent on a binary row. * * NOT here, and deliberately optional on {@link BinaryMarket}: `oracleQuestion`, * `oracleQuestionId`, `createdByTx`, `creator`, `nonce`, `operatorId`, `venueId`, * `context`, `finalized`, `netBacking` — a market discovered via the realtime * tail comes from `MarketCreator.MarketCreated`, whose narrower payload omits * them until the next snapshot fills them in. */ type BinaryAsserted = | "marketId" | "marketAddress" | "yesTokenId" | "noTokenId" | "collateral" | "asset" | "question" | "status" | "strike" | "tradingStart" | "expiry"; /** * SPOT's asserted columns — all from the static `POOLS` metadata in * `indexer/src/pools.ts`, stamped at lazy creation, never event-derived. */ type SpotAsserted = "baseToken" | "quoteToken" | "baseIsNative" | "tickSize" | "lotSize" | "minQuantity"; /** * PERP's asserted columns — from the static `PERP_POOLS` metadata, same as SPOT * plus the margin pair. `baseIsNative` is a hardcoded `false` there; note it is * bridged with `??` and not `||`, which would wrongly treat `false` as absent. */ type PerpAsserted = SpotAsserted | "marginBank" | "initialMarginBps"; const _binaryAssertedSet: SameKeys, BinaryAsserted> = true; const _spotAssertedSet: SameKeys, SpotAsserted> = true; const _perpAssertedSet: SameKeys, PerpAsserted> = true; // The three market PROJECTIONS embedded in portfolio rows. Each fragment // (`PortfolioMarketFields`, `SpotPortfolioMarketFields`, // `PerpPortfolioMarketFields`) selects a strict SUBSET of MarketFields' columns — // verified — so checking them against the same wire row covers every field they // use. Pinning them here is what KEEPS that true: a field added to a portfolio // fragment but not to MarketFields fails these checks instead of silently escaping // them, which matters because the portfolio mappings assert nullability through // `narrowMarketTypeProjection` and would otherwise erase a type change. const _portfolioMarketMatchesWire: NoMismatch = true; const _portfolioMarketFieldsExist: NothingMissing = true; const _spotPortfolioMarketMatchesWire: NoMismatch = true; const _spotPortfolioMarketFieldsExist: NothingMissing = true; const _perpPortfolioMarketMatchesWire: NoMismatch = true; const _perpPortfolioMarketFieldsExist: NothingMissing = true; // The FOURTH such projection: the market context carried on every order row // (`OrderMarketFields`). `interval` is DERIVED by the SDK rather than selected, // so it is omitted from the check the same way the portfolio projections omit // theirs. const _orderMarketMatchesWire: NoMismatch> = true; const _orderMarketFieldsExist: NothingMissing> = true; // Reference the bindings so `noUnusedLocals` (if enabled) stays quiet without // weakening the checks above. export type _MarketRowContractChecked = [ typeof _binaryMatchesWire, typeof _spotMatchesWire, typeof _perpMatchesWire, typeof _binaryFieldsExist, typeof _spotFieldsExist, typeof _perpFieldsExist, typeof _binaryAssertedSet, typeof _spotAssertedSet, typeof _perpAssertedSet, typeof _portfolioMarketMatchesWire, typeof _portfolioMarketFieldsExist, typeof _spotPortfolioMarketMatchesWire, typeof _spotPortfolioMarketFieldsExist, typeof _perpPortfolioMarketMatchesWire, typeof _perpPortfolioMarketFieldsExist, typeof _orderMarketMatchesWire, typeof _orderMarketFieldsExist, ];