Skip to content

PayPal sandbox only. No real money moves.

BuildersBuildersPackages

shared

The vocabulary every package agrees on: money, ids, wire messages, ports, config schema.

Verifiedbacked by a real run or committed evidence

Page last changed 7 Nov 2026 (from the git history)

@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. Cents is an integer from 0 to MAX_CENTS (99 999 999 999); Cents(x) throws bad_amount for a float, NaN, a negative or an out-of-range value, and add and sub re-check the result. EpochMs is an integer millisecond timestamp.
  • Branded ULIDs. AuctionId, BidderId, BidId, MsgId, ConnId, EventId and Jti are 26-character Crockford ULIDs; parseId rejects anything else with bad_message.
  • Time as a port. Clock has now() and setTimer(). SystemClock is the only place Date.now is 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) and encodeMsg / decodeMsg (wire/codec.ts). See the WebSocket protocol.
  • Ports. Interfaces only, no implementation: RoomStore, EnginePort, PaymentsPort, Transport, TokenSigner, Logger (ports.ts).
  • Error codes. ERROR_CODES and ERROR_TABLE, plus the HTTP error body ApiError (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.

AreaMain exports
MoneyCents, isCents, MAX_CENTS, add, sub, cmp, money formatting
IdsUlid, AuctionId, BidderId, isUlid, parseId, UlidMinter (ids-mint.ts)
TimeEpochMs, Clock, SystemClock, MAX_TIMEOUT_MS
WirePROTOCOL_VERSION, MAX_FRAME_BYTES, MAX_SERVER_FRAME_BYTES, encodeMsg, decodeMsg, client and server message schemas
ConfigAuctionConfigSchema, AUCTION_MODES, HOLD_SCOPES, endsInFuture
AuditAuditPayload, AUDIT_OUTCOMES, audit record helpers
ConfidencetoBps, confidenceToBps, MAX_CONFIDENCE_BPS
PolicyCEILING_PRESETS_CENTS, MIN_CEILING_CENTS, DEFAULT_MAX_CEILING_CENTS
ReplayREPLAY_BUNDLE_VERSION, REPLAY_EXPORT_VERSION, isReplayBundle

Prop

Type

Key files

money.ts
ids.ts
ids-mint.ts
time.ts
errors.ts
api-error.ts
enums.ts
config-schema.ts
config-rules.ts
ceilings.ts
events.ts
ports.ts
hold-contract.ts
client-key.ts
replay-bundle.ts
audit-record.ts
FileWhat it holds
money.tsThe Cents brand and checked arithmetic
ids.ts, ids-mint.tsULID validation and the minting port with its implementation
events.tsRoomEventSchema, the events the room stores
wire/envelope.ts{v:1, t, ...} strict message builder and the size and text limits
hold-contract.tsRequest and response schemas of the hold routes, plus the bidder-name rule
ceilings.tsThe ceiling policy copy that engine, payments and the room must keep equal
client-key.tsclientKey: an IPv6 client is counted by its first four groups
replay-bundle.tsThe redacted public export wrapped with its head hash

Invariants

  1. Money is never a float. Anything branded Cents passed isCents. Tests: packages/shared/test/money.test.ts, money-format.test.ts.
  2. Ids are valid ULIDs or they are refused. Tests: ids.test.ts, ids-mint.test.ts.
  3. Wire messages are strict. Each schema is .strict() and carries v: 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.
  4. A bidder name cannot hide in invisible characters. BIDDER_NAME_FORBIDDEN_PATTERN rejects control, zero-width and direction characters and angle brackets. Tests: hold-contract.test.ts.
  5. A client cannot dodge a rate limit by rotating addresses in its prefix. clientKey maps a whole /64 to one key and an IPv4-mapped address to the IPv4 address. Tests: client-key.test.ts; the server test packages/server/test/client-key-parity.test.ts keeps payments' own copy equal.
  6. Copies stay equal. The ceiling policy is copied into the engine, payments and the room config; packages/server/test/ceiling-policy.test.ts fails when a copy drifts, and packages/engine/test/contract/shared.test.ts compares the engine's constants with this package. The hold contract has one copy here; packages/server/test/contract-shared-drift.test.ts guards 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.

Edit on GitHub

Last updated on