@joystyk/rooms 0.0.0-stage → 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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/NOTICE +36 -0
- package/README.md +149 -2
- package/dist/canonical.d.ts +10 -0
- package/dist/canonical.js +30 -0
- package/dist/canonical.js.map +1 -0
- package/dist/core.d.ts +3 -0
- package/dist/core.js +1879 -0
- package/dist/core.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +7 -0
- package/dist/names.d.ts +19 -0
- package/dist/names.js +28 -0
- package/dist/names.js.map +1 -0
- package/dist/node/host.d.ts +67 -0
- package/dist/node/host.js +323 -0
- package/dist/node/host.js.map +1 -0
- package/dist/node/index.d.ts +6 -0
- package/dist/node/index.js +7 -0
- package/dist/node/jobs.d.ts +48 -0
- package/dist/node/jobs.js +176 -0
- package/dist/node/jobs.js.map +1 -0
- package/dist/node/registry.d.ts +32 -0
- package/dist/node/registry.js +43 -0
- package/dist/node/registry.js.map +1 -0
- package/dist/node/server.d.ts +59 -0
- package/dist/node/server.js +577 -0
- package/dist/node/server.js.map +1 -0
- package/dist/node/store.d.ts +53 -0
- package/dist/node/store.js +245 -0
- package/dist/node/store.js.map +1 -0
- package/dist/node/support.d.ts +67 -0
- package/dist/node/support.js +126 -0
- package/dist/node/support.js.map +1 -0
- package/dist/policy.d.ts +15 -0
- package/dist/policy.js +41 -0
- package/dist/policy.js.map +1 -0
- package/dist/protocol/index.d.ts +43 -0
- package/dist/protocol/index.js +48 -0
- package/dist/protocol/index.js.map +1 -0
- package/dist/protocol/schemas.d.ts +792 -0
- package/dist/protocol/schemas.js +332 -0
- package/dist/protocol/schemas.js.map +1 -0
- package/dist/protocol/types.d.ts +279 -0
- package/dist/protocol/types.js +1 -0
- package/dist/record.d.ts +173 -0
- package/dist/record.js +181 -0
- package/dist/record.js.map +1 -0
- package/dist/types.d.ts +374 -0
- package/dist/types.js +15 -0
- package/dist/types.js.map +1 -0
- package/package.json +62 -3
package/dist/record.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Json, Result } from '@joystyk/contracts';
|
|
2
|
+
import * as z from 'zod/mini';
|
|
3
|
+
import type { RoomRecord } from './types.js';
|
|
4
|
+
/** The record format this build writes. */
|
|
5
|
+
export declare const RECORD_FORMAT = 1;
|
|
6
|
+
export declare const RoomRecordSchema: z.ZodMiniObject<{
|
|
7
|
+
format: z.ZodMiniLiteral<1>;
|
|
8
|
+
code: z.ZodMiniString<string>;
|
|
9
|
+
epoch: z.ZodMiniInt;
|
|
10
|
+
writes: z.ZodMiniInt;
|
|
11
|
+
policy: z.ZodMiniObject<{
|
|
12
|
+
seats: z.ZodMiniObject<{
|
|
13
|
+
min: z.ZodMiniInt;
|
|
14
|
+
max: z.ZodMiniInt;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
start: z.ZodMiniEnum<{
|
|
17
|
+
host: "host";
|
|
18
|
+
immediate: "immediate";
|
|
19
|
+
}>;
|
|
20
|
+
lateJoin: z.ZodMiniEnum<{
|
|
21
|
+
spectate: "spectate";
|
|
22
|
+
"take-ai-seat": "take-ai-seat";
|
|
23
|
+
}>;
|
|
24
|
+
absent: z.ZodMiniEnum<{
|
|
25
|
+
ai: "ai";
|
|
26
|
+
timeout: "timeout";
|
|
27
|
+
wait: "wait";
|
|
28
|
+
}>;
|
|
29
|
+
graceMs: z.ZodMiniInt;
|
|
30
|
+
decisionMs: z.ZodMiniNullable<z.ZodMiniInt>;
|
|
31
|
+
bank: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniObject<{
|
|
32
|
+
initialMs: z.ZodMiniInt;
|
|
33
|
+
incrementMs: z.ZodMiniInt;
|
|
34
|
+
}, z.core.$strip>>>;
|
|
35
|
+
maxSpectators: z.ZodMiniInt;
|
|
36
|
+
retainMs: z.ZodMiniInt;
|
|
37
|
+
}, z.core.$strip>;
|
|
38
|
+
phase: z.ZodMiniEnum<{
|
|
39
|
+
closed: "closed";
|
|
40
|
+
lobby: "lobby";
|
|
41
|
+
playing: "playing";
|
|
42
|
+
finished: "finished";
|
|
43
|
+
}>;
|
|
44
|
+
revision: z.ZodMiniInt;
|
|
45
|
+
game: z.ZodMiniObject<{
|
|
46
|
+
id: z.ZodMiniString<string>;
|
|
47
|
+
build: z.ZodMiniString<string>;
|
|
48
|
+
rulesVersion: z.ZodMiniInt;
|
|
49
|
+
saveVersion: z.ZodMiniInt;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
config: z.ZodMiniCustom<Json, Json>;
|
|
52
|
+
aiLevel: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
53
|
+
seats: z.ZodMiniArray<z.ZodMiniObject<{
|
|
54
|
+
id: z.ZodMiniInt;
|
|
55
|
+
claim: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
56
|
+
tokenHash: z.ZodMiniString<string>;
|
|
57
|
+
name: z.ZodMiniString<string>;
|
|
58
|
+
}, z.core.$strip>>;
|
|
59
|
+
controller: z.ZodMiniEnum<{
|
|
60
|
+
human: "human";
|
|
61
|
+
ai: "ai";
|
|
62
|
+
}>;
|
|
63
|
+
aiLevel: z.ZodMiniNullable<z.ZodMiniString<string>>;
|
|
64
|
+
ready: z.ZodMiniBoolean<boolean>;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
host: z.ZodMiniNullable<z.ZodMiniInt>;
|
|
67
|
+
match: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
68
|
+
number: z.ZodMiniInt;
|
|
69
|
+
game: z.ZodMiniObject<{
|
|
70
|
+
id: z.ZodMiniString<string>;
|
|
71
|
+
build: z.ZodMiniString<string>;
|
|
72
|
+
rulesVersion: z.ZodMiniInt;
|
|
73
|
+
saveVersion: z.ZodMiniInt;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
config: z.ZodMiniCustom<Json, Json>;
|
|
76
|
+
seats: z.ZodMiniArray<z.ZodMiniObject<{
|
|
77
|
+
id: z.ZodMiniInt;
|
|
78
|
+
name: z.ZodMiniString<string>;
|
|
79
|
+
controller: z.ZodMiniEnum<{
|
|
80
|
+
human: "human";
|
|
81
|
+
ai: "ai";
|
|
82
|
+
}>;
|
|
83
|
+
aiLevel: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
84
|
+
}, z.core.$strip>>;
|
|
85
|
+
seed: z.ZodMiniObject<{
|
|
86
|
+
algorithm: z.ZodMiniString<string>;
|
|
87
|
+
words: z.ZodMiniArray<z.ZodMiniNumber<number>>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
state: z.ZodMiniCustom<Json, Json>;
|
|
90
|
+
rng: z.ZodMiniObject<{
|
|
91
|
+
algorithm: z.ZodMiniString<string>;
|
|
92
|
+
words: z.ZodMiniArray<z.ZodMiniNumber<number>>;
|
|
93
|
+
}, z.core.$strip>;
|
|
94
|
+
aiRng: z.ZodMiniObject<{
|
|
95
|
+
algorithm: z.ZodMiniString<string>;
|
|
96
|
+
words: z.ZodMiniArray<z.ZodMiniNumber<number>>;
|
|
97
|
+
}, z.core.$strip>;
|
|
98
|
+
decisionRevision: z.ZodMiniArray<z.ZodMiniInt>;
|
|
99
|
+
clocks: z.ZodMiniArray<z.ZodMiniObject<{
|
|
100
|
+
seat: z.ZodMiniInt;
|
|
101
|
+
at: z.ZodMiniNumber<number>;
|
|
102
|
+
decisionRevision: z.ZodMiniInt;
|
|
103
|
+
startsAt: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
104
|
+
onBank: z.ZodMiniOptional<z.ZodMiniBoolean<boolean>>;
|
|
105
|
+
}, z.core.$strip>>;
|
|
106
|
+
banks: z.ZodMiniOptional<z.ZodMiniArray<z.ZodMiniNumber<number>>>;
|
|
107
|
+
jobs: z.ZodMiniArray<z.ZodMiniObject<{
|
|
108
|
+
request: z.ZodMiniObject<{
|
|
109
|
+
id: z.ZodMiniString<string>;
|
|
110
|
+
kind: z.ZodMiniString<string>;
|
|
111
|
+
payload: z.ZodMiniCustom<Json, Json>;
|
|
112
|
+
}, z.core.$strip>;
|
|
113
|
+
revision: z.ZodMiniInt;
|
|
114
|
+
}, z.core.$strip>>;
|
|
115
|
+
presentation: z.ZodMiniNullable<z.ZodMiniCustom<Json, Json>>;
|
|
116
|
+
log: z.ZodMiniArray<z.ZodMiniCustom<Json, Json>>;
|
|
117
|
+
outcome: z.ZodMiniNullable<z.ZodMiniObject<{
|
|
118
|
+
result: z.ZodMiniEnum<{
|
|
119
|
+
won: "won";
|
|
120
|
+
lost: "lost";
|
|
121
|
+
draw: "draw";
|
|
122
|
+
completed: "completed";
|
|
123
|
+
abandoned: "abandoned";
|
|
124
|
+
}>;
|
|
125
|
+
placements: z.ZodMiniArray<z.ZodMiniObject<{
|
|
126
|
+
seat: z.ZodMiniInt;
|
|
127
|
+
place: z.ZodMiniInt;
|
|
128
|
+
score: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
129
|
+
}, z.core.$strip>>;
|
|
130
|
+
summary: z.ZodMiniString<string>;
|
|
131
|
+
stats: z.ZodMiniOptional<z.ZodMiniRecord<z.ZodMiniString<string>, z.ZodMiniNumber<number>>>;
|
|
132
|
+
elapsedMs: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
133
|
+
}, z.core.$strip>>;
|
|
134
|
+
startedAt: z.ZodMiniNumber<number>;
|
|
135
|
+
pausedAt: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
136
|
+
}, z.core.$strip>>;
|
|
137
|
+
offer: z.ZodMiniOptional<z.ZodMiniNullable<z.ZodMiniObject<{
|
|
138
|
+
id: z.ZodMiniInt;
|
|
139
|
+
kind: z.ZodMiniEnum<{
|
|
140
|
+
rematch: "rematch";
|
|
141
|
+
end: "end";
|
|
142
|
+
}>;
|
|
143
|
+
by: z.ZodMiniInt;
|
|
144
|
+
status: z.ZodMiniEnum<{
|
|
145
|
+
open: "open";
|
|
146
|
+
declined: "declined";
|
|
147
|
+
withdrawn: "withdrawn";
|
|
148
|
+
}>;
|
|
149
|
+
accepted: z.ZodMiniArray<z.ZodMiniInt>;
|
|
150
|
+
declinedBy: z.ZodMiniNullable<z.ZodMiniInt>;
|
|
151
|
+
endsAt: z.ZodMiniNullable<z.ZodMiniNumber<number>>;
|
|
152
|
+
}, z.core.$strip>>>;
|
|
153
|
+
receipts: z.ZodMiniArray<z.ZodMiniObject<{
|
|
154
|
+
seat: z.ZodMiniInt;
|
|
155
|
+
id: z.ZodMiniString<string>;
|
|
156
|
+
fingerprint: z.ZodMiniString<string>;
|
|
157
|
+
revision: z.ZodMiniInt;
|
|
158
|
+
}, z.core.$strip>>;
|
|
159
|
+
createdAt: z.ZodMiniNumber<number>;
|
|
160
|
+
updatedAt: z.ZodMiniNumber<number>;
|
|
161
|
+
}, z.core.$strip>;
|
|
162
|
+
export type DecodeError = {
|
|
163
|
+
readonly code: 'newer-format' | 'invalid';
|
|
164
|
+
readonly message: string;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Decodes a stored record: migrates older formats, then validates. `migrated` tells the
|
|
168
|
+
* store to archive the original row before rewriting it.
|
|
169
|
+
*/
|
|
170
|
+
export declare const decodeRecord: (raw: unknown) => Result<{
|
|
171
|
+
readonly record: RoomRecord;
|
|
172
|
+
readonly migrated: boolean;
|
|
173
|
+
}, DecodeError>;
|
package/dist/record.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { GamePinSchema, JsonValue, RoomPhaseSchema } from "./protocol/schemas.js";
|
|
2
|
+
import { err, ok } from "@joystyk/contracts";
|
|
3
|
+
import * as z from "zod/mini";
|
|
4
|
+
//#region src/record.ts
|
|
5
|
+
/** The record format this build writes. */
|
|
6
|
+
var RECORD_FORMAT = 1;
|
|
7
|
+
var Int = z.int();
|
|
8
|
+
var NonNegative = z.int().check(z.nonnegative());
|
|
9
|
+
var Seat = z.int().check(z.nonnegative(), z.maximum(63));
|
|
10
|
+
var RngStateSchema = z.object({
|
|
11
|
+
algorithm: z.string(),
|
|
12
|
+
words: z.array(z.number())
|
|
13
|
+
});
|
|
14
|
+
var Time = z.number();
|
|
15
|
+
var OutcomeSchema = z.object({
|
|
16
|
+
result: z.enum([
|
|
17
|
+
"won",
|
|
18
|
+
"lost",
|
|
19
|
+
"draw",
|
|
20
|
+
"completed",
|
|
21
|
+
"abandoned"
|
|
22
|
+
]),
|
|
23
|
+
placements: z.array(z.object({
|
|
24
|
+
seat: Seat,
|
|
25
|
+
place: Int,
|
|
26
|
+
score: z.optional(z.number())
|
|
27
|
+
})),
|
|
28
|
+
summary: z.string(),
|
|
29
|
+
stats: z.optional(z.record(z.string(), z.number())),
|
|
30
|
+
elapsedMs: z.optional(z.number())
|
|
31
|
+
});
|
|
32
|
+
var SeatInfoSchema = z.object({
|
|
33
|
+
id: Seat,
|
|
34
|
+
name: z.string(),
|
|
35
|
+
controller: z.enum(["human", "ai"]),
|
|
36
|
+
aiLevel: z.optional(z.string())
|
|
37
|
+
});
|
|
38
|
+
var MatchSchema = z.object({
|
|
39
|
+
number: z.int().check(z.positive()),
|
|
40
|
+
game: GamePinSchema,
|
|
41
|
+
config: JsonValue,
|
|
42
|
+
seats: z.array(SeatInfoSchema),
|
|
43
|
+
seed: RngStateSchema,
|
|
44
|
+
state: JsonValue,
|
|
45
|
+
rng: RngStateSchema,
|
|
46
|
+
aiRng: RngStateSchema,
|
|
47
|
+
decisionRevision: z.array(NonNegative),
|
|
48
|
+
clocks: z.array(z.object({
|
|
49
|
+
seat: Seat,
|
|
50
|
+
at: Time,
|
|
51
|
+
decisionRevision: NonNegative,
|
|
52
|
+
startsAt: z.optional(Time),
|
|
53
|
+
onBank: z.optional(z.boolean())
|
|
54
|
+
})),
|
|
55
|
+
banks: z.optional(z.array(z.number().check(z.nonnegative()))),
|
|
56
|
+
jobs: z.array(z.object({
|
|
57
|
+
request: z.object({
|
|
58
|
+
id: z.string(),
|
|
59
|
+
kind: z.string(),
|
|
60
|
+
payload: JsonValue
|
|
61
|
+
}),
|
|
62
|
+
revision: NonNegative
|
|
63
|
+
})),
|
|
64
|
+
presentation: z.nullable(JsonValue),
|
|
65
|
+
log: z.array(JsonValue),
|
|
66
|
+
outcome: z.nullable(OutcomeSchema),
|
|
67
|
+
startedAt: Time,
|
|
68
|
+
pausedAt: z.optional(Time)
|
|
69
|
+
});
|
|
70
|
+
var OfferSchema = z.object({
|
|
71
|
+
id: NonNegative,
|
|
72
|
+
kind: z.enum(["rematch", "end"]),
|
|
73
|
+
by: Seat,
|
|
74
|
+
status: z.enum([
|
|
75
|
+
"open",
|
|
76
|
+
"declined",
|
|
77
|
+
"withdrawn"
|
|
78
|
+
]),
|
|
79
|
+
accepted: z.array(Seat),
|
|
80
|
+
declinedBy: z.nullable(Seat),
|
|
81
|
+
endsAt: z.nullable(Time)
|
|
82
|
+
});
|
|
83
|
+
var RoomRecordSchema = z.object({
|
|
84
|
+
format: z.literal(1),
|
|
85
|
+
code: z.string(),
|
|
86
|
+
epoch: NonNegative,
|
|
87
|
+
writes: NonNegative,
|
|
88
|
+
policy: z.object({
|
|
89
|
+
seats: z.object({
|
|
90
|
+
min: Int,
|
|
91
|
+
max: Int
|
|
92
|
+
}),
|
|
93
|
+
start: z.enum(["host", "immediate"]),
|
|
94
|
+
lateJoin: z.enum(["spectate", "take-ai-seat"]),
|
|
95
|
+
absent: z.enum([
|
|
96
|
+
"ai",
|
|
97
|
+
"timeout",
|
|
98
|
+
"wait"
|
|
99
|
+
]),
|
|
100
|
+
graceMs: NonNegative,
|
|
101
|
+
decisionMs: z.nullable(z.int().check(z.positive())),
|
|
102
|
+
bank: z.optional(z.nullable(z.object({
|
|
103
|
+
initialMs: z.int().check(z.positive()),
|
|
104
|
+
incrementMs: NonNegative
|
|
105
|
+
}))),
|
|
106
|
+
maxSpectators: NonNegative,
|
|
107
|
+
retainMs: z.int().check(z.positive())
|
|
108
|
+
}),
|
|
109
|
+
phase: RoomPhaseSchema,
|
|
110
|
+
revision: NonNegative,
|
|
111
|
+
game: GamePinSchema,
|
|
112
|
+
config: JsonValue,
|
|
113
|
+
aiLevel: z.nullable(z.string()),
|
|
114
|
+
seats: z.array(z.object({
|
|
115
|
+
id: Seat,
|
|
116
|
+
claim: z.nullable(z.object({
|
|
117
|
+
tokenHash: z.string(),
|
|
118
|
+
name: z.string()
|
|
119
|
+
})),
|
|
120
|
+
controller: z.enum(["human", "ai"]),
|
|
121
|
+
aiLevel: z.nullable(z.string()),
|
|
122
|
+
ready: z.boolean()
|
|
123
|
+
})),
|
|
124
|
+
host: z.nullable(Seat),
|
|
125
|
+
match: z.nullable(MatchSchema),
|
|
126
|
+
offer: z.optional(z.nullable(OfferSchema)),
|
|
127
|
+
receipts: z.array(z.object({
|
|
128
|
+
seat: Seat,
|
|
129
|
+
id: z.string(),
|
|
130
|
+
fingerprint: z.string(),
|
|
131
|
+
revision: NonNegative
|
|
132
|
+
})),
|
|
133
|
+
createdAt: Time,
|
|
134
|
+
updatedAt: Time
|
|
135
|
+
});
|
|
136
|
+
/**
|
|
137
|
+
* Pure migrations from format n to n + 1, applied on load. There are none yet; the first
|
|
138
|
+
* change to `RoomRecord` adds `1: (record) => …` here and bumps RECORD_FORMAT.
|
|
139
|
+
*/
|
|
140
|
+
var MIGRATIONS = {};
|
|
141
|
+
/**
|
|
142
|
+
* Decodes a stored record: migrates older formats, then validates. `migrated` tells the
|
|
143
|
+
* store to archive the original row before rewriting it.
|
|
144
|
+
*/
|
|
145
|
+
var decodeRecord = (raw) => {
|
|
146
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return err({
|
|
147
|
+
code: "invalid",
|
|
148
|
+
message: "The record is not an object."
|
|
149
|
+
});
|
|
150
|
+
let value = raw;
|
|
151
|
+
const format = value["format"];
|
|
152
|
+
if (typeof format !== "number" || !Number.isInteger(format)) return err({
|
|
153
|
+
code: "invalid",
|
|
154
|
+
message: "The record has no format."
|
|
155
|
+
});
|
|
156
|
+
if (format > 1) return err({
|
|
157
|
+
code: "newer-format",
|
|
158
|
+
message: `Record format ${format} is newer than this build's 1.`
|
|
159
|
+
});
|
|
160
|
+
for (let from = format; from < 1; from += 1) {
|
|
161
|
+
const migrate = MIGRATIONS[from];
|
|
162
|
+
if (migrate === void 0) return err({
|
|
163
|
+
code: "invalid",
|
|
164
|
+
message: `No migration from record format ${from}.`
|
|
165
|
+
});
|
|
166
|
+
value = migrate(value);
|
|
167
|
+
}
|
|
168
|
+
const parsed = RoomRecordSchema.safeParse(value);
|
|
169
|
+
if (!parsed.success) return err({
|
|
170
|
+
code: "invalid",
|
|
171
|
+
message: parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")
|
|
172
|
+
});
|
|
173
|
+
return ok({
|
|
174
|
+
record: parsed.data,
|
|
175
|
+
migrated: format !== 1
|
|
176
|
+
});
|
|
177
|
+
};
|
|
178
|
+
//#endregion
|
|
179
|
+
export { RECORD_FORMAT, RoomRecordSchema, decodeRecord };
|
|
180
|
+
|
|
181
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","names":[],"sources":["../src/record.ts"],"sourcesContent":["// The stored room record's schema and its migrations (room-server.md sections 6.4 and 6.5).\n// A stored row is decoded through `decodeRecord` before the core ever sees it; anything that\n// fails is quarantined, never repaired by guesswork (core belief 9).\nimport type { Json, Result } from '@joystyk/contracts';\nimport { err, ok } from '@joystyk/contracts';\nimport * as z from 'zod/mini';\nimport { GamePinSchema, JsonValue, RoomPhaseSchema } from './protocol/index.ts';\nimport type { RoomRecord } from './types.ts';\n\n/** The record format this build writes. */\nexport const RECORD_FORMAT = 1;\n\nconst Int = z.int();\nconst NonNegative = z.int().check(z.nonnegative());\nconst Seat = z.int().check(z.nonnegative(), z.maximum(63));\nconst RngStateSchema = z.object({ algorithm: z.string(), words: z.array(z.number()) });\nconst Time = z.number();\n\nconst OutcomeSchema = z.object({\n result: z.enum(['won', 'lost', 'draw', 'completed', 'abandoned']),\n placements: z.array(z.object({ seat: Seat, place: Int, score: z.optional(z.number()) })),\n summary: z.string(),\n stats: z.optional(z.record(z.string(), z.number())),\n elapsedMs: z.optional(z.number()),\n});\n\nconst SeatInfoSchema = z.object({\n id: Seat,\n name: z.string(),\n controller: z.enum(['human', 'ai']),\n aiLevel: z.optional(z.string()),\n});\n\nconst MatchSchema = z.object({\n number: z.int().check(z.positive()),\n game: GamePinSchema,\n config: JsonValue,\n seats: z.array(SeatInfoSchema),\n seed: RngStateSchema,\n state: JsonValue,\n rng: RngStateSchema,\n aiRng: RngStateSchema,\n decisionRevision: z.array(NonNegative),\n clocks: z.array(\n z.object({\n seat: Seat,\n at: Time,\n decisionRevision: NonNegative,\n startsAt: z.optional(Time),\n onBank: z.optional(z.boolean()),\n }),\n ),\n banks: z.optional(z.array(z.number().check(z.nonnegative()))),\n jobs: z.array(\n z.object({ request: z.object({ id: z.string(), kind: z.string(), payload: JsonValue }), revision: NonNegative }),\n ),\n presentation: z.nullable(JsonValue),\n log: z.array(JsonValue),\n outcome: z.nullable(OutcomeSchema),\n startedAt: Time,\n pausedAt: z.optional(Time),\n});\n\nconst OfferSchema = z.object({\n id: NonNegative,\n kind: z.enum(['rematch', 'end']),\n by: Seat,\n status: z.enum(['open', 'declined', 'withdrawn']),\n accepted: z.array(Seat),\n declinedBy: z.nullable(Seat),\n endsAt: z.nullable(Time),\n});\n\nexport const RoomRecordSchema = z.object({\n format: z.literal(RECORD_FORMAT),\n code: z.string(),\n epoch: NonNegative,\n writes: NonNegative,\n policy: z.object({\n seats: z.object({ min: Int, max: Int }),\n start: z.enum(['host', 'immediate']),\n lateJoin: z.enum(['spectate', 'take-ai-seat']),\n absent: z.enum(['ai', 'timeout', 'wait']),\n graceMs: NonNegative,\n decisionMs: z.nullable(z.int().check(z.positive())),\n bank: z.optional(z.nullable(z.object({ initialMs: z.int().check(z.positive()), incrementMs: NonNegative }))),\n maxSpectators: NonNegative,\n retainMs: z.int().check(z.positive()),\n }),\n phase: RoomPhaseSchema,\n revision: NonNegative,\n game: GamePinSchema,\n config: JsonValue,\n aiLevel: z.nullable(z.string()),\n seats: z.array(\n z.object({\n id: Seat,\n claim: z.nullable(z.object({ tokenHash: z.string(), name: z.string() })),\n controller: z.enum(['human', 'ai']),\n aiLevel: z.nullable(z.string()),\n ready: z.boolean(),\n }),\n ),\n host: z.nullable(Seat),\n match: z.nullable(MatchSchema),\n offer: z.optional(z.nullable(OfferSchema)),\n receipts: z.array(z.object({ seat: Seat, id: z.string(), fingerprint: z.string(), revision: NonNegative })),\n createdAt: Time,\n updatedAt: Time,\n});\n\n/**\n * Pure migrations from format n to n + 1, applied on load. There are none yet; the first\n * change to `RoomRecord` adds `1: (record) => …` here and bumps RECORD_FORMAT.\n */\nconst MIGRATIONS: {\n readonly [from: number]: (record: { readonly [key: string]: Json }) => { readonly [key: string]: Json };\n} = {};\n\nexport type DecodeError = { readonly code: 'newer-format' | 'invalid'; readonly message: string };\n\n/**\n * Decodes a stored record: migrates older formats, then validates. `migrated` tells the\n * store to archive the original row before rewriting it.\n */\nexport const decodeRecord = (\n raw: unknown,\n): Result<{ readonly record: RoomRecord; readonly migrated: boolean }, DecodeError> => {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw))\n return err({ code: 'invalid', message: 'The record is not an object.' });\n let value = raw as { readonly [key: string]: Json };\n const format = value['format'];\n if (typeof format !== 'number' || !Number.isInteger(format))\n return err({ code: 'invalid', message: 'The record has no format.' });\n if (format > RECORD_FORMAT)\n return err({\n code: 'newer-format',\n message: `Record format ${format} is newer than this build's ${RECORD_FORMAT}.`,\n });\n for (let from = format; from < RECORD_FORMAT; from += 1) {\n const migrate = MIGRATIONS[from];\n if (migrate === undefined) return err({ code: 'invalid', message: `No migration from record format ${from}.` });\n value = migrate(value);\n }\n const parsed = RoomRecordSchema.safeParse(value);\n if (!parsed.success)\n return err({\n code: 'invalid',\n message: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join('; '),\n });\n return ok({ record: parsed.data as RoomRecord, migrated: format !== RECORD_FORMAT });\n};\n"],"mappings":"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"}
|