Usage
How to consume @workspace/bridge — the configured engine versus direct sub-path imports.
Installation
Add the workspace dependency to the consuming app or package:
{
"dependencies": {
"@workspace/bridge": "workspace:*"
}
}There is no root entry point. Import from the sub-paths listed in the Overview; the two most common are @workspace/bridge/engine and @workspace/bridge/<module> (auction, hands, player, …).
Two ways in
Engine (@workspace/bridge/engine) | Direct imports (<module>, utils/*, format/*, pbn, …) | |
|---|---|---|
| Configuration | Bound to a BridgeContext (teaching mode + locale) | None: every function is independent of the context |
| Typical caller | Any code that depends on the teaching mode or the locale, React or not | Pure helpers, repositories, scripts, tests |
| Surface | 13 modules plus getCtx() | One file per concern, tree-shakeable |
The split follows one rule: a function is public outside the engine if and only if it is independent of the context. Anything that would take a CoreConfig, BridgeContext, TeachingMode, SuitVariant or Locale is only reachable through engine.<module>, which pre-binds those arguments (for example engine.hands.areComplete(hands) applies the mode's CoreConfig). Outside React, build the engine for the mode you hold; createBridgeEngine is memoized. Two documented exceptions take a mode as data: createBridgeContext/createBridgeEngine themselves, and validateDeal, which validates a deal that carries its own mode.
Creating an engine
createBridgeContext(partial?) fills the defaults (teachingMode: "auction", locale: "en"); createBridgeEngine(ctx) takes a complete BridgeContext.
const ctx: BridgeContext = createBridgeContext({
teachingMode: "auction",
locale: "fr",
});
const engine = createBridgeEngine(ctx);createBridgeContext(); // => { teachingMode: "auction", locale: "en" }
createBridgeContext({ locale: "fr" }); // => { teachingMode: "auction", locale: "fr" }The teachingMode resolves the CoreConfig (cards per hand, valid ranks, auction kind, denominations, deck); the locale drives rank and suit notation. See Game Modes for every mode.
Memoization rule
Engines are pure projections of teachingMode and locale and hold no per-call state, so createBridgeEngine caches one instance per teachingMode|locale key. Calling it again with an equivalent context returns the same object; a nullish locale falls back to "en" before the key is built.
const again = createBridgeEngine(
createBridgeContext({ teachingMode: "auction", locale: "fr" })
);
again === engine; // => true: one engine per `teachingMode|locale` pairBecause of this cache, never mutate anything returned by an engine (engine.hands.config, engine.constants.*, …): the next caller with the same mode and locale would see the mutation.
Engine modules
| Member | Role | Reference |
|---|---|---|
engine.getCtx() | Returns the normalised BridgeContext (teachingMode, locale) the engine was built from | Engine |
engine.ctx | Flat read-only flags derived from the CoreConfig (auctionInput, contractDisplay, visibility, …) | Engine |
engine.hands | Completeness checks, distribution and transforms bound to the mode's CoreConfig | Hands |
engine.auction | Per-call view, building an auction, bid validation, contract calculation, isEnabled | Auction |
engine.notation | Locale-aware rank parsing/formatting, rank sort, validRanks | Notation |
engine.play | validateTricks replay against the deal, trick winner, follow-suit rules | Play |
engine.player | Seat navigation, rotation, partnerships | Player |
engine.pbn | PBN file parsing, conversion, serialization, encoding helpers | PBN |
engine.format | formatContract | Format |
engine.schemas | Zod schema namespaces (player, card, bid, hands, play, scoring, vulnerability, gameParams, teachParams) | Schemas |
engine.constants | Core constants and defaults | Constants |
engine.parse | Card, bid, contract, scoring and vulnerability parsers and guards | Engine |
engine.stats | HCP / THT computers, contract level, player vulnerability status | Engine |
The full member list of every module is on the Engine page.
Examples
Hands, bound to the mode's card count:
const hands = handsToHandsData(
new HandsBuilder().fromCompact("AKQ2.KQ3.A54.K32 JT98.JT9.KQJ.QJ9 7654.A87.T98.AT8 3.6542.7632.7654").build()
);
engine.hands.isHandComplete(hands.N); // => true (13 cards in auction mode)
engine.hands.areComplete(hands); // => true
engine.hands.config.nbCardsPerHand; // => 13Notation, bound to the locale:
engine.notation.parseRanks("RDV"); // => ["K", "Q", "J"]
engine.notation.formatRanks(["K", "Q", "J"]); // => "R D V"
engine.notation.formatRank("K"); // => "R"Auction:
const auction = new AuctionBuilder().fromCompact("N / 1S pass 2S AP").build();
const result = engine.auction.calculateContract(auction);
// result => { type: "confirmed", value: { level: 2, suit: "S", declarer: "N", … } }
engine.auction.isAuctionComplete(auction); // => trueContext flags:
engine.getCtx(); // => { teachingMode: "auction", locale: "fr" }
engine.ctx.auctionsEnabled; // => true
engine.ctx.auctionInput; // => "sequence"
engine.ctx.contractDisplay; // => "full"
engine.ctx.cardSet; // => "standard"Direct imports
Context-free functions are imported from their sub-path. They are stateless and need no BridgeContext.
getPlayersInTrickOrder("W"); // => ["W", "N", "E", "S"]
parseCard("SA"); // => "SA"
parseCard("S1"); // => null
computeHandHCP(hands.N); // => 21Test data in examples comes from @workspace/bridge/testing (makeDeal and the builders such as HandsBuilder and AuctionBuilder), which is also the recommended way to build fixtures in app tests.