@joystyk/rooms 0.0.0 → 0.2.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +147 -2
  3. package/dist/canonical.d.ts +10 -0
  4. package/dist/canonical.js +30 -0
  5. package/dist/canonical.js.map +1 -0
  6. package/dist/core.d.ts +3 -0
  7. package/dist/core.js +1879 -0
  8. package/dist/core.js.map +1 -0
  9. package/dist/index.d.ts +6 -0
  10. package/dist/index.js +7 -0
  11. package/dist/names.d.ts +19 -0
  12. package/dist/names.js +28 -0
  13. package/dist/names.js.map +1 -0
  14. package/dist/node/host.d.ts +67 -0
  15. package/dist/node/host.js +323 -0
  16. package/dist/node/host.js.map +1 -0
  17. package/dist/node/index.d.ts +6 -0
  18. package/dist/node/index.js +7 -0
  19. package/dist/node/jobs.d.ts +48 -0
  20. package/dist/node/jobs.js +176 -0
  21. package/dist/node/jobs.js.map +1 -0
  22. package/dist/node/registry.d.ts +32 -0
  23. package/dist/node/registry.js +43 -0
  24. package/dist/node/registry.js.map +1 -0
  25. package/dist/node/server.d.ts +59 -0
  26. package/dist/node/server.js +577 -0
  27. package/dist/node/server.js.map +1 -0
  28. package/dist/node/store.d.ts +53 -0
  29. package/dist/node/store.js +245 -0
  30. package/dist/node/store.js.map +1 -0
  31. package/dist/node/support.d.ts +67 -0
  32. package/dist/node/support.js +126 -0
  33. package/dist/node/support.js.map +1 -0
  34. package/dist/policy.d.ts +15 -0
  35. package/dist/policy.js +41 -0
  36. package/dist/policy.js.map +1 -0
  37. package/dist/protocol/index.d.ts +43 -0
  38. package/dist/protocol/index.js +48 -0
  39. package/dist/protocol/index.js.map +1 -0
  40. package/dist/protocol/schemas.d.ts +792 -0
  41. package/dist/protocol/schemas.js +332 -0
  42. package/dist/protocol/schemas.js.map +1 -0
  43. package/dist/protocol/types.d.ts +279 -0
  44. package/dist/protocol/types.js +1 -0
  45. package/dist/record.d.ts +173 -0
  46. package/dist/record.js +181 -0
  47. package/dist/record.js.map +1 -0
  48. package/dist/types.d.ts +374 -0
  49. package/dist/types.js +15 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +56 -11
@@ -0,0 +1,332 @@
1
+ import { isJson } from "@joystyk/contracts";
2
+ import * as z from "zod/mini";
3
+ //#region src/protocol/schemas.ts
4
+ /** The current wire protocol version. */
5
+ var PROTOCOL_VERSION = 1;
6
+ /** Versions the server accepts: the current one and, once it exists, the previous one (Rule 4). */
7
+ var SUPPORTED_PROTOCOLS = [1];
8
+ /** Invite-code letters: no 0/O or 1/I. */
9
+ var ROOM_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
10
+ var ROOM_CODE_LENGTH = 6;
11
+ /** Any JSON value, checked with the contract's `isJson` (no NaN, Infinity or -0). */
12
+ var JsonValue = z.custom((value) => isJson(value), { message: "Expected a JSON value." });
13
+ var RoomCode = z.string().check(z.regex(/^[A-HJ-NP-Z2-9]{6}$/));
14
+ /** Generated by the client for each action: 8–40 URL-safe characters. */
15
+ var ActionId = z.string().check(z.regex(/^[A-Za-z0-9_-]{8,40}$/));
16
+ /** 24 random bytes, base64url. */
17
+ var SeatToken = z.string().check(z.regex(/^[A-Za-z0-9_-]{32}$/));
18
+ var GameId = z.string().check(z.maxLength(64), z.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/));
19
+ /**
20
+ * 1–24 characters, no control or format characters, no leading or trailing space. This schema
21
+ * is only the shape: the room core runs the identity package's name filter and replaces a
22
+ * refused name with a generated one (notice `name-replaced`).
23
+ */
24
+ var DisplayName = z.string().check(z.minLength(1), z.maxLength(24), z.regex(/^(?![\s\S]*[\p{Cc}\p{Cf}])\S(?:[\s\S]*\S)?$/u));
25
+ var Revision = z.int().check(z.nonnegative());
26
+ var SeatIdSchema = z.int().check(z.nonnegative(), z.maximum(63));
27
+ var BuildId = z.string().check(z.minLength(1), z.maxLength(64), z.regex(/^[A-Za-z0-9._+-]+$/));
28
+ var Version = z.int().check(z.positive());
29
+ /** A decision clock in milliseconds: 1 s to 24 h. */
30
+ var DecisionMs = z.int().check(z.minimum(1e3), z.maximum(864e5));
31
+ var AiLevel = z.string().check(z.minLength(1), z.maxLength(32));
32
+ /** A time bank: 1 s to 24 h to start with, and up to 1 h added back per move. */
33
+ var BankSettingSchema = z.strictObject({
34
+ initialMs: z.int().check(z.minimum(1e3), z.maximum(864e5)),
35
+ incrementMs: z.int().check(z.nonnegative(), z.maximum(36e5))
36
+ });
37
+ var Time = z.number().check(z.nonnegative());
38
+ var GamePinSchema = z.object({
39
+ id: GameId,
40
+ build: BuildId,
41
+ rulesVersion: z.int().check(z.nonnegative()),
42
+ saveVersion: z.int().check(z.nonnegative())
43
+ });
44
+ var RoomPhaseSchema = z.enum([
45
+ "lobby",
46
+ "playing",
47
+ "finished",
48
+ "closed"
49
+ ]);
50
+ var OfferKindSchema = z.enum(["rematch", "end"]);
51
+ /** An offer's id: the revision that opened it. */
52
+ var OfferId = Revision;
53
+ var RoomControlSchema = z.discriminatedUnion("kind", [
54
+ z.strictObject({
55
+ kind: z.literal("ready"),
56
+ ready: z.boolean()
57
+ }),
58
+ z.strictObject({
59
+ kind: z.literal("settings"),
60
+ decisionMs: z.optional(z.nullable(DecisionMs)),
61
+ bank: z.optional(z.nullable(BankSettingSchema)),
62
+ config: z.optional(JsonValue),
63
+ aiLevel: z.optional(AiLevel)
64
+ }),
65
+ z.strictObject({
66
+ kind: z.literal("fill-with-ai"),
67
+ seat: SeatIdSchema
68
+ }),
69
+ z.strictObject({
70
+ kind: z.literal("remove-seat"),
71
+ seat: SeatIdSchema
72
+ }),
73
+ z.strictObject({ kind: z.literal("start") }),
74
+ z.strictObject({
75
+ kind: z.literal("offer"),
76
+ offer: OfferKindSchema
77
+ }),
78
+ z.strictObject({
79
+ kind: z.literal("accept"),
80
+ offer: OfferId
81
+ }),
82
+ z.strictObject({
83
+ kind: z.literal("decline"),
84
+ offer: OfferId
85
+ }),
86
+ z.strictObject({
87
+ kind: z.literal("withdraw"),
88
+ offer: OfferId
89
+ }),
90
+ z.strictObject({ kind: z.literal("leave-seat") }),
91
+ z.strictObject({ kind: z.literal("resign") })
92
+ ]);
93
+ var JoinMessageSchema = z.strictObject({
94
+ v: Version,
95
+ t: z.literal("join"),
96
+ room: RoomCode,
97
+ game: GameId,
98
+ name: DisplayName,
99
+ token: z.optional(SeatToken),
100
+ watch: z.optional(z.boolean()),
101
+ build: BuildId
102
+ });
103
+ var ActMessageSchema = z.strictObject({
104
+ v: Version,
105
+ t: z.literal("act"),
106
+ id: ActionId,
107
+ rev: Revision,
108
+ command: JsonValue
109
+ });
110
+ var ControlMessageSchema = z.strictObject({
111
+ v: Version,
112
+ t: z.literal("control"),
113
+ id: ActionId,
114
+ rev: Revision,
115
+ control: RoomControlSchema
116
+ });
117
+ var ResyncMessageSchema = z.strictObject({
118
+ v: Version,
119
+ t: z.literal("resync")
120
+ });
121
+ var ClientMessageSchema = z.discriminatedUnion("t", [
122
+ JoinMessageSchema,
123
+ ActMessageSchema,
124
+ ControlMessageSchema,
125
+ ResyncMessageSchema
126
+ ]);
127
+ var PublicSeatSchema = z.object({
128
+ id: SeatIdSchema,
129
+ name: z.string(),
130
+ controller: z.enum(["human", "ai"]),
131
+ aiLevel: z.nullable(z.string()),
132
+ claimed: z.boolean(),
133
+ ready: z.boolean()
134
+ });
135
+ var PresenceSchema = z.object({
136
+ connected: z.array(z.boolean()),
137
+ clocks: z.array(z.object({
138
+ seat: SeatIdSchema,
139
+ remainingMs: Time,
140
+ running: z.boolean(),
141
+ paused: z.optional(z.boolean())
142
+ })),
143
+ paused: z.boolean(),
144
+ spectators: z.int().check(z.nonnegative())
145
+ });
146
+ var SettingsSchema = z.object({
147
+ decisionMs: z.nullable(z.int()),
148
+ bank: z.nullable(z.object({
149
+ initialMs: z.int(),
150
+ incrementMs: z.int()
151
+ })),
152
+ aiLevel: z.nullable(z.string())
153
+ });
154
+ var OutcomeSchema = z.object({
155
+ result: z.enum([
156
+ "won",
157
+ "lost",
158
+ "draw",
159
+ "completed",
160
+ "abandoned"
161
+ ]),
162
+ placements: z.array(z.object({
163
+ seat: SeatIdSchema,
164
+ place: z.int(),
165
+ score: z.optional(z.number())
166
+ })),
167
+ summary: z.string(),
168
+ stats: z.optional(z.record(z.string(), z.number())),
169
+ elapsedMs: z.optional(Time)
170
+ });
171
+ var RoomOfferSchema = z.object({
172
+ id: OfferId,
173
+ kind: OfferKindSchema,
174
+ by: SeatIdSchema,
175
+ status: z.enum([
176
+ "open",
177
+ "declined",
178
+ "withdrawn"
179
+ ]),
180
+ accepted: z.array(SeatIdSchema),
181
+ declinedBy: z.nullable(SeatIdSchema),
182
+ endsAt: z.nullable(Time)
183
+ });
184
+ var ViewBeatSchema = z.object({
185
+ event: JsonValue,
186
+ view: z.optional(JsonValue)
187
+ });
188
+ var WelcomeMessageSchema = z.object({
189
+ v: Version,
190
+ t: z.literal("welcome"),
191
+ room: RoomCode,
192
+ seat: z.nullable(SeatIdSchema),
193
+ token: z.optional(SeatToken),
194
+ host: z.nullable(SeatIdSchema),
195
+ game: GamePinSchema,
196
+ protocol: Version
197
+ });
198
+ var SnapshotMessageSchema = z.object({
199
+ v: Version,
200
+ t: z.literal("snapshot"),
201
+ epoch: z.int().check(z.nonnegative()),
202
+ revision: Revision,
203
+ phase: RoomPhaseSchema,
204
+ seat: z.nullable(SeatIdSchema),
205
+ seats: z.array(PublicSeatSchema),
206
+ host: z.nullable(SeatIdSchema),
207
+ game: GamePinSchema,
208
+ settings: SettingsSchema,
209
+ view: z.nullable(JsonValue),
210
+ history: z.array(JsonValue),
211
+ pending: z.array(SeatIdSchema),
212
+ outcome: z.nullable(OutcomeSchema),
213
+ presentation: z.nullable(JsonValue),
214
+ offer: z.optional(z.nullable(RoomOfferSchema)),
215
+ presence: PresenceSchema,
216
+ serverTime: Time
217
+ });
218
+ var UpdateMessageSchema = z.object({
219
+ v: Version,
220
+ t: z.literal("update"),
221
+ epoch: z.int().check(z.nonnegative()),
222
+ revision: Revision,
223
+ from: Revision,
224
+ phase: RoomPhaseSchema,
225
+ host: z.nullable(SeatIdSchema),
226
+ seats: z.optional(z.array(PublicSeatSchema)),
227
+ settings: z.optional(SettingsSchema),
228
+ beats: z.array(ViewBeatSchema),
229
+ view: z.nullable(JsonValue),
230
+ pending: z.array(SeatIdSchema),
231
+ outcome: z.nullable(OutcomeSchema),
232
+ presentation: z.optional(z.nullable(JsonValue)),
233
+ offer: z.optional(z.nullable(RoomOfferSchema)),
234
+ presence: PresenceSchema,
235
+ serverTime: Time
236
+ });
237
+ /** Platform rejection codes, or a game's `GameError.code` with a `game:` prefix. */
238
+ var RejectionCodeSchema = z.string().check(z.regex(/^(?:[a-z-]+|game:.+)$/));
239
+ var ReceiptMessageSchema = z.object({
240
+ v: Version,
241
+ t: z.literal("receipt"),
242
+ id: ActionId,
243
+ result: z.discriminatedUnion("status", [z.object({
244
+ status: z.literal("accepted"),
245
+ revision: Revision
246
+ }), z.object({
247
+ status: z.literal("rejected"),
248
+ code: RejectionCodeSchema,
249
+ message: z.string(),
250
+ revision: Revision
251
+ })])
252
+ });
253
+ var PresenceMessageSchema = z.object({
254
+ v: Version,
255
+ t: z.literal("presence"),
256
+ presence: PresenceSchema,
257
+ serverTime: Time
258
+ });
259
+ var NoticeCodeSchema = z.enum([
260
+ "bad-message",
261
+ "too-fast",
262
+ "no-such-room",
263
+ "room-full",
264
+ "protocol-unsupported",
265
+ "server-busy",
266
+ "name-replaced"
267
+ ]);
268
+ var NoticeMessageSchema = z.object({
269
+ v: Version,
270
+ t: z.literal("notice"),
271
+ code: NoticeCodeSchema,
272
+ message: z.string()
273
+ });
274
+ var ReplacedMessageSchema = z.object({
275
+ v: Version,
276
+ t: z.literal("replaced")
277
+ });
278
+ var ClosingMessageSchema = z.object({
279
+ v: Version,
280
+ t: z.literal("closing"),
281
+ reason: z.enum([
282
+ "restart",
283
+ "expired",
284
+ "closed"
285
+ ]),
286
+ retryAfterMs: z.nullable(Time)
287
+ });
288
+ var ServerMessageSchema = z.discriminatedUnion("t", [
289
+ WelcomeMessageSchema,
290
+ SnapshotMessageSchema,
291
+ UpdateMessageSchema,
292
+ ReceiptMessageSchema,
293
+ PresenceMessageSchema,
294
+ NoticeMessageSchema,
295
+ ReplacedMessageSchema,
296
+ ClosingMessageSchema
297
+ ]);
298
+ var CreateRoomRequestSchema = z.strictObject({
299
+ game: GameId,
300
+ name: DisplayName,
301
+ config: z.optional(JsonValue),
302
+ settings: z.optional(z.strictObject({
303
+ decisionMs: z.optional(z.nullable(DecisionMs)),
304
+ bank: z.optional(z.nullable(BankSettingSchema))
305
+ }))
306
+ });
307
+ var CreateRoomResponseSchema = z.object({
308
+ room: RoomCode,
309
+ token: SeatToken,
310
+ name: z.string(),
311
+ notice: z.optional(z.object({
312
+ code: NoticeCodeSchema,
313
+ message: z.string()
314
+ })),
315
+ game: GamePinSchema,
316
+ expiresAt: Time
317
+ });
318
+ var RoomInfoSchema = z.object({
319
+ game: GamePinSchema,
320
+ phase: RoomPhaseSchema,
321
+ seatsOpen: z.int().check(z.nonnegative()),
322
+ spectatorsAllowed: z.boolean()
323
+ });
324
+ var HostedGamesSchema = z.object({
325
+ protocol: Version,
326
+ protocols: z.array(Version),
327
+ games: z.array(GameId)
328
+ });
329
+ //#endregion
330
+ export { ActMessageSchema, ActionId, BankSettingSchema, BuildId, ClientMessageSchema, ClosingMessageSchema, ControlMessageSchema, CreateRoomRequestSchema, CreateRoomResponseSchema, DecisionMs, DisplayName, GameId, GamePinSchema, HostedGamesSchema, JoinMessageSchema, JsonValue, NoticeCodeSchema, NoticeMessageSchema, OfferKindSchema, PROTOCOL_VERSION, PresenceMessageSchema, ROOM_ALPHABET, ROOM_CODE_LENGTH, ReceiptMessageSchema, RejectionCodeSchema, ReplacedMessageSchema, ResyncMessageSchema, Revision, RoomCode, RoomControlSchema, RoomInfoSchema, RoomOfferSchema, RoomPhaseSchema, SUPPORTED_PROTOCOLS, SeatIdSchema, SeatToken, ServerMessageSchema, SnapshotMessageSchema, UpdateMessageSchema, WelcomeMessageSchema };
331
+
332
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","names":[],"sources":["../../src/protocol/schemas.ts"],"sourcesContent":["// Zod 4 schemas (zod/mini, D-22) for every wire message. They are Standard Schemas (D-18),\n// shared by the server, the session package's remote client and the P2P host. The game's\n// own schemas validate the payloads inside (`command`, `view`, events).\nimport { isJson, type Json } from '@joystyk/contracts';\nimport * as z from 'zod/mini';\n\n/** The current wire protocol version. */\nexport const PROTOCOL_VERSION = 1;\n\n/** Versions the server accepts: the current one and, once it exists, the previous one (Rule 4). */\nexport const SUPPORTED_PROTOCOLS: readonly number[] = [PROTOCOL_VERSION];\n\n/** Invite-code letters: no 0/O or 1/I. */\nexport const ROOM_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';\nexport const ROOM_CODE_LENGTH = 6;\n\n/** Any JSON value, checked with the contract's `isJson` (no NaN, Infinity or -0). */\nexport const JsonValue = z.custom<Json>((value) => isJson(value), { message: 'Expected a JSON value.' });\n\nexport const RoomCode = z.string().check(z.regex(/^[A-HJ-NP-Z2-9]{6}$/));\n/** A six-character invite code from `ROOM_ALPHABET`. */\nexport type RoomCode = string;\n/** Generated by the client for each action: 8–40 URL-safe characters. */\nexport const ActionId = z.string().check(z.regex(/^[A-Za-z0-9_-]{8,40}$/));\nexport type ActionId = string;\n/** 24 random bytes, base64url. */\nexport const SeatToken = z.string().check(z.regex(/^[A-Za-z0-9_-]{32}$/));\nexport const GameId = z.string().check(z.maxLength(64), z.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/));\n/**\n * 1–24 characters, no control or format characters, no leading or trailing space. This schema\n * is only the shape: the room core runs the identity package's name filter and replaces a\n * refused name with a generated one (notice `name-replaced`).\n */\nexport const DisplayName = z\n .string()\n .check(z.minLength(1), z.maxLength(24), z.regex(/^(?![\\s\\S]*[\\p{Cc}\\p{Cf}])\\S(?:[\\s\\S]*\\S)?$/u));\nexport const Revision = z.int().check(z.nonnegative());\nexport const SeatIdSchema = z.int().check(z.nonnegative(), z.maximum(63));\nexport const BuildId = z.string().check(z.minLength(1), z.maxLength(64), z.regex(/^[A-Za-z0-9._+-]+$/));\nconst Version = z.int().check(z.positive());\n/** A decision clock in milliseconds: 1 s to 24 h. */\nexport const DecisionMs = z.int().check(z.minimum(1_000), z.maximum(86_400_000));\nconst AiLevel = z.string().check(z.minLength(1), z.maxLength(32));\n/** A time bank: 1 s to 24 h to start with, and up to 1 h added back per move. */\nexport const BankSettingSchema = z.strictObject({\n initialMs: z.int().check(z.minimum(1_000), z.maximum(86_400_000)),\n incrementMs: z.int().check(z.nonnegative(), z.maximum(3_600_000)),\n});\nconst Time = z.number().check(z.nonnegative());\n\nexport const GamePinSchema = z.object({\n id: GameId,\n build: BuildId,\n rulesVersion: z.int().check(z.nonnegative()),\n saveVersion: z.int().check(z.nonnegative()),\n});\n\nexport const RoomPhaseSchema = z.enum(['lobby', 'playing', 'finished', 'closed']);\n\nexport const OfferKindSchema = z.enum(['rematch', 'end']);\n/** An offer's id: the revision that opened it. */\nconst OfferId = Revision;\n\nexport const RoomControlSchema = z.discriminatedUnion('kind', [\n z.strictObject({ kind: z.literal('ready'), ready: z.boolean() }),\n z.strictObject({\n kind: z.literal('settings'),\n decisionMs: z.optional(z.nullable(DecisionMs)),\n bank: z.optional(z.nullable(BankSettingSchema)),\n config: z.optional(JsonValue),\n aiLevel: z.optional(AiLevel),\n }),\n z.strictObject({ kind: z.literal('fill-with-ai'), seat: SeatIdSchema }),\n z.strictObject({ kind: z.literal('remove-seat'), seat: SeatIdSchema }),\n z.strictObject({ kind: z.literal('start') }),\n z.strictObject({ kind: z.literal('offer'), offer: OfferKindSchema }),\n z.strictObject({ kind: z.literal('accept'), offer: OfferId }),\n z.strictObject({ kind: z.literal('decline'), offer: OfferId }),\n z.strictObject({ kind: z.literal('withdraw'), offer: OfferId }),\n z.strictObject({ kind: z.literal('leave-seat') }),\n z.strictObject({ kind: z.literal('resign') }),\n]);\n\n// ── Client → server ─────────────────────────────────────────────────────────────────────\n\nexport const JoinMessageSchema = z.strictObject({\n v: Version,\n t: z.literal('join'),\n room: RoomCode,\n game: GameId,\n name: DisplayName,\n token: z.optional(SeatToken),\n watch: z.optional(z.boolean()),\n build: BuildId,\n});\n\nexport const ActMessageSchema = z.strictObject({\n v: Version,\n t: z.literal('act'),\n id: ActionId,\n rev: Revision,\n command: JsonValue,\n});\n\nexport const ControlMessageSchema = z.strictObject({\n v: Version,\n t: z.literal('control'),\n id: ActionId,\n rev: Revision,\n control: RoomControlSchema,\n});\n\nexport const ResyncMessageSchema = z.strictObject({ v: Version, t: z.literal('resync') });\n\nexport const ClientMessageSchema = z.discriminatedUnion('t', [\n JoinMessageSchema,\n ActMessageSchema,\n ControlMessageSchema,\n ResyncMessageSchema,\n]);\n\n// ── Server → client ─────────────────────────────────────────────────────────────────────\n\nconst PublicSeatSchema = z.object({\n id: SeatIdSchema,\n name: z.string(),\n controller: z.enum(['human', 'ai']),\n aiLevel: z.nullable(z.string()),\n claimed: z.boolean(),\n ready: z.boolean(),\n});\n\nconst PresenceSchema = z.object({\n connected: z.array(z.boolean()),\n clocks: z.array(\n z.object({ seat: SeatIdSchema, remainingMs: Time, running: z.boolean(), paused: z.optional(z.boolean()) }),\n ),\n paused: z.boolean(),\n spectators: z.int().check(z.nonnegative()),\n});\n\nconst SettingsSchema = z.object({\n decisionMs: z.nullable(z.int()),\n bank: z.nullable(z.object({ initialMs: z.int(), incrementMs: z.int() })),\n aiLevel: z.nullable(z.string()),\n});\n\nconst OutcomeSchema = z.object({\n result: z.enum(['won', 'lost', 'draw', 'completed', 'abandoned']),\n placements: z.array(z.object({ seat: SeatIdSchema, place: z.int(), score: z.optional(z.number()) })),\n summary: z.string(),\n stats: z.optional(z.record(z.string(), z.number())),\n elapsedMs: z.optional(Time),\n});\n\nexport const RoomOfferSchema = z.object({\n id: OfferId,\n kind: OfferKindSchema,\n by: SeatIdSchema,\n status: z.enum(['open', 'declined', 'withdrawn']),\n accepted: z.array(SeatIdSchema),\n declinedBy: z.nullable(SeatIdSchema),\n endsAt: z.nullable(Time),\n});\n\nconst ViewBeatSchema = z.object({ event: JsonValue, view: z.optional(JsonValue) });\n\nexport const WelcomeMessageSchema = z.object({\n v: Version,\n t: z.literal('welcome'),\n room: RoomCode,\n seat: z.nullable(SeatIdSchema),\n token: z.optional(SeatToken),\n host: z.nullable(SeatIdSchema),\n game: GamePinSchema,\n protocol: Version,\n});\n\nexport const SnapshotMessageSchema = z.object({\n v: Version,\n t: z.literal('snapshot'),\n epoch: z.int().check(z.nonnegative()),\n revision: Revision,\n phase: RoomPhaseSchema,\n seat: z.nullable(SeatIdSchema),\n seats: z.array(PublicSeatSchema),\n host: z.nullable(SeatIdSchema),\n game: GamePinSchema,\n settings: SettingsSchema,\n view: z.nullable(JsonValue),\n history: z.array(JsonValue),\n pending: z.array(SeatIdSchema),\n outcome: z.nullable(OutcomeSchema),\n presentation: z.nullable(JsonValue),\n offer: z.optional(z.nullable(RoomOfferSchema)),\n presence: PresenceSchema,\n serverTime: Time,\n});\n\nexport const UpdateMessageSchema = z.object({\n v: Version,\n t: z.literal('update'),\n epoch: z.int().check(z.nonnegative()),\n revision: Revision,\n from: Revision,\n phase: RoomPhaseSchema,\n host: z.nullable(SeatIdSchema),\n seats: z.optional(z.array(PublicSeatSchema)),\n settings: z.optional(SettingsSchema),\n beats: z.array(ViewBeatSchema),\n view: z.nullable(JsonValue),\n pending: z.array(SeatIdSchema),\n outcome: z.nullable(OutcomeSchema),\n presentation: z.optional(z.nullable(JsonValue)),\n offer: z.optional(z.nullable(RoomOfferSchema)),\n presence: PresenceSchema,\n serverTime: Time,\n});\n\n/** Platform rejection codes, or a game's `GameError.code` with a `game:` prefix. */\nexport const RejectionCodeSchema = z.string().check(z.regex(/^(?:[a-z-]+|game:.+)$/));\n\nexport const ReceiptMessageSchema = z.object({\n v: Version,\n t: z.literal('receipt'),\n id: ActionId,\n result: z.discriminatedUnion('status', [\n z.object({ status: z.literal('accepted'), revision: Revision }),\n z.object({ status: z.literal('rejected'), code: RejectionCodeSchema, message: z.string(), revision: Revision }),\n ]),\n});\n\nexport const PresenceMessageSchema = z.object({\n v: Version,\n t: z.literal('presence'),\n presence: PresenceSchema,\n serverTime: Time,\n});\n\nexport const NoticeCodeSchema = z.enum([\n 'bad-message',\n 'too-fast',\n 'no-such-room',\n 'room-full',\n 'protocol-unsupported',\n 'server-busy',\n 'name-replaced',\n]);\n\nexport const NoticeMessageSchema = z.object({\n v: Version,\n t: z.literal('notice'),\n code: NoticeCodeSchema,\n message: z.string(),\n});\n\nexport const ReplacedMessageSchema = z.object({ v: Version, t: z.literal('replaced') });\n\nexport const ClosingMessageSchema = z.object({\n v: Version,\n t: z.literal('closing'),\n reason: z.enum(['restart', 'expired', 'closed']),\n retryAfterMs: z.nullable(Time),\n});\n\nexport const ServerMessageSchema = z.discriminatedUnion('t', [\n WelcomeMessageSchema,\n SnapshotMessageSchema,\n UpdateMessageSchema,\n ReceiptMessageSchema,\n PresenceMessageSchema,\n NoticeMessageSchema,\n ReplacedMessageSchema,\n ClosingMessageSchema,\n]);\n\n// ── HTTP ────────────────────────────────────────────────────────────────────────────────\n\nexport const CreateRoomRequestSchema = z.strictObject({\n game: GameId,\n name: DisplayName,\n config: z.optional(JsonValue),\n settings: z.optional(\n z.strictObject({ decisionMs: z.optional(z.nullable(DecisionMs)), bank: z.optional(z.nullable(BankSettingSchema)) }),\n ),\n});\n\nexport const CreateRoomResponseSchema = z.object({\n room: RoomCode,\n token: SeatToken,\n name: z.string(),\n notice: z.optional(z.object({ code: NoticeCodeSchema, message: z.string() })),\n game: GamePinSchema,\n expiresAt: Time,\n});\n\nexport const RoomInfoSchema = z.object({\n game: GamePinSchema,\n phase: RoomPhaseSchema,\n seatsOpen: z.int().check(z.nonnegative()),\n spectatorsAllowed: z.boolean(),\n});\n\nexport const HostedGamesSchema = z.object({\n protocol: Version,\n protocols: z.array(Version),\n games: z.array(GameId),\n});\n"],"mappings":"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"}
@@ -0,0 +1,279 @@
1
+ import type { Json, Outcome, SeatClock, SeatId, ViewBeat } from '@joystyk/contracts';
2
+ import type { ActionId, RoomCode } from './schemas.js';
3
+ /** The game build and versions a match runs on; fixed for the match (section 6.1). */
4
+ export type GamePin = {
5
+ /** Manifest id, e.g. "hearts". */
6
+ readonly id: string;
7
+ /** Build of the game package, e.g. "1.4.0". Clients load this build. */
8
+ readonly build: string;
9
+ readonly rulesVersion: number;
10
+ readonly saveVersion: number;
11
+ };
12
+ export type RoomPhase = 'lobby' | 'playing' | 'finished' | 'closed';
13
+ /** A seat as every viewer sees it. No token or token hash ever leaves the server. */
14
+ export type PublicSeat = {
15
+ readonly id: SeatId;
16
+ readonly name: string;
17
+ readonly controller: 'human' | 'ai';
18
+ readonly aiLevel: string | null;
19
+ /** True when a player holds the seat's token (also while the AI covers for them). */
20
+ readonly claimed: boolean;
21
+ readonly ready: boolean;
22
+ };
23
+ /** A time bank with increment (the manifest's `clock.mode: 'bank'`). */
24
+ export type BankSetting = {
25
+ readonly initialMs: number;
26
+ readonly incrementMs: number;
27
+ };
28
+ /** Live facts that never change the revision: connections and running clocks. */
29
+ export type Presence = {
30
+ /** Indexed by seat: whether a player is connected to it. */
31
+ readonly connected: readonly boolean[];
32
+ /**
33
+ * Decision clocks as the contract's `SeatClock`: per-move clocks for pending seats, or
34
+ * every seat's bank. A clock that isn't `running` (paused room, playback hold, a seat
35
+ * that isn't pending) doesn't count down.
36
+ */
37
+ readonly clocks: readonly SeatClock[];
38
+ /**
39
+ * True when no player is connected, so the table is paused (section 3.5 rule 8, D-58):
40
+ * its clocks keep the time they had and say so with `paused`.
41
+ */
42
+ readonly paused: boolean;
43
+ readonly spectators: number;
44
+ };
45
+ /** What an offer proposes: a new match after this one, or ending this one early with no result (D-61). */
46
+ export type OfferKind = 'rematch' | 'end';
47
+ /**
48
+ * A rematch or end-match offer as every viewer sees it (section 3.10). Any seated player may
49
+ * make one. A declined or withdrawn offer stays until the next one, so the table can say so.
50
+ */
51
+ export type RoomOffer = {
52
+ /** Names the offer in answers: the revision that opened it. */
53
+ readonly id: number;
54
+ readonly kind: OfferKind;
55
+ /** The seat that offered. */
56
+ readonly by: SeatId;
57
+ readonly status: 'open' | 'declined' | 'withdrawn';
58
+ /** Seats that have accepted: the proposer, each player who said yes and, for a rematch, every computer seat. */
59
+ readonly accepted: readonly SeatId[];
60
+ /** The seat that declined, if one did. */
61
+ readonly declinedBy: SeatId | null;
62
+ /** An end offer: when the match ends if nobody declines, in server time. Null for a rematch. */
63
+ readonly endsAt: number | null;
64
+ };
65
+ /** Room settings the host may change in the lobby. */
66
+ export type RoomSettings = {
67
+ /** Per-move clock; null when off or when a bank runs. */
68
+ readonly decisionMs: number | null;
69
+ readonly bank: BankSetting | null;
70
+ readonly aiLevel: string | null;
71
+ };
72
+ /** A room control: lobby and match actions that belong to the room, not the game. */
73
+ export type RoomControl = {
74
+ readonly kind: 'ready';
75
+ readonly ready: boolean;
76
+ } | {
77
+ readonly kind: 'settings';
78
+ /** A per-move clock (turns the bank off), or null for none. */
79
+ readonly decisionMs?: number | null;
80
+ /** A time bank (turns the per-move clock off), or null for none. */
81
+ readonly bank?: BankSetting | null;
82
+ readonly config?: Json;
83
+ readonly aiLevel?: string;
84
+ } | {
85
+ readonly kind: 'fill-with-ai';
86
+ readonly seat: SeatId;
87
+ } | {
88
+ readonly kind: 'remove-seat';
89
+ readonly seat: SeatId;
90
+ } | {
91
+ readonly kind: 'start';
92
+ }
93
+ /** Offer a rematch (after the match) or to end the match early (during it); D-61. */
94
+ | {
95
+ readonly kind: 'offer';
96
+ readonly offer: OfferKind;
97
+ }
98
+ /** Answer the open offer, named by its id: the other players accept or decline, the proposer withdraws. */
99
+ | {
100
+ readonly kind: 'accept' | 'decline' | 'withdraw';
101
+ readonly offer: number;
102
+ } | {
103
+ readonly kind: 'leave-seat';
104
+ }
105
+ /** The contract's `resign` input, offered by the platform at any time during a match. */
106
+ | {
107
+ readonly kind: 'resign';
108
+ };
109
+ export type JoinMessage = {
110
+ readonly v: number;
111
+ readonly t: 'join';
112
+ readonly room: RoomCode;
113
+ readonly game: string;
114
+ readonly name: string;
115
+ readonly token?: string;
116
+ readonly watch?: boolean;
117
+ readonly build: string;
118
+ };
119
+ export type ActMessage = {
120
+ readonly v: number;
121
+ readonly t: 'act';
122
+ readonly id: ActionId;
123
+ /** The revision the client last saw; staleness is judged per seat (section 3.8). */
124
+ readonly rev: number;
125
+ readonly command: Json;
126
+ };
127
+ export type ControlMessage = {
128
+ readonly v: number;
129
+ readonly t: 'control';
130
+ readonly id: ActionId;
131
+ readonly rev: number;
132
+ readonly control: RoomControl;
133
+ };
134
+ export type ResyncMessage = {
135
+ readonly v: number;
136
+ readonly t: 'resync';
137
+ };
138
+ export type ClientMessage = JoinMessage | ActMessage | ControlMessage | ResyncMessage;
139
+ export type WelcomeMessage = {
140
+ readonly v: number;
141
+ readonly t: 'welcome';
142
+ readonly room: RoomCode;
143
+ /** Null for a spectator. */
144
+ readonly seat: SeatId | null;
145
+ /** Present only when the server issued a new seat token to this connection. */
146
+ readonly token?: string;
147
+ readonly host: SeatId | null;
148
+ readonly game: GamePin;
149
+ readonly protocol: number;
150
+ };
151
+ export type SnapshotMessage = {
152
+ readonly v: number;
153
+ readonly t: 'snapshot';
154
+ readonly epoch: number;
155
+ readonly revision: number;
156
+ readonly phase: RoomPhase;
157
+ /** The viewer's own seat (null for a spectator). It can change when a match starts. */
158
+ readonly seat: SeatId | null;
159
+ readonly seats: readonly PublicSeat[];
160
+ readonly host: SeatId | null;
161
+ readonly game: GamePin;
162
+ readonly settings: RoomSettings;
163
+ /** `viewFor(state, seat)`; null in a lobby before the first match. */
164
+ readonly view: Json | null;
165
+ /** The match's recent events, each through `eventFor(event, seat)`. */
166
+ readonly history: readonly Json[];
167
+ readonly pending: readonly SeatId[];
168
+ readonly outcome: Outcome | null;
169
+ readonly presentation: Json | null;
170
+ /** The latest rematch or end-match offer; absent from servers older than D-61. */
171
+ readonly offer?: RoomOffer | null;
172
+ readonly presence: Presence;
173
+ readonly serverTime: number;
174
+ };
175
+ export type UpdateMessage = {
176
+ readonly v: number;
177
+ readonly t: 'update';
178
+ readonly epoch: number;
179
+ readonly revision: number;
180
+ /** The revision this update follows. A client whose revision differs sends `resync`. */
181
+ readonly from: number;
182
+ readonly phase: RoomPhase;
183
+ readonly host: SeatId | null;
184
+ /** Present when a seat changed. */
185
+ readonly seats?: readonly PublicSeat[];
186
+ /** Present when the settings changed. */
187
+ readonly settings?: RoomSettings;
188
+ readonly beats: readonly ViewBeat<Json, Json>[];
189
+ readonly view: Json | null;
190
+ readonly pending: readonly SeatId[];
191
+ readonly outcome: Outcome | null;
192
+ /** Present when a job delivered new presentation data. */
193
+ readonly presentation?: Json | null;
194
+ /** The latest rematch or end-match offer; absent from servers older than D-61. */
195
+ readonly offer?: RoomOffer | null;
196
+ readonly presence: Presence;
197
+ readonly serverTime: number;
198
+ };
199
+ export type ReceiptResult = {
200
+ readonly status: 'accepted';
201
+ readonly revision: number;
202
+ } | {
203
+ readonly status: 'rejected';
204
+ readonly code: string;
205
+ readonly message: string;
206
+ readonly revision: number;
207
+ };
208
+ export type ReceiptMessage = {
209
+ readonly v: number;
210
+ readonly t: 'receipt';
211
+ readonly id: ActionId;
212
+ readonly result: ReceiptResult;
213
+ };
214
+ export type PresenceMessage = {
215
+ readonly v: number;
216
+ readonly t: 'presence';
217
+ readonly presence: Presence;
218
+ readonly serverTime: number;
219
+ };
220
+ /** `name-replaced`: the display name was refused, so the seat got a generated one; the message says why. */
221
+ export type NoticeCode = 'bad-message' | 'too-fast' | 'no-such-room' | 'room-full' | 'protocol-unsupported' | 'server-busy' | 'name-replaced';
222
+ export type NoticeMessage = {
223
+ readonly v: number;
224
+ readonly t: 'notice';
225
+ readonly code: NoticeCode;
226
+ readonly message: string;
227
+ };
228
+ export type ReplacedMessage = {
229
+ readonly v: number;
230
+ readonly t: 'replaced';
231
+ };
232
+ export type ClosingMessage = {
233
+ readonly v: number;
234
+ readonly t: 'closing';
235
+ readonly reason: 'restart' | 'expired' | 'closed';
236
+ readonly retryAfterMs: number | null;
237
+ };
238
+ export type ServerMessage = WelcomeMessage | SnapshotMessage | UpdateMessage | ReceiptMessage | PresenceMessage | NoticeMessage | ReplacedMessage | ClosingMessage;
239
+ export type CreateRoomRequest = {
240
+ readonly game: string;
241
+ readonly name: string;
242
+ readonly config?: Json;
243
+ readonly settings?: {
244
+ readonly decisionMs?: number | null;
245
+ readonly bank?: BankSetting | null;
246
+ };
247
+ };
248
+ export type CreateRoomResponse = {
249
+ readonly room: RoomCode;
250
+ /** The creator's seat token for seat 0. Store it before opening the socket. */
251
+ readonly token: string;
252
+ /** The name seat 0 holds: the one asked for, normalized, or a generated one. */
253
+ readonly name: string;
254
+ /** Present when the name asked for was refused: `code` is `name-replaced`, `message` says why. */
255
+ readonly notice?: {
256
+ readonly code: NoticeCode;
257
+ readonly message: string;
258
+ };
259
+ readonly game: GamePin;
260
+ readonly expiresAt: number;
261
+ };
262
+ export type RoomInfo = {
263
+ readonly game: GamePin;
264
+ readonly phase: RoomPhase;
265
+ readonly seatsOpen: number;
266
+ readonly spectatorsAllowed: boolean;
267
+ };
268
+ /**
269
+ * `GET /api/rooms/games`: what this room server hosts, so a launcher offers "Online with
270
+ * friends" only for games it can open (section 5.4).
271
+ */
272
+ export type HostedGames = {
273
+ /** The server's current wire protocol version. */
274
+ readonly protocol: number;
275
+ /** Every protocol version it accepts. */
276
+ readonly protocols: readonly number[];
277
+ /** Ids of the games it opens new rooms for. */
278
+ readonly games: readonly string[];
279
+ };
@@ -0,0 +1 @@
1
+ export {};