@pithy-sh/matchmaking 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,79 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ import { InternalError } from "@pithy-sh/core/src/error/pithyError";
5
+ import type { MatchmakingSnapshot } from "../config/config";
6
+
7
+ /**
8
+ * The session-minter seam — matchmaking's one dependency on `@pithy-sh/multiplayer`, kept at arm's
9
+ * length. Every pairing path (room code, invite accept, an open-queue match) ends by minting a
10
+ * multiplayer session and seating its roster; this is the single place that does it.
11
+ *
12
+ * The default implementation talks to the `SESSIONS` Durable Object namespace binding directly — the
13
+ * create-then-join dance multiplayer's own routes use (`newUniqueId` → `get` → `create(snapshot, host)`
14
+ * → `join(player)`). It is typed against a minimal local RPC shape, so matchmaking never imports
15
+ * multiplayer. Tests inject a fake minter. Matchmaking's output is always a session id.
16
+ */
17
+ export interface SessionMinter {
18
+ /** Mint a session for a game from its snapshot, seat the roster (first id is the creator), return its id. */
19
+ mint(snapshot: MatchmakingSnapshot, players: number, roster: readonly string[]): Promise<string>;
20
+ /** Seat one more player into an already-minted session (the room-code path: host mints, others join). */
21
+ join(sessionId: string, userId: string): Promise<void>;
22
+ }
23
+
24
+ /** The minimal RPC surface the multiplayer session DO exposes — declared locally to avoid a hard dep. */
25
+ interface SessionStub {
26
+ create(snapshot: unknown, creatorUserId: string): Promise<{ sessionId: string }>;
27
+ join(userId: string): Promise<{ sessionId: string }>;
28
+ }
29
+
30
+ /** A DO namespace typed to the minimal session stub — what `env.SESSIONS` provides at runtime. */
31
+ export interface SessionNamespace {
32
+ newUniqueId(): { toString(): string };
33
+ idFromString(hex: string): unknown;
34
+ get(id: unknown): SessionStub;
35
+ }
36
+
37
+ /**
38
+ * The default minter over a `SESSIONS` DO namespace binding. Builds a multiplayer `GameSnapshot` from the
39
+ * matchmaking snapshot (roster size included), creates the session with the first player as creator, and
40
+ * joins the rest.
41
+ */
42
+ export function sessionMinter(sessions: SessionNamespace | undefined): SessionMinter {
43
+ return {
44
+ async mint(snapshot, players, roster) {
45
+ if (!sessions) {
46
+ throw new InternalError({
47
+ detail: "Minting a session requires the SESSIONS Durable Object binding (install @pithy-sh/multiplayer).",
48
+ });
49
+ }
50
+ const [creator, ...rest] = roster;
51
+ if (creator === undefined) {
52
+ throw new InternalError({ detail: "Cannot mint a session for an empty roster." });
53
+ }
54
+ const gameSnapshot = {
55
+ kind: snapshot.kind,
56
+ mode: snapshot.mode,
57
+ players,
58
+ turnTimeoutMs: snapshot.turnTimeoutMs,
59
+ leaderboard: null,
60
+ rules: snapshot.rules,
61
+ };
62
+ const id = sessions.newUniqueId();
63
+ const view = await sessions.get(id).create(gameSnapshot, creator);
64
+ for (const player of rest) {
65
+ await sessions.get(sessions.idFromString(view.sessionId)).join(player);
66
+ }
67
+ return view.sessionId;
68
+ },
69
+
70
+ async join(sessionId, userId) {
71
+ if (!sessions) {
72
+ throw new InternalError({
73
+ detail: "Joining a session requires the SESSIONS Durable Object binding (install @pithy-sh/multiplayer).",
74
+ });
75
+ }
76
+ await sessions.get(sessions.idFromString(sessionId)).join(userId);
77
+ },
78
+ };
79
+ }
@@ -0,0 +1,14 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ // The Miniflare `main` for `*.workers.test.ts`: it exists only to register the Durable Object classes so
5
+ // the test bindings (`QUEUE`, `PRESENCE`) resolve. Its fetch is inert.
6
+
7
+ export { MatchmakingPresence } from "./presence/durableObject";
8
+ export { MatchmakingQueue } from "./queue/durableObject";
9
+
10
+ export default {
11
+ fetch(): Response {
12
+ return new Response("matchmaking test worker", { status: 200 });
13
+ },
14
+ };
@@ -0,0 +1,16 @@
1
+ // SPDX-FileCopyrightText: 2026 Pithy
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ // GENERATED by scripts/stampVersions.ts — do not edit by hand. Regenerate with `bun run stamp-versions`.
5
+ //
6
+ // A Worker cannot read its own package.json, so this is how @pithy-sh/matchmaking knows its own version at
7
+ // runtime. The capability attaches it, and `GET /control-plane/manifest` reports it per capability —
8
+ // which is what answers "should this project upgrade" and "is this customer exposed to what we just
9
+ // fixed". Those questions are only answerable per module, because a project composes some capabilities
10
+ // and not others.
11
+
12
+ /** This package's npm name — the join key against a release feed. */
13
+ export const PACKAGE_NAME = "@pithy-sh/matchmaking";
14
+
15
+ /** This package's version, stamped from its own package.json at generation time. */
16
+ export const PACKAGE_VERSION = "0.1.0";