@voltro/plugin-matchmaking 0.0.0-stage → 0.88.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 (47) hide show
  1. package/CHANGELOG.md +24492 -0
  2. package/LICENSE +61 -0
  3. package/README.md +272 -2
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +8333 -0
  6. package/dist/admission.entity.js +17 -0
  7. package/dist/assignment.js +125 -0
  8. package/dist/avoidPairs.js +4 -0
  9. package/dist/contract.js +105 -0
  10. package/dist/director-app/matchmaking.director.workflow.server.tsx +5 -0
  11. package/dist/director-app/matchmaking.director.workflow.tsx +13 -0
  12. package/dist/director.js +72 -0
  13. package/dist/directorContract.js +15 -0
  14. package/dist/directorWorkflow.js +5 -0
  15. package/dist/errors.d.ts +13 -0
  16. package/dist/errors.js +20 -0
  17. package/dist/index.d.ts +523 -0
  18. package/dist/index.js +12 -0
  19. package/dist/inspect.js +40 -0
  20. package/dist/packing.js +71 -0
  21. package/dist/parties.js +316 -0
  22. package/dist/party.entity.js +33 -0
  23. package/dist/partyComponents.js +176 -0
  24. package/dist/partyContract.js +24 -0
  25. package/dist/plugin.js +257 -0
  26. package/dist/port.d.ts +116 -0
  27. package/dist/port.js +4 -0
  28. package/dist/queue.d.ts +91 -0
  29. package/dist/queue.js +79 -0
  30. package/dist/queueDeclarations.js +27 -0
  31. package/dist/roomsAllocator.d.ts +67 -0
  32. package/dist/roomsAllocator.js +75 -0
  33. package/dist/rpc.d.ts +247 -0
  34. package/dist/rpc.js +201 -0
  35. package/dist/server.d.ts +337 -0
  36. package/dist/server.js +8 -0
  37. package/dist/service.js +369 -0
  38. package/dist/strategies.d.ts +97 -0
  39. package/dist/strategies.js +140 -0
  40. package/dist/testing.d.ts +296 -0
  41. package/dist/testing.js +151 -0
  42. package/dist/ticketContract.d.ts +52 -0
  43. package/dist/ticketContract.js +30 -0
  44. package/dist/tickets.entity.js +49 -0
  45. package/dist/web.d.ts +279 -0
  46. package/dist/web.js +7 -0
  47. package/package.json +104 -4
@@ -0,0 +1,316 @@
1
+ import { MatchmakingError as e, matchmakingError as t } from "./errors.js";
2
+ import { matchmakingPartiesTable as n, matchmakingPartyInvitesTable as r, matchmakingPartyMembersTable as i } from "./party.entity.js";
3
+ import { matchmakingTicketsTable as a } from "./tickets.entity.js";
4
+ import { matchmakingSubjectsTable as o } from "./admission.entity.js";
5
+ import { Effect as s } from "effect";
6
+ import { and as c, eq as l, queryFor as u } from "@voltro/database";
7
+ import { createHash as d } from "node:crypto";
8
+ // src/parties.ts
9
+ var f = (e) => d("sha256").update(JSON.stringify(e)).digest("hex"), p = (n) => n instanceof e ? n : t("unavailable"), m = (e) => e === !0 || e === 1, h = (e) => s.tryPromise({
10
+ try: e,
11
+ catch: p
12
+ }), g = (e) => {
13
+ if (typeof e != "string" || !e.trim() || e.length > 256 || e.includes("\0")) throw t("invalid_input");
14
+ return e;
15
+ }, _ = (e) => {
16
+ if (!e || e.type !== "user") throw t("identity_required");
17
+ return {
18
+ id: g(e.id),
19
+ tenantId: g(e.tenantId)
20
+ };
21
+ }, v = (e) => {
22
+ let n = e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : NaN;
23
+ if (!Number.isSafeInteger(n) || n < 0) throw t("unavailable");
24
+ return n;
25
+ }, y = (e, n) => {
26
+ let r = e[n];
27
+ if (typeof r != "string") throw t("unavailable");
28
+ return r;
29
+ }, b = (e = {}) => {
30
+ let n = e.maxPartySize ?? 8, r = e.partyInviteTtlMs ?? 6e5, i = e.now ?? Date.now;
31
+ if (!Number.isSafeInteger(n) || n < 1 || n > 256 || !Number.isSafeInteger(r) || r < 1e3 || r > 864e5 || typeof i != "function") throw t("invalid_input");
32
+ return Object.freeze({
33
+ maxPartySize: n,
34
+ partyInviteTtlMs: r,
35
+ now: i
36
+ });
37
+ }, x = (d, x = {}) => {
38
+ let { maxPartySize: S, partyInviteTtlMs: C, now: w } = b(x), T = () => {
39
+ let e = w();
40
+ if (!Number.isSafeInteger(e) || e < 0) throw t("unavailable");
41
+ return e;
42
+ }, E = (e, t) => "mmpm_" + f([e, t]).slice(0, 56), D = (e, t, n) => "mmpi_" + f([
43
+ e,
44
+ t,
45
+ n
46
+ ]).slice(0, 56), O = async (e, n, r) => {
47
+ let a = await e.query(u(i).where(c(l("tenantId", n), l("partyId", r))).orderBy("joinedAt", "asc").orderBy("id", "asc").limit(257).descriptor);
48
+ if (a.length > 256 || a.some((e) => e.tenantId !== n || e.partyId !== r)) throw t("unavailable");
49
+ return a;
50
+ }, k = async (e, n, r) => {
51
+ let i = await e.query(u(a).where(c(l("tenantId", n), l("partyId", r), l("state", "open"))).orderBy("id", "asc").limit(9).descriptor);
52
+ if (i.some((e) => e.tenantId !== n || e.partyId !== r)) throw t("unavailable");
53
+ return i;
54
+ }, A = async (e, t, n) => {
55
+ for (let r of await k(e, t, n)) {
56
+ let n = y(r, "id");
57
+ await e.update(a.tableName, n, {
58
+ state: "cancelled",
59
+ closedAt: new Date(T())
60
+ }), await e.updateMany(o.tableName, { activeTicketId: null }, { where: c(l("tenantId", t), l("activeTicketId", n)) });
61
+ }
62
+ }, j = async (e, t) => {
63
+ let n = await O(e, y(t, "tenantId"), y(t, "id")), r = (await k(e, y(t, "tenantId"), y(t, "id")))[0];
64
+ return {
65
+ id: y(t, "id"),
66
+ searchTicketId: r === void 0 ? null : y(r, "id"),
67
+ leaderId: y(t, "leaderId"),
68
+ maxSize: Number(t.maxSize),
69
+ approval: t.approval === "open" ? "open" : "leader",
70
+ createdAt: v(t.createdAt),
71
+ members: n.map((e) => ({
72
+ subjectId: y(e, "subjectId"),
73
+ role: e.role === "leader" ? "leader" : "member",
74
+ ready: m(e.ready),
75
+ joinedAt: v(e.joinedAt)
76
+ })).sort((e, t) => e.role === t.role ? e.joinedAt === t.joinedAt ? e.subjectId < t.subjectId ? -1 : +(e.subjectId > t.subjectId) : e.joinedAt - t.joinedAt : e.role === "leader" ? -1 : 1)
77
+ };
78
+ }, M = async (e, r, i) => {
79
+ if (g(i), await e.updateMany(n.tableName, { admission: 0 }, { where: l("id", i) }) !== 1) throw t("not_found");
80
+ let a = (await e.query(u(n).where(l("id", i)).limit(1).descriptor))[0];
81
+ if (!a || a.tenantId !== r) throw t("not_found");
82
+ return a;
83
+ }, N = async (e, n) => {
84
+ let r = (await e.query(u(i).where(l("id", E(n.tenantId, n.id))).limit(1).descriptor))[0];
85
+ if (r && (r.tenantId !== n.tenantId || r.subjectId !== n.id)) throw t("unavailable");
86
+ return r ?? null;
87
+ }, P = async (e, n, r) => {
88
+ let i = await N(e, n);
89
+ if (!i || i.partyId !== r) throw t("forbidden");
90
+ return i;
91
+ }, F = async (e, n, r) => {
92
+ if ((await P(e, n, y(r, "id"))).role !== "leader" || r.leaderId !== n.id) throw t("forbidden");
93
+ }, I = async (e, t, a) => {
94
+ let o = y(t, "tenantId"), s = y(t, "id");
95
+ await A(e, o, s), await e.delete(i.tableName, E(o, a));
96
+ let u = await O(e, o, s);
97
+ if (u.length === 0) await e.deleteMany(r.tableName, { where: c(l("tenantId", o), l("partyId", s)) }), await e.delete(n.tableName, s);
98
+ else if (t.leaderId === a) {
99
+ let t = u[0];
100
+ await e.update(i.tableName, y(t, "id"), { role: "leader" }), await e.update(n.tableName, s, { leaderId: y(t, "subjectId") });
101
+ }
102
+ }, L = (e, t) => ({
103
+ who: _(structuredClone(e)),
104
+ value: structuredClone(t)
105
+ }), R = (e, t, n) => {
106
+ let r;
107
+ try {
108
+ r = L(e, t);
109
+ } catch (e) {
110
+ return s.fail(p(e));
111
+ }
112
+ return h(() => n(r.who, r.value));
113
+ };
114
+ return {
115
+ service: {
116
+ create: (r, a) => R(r, a, async (r, a) => {
117
+ g(a.requestId);
118
+ let o = a.maxSize ?? S, s = a.approval ?? "leader";
119
+ if (!Number.isSafeInteger(o) || o < 1 || o > S || s !== "open" && s !== "leader") throw t("invalid_input");
120
+ let c = "mmp_" + f([
121
+ r.tenantId,
122
+ r.id,
123
+ a.requestId
124
+ ]).slice(0, 56), p = f([o, s]);
125
+ return d.transactional(async (e) => {
126
+ let a = (await e.query(u(n).where(l("id", c)).limit(1).descriptor))[0];
127
+ if (a) {
128
+ if (a.tenantId !== r.tenantId || a.requestKey !== p) throw t("conflict");
129
+ return j(e, a);
130
+ }
131
+ if (await N(e, r)) throw t("already_in_party");
132
+ let d = new Date(T()), f = await e.insert(n.tableName, {
133
+ id: c,
134
+ tenantId: r.tenantId,
135
+ leaderId: r.id,
136
+ requestKey: p,
137
+ maxSize: o,
138
+ approval: s,
139
+ admission: 0,
140
+ createdAt: d
141
+ });
142
+ return await e.insert(i.tableName, {
143
+ id: E(r.tenantId, r.id),
144
+ tenantId: r.tenantId,
145
+ partyId: c,
146
+ subjectId: r.id,
147
+ role: "leader",
148
+ ready: !1,
149
+ joinedAt: d
150
+ }), j(e, f);
151
+ }).catch(async (n) => {
152
+ if (n instanceof e) throw n;
153
+ let i = await N(d, r);
154
+ throw i && i.partyId !== c ? t("already_in_party") : n;
155
+ });
156
+ }),
157
+ mine: (e) => R(e, null, async (e) => d.transactional(async (r) => {
158
+ let i = await N(r, e);
159
+ if (!i) return null;
160
+ let a = (await r.query(u(n).where(l("id", y(i, "partyId"))).limit(1).descriptor))[0];
161
+ if (!a || a.tenantId !== e.tenantId) throw t("unavailable");
162
+ return j(r, a);
163
+ })),
164
+ invites: (e) => R(e, null, async (e) => {
165
+ let t = await d.query(u(r).where(c(l("tenantId", e.tenantId), l("inviteeId", e.id))).orderBy("id", "asc").limit(65).descriptor), n = T();
166
+ return t.filter((t) => t.tenantId === e.tenantId && t.inviteeId === e.id && v(t.expiresAt) > n).slice(0, 64).map((e) => ({
167
+ partyId: y(e, "partyId"),
168
+ inviterId: y(e, "inviterId"),
169
+ expiresAt: v(e.expiresAt)
170
+ }));
171
+ }),
172
+ invite: (e, n) => R(e, n, async (e, n) => {
173
+ let i = g(n.subjectId);
174
+ if (i === e.id) throw t("invalid_input");
175
+ return d.transactional(async (t) => {
176
+ let a = await M(t, e.tenantId, n.partyId);
177
+ await F(t, e, a);
178
+ let o = T() + C, s = D(e.tenantId, n.partyId, i);
179
+ return (await t.query(u(r).where(l("id", s)).limit(1).descriptor))[0] ? await t.update(r.tableName, s, {
180
+ inviterId: e.id,
181
+ expiresAt: new Date(o)
182
+ }) : await t.insert(r.tableName, {
183
+ id: s,
184
+ tenantId: e.tenantId,
185
+ partyId: n.partyId,
186
+ inviteeId: i,
187
+ inviterId: e.id,
188
+ expiresAt: new Date(o)
189
+ }), {
190
+ partyId: n.partyId,
191
+ inviterId: e.id,
192
+ expiresAt: o
193
+ };
194
+ });
195
+ }),
196
+ join: (e, n) => R(e, n, async (e, n) => d.transactional(async (a) => {
197
+ let o = await M(a, e.tenantId, n), s = await N(a, e);
198
+ if (s) {
199
+ if (s.partyId === n) return j(a, o);
200
+ throw t("already_in_party");
201
+ }
202
+ let c = (await a.query(u(r).where(l("id", D(e.tenantId, n, e.id))).limit(1).descriptor))[0];
203
+ if (!(c !== void 0 && c.tenantId === e.tenantId && c.inviteeId === e.id && v(c.expiresAt) > T()) && o.approval !== "open") throw t("forbidden");
204
+ if ((await O(a, e.tenantId, n)).length >= Number(o.maxSize)) throw t("party_full");
205
+ return c && await a.delete(r.tableName, y(c, "id")), await A(a, e.tenantId, n), await a.insert(i.tableName, {
206
+ id: E(e.tenantId, e.id),
207
+ tenantId: e.tenantId,
208
+ partyId: n,
209
+ subjectId: e.id,
210
+ role: "member",
211
+ ready: !1,
212
+ joinedAt: new Date(T())
213
+ }), j(a, o);
214
+ })),
215
+ leave: (e, t) => R(e, t, async (e, t) => d.transactional(async (n) => {
216
+ let r = await M(n, e.tenantId, t);
217
+ await P(n, e, t), await I(n, r, e.id);
218
+ })),
219
+ kick: (e, n) => R(e, n, async (e, n) => d.transactional(async (r) => {
220
+ let i = g(n.subjectId), a = await M(r, e.tenantId, n.partyId);
221
+ if (await F(r, e, a), i === e.id) throw t("invalid_input");
222
+ return await P(r, {
223
+ id: i,
224
+ tenantId: e.tenantId
225
+ }, n.partyId), await I(r, a, i), j(r, await M(r, e.tenantId, n.partyId));
226
+ })),
227
+ setReady: (e, n) => R(e, n, async (e, n) => d.transactional(async (r) => {
228
+ if (typeof n.ready != "boolean") throw t("invalid_input");
229
+ let a = await M(r, e.tenantId, n.partyId), o = await P(r, e, n.partyId);
230
+ return n.ready || await A(r, e.tenantId, n.partyId), await r.update(i.tableName, y(o, "id"), { ready: n.ready }), j(r, a);
231
+ })),
232
+ transferLeader: (e, t) => R(e, t, async (e, t) => d.transactional(async (r) => {
233
+ let a = g(t.subjectId), o = await M(r, e.tenantId, t.partyId);
234
+ await F(r, e, o);
235
+ let s = await P(r, {
236
+ id: a,
237
+ tenantId: e.tenantId
238
+ }, t.partyId);
239
+ return a === e.id ? j(r, o) : (await A(r, e.tenantId, t.partyId), await r.update(i.tableName, E(e.tenantId, e.id), { role: "member" }), await r.update(i.tableName, y(s, "id"), { role: "leader" }), j(r, await r.update(n.tableName, t.partyId, { leaderId: a })));
240
+ })),
241
+ cancelSearch: (e, t) => R(e, t, async (e, t) => d.transactional(async (n) => {
242
+ let r = await M(n, e.tenantId, t);
243
+ return await P(n, e, t), await A(n, e.tenantId, t), j(n, r);
244
+ })),
245
+ isMember: (e, t) => {
246
+ if (!e || e.type !== "user" || typeof e.tenantId != "string") return s.succeed(!1);
247
+ let n = {
248
+ id: String(e.id),
249
+ tenantId: e.tenantId
250
+ };
251
+ return h(async () => {
252
+ let e = await N(d, n);
253
+ return e !== null && e.partyId === t;
254
+ });
255
+ }
256
+ },
257
+ exportSubject: (e) => h(async () => {
258
+ if (e.tenantId === null) throw t("identity_required");
259
+ let n = {
260
+ id: g(e.subjectId),
261
+ tenantId: g(e.tenantId)
262
+ }, i = await N(d, n), a = await d.query(u(r).where(c(l("tenantId", n.tenantId), l("inviterId", n.id))).limit(1001).descriptor), o = await d.query(u(r).where(c(l("tenantId", n.tenantId), l("inviteeId", n.id))).limit(1001).descriptor);
263
+ if (a.length > 1e3 || o.length > 1e3) throw t("export_limit");
264
+ return JSON.parse(JSON.stringify({
265
+ membership: i,
266
+ invitesSent: a,
267
+ invitesReceived: o
268
+ }));
269
+ }),
270
+ eraseSubject: (e) => h(async () => {
271
+ if (e.tenantId === null) throw t("identity_required");
272
+ let n = {
273
+ id: g(e.subjectId),
274
+ tenantId: g(e.tenantId)
275
+ };
276
+ await d.transactional(async (e) => {
277
+ let t = await N(e, n);
278
+ t && await I(e, await M(e, n.tenantId, y(t, "partyId")), n.id), await e.deleteMany(r.tableName, { where: c(l("tenantId", n.tenantId), l("inviteeId", n.id)) }), await e.deleteMany(r.tableName, { where: c(l("tenantId", n.tenantId), l("inviterId", n.id)) });
279
+ });
280
+ }),
281
+ prepareSearch: async (e, n, r) => {
282
+ let i = _(n), a = await M(e, i.tenantId, r);
283
+ await F(e, i, a);
284
+ let o = await O(e, i.tenantId, r);
285
+ if (o.some((e) => !m(e.ready))) throw t("party_not_ready");
286
+ return o.map((e) => y(e, "subjectId"));
287
+ },
288
+ previewRoster: async (e, t) => {
289
+ let n = _(e);
290
+ return g(t), (await O(d, n.tenantId, t)).map((e) => y(e, "subjectId"));
291
+ },
292
+ readRoster: (e, r) => {
293
+ let i, a;
294
+ try {
295
+ i = g(e), a = g(r);
296
+ } catch (e) {
297
+ return s.fail(p(e));
298
+ }
299
+ return h(() => d.transactional(async (e) => {
300
+ let r = await e.query(u(n).where(c(l("id", a), l("tenantId", i))).limit(2).forUpdate().descriptor);
301
+ if (r.length === 0) return null;
302
+ let o = r[0];
303
+ if (r.length !== 1 || o.id !== a || o.tenantId !== i) throw t("unavailable");
304
+ let s = await O(e, i, a), d = y(o, "leaderId"), f = s.map((e) => y(e, "subjectId"));
305
+ if (!s.length || new Set(f).size !== f.length || f.some((e) => !e.trim() || e.length > 256 || e.includes("\0")) || !Number.isSafeInteger(o.maxSize) || Number(o.maxSize) < s.length || Number(o.maxSize) > 256 || s.filter((e) => e.role === "leader").length !== 1 || !s.some((e) => e.subjectId === d && e.role === "leader") || s.some((e) => e.role !== "leader" && e.role !== "member")) throw t("unavailable");
306
+ return f.sort((e, t) => e === d ? -1 : t === d ? 1 : e < t ? -1 : +(e > t)), Object.freeze({
307
+ tenantId: i,
308
+ partyId: a,
309
+ leaderId: d,
310
+ memberIds: Object.freeze(f)
311
+ });
312
+ }));
313
+ }
314
+ };
315
+ };
316
+ export { x as dataStoreParties, b as resolvePartyOptions };
@@ -0,0 +1,33 @@
1
+ import { PARTIES_TABLE as e, PARTY_INVITES_TABLE as t, PARTY_MEMBERS_TABLE as n } from "./partyContract.js";
2
+ import { boolean as r, id as i, integer as a, table as o, text as s, timestamp as c } from "@voltro/database";
3
+ // src/party.entity.ts
4
+ var l = o(e, {
5
+ id: i({ prefix: "mmp" }),
6
+ tenantId: s().maxLength(256).externalId().sensitive("pii"),
7
+ leaderId: s().maxLength(256).externalId().sensitive("pii"),
8
+ requestKey: s().maxLength(64).serverOnly(),
9
+ maxSize: a().safe(),
10
+ approval: s().maxLength(16).oneOf(["open", "leader"]).safe(),
11
+ admission: a().serverOnly(),
12
+ createdAt: c().safe()
13
+ }).index(["tenantId", "leaderId"]), u = o(n, {
14
+ id: i({ prefix: "mmpm" }),
15
+ tenantId: s().maxLength(256).externalId().sensitive("pii"),
16
+ partyId: s().maxLength(64).safe(),
17
+ subjectId: s().maxLength(256).externalId().sensitive("pii"),
18
+ role: s().maxLength(16).oneOf(["leader", "member"]).safe(),
19
+ ready: r().safe(),
20
+ joinedAt: c().safe()
21
+ }).index(["tenantId", "partyId"]).index(["tenantId", "subjectId"]), d = o(t, {
22
+ id: i({ prefix: "mmpi" }),
23
+ tenantId: s().maxLength(256).externalId().sensitive("pii"),
24
+ partyId: s().maxLength(64).safe(),
25
+ inviteeId: s().maxLength(256).externalId().sensitive("pii"),
26
+ inviterId: s().maxLength(256).externalId().sensitive("pii"),
27
+ expiresAt: c().safe()
28
+ }).index(["tenantId", "inviteeId"]).index(["tenantId", "partyId"]), f = [
29
+ l,
30
+ u,
31
+ d
32
+ ];
33
+ export { l as matchmakingPartiesTable, d as matchmakingPartyInvitesTable, u as matchmakingPartyMembersTable, f as partyTables };
@@ -0,0 +1,176 @@
1
+ import { useId as e, useState as t } from "react";
2
+ import { Button as n, Input as r } from "@voltro/ui-shadcn";
3
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
4
+ // src/partyComponents.tsx
5
+ var o = {
6
+ en: {
7
+ title: "Party",
8
+ empty: "You are not in a party.",
9
+ create: "Create party",
10
+ leader: "Leader",
11
+ member: "Member",
12
+ ready: "Ready",
13
+ notReady: "Not ready",
14
+ markReady: "I am ready",
15
+ markNotReady: "Not ready yet",
16
+ invite: "Invite",
17
+ invitePlaceholder: "Player ID",
18
+ leave: "Leave party",
19
+ kick: "Remove",
20
+ promote: "Make leader",
21
+ unknown: "Player unavailable",
22
+ full: "The party is full."
23
+ },
24
+ de: {
25
+ title: "Party",
26
+ empty: "Du bist in keiner Party.",
27
+ create: "Party erstellen",
28
+ leader: "Leader",
29
+ member: "Mitglied",
30
+ ready: "Bereit",
31
+ notReady: "Nicht bereit",
32
+ markReady: "Ich bin bereit",
33
+ markNotReady: "Noch nicht bereit",
34
+ invite: "Einladen",
35
+ invitePlaceholder: "Spieler-ID",
36
+ leave: "Party verlassen",
37
+ kick: "Entfernen",
38
+ promote: "Zum Leader machen",
39
+ unknown: "Spieler nicht verfügbar",
40
+ full: "Die Party ist voll."
41
+ }
42
+ }, s = ({ party: s, selfId: c, names: l = {}, labels: u = o.en, pending: d = !1, onCreate: f, onInvite: p, onLeave: m, onKick: h, onPromote: g, onReady: _ }) => {
43
+ let v = e(), y = e(), [b, x] = t("");
44
+ if (s === null) return /* @__PURE__ */ a("section", {
45
+ "aria-labelledby": v,
46
+ className: "space-y-3",
47
+ children: [
48
+ /* @__PURE__ */ i("h2", {
49
+ id: v,
50
+ className: "text-lg font-semibold",
51
+ children: u.title
52
+ }),
53
+ /* @__PURE__ */ i("p", {
54
+ className: "text-sm text-muted-foreground",
55
+ children: u.empty
56
+ }),
57
+ f ? /* @__PURE__ */ i(n, {
58
+ type: "button",
59
+ "aria-disabled": d,
60
+ onClick: () => {
61
+ d || f();
62
+ },
63
+ children: u.create
64
+ }) : null
65
+ ]
66
+ });
67
+ let S = s.leaderId === c, C = s.members.find((e) => e.subjectId === c), w = s.members.length >= s.maxSize;
68
+ return /* @__PURE__ */ a("section", {
69
+ "aria-labelledby": v,
70
+ className: "space-y-3",
71
+ children: [
72
+ /* @__PURE__ */ a("h2", {
73
+ id: v,
74
+ className: "text-lg font-semibold",
75
+ children: [
76
+ u.title,
77
+ " ",
78
+ /* @__PURE__ */ a("span", {
79
+ className: "text-sm text-muted-foreground tabular-nums",
80
+ children: [
81
+ s.members.length,
82
+ "/",
83
+ s.maxSize
84
+ ]
85
+ })
86
+ ]
87
+ }),
88
+ /* @__PURE__ */ i("ul", {
89
+ className: "divide-y divide-border",
90
+ children: s.members.map((e) => /* @__PURE__ */ a("li", {
91
+ className: "flex flex-wrap items-center gap-3 py-2 text-sm",
92
+ children: [
93
+ /* @__PURE__ */ i("span", {
94
+ className: "min-w-0 flex-1 break-words font-medium",
95
+ children: l[e.subjectId] ?? u.unknown
96
+ }),
97
+ /* @__PURE__ */ i("span", {
98
+ className: "text-muted-foreground",
99
+ children: e.role === "leader" ? u.leader : u.member
100
+ }),
101
+ /* @__PURE__ */ i("span", { children: e.ready ? u.ready : u.notReady }),
102
+ S && e.subjectId !== c && g ? /* @__PURE__ */ i(n, {
103
+ type: "button",
104
+ variant: "outline",
105
+ size: "sm",
106
+ "aria-disabled": d,
107
+ onClick: () => {
108
+ d || g(e.subjectId);
109
+ },
110
+ children: u.promote
111
+ }) : null,
112
+ S && e.subjectId !== c && h ? /* @__PURE__ */ i(n, {
113
+ type: "button",
114
+ variant: "ghost",
115
+ size: "sm",
116
+ "aria-disabled": d,
117
+ onClick: () => {
118
+ d || h(e.subjectId);
119
+ },
120
+ children: u.kick
121
+ }) : null
122
+ ]
123
+ }, e.subjectId))
124
+ }),
125
+ C && _ ? /* @__PURE__ */ i(n, {
126
+ type: "button",
127
+ variant: "outline",
128
+ "aria-disabled": d,
129
+ onClick: () => {
130
+ d || _(!C.ready);
131
+ },
132
+ children: C.ready ? u.markNotReady : u.markReady
133
+ }) : null,
134
+ S && p ? w ? /* @__PURE__ */ i("p", {
135
+ className: "text-sm text-muted-foreground",
136
+ children: u.full
137
+ }) : /* @__PURE__ */ a("form", {
138
+ className: "flex flex-wrap gap-2",
139
+ onSubmit: (e) => {
140
+ e.preventDefault();
141
+ let t = b.trim();
142
+ !d && t && (p(t), x(""));
143
+ },
144
+ children: [
145
+ /* @__PURE__ */ i("label", {
146
+ htmlFor: y,
147
+ className: "sr-only",
148
+ children: u.invitePlaceholder
149
+ }),
150
+ /* @__PURE__ */ i(r, {
151
+ id: y,
152
+ value: b,
153
+ maxLength: 256,
154
+ placeholder: u.invitePlaceholder,
155
+ onChange: (e) => x(e.target.value)
156
+ }),
157
+ /* @__PURE__ */ i(n, {
158
+ type: "submit",
159
+ "aria-disabled": d || !b.trim(),
160
+ children: u.invite
161
+ })
162
+ ]
163
+ }) : null,
164
+ C && m ? /* @__PURE__ */ i(n, {
165
+ type: "button",
166
+ variant: "ghost",
167
+ "aria-disabled": d,
168
+ onClick: () => {
169
+ d || m();
170
+ },
171
+ children: u.leave
172
+ }) : null
173
+ ]
174
+ });
175
+ };
176
+ export { s as PartyPanel, o as partyLabels };
@@ -0,0 +1,24 @@
1
+ import { Schema as e } from "effect";
2
+ // src/partyContract.ts
3
+ var t = "_voltro_mm_parties", n = "_voltro_mm_party_members", r = "_voltro_mm_party_invites", i = e.Number.pipe(e.check(e.isInt()), e.check(e.isGreaterThanOrEqualTo(0))), a = e.Struct({
4
+ subjectId: e.String,
5
+ role: e.Literals(["leader", "member"]),
6
+ ready: e.Boolean,
7
+ joinedAt: i
8
+ }), o = e.Struct({
9
+ id: e.String,
10
+ leaderId: e.String,
11
+ maxSize: e.Number.pipe(e.check(e.isInt()), e.check(e.isBetween({
12
+ minimum: 1,
13
+ maximum: 256
14
+ }))),
15
+ approval: e.Literals(["open", "leader"]),
16
+ createdAt: i,
17
+ members: e.Array(a),
18
+ searchTicketId: e.NullOr(e.String)
19
+ }), s = e.Struct({
20
+ partyId: e.String,
21
+ inviterId: e.String,
22
+ expiresAt: i
23
+ });
24
+ export { t as PARTIES_TABLE, r as PARTY_INVITES_TABLE, n as PARTY_MEMBERS_TABLE, s as PartyInviteSchema, a as PartyMemberSchema, o as PartySchema };