npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ticketlayer/seatmap

v0.1.2

Published

The Ticketlayer seat map formats and geometry: TLSC, TLSV and TLST readers and encoders, the event plan overlay, the seat map token, hit testing, outlines, the curved stage and the single seat gap check. Pure TypeScript, no DOM

Downloads

498

Readme

@ticketlayer/seatmap

The seat map's formats and geometry, exactly as the shared contract defines them (3dvenue/docs/contract.md, sections 3 to 6). Pure TypeScript: no DOM, no network, no Node or Workers globals beyond WebCrypto for the token. One copy of each rule, used by the elements core, @ticketlayer/seatmap-render, Live (apps/live), the seat map Worker (apps/seatmap), Manage and Box Office.

pnpm --filter @ticketlayer/seatmap build   # dist/esm (with .d.ts) and dist/cjs
pnpm --filter @ticketlayer/seatmap test    # node --test against dist/esm

What a seat is

A seat is its index within a venue plan version (0..n-1, section by section, row by row, as the publisher writes them). Nothing here carries a seat id. A version is addressed on the wire by its publish key pv, 16 lowercase hex.

API

Seat geometry: TLSC/1

decodeTLSC(bytes: Uint8Array): SeatChunk
encodeTLSC(chunk: SeatChunkInput): Uint8Array
chunkFromSeats(seats: PlanSeat[], seatBase: number, quantum = 0.1): SeatChunkInput
rowsOfChunk(chunk): number[][]          // global seat indexes per row
localSeatOf(chunk, seat): number         // -1 when the chunk does not hold it
seatIndexOf(chunk, local): number
rotationDegrees(chunk, local): number

A SeatChunk holds the block's rows (label, seatCount), the quantised integers (qx, qy), the plan positions (x, y, Float64Array), numbers (strings), rotation (256 steps), zone (index into overview.zones), attrs (bits in overview.attributes order), rowOf and rowStart. encodeTLSC(decodeTLSC(b)) is b, byte for byte. The reader refuses a chunk with flag bit 0 (an id column), another version, unknown flag bits, and short or over-long bytes, with a SeatmapFormatError.

Views: TLSV/1

decodeTLSV(bytes): SeatViews
encodeTLSV(views: SeatViewsInput): Uint8Array
seatView(views, local): { eye, yawDegrees, score, distanceMetres, stageVisible, viewpoint, obstructions }

score is decoded but is shown only as words or a band, and only when the overview says viewsPublic. That decision is the caller's.

Availability: TLST/1 and the summary

decodeTLST(bytes): SeatStatus            // { statusVersion, seatCount, bits }
encodeTLST(status): Uint8Array
statusFromAvailable(statusVersion, seatCount, seats: Iterable<number>): SeatStatus
isAvailable(status, seat): boolean
countAvailable(status, from?, to?): number
parseStatusSummary(json): SeatStatusSummary   // tl-seat-status/1

The event plan overlay

decodeOverlay(doc, seatCount?): SeatOverlay   // categories, seatCategory (u8), killed (bitset)
encodeOverlay(overlay): SeatOverlayDocument
categoryOf(overlay, seat): OverlayCategory | null   // null: 255, not sold on this plan
isKilled(overlay, seat): boolean
killedBitset(seatCount, seats): Uint8Array
NOT_SOLD // 255

The overview and the pointer

parseOverview(json): SeatmapOverview      // floors, zones, attributes, decorations, sections
sectionOfSeat(overview, seat): OverviewSection | null
sectionsByChunk(overview): Map<chunkName, OverviewSection[]>
pillarsOf(overview): OverviewPillar[]     // { floorId, label, centre, radius | null, rect | null }
aislesOf(overview): OverviewAisle[]       // { floorId, label, from, to, width | null }
textsOf(overview): OverviewText[]         // { floorId, text, at, size | null }
parsePointer(json): SeatmapPointer        // GET /sales/occurrences/{occ}/seatmap

A section carries name (its display name), label (the label anchor), outline, seatBase, seatCount, rowCount, chunk (the TLSC block holding it), views, and block and rowBase. The overview's blocks list each block's chunk, seat range and sections: whole consecutive areas, greedily, up to 4,000 seats. section.seatBase - chunk.seatBase is where a section's seats start inside its chunk. Zone and category colours are spelled color, as on the wire.

Decorations

overview.decorations is what is drawn on the plan besides the seats: the stage, and since TKT-515 the pillars, aisles and text a venue's plan carries, so a buyer sees what a restricted view is restricted by. The list is optional and backwards compatible: absent or empty means none, and an overview written before this reads exactly as it did. Every position and length is in plan units, the frame the section outlines are in. Every entry has a type, and may name its floorId (one of overview.floors) and a label.

| type | Fields | Drawn | |---|---|---| | stage | rect [x, y, width, height] or outline [[x, y], ...]; label ("Stage", "Pitch") | the stage, curved towards the house | | pillar | round: centre [x, y] and radius (> 0). Rectangular: rect [x, y, width, height] (width and height > 0) and no radius. A round pillar may carry its bounding rect too; centre and radius win | solid, in map-pillar with a map-pillar-edge outline, under the seats, at any size it can be seen at | | aisle | from [x, y] and to [x, y], two different points: the walkway's centre line. width (> 0) across, optional | the walkway in map-aisle with a dashed map-aisle-line marking along it; the marking alone without a width | | text | text (not blank), at [x, y] (the text's centre), size (> 0, the letter height), optional | upright, in map-label, at size (the map's label size without one), once it is big enough to read and only where it covers no seat | | custom | rect or outline, label | kept for the reader that wants it; the renderer does not draw it |

parseOverview hands back a pillar, aisle or text with exactly those fields and drops one that lacks what it needs, so a renderer never meets half a pillar. A type it does not know is kept as it came, so a newer publisher's decorations reach a reader that knows them without breaking one that does not. pillarsOf, aislesOf and textsOf read them typed.

What the publisher emits

The Backstage API's publisher (src/domains/inventory/seatmap/encode.ts, encodeVersion, the decorations of the overview) and the reference encoder (3dvenue scripts/chunk_package.py) write them from the plan's own decorations: Manage's plan editor keeps pillars, aisles and text in the version's structure (apps/manage/src/components/plan-editor/model/output.ts, planStructure), already in plan units. The publisher maps each structure entry to one overview entry, in the structure's order, every number through the same pyFloat as the rest of the overview, and no id (the overview is public and immutable, and nothing reads a decoration by id):

| In the structure | In the overview | |---|---| | { type: 'stage', floorId, label, rect, height } | { type: 'stage', label, rect }, as today | | { type: 'pillar', id, floorId, name, centre, radius, rect } | { type: 'pillar', floorId, label: name, centre, radius } | | a pillar with rect and no centre or radius (a square column from an import) | { type: 'pillar', floorId, label: name, rect } | | { type: 'aisle', id, floorId, name, from, to, width } | { type: 'aisle', floorId, label: name, from, to, width } | | { type: 'label', id, floorId, label, at, size } | { type: 'text', floorId, text: label, at, size } | | { type: 'custom', label or name, rect } | { type: 'custom', label, rect }, as today |

Note the one rename: the structure's text decoration is type: 'label' with its words in label; the overview's is type: 'text' with its words in text, because label is every decoration's optional caption. A structure entry of any other type, or one missing the fields above, is left out. For a worked example, the Playhouse fixture (test/fixtures/contract-v1/playhouse) carries two pillars where its 3D model stands them, named as its views name the obstruction ("Pillar Left", "Pillar Right"), a cross aisle behind the stalls and a text; the Storybook workbench draws them from it.

Viewpoints and media

parseViewpoints(json): SeatViewpoints    // tl-viewpoints/1: per section-row, eye, yaw, stills, pano
stillPathOf(viewpoint, seat): string | null              // still/<row>.<h>/<seat>.jpg
panoTilePathOf(viewpoint, z, x, y): string | null        // pano/<row>.<h>/<z>/<x>_<y>.jpg
nearestStillSeat(viewpoint, seat): { seat, along } | null
isMediaPath(path): boolean                               // stills/, still/ or pano/, as the Worker routes them

A seat's viewpoint is TLSV/1's viewpoint column, an index into the document's viewpoints. A row's stills name its thumbnail atlas (stills/<row>.<h>.jpg, tiles mapping a seat index to its cell) and the directory of its full stills; its pano names the equirectangular tile pyramid (z0 the whole sphere at 512x256, then 2x1, 4x2 and 8x4 tiles of 512 px) with each level's [width, columns, rows]. Every media path is checked against the contract's forms, and one that is not is dropped, so a reader never fetches anything the publisher would not have written. Whether a version has media at all is the pointer's hasStills.

The venue model

modelOf(overview): SeatmapModel | null   // the overview's `model`: one display mesh per level, base first
isMeshPath(path): boolean                // mesh/<level>(.meshopt).<h>.glb, as the Worker routes it

When the 3D pipeline has run, the overview names a display mesh per level (base, then level-<n> for floors[n]), and the meshopt twin of each when there is one; modelOf picks the twin, drops any path that is not in the contract's form, and says which floor each level is. A mesh is glTF 2.0, Y up, with its materials named for their render role (plate, wall, detail, void, stage); a glTF point (x, y, z) is (x, -z, y) in the views' frame (metres, z up, +y towards the stage), so the seats' eyes from TLSV/1 and the meshes line up. Whether a version has a model at all is the pointer's hasModel. tl-seat-explorer's 3D mode draws them.

Geometry

Plan units, x right and y down the page.

convexHull(points), pointInPolygon(x, y, poly), boundsOf(points), centroidOf(points), signedArea2(poly)
seatHullOutline(seats, pad)               // the publisher's outline: hull of padded seats
rowOutline(rows: Point[][], offset, smooth = 2)   // the prototype's outline: follows curved rows
offsetPolygon(poly, d), chaikin(poly, n), dedupe(poly)
curvedStage(rect | corners, audience: Point): { rect, poly, centre, front }
sectionBearing(rotationsDegrees, centroid, focus): radians   // turns a section's seats to face up
frame(points, viewport, { fill, bearing, maxScale }): { centre, scale, bearing }   // focus framing
rotate(point, bearing), wrapAngle(a)
seatPitch(chunk): number                  // median neighbour distance in a row
new PointGrid(xs, ys, cell, include?).nearest(x, y, radius, accept?): index | -1

The single seat gap check

checkSingleSeatGap({ rows: GapRow[], isFree(seat), requested: number[] }):
  { ok, strands: number[], suggestion: number[] | null }
gapRowsOf(chunk): GapRow[]

A seat is stranded when the request leaves it free and alone between two taken seats, or between a taken seat and the row end, and a neighbour is one of the requested seats. A lone seat that was already there is not the request's. suggestion is the nearest shift of the request along its row that strands nothing, keeping its shape and size; ties go to the shift that takes a seat the original would have stranded, then to the left. Online (warn) the client shows the warning and offers the suggestion; Box Office (confirm) may go ahead after a confirm.

The token

SEATMAP_SCOPES, QUEUED_SCOPES                   // overview, overlay, sections, views, viewpoints, status, mesh, stills, still, pano; ['overview'] while queued
parseTokenKeys('kid:base64,kid:base64')         // the first signs, every one verifies
signingKey(keys)
mintSeatmapToken(claims, kid, secret): Promise<string>
verifySeatmapToken(token, keys, nowSeconds?): Promise<{ ok: true, claims, kid } | { ok: false, reason }>
tokenAllows(claims, { scope, pv?, occ? }): boolean
originAllowed(claims, originHeader): boolean   // present and different from org: refused
sessionHash(secret, sessionId): Promise<string> // sid: 22 base64url characters

Lifetime at most 15 minutes (MAX_TOKEN_LIFETIME_SECONDS), 30 seconds of skew, renew after 12. Imported HMAC keys are cached per isolate.

Tests

test/formats.test.mjs builds a curved section and round-trips every format through its encoder and reader, byte for byte, and checks the header offsets the contract fixes.

test/fixtures.test.mjs reads the contract fixtures, the Playhouse and the arena as 3dvenue's reference encoder (scripts/chunk_package.py --fixtures) publishes them, and asserts what their README asks: index.json hashes to the pv, every listed file has its bytes and hash, every twin decompresses to its file, the blocks, zones and attributes, every TLSC header, every TLSC, TLSV and TLST file round-trips byte for byte, the sample seats, and the overlay, the snapshot and the summary agreeing. The fixtures are committed under test/fixtures/contract-v1/ as 3dvenue wrote them and are never edited here:

pnpm --filter @ticketlayer/seatmap run fixtures:sync   # from ../3dvenue, or SEATMAP_FIXTURES_SOURCE