shared
The vocabulary every package agrees on: money, ids, wire messages, ports, config schema.
@gavel/shared (packages/shared) is the bottom of the dependency graph. It imports nothing from the workspace and has one runtime dependency, zod. Its job is to give every other package one copy of the things they must agree on, so that two packages cannot drift apart.
Responsibilities
- Money and time as branded numbers.
Centsis an integer from 0 toMAX_CENTS(99 999 999 999);Cents(x)throwsbad_amountfor a float, NaN, a negative or an out-of-range value, andaddandsubre-check the result.EpochMsis an integer millisecond timestamp. - Branded ULIDs.
AuctionId,BidderId,BidId,MsgId,ConnId,EventIdandJtiare 26-character Crockford ULIDs;parseIdrejects anything else withbad_message. - Time as a port.
Clockhasnow()andsetTimer().SystemClockis the only placeDate.nowis read in this package, so tests inject a fake clock. - The wire protocol. Strict zod schemas for client and server messages (
wire/client.ts,wire/server.ts), the envelope with its size limits (wire/envelope.ts) andencodeMsg/decodeMsg(wire/codec.ts). See the WebSocket protocol. - Ports. Interfaces only, no implementation:
RoomStore,EnginePort,PaymentsPort,Transport,TokenSigner,Logger(ports.ts). - Error codes.
ERROR_CODESandERROR_TABLE, plus the HTTP error bodyApiError(api-error.ts). - Cross-package contracts. The hold route contract (
hold-contract.ts), the ceiling policy (ceilings.ts), the auction config schema (config-schema.ts), the replay bundle format (replay-bundle.ts) and the per-client rate-limit key (client-key.ts).
Public interface
Everything is exported from packages/shared/src/index.ts; package.json has a single export . that points at the TypeScript source.
| Area | Main exports |
|---|---|
| Money | Cents, isCents, MAX_CENTS, add, sub, cmp, money formatting |
| Ids | Ulid, AuctionId, BidderId, isUlid, parseId, UlidMinter (ids-mint.ts) |
| Time | EpochMs, Clock, SystemClock, MAX_TIMEOUT_MS |
| Wire | PROTOCOL_VERSION, MAX_FRAME_BYTES, MAX_SERVER_FRAME_BYTES, encodeMsg, decodeMsg, client and server message schemas |
| Config | AuctionConfigSchema, AUCTION_MODES, HOLD_SCOPES, endsInFuture |
| Audit | AuditPayload, AUDIT_OUTCOMES, audit record helpers |
| Confidence | toBps, confidenceToBps, MAX_CONFIDENCE_BPS |
| Policy | CEILING_PRESETS_CENTS, MIN_CEILING_CENTS, DEFAULT_MAX_CEILING_CENTS |
| Replay | REPLAY_BUNDLE_VERSION, REPLAY_EXPORT_VERSION, isReplayBundle |
Prop
Type
Key files
| File | What it holds |
|---|---|
money.ts | The Cents brand and checked arithmetic |
ids.ts, ids-mint.ts | ULID validation and the minting port with its implementation |
events.ts | RoomEventSchema, the events the room stores |
wire/envelope.ts | {v:1, t, ...} strict message builder and the size and text limits |
hold-contract.ts | Request and response schemas of the hold routes, plus the bidder-name rule |
ceilings.ts | The ceiling policy copy that engine, payments and the room must keep equal |
client-key.ts | clientKey: an IPv6 client is counted by its first four groups |
replay-bundle.ts | The redacted public export wrapped with its head hash |
Invariants
- Money is never a float. Anything branded
CentspassedisCents. Tests:packages/shared/test/money.test.ts,money-format.test.ts. - Ids are valid ULIDs or they are refused. Tests:
ids.test.ts,ids-mint.test.ts. - Wire messages are strict. Each schema is
.strict()and carriesv: 1; unknown fields are rejected. Tests:wire-envelope.test.ts,wire-client.test.ts,wire-server.test.ts,wire-codec.test.ts,golden.test.ts. - A bidder name cannot hide in invisible characters.
BIDDER_NAME_FORBIDDEN_PATTERNrejects control, zero-width and direction characters and angle brackets. Tests:hold-contract.test.ts. - A client cannot dodge a rate limit by rotating addresses in its prefix.
clientKeymaps a whole /64 to one key and an IPv4-mapped address to the IPv4 address. Tests:client-key.test.ts; the server testpackages/server/test/client-key-parity.test.tskeeps payments' own copy equal. - Copies stay equal. The ceiling policy is copied into the engine, payments and the room config;
packages/server/test/ceiling-policy.test.tsfails when a copy drifts, andpackages/engine/test/contract/shared.test.tscompares the engine's constants with this package. The hold contract has one copy here;packages/server/test/contract-shared-drift.test.tsguards the server side.
Where it sits
shared may import nothing from the workspace (no-shared-to-forbidden in .dependency-cruiser.cjs). It is imported by every other package. The engine copies a few constants instead of importing them, and a test keeps them equal.
- packages/shared/src/index.tsthe full export list
- packages/shared/src/money.tsCents and checked arithmetic
- packages/shared/src/wire/envelope.tsmessage builder and limits
- packages/shared/src/ports.tsport interfaces
- packages/shared/src/config-schema.tsAuctionConfigSchema
- packages/shared/testthe tests named above
Last updated on