@voltro/plugin-matchmaking 0.0.0-stage → 0.87.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 +24329 -0
- package/LICENSE +61 -0
- package/README.md +272 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +7737 -0
- package/dist/admission.entity.js +18 -0
- package/dist/assignment.js +126 -0
- package/dist/avoidPairs.js +5 -0
- package/dist/contract.js +106 -0
- package/dist/director-app/matchmaking.director.workflow.server.tsx +5 -0
- package/dist/director-app/matchmaking.director.workflow.tsx +13 -0
- package/dist/director.js +73 -0
- package/dist/directorContract.js +16 -0
- package/dist/directorWorkflow.js +6 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +21 -0
- package/dist/index.d.ts +523 -0
- package/dist/index.js +12 -0
- package/dist/inspect.js +41 -0
- package/dist/packing.js +72 -0
- package/dist/parties.js +317 -0
- package/dist/party.entity.js +34 -0
- package/dist/partyComponents.js +177 -0
- package/dist/partyContract.js +25 -0
- package/dist/plugin.js +258 -0
- package/dist/port.d.ts +116 -0
- package/dist/port.js +5 -0
- package/dist/queue.d.ts +91 -0
- package/dist/queue.js +80 -0
- package/dist/queueDeclarations.js +28 -0
- package/dist/roomsAllocator.d.ts +67 -0
- package/dist/roomsAllocator.js +76 -0
- package/dist/rpc.d.ts +247 -0
- package/dist/rpc.js +202 -0
- package/dist/server.d.ts +337 -0
- package/dist/server.js +8 -0
- package/dist/service.js +370 -0
- package/dist/strategies.d.ts +97 -0
- package/dist/strategies.js +141 -0
- package/dist/testing.d.ts +296 -0
- package/dist/testing.js +152 -0
- package/dist/ticketContract.d.ts +52 -0
- package/dist/ticketContract.js +31 -0
- package/dist/tickets.entity.js +50 -0
- package/dist/web.d.ts +279 -0
- package/dist/web.js +8 -0
- package/package.json +104 -4
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CLAIMS_TABLE as e, QUEUES_TABLE as t } from "./ticketContract.js";
|
|
2
|
+
import { boolean as n, id as r, integer as i, table as a, text as o } from "@voltro/database";
|
|
3
|
+
//#region src/admission.entity.ts
|
|
4
|
+
var s = a(e, {
|
|
5
|
+
id: r({ prefix: "mms" }),
|
|
6
|
+
tenantId: o().maxLength(256).externalId().sensitive("pii"),
|
|
7
|
+
subjectId: o().maxLength(256).nullable().externalId().sensitive("pii"),
|
|
8
|
+
activeTicketId: o().maxLength(64).nullable().serverOnly(),
|
|
9
|
+
admission: i().serverOnly(),
|
|
10
|
+
erased: n().serverOnly()
|
|
11
|
+
}).index(["tenantId", "subjectId"]), c = a(t, {
|
|
12
|
+
id: r({ prefix: "mmq" }),
|
|
13
|
+
tenantId: o().maxLength(256).externalId().sensitive("pii"),
|
|
14
|
+
queue: o().maxLength(256).safe(),
|
|
15
|
+
admission: i().serverOnly()
|
|
16
|
+
}).index(["tenantId", "queue"]);
|
|
17
|
+
//#endregion
|
|
18
|
+
export { c as matchmakingQueuesTable, s as matchmakingSubjectsTable };
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { MatchmakingError as e, matchmakingError as t } from "./errors.js";
|
|
2
|
+
import { identifier as n } from "./contract.js";
|
|
3
|
+
import { matchmakingAssignmentsTable as r, matchmakingTicketsTable as i } from "./tickets.entity.js";
|
|
4
|
+
import { matchmakingSubjectsTable as a } from "./admission.entity.js";
|
|
5
|
+
import { matchmakingSubjectKey as o } from "./service.js";
|
|
6
|
+
import { Effect as s, Result as c } from "effect";
|
|
7
|
+
import { and as l, eq as u, inSet as d, queryFor as f } from "@voltro/database";
|
|
8
|
+
import { createHash as p } from "node:crypto";
|
|
9
|
+
//#region src/assignment.ts
|
|
10
|
+
var m = (e) => p("sha256").update(JSON.stringify(e)).digest("hex"), h = (n) => n instanceof e ? n : t("unavailable"), g = (e) => s.tryPromise({
|
|
11
|
+
try: e,
|
|
12
|
+
catch: h
|
|
13
|
+
}), _ = class extends Error {}, v = (e, p) => {
|
|
14
|
+
if (!p || typeof p.allocator?.allocate != "function" || typeof p.allocator.release != "function") throw t("invalid_queue");
|
|
15
|
+
let h = p.allocator, v = p.maxAttempts ?? 3, y = p.now ?? Date.now;
|
|
16
|
+
if (!Number.isSafeInteger(v) || v < 1 || v > 100) throw t("invalid_queue");
|
|
17
|
+
let b = () => {
|
|
18
|
+
let e = y();
|
|
19
|
+
if (!Number.isSafeInteger(e) || e < 0) throw t("unavailable");
|
|
20
|
+
return e;
|
|
21
|
+
}, x = async (e, t, n) => {
|
|
22
|
+
for (let r of [...n].sort()) if (await e.updateMany(a.tableName, { admission: 0 }, { where: u("id", o(t, r)) }) !== 1) throw new _();
|
|
23
|
+
}, S = (t, n, r) => e.transactional(async (e) => {
|
|
24
|
+
await x(e, t, r);
|
|
25
|
+
let s = new Date(b());
|
|
26
|
+
for (let r of await e.query(f(i).where(l(u("tenantId", t), d("id", [...n]))).descriptor)) {
|
|
27
|
+
if (r.state !== "matched") continue;
|
|
28
|
+
let n = Number(r.attempts ?? 0) + 1;
|
|
29
|
+
if (n >= v) {
|
|
30
|
+
await e.update(i.tableName, String(r.id), {
|
|
31
|
+
state: "expired",
|
|
32
|
+
attempts: n,
|
|
33
|
+
closedAt: s
|
|
34
|
+
});
|
|
35
|
+
for (let n of r.members) await e.updateMany(a.tableName, { activeTicketId: null }, { where: l(u("id", o(t, n)), u("activeTicketId", String(r.id))) });
|
|
36
|
+
} else await e.update(i.tableName, String(r.id), {
|
|
37
|
+
state: "open",
|
|
38
|
+
attempts: n,
|
|
39
|
+
matchedAt: null
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return { assign: (d, f) => {
|
|
44
|
+
if (d?.type !== "system") return s.fail(t("identity_required"));
|
|
45
|
+
if (!n(f?.tenantId) || !n(f.queue)) return s.fail(t("invalid_input"));
|
|
46
|
+
let p = f.tenantId, v = f.queue;
|
|
47
|
+
return s.gen(function* () {
|
|
48
|
+
let d = [], y = 0, C = 0;
|
|
49
|
+
for (let w of f.proposals) {
|
|
50
|
+
let f = w.teams.flat(), T = f.map((e) => e.id), E = f.flatMap((e) => e.members), D = Object.freeze({
|
|
51
|
+
tenantId: p,
|
|
52
|
+
queue: v,
|
|
53
|
+
requestId: "mmr_" + m([
|
|
54
|
+
p,
|
|
55
|
+
v,
|
|
56
|
+
[...T].sort()
|
|
57
|
+
]).slice(0, 48),
|
|
58
|
+
teams: Object.freeze(w.teams.map((e) => Object.freeze(e.flatMap((e) => e.members))))
|
|
59
|
+
});
|
|
60
|
+
if (!(yield* g(() => e.transactional(async (e) => {
|
|
61
|
+
await x(e, p, E);
|
|
62
|
+
let t = new Date(b());
|
|
63
|
+
for (let n of T) if (await e.updateMany(i.tableName, {
|
|
64
|
+
state: "matched",
|
|
65
|
+
matchedAt: t
|
|
66
|
+
}, { where: l(u("id", n), u("tenantId", p), u("queue", v), u("state", "open")) }) !== 1) throw new _();
|
|
67
|
+
return !0;
|
|
68
|
+
}).catch((e) => {
|
|
69
|
+
if (e instanceof _) return !1;
|
|
70
|
+
throw e;
|
|
71
|
+
})))) {
|
|
72
|
+
y++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
let O = yield* s.result(s.suspend(() => h.allocate(D)).pipe(s.catchDefect(() => s.fail(t("unavailable")))));
|
|
76
|
+
if (c.isFailure(O) || !n(O.success.roomId) || E.some((e) => !n(O.success.seats[e]))) {
|
|
77
|
+
c.isSuccess(O) && (yield* s.ignore(s.suspend(() => h.release(D, O.success)))), yield* g(() => S(p, T, E)), C++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
let { roomId: k, seats: A } = O.success, j = yield* s.result(g(() => e.transactional(async (e) => {
|
|
81
|
+
await x(e, p, E);
|
|
82
|
+
let t = new Date(b());
|
|
83
|
+
for (let n of f) {
|
|
84
|
+
if (await e.updateMany(i.tableName, {
|
|
85
|
+
state: "assigned",
|
|
86
|
+
closedAt: t
|
|
87
|
+
}, { where: l(u("id", n.id), u("tenantId", p), u("state", "matched")) }) !== 1) throw new _();
|
|
88
|
+
for (let i of n.members) await e.insert(r.tableName, {
|
|
89
|
+
id: "mma_" + m([
|
|
90
|
+
p,
|
|
91
|
+
n.id,
|
|
92
|
+
i
|
|
93
|
+
]).slice(0, 56),
|
|
94
|
+
tenantId: p,
|
|
95
|
+
subjectId: i,
|
|
96
|
+
ticketId: n.id,
|
|
97
|
+
queue: v,
|
|
98
|
+
roomId: k,
|
|
99
|
+
sessionId: A[i],
|
|
100
|
+
createdAt: t
|
|
101
|
+
});
|
|
102
|
+
for (let t of n.members) await e.updateMany(a.tableName, { activeTicketId: null }, { where: l(u("id", o(p, t)), u("activeTicketId", n.id)) });
|
|
103
|
+
}
|
|
104
|
+
})));
|
|
105
|
+
c.isFailure(j) ? (yield* s.ignore(s.suspend(() => h.release(D, O.success))), yield* g(() => S(p, T, E)), C++) : d.push(Object.freeze({
|
|
106
|
+
requestId: D.requestId,
|
|
107
|
+
roomId: k,
|
|
108
|
+
tickets: Object.freeze(T)
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
return Object.freeze({
|
|
112
|
+
assigned: Object.freeze(d),
|
|
113
|
+
skipped: y,
|
|
114
|
+
failed: C
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
} };
|
|
118
|
+
}, y = async (e, t, n, i = 20) => (await e.query(f(r).where(l(u("tenantId", t), u("subjectId", n))).orderBy("createdAt", "desc").orderBy("id", "asc").limit(i).descriptor)).filter((e) => e.tenantId === t && e.subjectId === n).map((e) => Object.freeze({
|
|
119
|
+
ticketId: String(e.ticketId),
|
|
120
|
+
queue: String(e.queue),
|
|
121
|
+
roomId: String(e.roomId),
|
|
122
|
+
sessionId: String(e.sessionId),
|
|
123
|
+
createdAt: e.createdAt instanceof Date ? e.createdAt.getTime() : Date.parse(String(e.createdAt))
|
|
124
|
+
}));
|
|
125
|
+
//#endregion
|
|
126
|
+
export { v as dataStoreMatchmakingAssigner, y as readMatchmakingAssignments };
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { matchmakingError as e } from "./errors.js";
|
|
2
|
+
//#region src/contract.ts
|
|
3
|
+
var t = (e) => typeof e == "string" && e.length > 0 && e.length <= 256 && e.trim().length > 0 && !e.includes("\0") && e !== "~", n = (e, t) => Number.isSafeInteger(e) && e >= 1 && e <= t, r = (r) => {
|
|
4
|
+
if (!r || !t(r.name) || !r.teams || !n(r.teams.count, 64) || !n(r.teams.size, 256) || r.teams.count * r.teams.size > 4096 || !n(r.maxTickets, 1e4) || !n(r.maxSearchNodes, 1e6) || !n(r.maxAvoidPairs, 1e5) || !n(r.maxAttributeBytes, 65536) || !r.minQuality || !Number.isFinite(r.minQuality.initial) || r.minQuality.initial < 0 || r.minQuality.initial > 1 || !Array.isArray(r.minQuality.after) || r.minQuality.after.length > 256) throw e("invalid_queue");
|
|
5
|
+
let i = 0, a = r.minQuality.initial, o = Array.from(r.minQuality.after, (t) => {
|
|
6
|
+
if (!Array.isArray(t) || t.length !== 2 || !Number.isSafeInteger(t[0]) || t[0] <= i || !Number.isFinite(t[1]) || t[1] < 0 || t[1] > a) throw e("invalid_queue");
|
|
7
|
+
return i = t[0], a = t[1], Object.freeze([t[0], t[1]]);
|
|
8
|
+
});
|
|
9
|
+
return Object.freeze({
|
|
10
|
+
name: r.name,
|
|
11
|
+
teams: Object.freeze({
|
|
12
|
+
count: r.teams.count,
|
|
13
|
+
size: r.teams.size
|
|
14
|
+
}),
|
|
15
|
+
maxTickets: r.maxTickets,
|
|
16
|
+
maxSearchNodes: r.maxSearchNodes,
|
|
17
|
+
maxAvoidPairs: r.maxAvoidPairs,
|
|
18
|
+
maxAttributeBytes: r.maxAttributeBytes,
|
|
19
|
+
minQuality: Object.freeze({
|
|
20
|
+
initial: r.minQuality.initial,
|
|
21
|
+
after: Object.freeze(o)
|
|
22
|
+
})
|
|
23
|
+
});
|
|
24
|
+
}, i = (t, n, r, a = !0) => {
|
|
25
|
+
if (r > 32) throw e("invalid_input");
|
|
26
|
+
if (t !== null && typeof t != "string" && typeof t != "boolean" && !(typeof t == "number" && Number.isFinite(t))) {
|
|
27
|
+
if (typeof t != "object" || n.has(t) || (n.add(t), !Array.isArray(t) && Object.getPrototypeOf(t) !== Object.prototype && Object.getPrototypeOf(t) !== null) || Object.getOwnPropertySymbols(t).length > 0) throw e("invalid_input");
|
|
28
|
+
if (Array.isArray(t)) {
|
|
29
|
+
for (let n = 0; n < t.length; n++) if (!Object.hasOwn(t, n)) throw e("invalid_input");
|
|
30
|
+
}
|
|
31
|
+
if (Array.isArray(t) ? Object.keys(t).length !== t.length : Object.getOwnPropertyNames(t).length !== Object.keys(t).length) throw e("invalid_input");
|
|
32
|
+
for (let e of Object.values(t)) i(e, n, r + 1, a);
|
|
33
|
+
n.delete(t), a && Object.freeze(t);
|
|
34
|
+
}
|
|
35
|
+
}, a = (n) => {
|
|
36
|
+
if (!n || typeof n != "object") throw e("invalid_input");
|
|
37
|
+
let a = r(n.queue);
|
|
38
|
+
if (!Number.isSafeInteger(n.now) || n.now < 0 || n.now > 864e13 || !Array.isArray(n.tickets) || n.tickets.length > a.maxTickets || !Array.isArray(n.avoid) || n.avoid.length > a.maxAvoidPairs) throw e("invalid_input");
|
|
39
|
+
let o = /* @__PURE__ */ new Set(), s = /* @__PURE__ */ new Set(), c, l = Array.from(n.tickets, (r) => {
|
|
40
|
+
if (!r || !t(r.id) || !t(r.tenantId) || r.queue !== a.name || o.has(r.id) || c !== void 0 && c !== r.tenantId || !Number.isSafeInteger(r.createdAt) || r.createdAt < 0 || r.createdAt > n.now || !Array.isArray(r.members) || r.members.length < 1 || r.members.length > 256) throw e("invalid_input");
|
|
41
|
+
o.add(r.id), c = r.tenantId;
|
|
42
|
+
let l = Array.from(r.members, (n) => {
|
|
43
|
+
if (!t(n) || s.has(n)) throw e("invalid_input");
|
|
44
|
+
return s.add(n), n;
|
|
45
|
+
}), u;
|
|
46
|
+
try {
|
|
47
|
+
i(r.attributes, /* @__PURE__ */ new Set(), 0, !1);
|
|
48
|
+
let t = structuredClone(r.attributes);
|
|
49
|
+
i(t, /* @__PURE__ */ new Set(), 0);
|
|
50
|
+
let n = JSON.stringify(t);
|
|
51
|
+
if (n === void 0 || new TextEncoder().encode(n).byteLength > a.maxAttributeBytes) throw e("invalid_input");
|
|
52
|
+
u = t;
|
|
53
|
+
} catch {
|
|
54
|
+
throw e("invalid_input");
|
|
55
|
+
}
|
|
56
|
+
return Object.freeze({
|
|
57
|
+
id: r.id,
|
|
58
|
+
tenantId: r.tenantId,
|
|
59
|
+
queue: r.queue,
|
|
60
|
+
members: Object.freeze(l),
|
|
61
|
+
createdAt: r.createdAt,
|
|
62
|
+
attributes: u
|
|
63
|
+
});
|
|
64
|
+
}).sort((e, t) => e.createdAt - t.createdAt || (e.id < t.id ? -1 : +(e.id > t.id))), u = Array.from(n.avoid, (n) => {
|
|
65
|
+
if (!Array.isArray(n) || n.length !== 2 || !t(n[0]) || !t(n[1]) || n[0] === n[1]) throw e("invalid_input");
|
|
66
|
+
return Object.freeze([n[0], n[1]]);
|
|
67
|
+
});
|
|
68
|
+
return Object.freeze({
|
|
69
|
+
tickets: Object.freeze(l),
|
|
70
|
+
now: n.now,
|
|
71
|
+
queue: a,
|
|
72
|
+
avoid: Object.freeze(u)
|
|
73
|
+
});
|
|
74
|
+
}, o = (t, r) => {
|
|
75
|
+
if (!n(r, 65536)) throw e("invalid_input");
|
|
76
|
+
try {
|
|
77
|
+
i(t, /* @__PURE__ */ new Set(), 0, !1);
|
|
78
|
+
let n = structuredClone(t);
|
|
79
|
+
i(n, /* @__PURE__ */ new Set(), 0);
|
|
80
|
+
let a = JSON.stringify(n);
|
|
81
|
+
if (a === void 0 || new TextEncoder().encode(a).byteLength > r) throw e("invalid_input");
|
|
82
|
+
return n;
|
|
83
|
+
} catch {
|
|
84
|
+
throw e("invalid_input");
|
|
85
|
+
}
|
|
86
|
+
}, s = (e, t) => {
|
|
87
|
+
let n = (e) => {
|
|
88
|
+
let t = structuredClone(e);
|
|
89
|
+
return i(t, /* @__PURE__ */ new Set(), 0), JSON.stringify(t, (e, t) => typeof t == "object" && t && !Array.isArray(t) ? Object.fromEntries(Object.entries(t).sort(([e], [t]) => e < t ? -1 : +(e > t))) : t);
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
return n(e) === n(t);
|
|
93
|
+
} catch {
|
|
94
|
+
return !1;
|
|
95
|
+
}
|
|
96
|
+
}, c = (t, n) => {
|
|
97
|
+
if (!Number.isSafeInteger(n) || n < 0) throw e("invalid_input");
|
|
98
|
+
let r = t.minQuality.initial;
|
|
99
|
+
for (let [e, i] of t.minQuality.after) {
|
|
100
|
+
if (n < e) break;
|
|
101
|
+
r = i;
|
|
102
|
+
}
|
|
103
|
+
return r;
|
|
104
|
+
};
|
|
105
|
+
//#endregion
|
|
106
|
+
export { o as captureJsonData, a as captureMatchInput, r as captureQueueRules, t as identifier, c as minimumQuality, s as sameJson };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppContext } from '@voltro/runtime'
|
|
2
|
+
import { matchmakingDirectorWorkflowExecutor } from '@voltro/plugin-matchmaking'
|
|
3
|
+
|
|
4
|
+
/** Services come from the normal app workflow runtime, not globals. */
|
|
5
|
+
export default (_context: AppContext): typeof matchmakingDirectorWorkflowExecutor => matchmakingDirectorWorkflowExecutor
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { workflow } from '@voltro/workflow/define'
|
|
2
|
+
import { MatchmakingDirectorPayload, MatchmakingDirectorResult } from '@voltro/plugin-matchmaking/rpc'
|
|
3
|
+
import { MatchmakingError } from '@voltro/plugin-matchmaking/errors'
|
|
4
|
+
|
|
5
|
+
/** Internal API-lane Director. The scheduler owns firing and overlap; one run at a time. */
|
|
6
|
+
export const MatchmakingDirector = workflow({ name: 'matchmaking.director', payload: MatchmakingDirectorPayload,
|
|
7
|
+
success: MatchmakingDirectorResult, error: MatchmakingError,
|
|
8
|
+
internal: true, controls: { guards: [{ scope: 'matchmaking:operate' }] },
|
|
9
|
+
idempotencyKey: ({ runKey }) => `matchmaking.director:${runKey}`,
|
|
10
|
+
singleton: { key: () => 'matchmaking.director', mode: 'skip' },
|
|
11
|
+
schedule: { cron: '*/2 * * * * *', timezone: 'UTC', onOverlap: 'skip', backfill: 'skip',
|
|
12
|
+
payload: ({ scheduledAt }) => ({ runKey: scheduledAt.toISOString(), maxPairs: 64 }) },
|
|
13
|
+
})
|
package/dist/director.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { MatchmakingError as e, matchmakingError as t } from "./errors.js";
|
|
2
|
+
import { captureJsonData as n, captureMatchInput as r, identifier as i } from "./contract.js";
|
|
3
|
+
import { runQueueMatch as a } from "./queue.js";
|
|
4
|
+
import { matchmakingTicketsTable as o } from "./tickets.entity.js";
|
|
5
|
+
import { captureQueueDeclarations as s } from "./queueDeclarations.js";
|
|
6
|
+
import { Effect as c, Result as l } from "effect";
|
|
7
|
+
import { and as u, eq as d, gt as f, queryFor as p } from "@voltro/database";
|
|
8
|
+
//#region src/director.ts
|
|
9
|
+
var m = (n) => n instanceof e ? n : t("unavailable"), h = (e) => {
|
|
10
|
+
let n = e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : NaN;
|
|
11
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 864e13) throw t("unavailable");
|
|
12
|
+
return n;
|
|
13
|
+
}, g = (e, g) => {
|
|
14
|
+
if (!g || typeof g.now != "function" || typeof g.avoidPairs != "function") throw t("invalid_queue");
|
|
15
|
+
let _ = new Map(s(g.queues).map((e) => [e.name, e])), v = g.now, y = g.avoidPairs;
|
|
16
|
+
return { plan: (s, g) => {
|
|
17
|
+
let b, x;
|
|
18
|
+
try {
|
|
19
|
+
if (s?.type !== "system") throw t("identity_required");
|
|
20
|
+
if (b = n(g, 4096), !b || Object.keys(b).length !== 2 || !i(b.tenantId) || !i(b.queue)) throw t("invalid_input");
|
|
21
|
+
let e = _.get(b.queue);
|
|
22
|
+
if (!e) throw t("not_found");
|
|
23
|
+
x = e;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
return c.fail(m(e));
|
|
26
|
+
}
|
|
27
|
+
return c.tryPromise({
|
|
28
|
+
try: async () => {
|
|
29
|
+
let n = v();
|
|
30
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 864e13) throw t("unavailable");
|
|
31
|
+
let s = await e.query(p(o).where(u(d("tenantId", b.tenantId), d("queue", b.queue), d("state", "open"), f("expiresAt", new Date(n)))).orderBy("createdAt", "asc").orderBy("id", "asc").limit(x.maxTickets + 1).descriptor);
|
|
32
|
+
if (s.length > x.maxTickets) throw t("queue_full");
|
|
33
|
+
let m = s.map((e) => {
|
|
34
|
+
if (e.tenantId !== b.tenantId || e.queue !== b.queue || e.state !== "open" || h(e.expiresAt) <= n || !Array.isArray(e.members) || !e.members.includes(e.subjectId) || !i(e.id) || (e.partyId === null || e.partyId === void 0) && e.members.length !== 1) throw t("unavailable");
|
|
35
|
+
return {
|
|
36
|
+
id: e.id,
|
|
37
|
+
tenantId: b.tenantId,
|
|
38
|
+
queue: b.queue,
|
|
39
|
+
members: e.members,
|
|
40
|
+
createdAt: h(e.createdAt),
|
|
41
|
+
attributes: e.attributes
|
|
42
|
+
};
|
|
43
|
+
}), g = r({
|
|
44
|
+
queue: x,
|
|
45
|
+
now: n,
|
|
46
|
+
tickets: m,
|
|
47
|
+
avoid: []
|
|
48
|
+
}), _ = Object.freeze({
|
|
49
|
+
tenantId: b.tenantId,
|
|
50
|
+
queue: b.queue,
|
|
51
|
+
tickets: g.tickets,
|
|
52
|
+
maxPairs: x.maxAvoidPairs,
|
|
53
|
+
query: e.query.bind(e)
|
|
54
|
+
}), S = await c.runPromise(c.result(c.suspend(() => y(_))));
|
|
55
|
+
if (l.isFailure(S)) throw S.failure;
|
|
56
|
+
let C = a(x, {
|
|
57
|
+
tickets: g.tickets,
|
|
58
|
+
now: n,
|
|
59
|
+
avoid: S.success
|
|
60
|
+
});
|
|
61
|
+
return Object.freeze({
|
|
62
|
+
...C,
|
|
63
|
+
tenantId: b.tenantId,
|
|
64
|
+
queue: b.queue,
|
|
65
|
+
observedAt: n
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
catch: m
|
|
69
|
+
}).pipe(c.catchDefect(() => c.fail(t("unavailable"))));
|
|
70
|
+
} };
|
|
71
|
+
};
|
|
72
|
+
//#endregion
|
|
73
|
+
export { g as dataStoreMatchmakingPlanner };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/directorContract.ts
|
|
3
|
+
var t = e.Number.pipe(e.check(e.isInt()), e.check(e.isGreaterThanOrEqualTo(0))), n = e.Struct({
|
|
4
|
+
runKey: e.NonEmptyString,
|
|
5
|
+
maxPairs: t.pipe(e.check(e.isBetween({
|
|
6
|
+
minimum: 1,
|
|
7
|
+
maximum: 1024
|
|
8
|
+
})))
|
|
9
|
+
}), r = e.Struct({
|
|
10
|
+
cycles: t,
|
|
11
|
+
assigned: t,
|
|
12
|
+
skipped: t,
|
|
13
|
+
failed: t
|
|
14
|
+
});
|
|
15
|
+
//#endregion
|
|
16
|
+
export { n as MatchmakingDirectorPayload, r as MatchmakingDirectorResult };
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import { YieldableError } from 'effect/Cause';
|
|
3
|
+
|
|
4
|
+
export declare class MatchmakingError extends MatchmakingError_base {
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export declare const matchmakingError: (code: MatchmakingError["code"]) => MatchmakingError;
|
|
8
|
+
|
|
9
|
+
declare const MatchmakingError_base: Schema.Class<MatchmakingError, Schema.TaggedStruct<"MatchmakingError", {
|
|
10
|
+
readonly code: Schema.Literals<readonly ["invalid_input", "invalid_queue", "invalid_proposal", "unavailable", "identity_required", "not_found", "already_searching", "conflict", "queue_full", "erased", "export_limit", "forbidden", "party_full", "already_in_party", "party_not_ready"]>;
|
|
11
|
+
}>, YieldableError>;
|
|
12
|
+
|
|
13
|
+
export { }
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Schema as e } from "effect";
|
|
2
|
+
//#region src/errors.ts
|
|
3
|
+
var t = class extends e.TaggedError()("MatchmakingError", { code: e.Literals([
|
|
4
|
+
"invalid_input",
|
|
5
|
+
"invalid_queue",
|
|
6
|
+
"invalid_proposal",
|
|
7
|
+
"unavailable",
|
|
8
|
+
"identity_required",
|
|
9
|
+
"not_found",
|
|
10
|
+
"already_searching",
|
|
11
|
+
"conflict",
|
|
12
|
+
"queue_full",
|
|
13
|
+
"erased",
|
|
14
|
+
"export_limit",
|
|
15
|
+
"forbidden",
|
|
16
|
+
"party_full",
|
|
17
|
+
"already_in_party",
|
|
18
|
+
"party_not_ready"
|
|
19
|
+
]) }) {}, n = (e) => new t({ code: e });
|
|
20
|
+
//#endregion
|
|
21
|
+
export { t as MatchmakingError, n as matchmakingError };
|