@moneypot/hub 1.19.15 → 1.20.0-dev.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/README.md +26 -0
- package/dist/dashboard/assets/index-Bl5lLFHa.css +1 -0
- package/dist/dashboard/assets/index-Gvpn1nS9.js +350 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/src/audit-log.d.ts +9 -9
- package/dist/src/audit-log.js +7 -1
- package/dist/src/config.js +4 -8
- package/dist/src/db/public.d.ts +8 -8
- package/dist/src/db/public.js +29 -7
- package/dist/src/db/transaction.d.ts +2 -5
- package/dist/src/db/transaction.js +0 -1
- package/dist/src/db/types.d.ts +11 -2
- package/dist/src/db/util.d.ts +1 -3
- package/dist/src/deprecated.d.ts +6 -0
- package/dist/src/deprecated.js +2 -0
- package/dist/src/hash-chain/plugins/hub-create-hash-chain.js +12 -12
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.js +4 -0
- package/dist/src/liability/internal/db.d.ts +8 -0
- package/dist/src/liability/internal/db.js +27 -0
- package/dist/src/liability/internal/index.d.ts +2 -0
- package/dist/src/liability/internal/index.js +2 -0
- package/dist/src/liability/internal/processor.d.ts +12 -0
- package/dist/src/liability/internal/processor.js +123 -0
- package/dist/src/liability/public/config.d.ts +36 -0
- package/dist/src/liability/public/config.js +12 -0
- package/dist/src/liability/public/db.d.ts +10 -0
- package/dist/src/liability/public/db.js +53 -0
- package/dist/src/liability/public/index.d.ts +2 -0
- package/dist/src/liability/public/index.js +1 -0
- package/dist/src/liability/types.d.ts +24 -0
- package/dist/src/liability/types.js +1 -0
- package/dist/src/migrations.js +2 -2
- package/dist/src/pg-versions/017-liability.sql +48 -0
- package/dist/src/plugins/hub-add-casino.js +5 -1
- package/dist/src/plugins/hub-make-outcome-bet.js +4 -4
- package/dist/src/plugins/hub-risk-limits.js +1 -1
- package/dist/src/plugins/validate-fields.d.ts +7 -0
- package/dist/src/plugins/validate-fields.js +1 -1
- package/dist/src/risk-policy.d.ts +2 -2
- package/dist/src/risk-policy.js +3 -3
- package/dist/src/server/index.d.ts +4 -2
- package/dist/src/server/index.js +14 -3
- package/dist/src/test/index.d.ts +7 -6
- package/dist/src/test/index.js +7 -2
- package/dist/src/util.d.ts +5 -0
- package/package.json +10 -11
- package/dist/dashboard/assets/index-32DtKox_.css +0 -5
- package/dist/dashboard/assets/index-BlgWJql2.js +0 -418
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/dashboard/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/dashboard/assets/index-Gvpn1nS9.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-Bl5lLFHa.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/dist/src/audit-log.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DbPlayerBalance, DbHouseBankroll } from "./db/types.js";
|
|
2
2
|
import { QueryExecutor } from "./db/util.js";
|
|
3
3
|
export type AuditLogType = "player-balance" | "house-bankroll" | "both";
|
|
4
4
|
export type HubAuditAction = "hub:init" | "hub:deposit" | "hub:take_request:pending" | "hub:take_request:refund" | "hub:outcome_bet";
|
|
@@ -16,16 +16,16 @@ export type BaseAudit = {
|
|
|
16
16
|
refId?: null;
|
|
17
17
|
});
|
|
18
18
|
export type NewBalanceAudit = {
|
|
19
|
-
balanceId:
|
|
20
|
-
balanceOld:
|
|
21
|
-
balanceNew:
|
|
22
|
-
balanceDelta:
|
|
19
|
+
balanceId: DbPlayerBalance["id"];
|
|
20
|
+
balanceOld: DbPlayerBalance["amount"];
|
|
21
|
+
balanceNew: DbPlayerBalance["amount"];
|
|
22
|
+
balanceDelta: DbPlayerBalance["amount"];
|
|
23
23
|
};
|
|
24
24
|
export type NewBankrollAudit = {
|
|
25
|
-
bankrollId:
|
|
26
|
-
bankrollOld:
|
|
27
|
-
bankrollNew:
|
|
28
|
-
bankrollDelta:
|
|
25
|
+
bankrollId: DbHouseBankroll["id"];
|
|
26
|
+
bankrollOld: DbHouseBankroll["amount"];
|
|
27
|
+
bankrollNew: DbHouseBankroll["amount"];
|
|
28
|
+
bankrollDelta: DbHouseBankroll["amount"];
|
|
29
29
|
};
|
|
30
30
|
export type NewBothAudit = NewBalanceAudit & NewBankrollAudit;
|
|
31
31
|
export declare function insertAuditLog(pgClient: QueryExecutor, type: "player-balance", audit: NewBalanceAudit & BaseAudit): Promise<void>;
|
package/dist/src/audit-log.js
CHANGED
|
@@ -5,7 +5,13 @@ export async function insertAuditLog(pgClient, type, audit) {
|
|
|
5
5
|
const { balanceOld, balanceNew, balanceDelta } = audit;
|
|
6
6
|
const expected = balanceOld + balanceDelta;
|
|
7
7
|
if (Math.abs(balanceNew - expected) > DELTA_TOLERANCE) {
|
|
8
|
-
logger.warn({
|
|
8
|
+
logger.warn({
|
|
9
|
+
balanceOld,
|
|
10
|
+
balanceDelta,
|
|
11
|
+
balanceNew,
|
|
12
|
+
expected,
|
|
13
|
+
action: audit.action,
|
|
14
|
+
}, "Audit balance delta mismatch");
|
|
9
15
|
}
|
|
10
16
|
}
|
|
11
17
|
if (type === "house-bankroll" || type === "both") {
|
package/dist/src/config.js
CHANGED
|
@@ -30,9 +30,6 @@ const logLevels = [
|
|
|
30
30
|
];
|
|
31
31
|
export const LOG_LEVEL = getEnvVariable("LOG_LEVEL", (value) => {
|
|
32
32
|
if (logLevels.includes(value)) {
|
|
33
|
-
if (value === "silent") {
|
|
34
|
-
console.log("LOG_LEVEL is set to silent. No logging will be done.");
|
|
35
|
-
}
|
|
36
33
|
return value;
|
|
37
34
|
}
|
|
38
35
|
if (value === "") {
|
|
@@ -101,15 +98,14 @@ export const SUPERUSER_DATABASE_URL = getEnvVariable("SUPERUSER_DATABASE_URL", (
|
|
|
101
98
|
export const HASHCHAINSERVER_URL = getEnvVariable("HASHCHAINSERVER_URL", (value) => {
|
|
102
99
|
value = value || "mock-server";
|
|
103
100
|
if (value === "mock-server") {
|
|
104
|
-
|
|
101
|
+
if (NODE_ENV !== "development" && NODE_ENV !== "test") {
|
|
102
|
+
console.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.", { NODE_ENV, foo: 32 });
|
|
103
|
+
}
|
|
105
104
|
}
|
|
106
|
-
else if (!URL.
|
|
105
|
+
else if (!URL.canParse(value)) {
|
|
107
106
|
console.warn("HASHCHAINSERVER_URL is not a valid URL. It can either be empty, 'mock-server', or URL but it was: " +
|
|
108
107
|
value);
|
|
109
108
|
}
|
|
110
|
-
if (NODE_ENV !== "development" && value === "mock-server") {
|
|
111
|
-
console.warn("Using mock-server for HASHCHAINSERVER_URL. This is only allowed in development.");
|
|
112
|
-
}
|
|
113
109
|
return value;
|
|
114
110
|
});
|
|
115
111
|
export const HASHCHAINSERVER_MAX_ITERATIONS = getEnvVariable("HASHCHAINSERVER_MAX_ITERATIONS", (value) => {
|
package/dist/src/db/public.d.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DbCasino, DbCurrency, DbSession, DbLockedPlayerBalance, DbLockedHouseBankrollWithAvailable, DbHouseBankrollWithAvailable } from "./types.js";
|
|
2
2
|
import { PgClientInTransaction, QueryExecutor } from "./index.js";
|
|
3
3
|
export declare function dbGetActiveSessionById(pgClient: QueryExecutor, sessionId: string): Promise<DbSession | undefined>;
|
|
4
|
-
export declare function
|
|
5
|
-
currencyKey:
|
|
6
|
-
casinoId:
|
|
7
|
-
}): Promise<DbCurrency |
|
|
4
|
+
export declare function dbGetCurrencyByKey(pgClient: QueryExecutor, { currencyKey, casinoId, }: {
|
|
5
|
+
currencyKey: DbCurrency["key"];
|
|
6
|
+
casinoId: DbCasino["id"];
|
|
7
|
+
}): Promise<DbCurrency | null>;
|
|
8
8
|
export declare function dbGetHouseBankrolls(pgClient: QueryExecutor, { casinoId }: {
|
|
9
9
|
casinoId: DbCasino["id"];
|
|
10
|
-
}): Promise<Pick<
|
|
10
|
+
}): Promise<Pick<DbHouseBankrollWithAvailable, "id" | "currency_key" | "grossAmount" | "available">[]>;
|
|
11
11
|
export declare function dbLockPlayerBalance(pgClient: PgClientInTransaction, { userId, casinoId, experienceId, currencyKey, }: {
|
|
12
12
|
userId: string;
|
|
13
13
|
casinoId: string;
|
|
14
14
|
experienceId: string;
|
|
15
15
|
currencyKey: string;
|
|
16
|
-
}): Promise<Pick<
|
|
16
|
+
}): Promise<Pick<DbLockedPlayerBalance, "id" | "amount"> | null>;
|
|
17
17
|
export declare function dbLockHouseBankroll(pgClient: PgClientInTransaction, { casinoId, currencyKey }: {
|
|
18
18
|
casinoId: string;
|
|
19
19
|
currencyKey: string;
|
|
20
|
-
}): Promise<
|
|
20
|
+
}): Promise<DbLockedHouseBankrollWithAvailable | null>;
|
package/dist/src/db/public.js
CHANGED
|
@@ -8,7 +8,7 @@ export async function dbGetActiveSessionById(pgClient, sessionId) {
|
|
|
8
8
|
`, [sessionId])
|
|
9
9
|
.then(maybeOneRow);
|
|
10
10
|
}
|
|
11
|
-
export async function
|
|
11
|
+
export async function dbGetCurrencyByKey(pgClient, { currencyKey, casinoId, }) {
|
|
12
12
|
return pgClient
|
|
13
13
|
.query(`
|
|
14
14
|
SELECT *
|
|
@@ -16,16 +16,23 @@ export async function dbGetCasinoCurrencyByKey(pgClient, { currencyKey, casinoId
|
|
|
16
16
|
WHERE key = $1
|
|
17
17
|
AND casino_id = $2
|
|
18
18
|
`, [currencyKey, casinoId])
|
|
19
|
-
.then(maybeOneRow)
|
|
19
|
+
.then(maybeOneRow)
|
|
20
|
+
.then((row) => row ?? null);
|
|
20
21
|
}
|
|
21
22
|
export async function dbGetHouseBankrolls(pgClient, { casinoId }) {
|
|
22
|
-
|
|
23
|
+
const y = pgClient
|
|
23
24
|
.query(`
|
|
24
|
-
SELECT id, currency_key, amount
|
|
25
|
+
SELECT id, currency_key, amount, liability, (amount - liability) AS available
|
|
25
26
|
FROM hub.bankroll
|
|
26
27
|
WHERE casino_id = $1
|
|
27
28
|
`, [casinoId])
|
|
28
|
-
.then((res) => res.rows)
|
|
29
|
+
.then((res) => res.rows.map((row) => ({
|
|
30
|
+
id: row.id,
|
|
31
|
+
currency_key: row.currency_key,
|
|
32
|
+
grossAmount: row.amount,
|
|
33
|
+
available: row.available,
|
|
34
|
+
})));
|
|
35
|
+
return y;
|
|
29
36
|
}
|
|
30
37
|
export async function dbLockPlayerBalance(pgClient, { userId, casinoId, experienceId, currencyKey, }) {
|
|
31
38
|
return pgClient
|
|
@@ -44,12 +51,27 @@ export async function dbLockPlayerBalance(pgClient, { userId, casinoId, experien
|
|
|
44
51
|
export async function dbLockHouseBankroll(pgClient, { casinoId, currencyKey }) {
|
|
45
52
|
return pgClient
|
|
46
53
|
.query(`
|
|
47
|
-
SELECT
|
|
54
|
+
SELECT
|
|
55
|
+
id,
|
|
56
|
+
casino_id,
|
|
57
|
+
currency_key,
|
|
58
|
+
amount,
|
|
59
|
+
liability,
|
|
60
|
+
(amount - liability) AS available
|
|
48
61
|
FROM hub.bankroll
|
|
49
62
|
WHERE casino_id = $1
|
|
50
63
|
AND currency_key = $2
|
|
51
64
|
FOR UPDATE
|
|
52
65
|
`, [casinoId, currencyKey])
|
|
53
66
|
.then(maybeOneRow)
|
|
54
|
-
.then((row) => row
|
|
67
|
+
.then((row) => row
|
|
68
|
+
? {
|
|
69
|
+
id: row.id,
|
|
70
|
+
casino_id: row.casino_id,
|
|
71
|
+
currency_key: row.currency_key,
|
|
72
|
+
liability: row.liability,
|
|
73
|
+
grossAmount: row.amount,
|
|
74
|
+
available: row.available,
|
|
75
|
+
}
|
|
76
|
+
: null);
|
|
55
77
|
}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as pg from "pg";
|
|
2
|
-
|
|
2
|
+
import { Branded } from "../util.js";
|
|
3
3
|
export declare class RollbackFailedError extends Error {
|
|
4
4
|
readonly originalError: unknown;
|
|
5
5
|
readonly rollbackError: unknown;
|
|
6
6
|
constructor(originalError: unknown, rollbackError: unknown);
|
|
7
7
|
}
|
|
8
|
-
export type PgClientInTransaction = pg.ClientBase
|
|
9
|
-
readonly [PgClientInTransactionBrand]: true;
|
|
10
|
-
};
|
|
8
|
+
export type PgClientInTransaction = Branded<"PgClientInTransaction", pg.ClientBase>;
|
|
11
9
|
export declare function isInTransaction(pgClient: pg.ClientBase): pgClient is PgClientInTransaction;
|
|
12
10
|
export declare function assertInTransaction(pgClient: pg.ClientBase): asserts pgClient is PgClientInTransaction;
|
|
13
11
|
export declare function getIsolationLevel(pgClient: PgClientInTransaction): IsolationLevel | null;
|
|
@@ -21,4 +19,3 @@ export declare function withPgClientTransaction<T>(pgClient: pg.ClientBase, call
|
|
|
21
19
|
export declare function withPgClientTransaction<T>(pgClient: pg.ClientBase, isolationLevel: IsolationLevel, callback: (pgClient: PgClientInTransaction) => Promise<T>): Promise<T>;
|
|
22
20
|
export declare function withPgPoolTransaction<T>(pool: pg.Pool, callback: (pgClient: PgClientInTransaction) => Promise<T>, retryCount?: number, maxRetries?: number): Promise<T>;
|
|
23
21
|
export declare function withPgPoolTransaction<T>(pool: pg.Pool, isolationLevel: IsolationLevel, callback: (pgClient: PgClientInTransaction) => Promise<T>, retryCount?: number, maxRetries?: number): Promise<T>;
|
|
24
|
-
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as pg from "pg";
|
|
2
2
|
import { logger } from "../logger.js";
|
|
3
3
|
import { setTimeout } from "node:timers/promises";
|
|
4
|
-
const PgClientInTransactionBrand = Symbol("PgClientInTransaction");
|
|
5
4
|
export class RollbackFailedError extends Error {
|
|
6
5
|
originalError;
|
|
7
6
|
rollbackError;
|
package/dist/src/db/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as jose from "jose";
|
|
2
|
+
import { Branded } from "../util.js";
|
|
2
3
|
export type DbSession = {
|
|
3
4
|
id: string;
|
|
4
5
|
user_id: string;
|
|
@@ -75,12 +76,20 @@ export type DbCasinoJwks = {
|
|
|
75
76
|
updated_at: Date;
|
|
76
77
|
forced_at: Date | null;
|
|
77
78
|
};
|
|
78
|
-
export type
|
|
79
|
+
export type DbHouseBankroll = {
|
|
79
80
|
id: string;
|
|
80
81
|
casino_id: string;
|
|
81
82
|
currency_key: string;
|
|
82
83
|
amount: number;
|
|
84
|
+
liability: number;
|
|
83
85
|
};
|
|
86
|
+
export type DbLockedHouseBankroll = Branded<"DbLockedHouseBankroll", DbHouseBankroll>;
|
|
87
|
+
export type DbLockedPlayerBalance = Branded<"DbLockedPlayerBalance", DbPlayerBalance>;
|
|
88
|
+
export type DbHouseBankrollWithAvailable = Omit<DbHouseBankroll, "amount"> & {
|
|
89
|
+
grossAmount: number;
|
|
90
|
+
available: number;
|
|
91
|
+
};
|
|
92
|
+
export type DbLockedHouseBankrollWithAvailable = Branded<"DbLockedHouseBankrollWithAvailable", DbHouseBankrollWithAvailable>;
|
|
84
93
|
export type DbProcessedTakeRequest = {
|
|
85
94
|
id: string;
|
|
86
95
|
mp_take_request_id: string;
|
|
@@ -91,7 +100,7 @@ export type DbProcessedTakeRequest = {
|
|
|
91
100
|
requested_amount: number | null;
|
|
92
101
|
transferred_amount: number;
|
|
93
102
|
};
|
|
94
|
-
export type
|
|
103
|
+
export type DbPlayerBalance = {
|
|
95
104
|
id: string;
|
|
96
105
|
casino_id: string;
|
|
97
106
|
user_id: string;
|
package/dist/src/db/util.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { QueryResult, QueryResultRow } from "pg";
|
|
2
2
|
import { PgClientResult } from "postgraphile/@dataplan/pg";
|
|
3
3
|
import * as pg from "pg";
|
|
4
|
-
export
|
|
5
|
-
query<T extends pg.QueryResultRow = any>(queryText: string, values?: any[]): Promise<pg.QueryResult<T>>;
|
|
6
|
-
}
|
|
4
|
+
export type QueryExecutor = Pick<pg.Pool, "query"> | Pick<pg.ClientBase, "query">;
|
|
7
5
|
type ResultType<T> = PgClientResult<T> | QueryResult<T extends QueryResultRow ? T : never>;
|
|
8
6
|
export declare function maybeOneRow<T>(result: ResultType<T>): T | undefined;
|
|
9
7
|
export declare function exactlyOneRow<T>(result: ResultType<T>): T;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DbPlayerBalance, DbHouseBankroll, DbHouseBankrollWithAvailable } from "./db/types.js";
|
|
2
|
+
export type DbBalance = DbPlayerBalance;
|
|
3
|
+
export type DbBankroll = DbHouseBankroll;
|
|
4
|
+
export type DbBankrollWithAvailable = DbHouseBankrollWithAvailable;
|
|
5
|
+
import { dbGetCurrencyByKey } from "./db/public.js";
|
|
6
|
+
export declare const dbGetCasinoCurrencyByKey: typeof dbGetCurrencyByKey;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { context, object, sideEffect } from "
|
|
2
|
-
import { gql, makeExtendSchemaPlugin } from "
|
|
1
|
+
import { context, object, sideEffect } from "../../grafast.js";
|
|
2
|
+
import { gql, makeExtendSchemaPlugin } from "../../graphile.js";
|
|
3
3
|
import { GraphQLError } from "graphql";
|
|
4
4
|
import { PgAdvisoryLock } from "../../pg-advisory-lock.js";
|
|
5
|
-
import { exactlyOneRow, withPgPoolTransaction } from "
|
|
5
|
+
import { exactlyOneRow, withPgPoolTransaction } from "../../db/index.js";
|
|
6
6
|
import * as HashCommon from "../get-hash.js";
|
|
7
7
|
import { DbHashKind } from "../../db/types.js";
|
|
8
8
|
import * as config from "../../config.js";
|
|
@@ -37,7 +37,7 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
37
37
|
await pgClient.query(`
|
|
38
38
|
UPDATE hub.hash_chain
|
|
39
39
|
SET active = false
|
|
40
|
-
WHERE user_id = $1
|
|
40
|
+
WHERE user_id = $1
|
|
41
41
|
AND experience_id = $2
|
|
42
42
|
AND casino_id = $3
|
|
43
43
|
AND active = true
|
|
@@ -49,11 +49,11 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
49
49
|
const dbHashChain = await pgClient
|
|
50
50
|
.query(`
|
|
51
51
|
INSERT INTO hub.hash_chain (
|
|
52
|
-
user_id,
|
|
53
|
-
experience_id,
|
|
54
|
-
casino_id,
|
|
55
|
-
active,
|
|
56
|
-
max_iteration,
|
|
52
|
+
user_id,
|
|
53
|
+
experience_id,
|
|
54
|
+
casino_id,
|
|
55
|
+
active,
|
|
56
|
+
max_iteration,
|
|
57
57
|
current_iteration
|
|
58
58
|
)
|
|
59
59
|
VALUES ($1, $2, $3, true, $4, $4)
|
|
@@ -73,9 +73,9 @@ export const HubCreateHashChainPlugin = makeExtendSchemaPlugin((build) => {
|
|
|
73
73
|
}
|
|
74
74
|
await pgClient.query(`
|
|
75
75
|
INSERT INTO hub.hash (
|
|
76
|
-
hash_chain_id,
|
|
77
|
-
kind,
|
|
78
|
-
digest,
|
|
76
|
+
hash_chain_id,
|
|
77
|
+
kind,
|
|
78
|
+
digest,
|
|
79
79
|
iteration
|
|
80
80
|
)
|
|
81
81
|
VALUES ($1, $2, $3, $4)
|
package/dist/src/index.d.ts
CHANGED
|
@@ -20,13 +20,16 @@ declare global {
|
|
|
20
20
|
export { HubGameConfigPlugin, type OutcomeBetConfigMap, type OutcomeBetConfig, } from "./plugins/hub-game-config-plugin.js";
|
|
21
21
|
export { type CustomGameConfig, type CustomGameConfigMap, } from "./custom-game-config.js";
|
|
22
22
|
export { validateRisk, type RiskPolicy, type RiskPolicyArgs, type RiskLimits, } from "./risk-policy.js";
|
|
23
|
+
export { type DbLiability, type NewLiability, type LiabilityConfig, type LiabilityHandler, type LiabilityResolved, type DbLockedLiability, LIABILITY_RESOLVED, createLiabilityConfig, } from "./liability/public/index.js";
|
|
23
24
|
export type PluginContext = Grafast.Context;
|
|
25
|
+
export * from "./db/types.js";
|
|
24
26
|
export { defaultPlugins, type PluginIdentity, type UserSessionContext, type HubPlugin, } from "./server/graphile.config.js";
|
|
25
27
|
export type { ConfigureAppArgs } from "./server/index.js";
|
|
26
28
|
import { runMigrations } from "./migrations.js";
|
|
27
29
|
export { runMigrations };
|
|
28
30
|
import type { HubPlugin } from "./server/graphile.config.js";
|
|
29
|
-
|
|
31
|
+
import type { LiabilityConfig } from "./liability/public/index.js";
|
|
32
|
+
export type ServerOptions<LiabilityRefType extends string = string> = {
|
|
30
33
|
configureApp?: (args: ConfigureAppArgs) => void;
|
|
31
34
|
plugins?: readonly HubPlugin[];
|
|
32
35
|
extraPgSchemas?: string[];
|
|
@@ -37,8 +40,10 @@ export type ServerOptions = {
|
|
|
37
40
|
port?: number;
|
|
38
41
|
superuserDatabaseUrl?: string;
|
|
39
42
|
postgraphileDatabaseUrl?: string;
|
|
43
|
+
liabilityConfig?: LiabilityConfig<LiabilityRefType>;
|
|
40
44
|
};
|
|
41
|
-
export declare function startAndListen(options: ServerOptions): Promise<{
|
|
45
|
+
export declare function startAndListen<LiabilityRefType extends string = string>(options: ServerOptions<LiabilityRefType>): Promise<{
|
|
42
46
|
port: number;
|
|
43
47
|
stop: () => Promise<void>;
|
|
44
48
|
}>;
|
|
49
|
+
export * from "./deprecated.js";
|
package/dist/src/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { getPgClient } from "./db/index.js";
|
|
|
4
4
|
import { createHubServer } from "./server/index.js";
|
|
5
5
|
export { HubGameConfigPlugin, } from "./plugins/hub-game-config-plugin.js";
|
|
6
6
|
export { validateRisk, } from "./risk-policy.js";
|
|
7
|
+
export { LIABILITY_RESOLVED, createLiabilityConfig, } from "./liability/public/index.js";
|
|
8
|
+
export * from "./db/types.js";
|
|
7
9
|
export { defaultPlugins, } from "./server/graphile.config.js";
|
|
8
10
|
import { runMigrations } from "./migrations.js";
|
|
9
11
|
export { runMigrations };
|
|
@@ -44,6 +46,7 @@ export async function startAndListen(options) {
|
|
|
44
46
|
enablePlayground: options.enablePlayground ?? true,
|
|
45
47
|
port: options.port ?? PORT,
|
|
46
48
|
runProcessors: NODE_ENV !== "test",
|
|
49
|
+
liabilityConfig: options.liabilityConfig,
|
|
47
50
|
});
|
|
48
51
|
if (process.env.NODE_ENV !== "test") {
|
|
49
52
|
const handler = () => {
|
|
@@ -64,3 +67,4 @@ export async function startAndListen(options) {
|
|
|
64
67
|
stop: () => hubServer.shutdown(),
|
|
65
68
|
};
|
|
66
69
|
}
|
|
70
|
+
export * from "./deprecated.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PgClientInTransaction } from "../../db/transaction.js";
|
|
2
|
+
import { DbLiability } from "../types.js";
|
|
3
|
+
export declare function dbGetAndLockLiabilitiesPastDeadline(pgClient: PgClientInTransaction, { limit }?: {
|
|
4
|
+
limit?: number;
|
|
5
|
+
}): Promise<DbLiability[]>;
|
|
6
|
+
export declare function dbGetAndLockLiability(pool: PgClientInTransaction, { id }: {
|
|
7
|
+
id: string;
|
|
8
|
+
}): Promise<DbLiability | null>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export async function dbGetAndLockLiabilitiesPastDeadline(pgClient, { limit = 100 } = {}) {
|
|
2
|
+
const result = await pgClient.query({
|
|
3
|
+
text: `
|
|
4
|
+
SELECT *
|
|
5
|
+
FROM hub.liability
|
|
6
|
+
WHERE resolved_at IS NULL
|
|
7
|
+
AND deadline < now()
|
|
8
|
+
ORDER BY deadline ASC
|
|
9
|
+
LIMIT $1
|
|
10
|
+
FOR UPDATE SKIP LOCKED
|
|
11
|
+
`,
|
|
12
|
+
values: [limit],
|
|
13
|
+
});
|
|
14
|
+
return result.rows;
|
|
15
|
+
}
|
|
16
|
+
export async function dbGetAndLockLiability(pool, { id }) {
|
|
17
|
+
const result = await pool.query({
|
|
18
|
+
text: `
|
|
19
|
+
SELECT *
|
|
20
|
+
FROM hub.liability
|
|
21
|
+
WHERE id = $1
|
|
22
|
+
FOR UPDATE SKIP LOCKED
|
|
23
|
+
`,
|
|
24
|
+
values: [id],
|
|
25
|
+
});
|
|
26
|
+
return result.rows[0] ?? null;
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import * as pg from "pg";
|
|
2
|
+
import { LiabilityConfigInternal } from "../public/config.js";
|
|
3
|
+
export declare function startLiabilityExpirationProcessor<RefType extends string>({ signal, pool, liabilityConfig, }: {
|
|
4
|
+
signal: AbortSignal;
|
|
5
|
+
pool: pg.Pool;
|
|
6
|
+
liabilityConfig: LiabilityConfigInternal<RefType>;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function processExpiredLiabilities<RefType extends string>({ pool, liabilityConfig, signal, }: {
|
|
9
|
+
pool: pg.Pool;
|
|
10
|
+
liabilityConfig: LiabilityConfigInternal<RefType>;
|
|
11
|
+
signal: AbortSignal;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { logger } from "../../logger.js";
|
|
2
|
+
import { withPgPoolTransaction, } from "../../db/transaction.js";
|
|
3
|
+
import { dbLockHouseBankroll, dbLockPlayerBalance } from "../../db/public.js";
|
|
4
|
+
const POLL_INTERVAL = 30_000;
|
|
5
|
+
const BATCH_SIZE = 100;
|
|
6
|
+
async function dbGetLiabilityIdsPastDeadline(pgClient, { limit = 100 } = {}) {
|
|
7
|
+
const result = await pgClient.query({
|
|
8
|
+
text: `
|
|
9
|
+
SELECT id
|
|
10
|
+
FROM hub.liability
|
|
11
|
+
WHERE resolved_at IS NULL
|
|
12
|
+
AND deadline < now()
|
|
13
|
+
ORDER BY deadline ASC
|
|
14
|
+
LIMIT $1
|
|
15
|
+
`,
|
|
16
|
+
values: [limit],
|
|
17
|
+
});
|
|
18
|
+
return result.rows.map((r) => r.id);
|
|
19
|
+
}
|
|
20
|
+
async function dbTryLockLiabilityById(pgClient, id) {
|
|
21
|
+
const result = await pgClient.query({
|
|
22
|
+
text: `
|
|
23
|
+
SELECT *
|
|
24
|
+
FROM hub.liability
|
|
25
|
+
WHERE id = $1
|
|
26
|
+
AND resolved_at IS NULL
|
|
27
|
+
FOR UPDATE SKIP LOCKED
|
|
28
|
+
`,
|
|
29
|
+
values: [id],
|
|
30
|
+
});
|
|
31
|
+
return result.rows[0] ?? null;
|
|
32
|
+
}
|
|
33
|
+
export function startLiabilityExpirationProcessor({ signal, pool, liabilityConfig, }) {
|
|
34
|
+
if (signal.aborted) {
|
|
35
|
+
logger.info(`[startLiabilityExpirationProcessor] AbortSignal aborted. Not starting processor.`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
logger.info(`Starting liability expiration processor`);
|
|
39
|
+
(async () => {
|
|
40
|
+
let shouldStop = false;
|
|
41
|
+
signal.addEventListener("abort", () => {
|
|
42
|
+
shouldStop = true;
|
|
43
|
+
});
|
|
44
|
+
while (!shouldStop && !signal.aborted) {
|
|
45
|
+
try {
|
|
46
|
+
await processExpiredLiabilities({ pool, liabilityConfig, signal });
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
logger.error(e, `Error processing expired liabilities`);
|
|
50
|
+
}
|
|
51
|
+
if (!shouldStop && !signal.aborted) {
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
logger.info(`Liability expiration processor stopped`);
|
|
56
|
+
})();
|
|
57
|
+
}
|
|
58
|
+
export async function processExpiredLiabilities({ pool, liabilityConfig, signal, }) {
|
|
59
|
+
const liabilityIds = await dbGetLiabilityIdsPastDeadline(pool, {
|
|
60
|
+
limit: BATCH_SIZE,
|
|
61
|
+
});
|
|
62
|
+
if (liabilityIds.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
logger.info(`Processor found ${liabilityIds.length} liability candidates...`);
|
|
66
|
+
for (const liabilityId of liabilityIds) {
|
|
67
|
+
if (signal.aborted) {
|
|
68
|
+
logger.info(`[processExpiredLiabilities] Aborted by graceful shutdown.`);
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
await withPgPoolTransaction(pool, async (pgClient) => {
|
|
72
|
+
const dbLockedLiability = await dbTryLockLiabilityById(pgClient, liabilityId);
|
|
73
|
+
if (!dbLockedLiability) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const refTypeConfig = liabilityConfig._refTypes[dbLockedLiability.ref_type];
|
|
77
|
+
if (!refTypeConfig?.handleDeadline) {
|
|
78
|
+
logger.error(`No handler configured for liability ref_type "${dbLockedLiability.ref_type}" (id: ${dbLockedLiability.id}). Skipping.`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
const dbLockedPlayerBalance = await dbLockPlayerBalance(pgClient, {
|
|
82
|
+
userId: dbLockedLiability.user_id,
|
|
83
|
+
casinoId: dbLockedLiability.casino_id,
|
|
84
|
+
experienceId: dbLockedLiability.experience_id,
|
|
85
|
+
currencyKey: dbLockedLiability.currency_key,
|
|
86
|
+
});
|
|
87
|
+
if (!dbLockedPlayerBalance) {
|
|
88
|
+
logger.error(`No player balance found for liability ${dbLockedLiability.id}. Skipping.`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const dbLockedHouseBankroll = await dbLockHouseBankroll(pgClient, {
|
|
92
|
+
casinoId: dbLockedLiability.casino_id,
|
|
93
|
+
currencyKey: dbLockedLiability.currency_key,
|
|
94
|
+
});
|
|
95
|
+
if (!dbLockedHouseBankroll) {
|
|
96
|
+
logger.error(`No house bankroll found for liability ${dbLockedLiability.id}.`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
await refTypeConfig.handleDeadline({
|
|
101
|
+
pgClientInTransaction: pgClient,
|
|
102
|
+
lockedLiability: dbLockedLiability,
|
|
103
|
+
lockedPlayerBalance: dbLockedPlayerBalance,
|
|
104
|
+
lockedHouseBankroll: dbLockedHouseBankroll,
|
|
105
|
+
});
|
|
106
|
+
const { resolved_at } = await pgClient
|
|
107
|
+
.query(`SELECT resolved_at FROM hub.liability WHERE id = $1`, [
|
|
108
|
+
dbLockedLiability.id,
|
|
109
|
+
])
|
|
110
|
+
.then((r) => r.rows[0]);
|
|
111
|
+
if (!resolved_at) {
|
|
112
|
+
throw new Error(`Handler returned LIABILITY_RESOLVED but did not resolve liability ${dbLockedLiability.id} (ref_type: ${dbLockedLiability.ref_type}, ref_id: ${dbLockedLiability.ref_id}). ` +
|
|
113
|
+
`Handler must call dbResolveLiabilityByRef before returning.`);
|
|
114
|
+
}
|
|
115
|
+
logger.debug(`Resolved liability ${dbLockedLiability.id}`);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
logger.error(e, `Error handling expiration for liability ${dbLockedLiability.id}`);
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { DbLockedHouseBankrollWithAvailable, DbLockedPlayerBalance, PgClientInTransaction } from "../../db/index.js";
|
|
2
|
+
import { Branded } from "../../util.js";
|
|
3
|
+
import type { DbLiability } from "../types.js";
|
|
4
|
+
import { dbCreateLiability, dbResolveLiabilityByRef } from "./db.js";
|
|
5
|
+
export type DbLockedLiability<RefType extends string = string> = DbLiability & {
|
|
6
|
+
ref_type: RefType;
|
|
7
|
+
};
|
|
8
|
+
export type LiabilityResolved = Branded<"LiabilityResolved", {
|
|
9
|
+
readonly resolved: true;
|
|
10
|
+
}>;
|
|
11
|
+
export declare const LIABILITY_RESOLVED: LiabilityResolved;
|
|
12
|
+
export type HandleDeadlineArgs<RefType extends string> = {
|
|
13
|
+
pgClientInTransaction: PgClientInTransaction;
|
|
14
|
+
lockedLiability: DbLockedLiability<RefType>;
|
|
15
|
+
lockedPlayerBalance: Pick<DbLockedPlayerBalance, "id" | "amount">;
|
|
16
|
+
lockedHouseBankroll: Pick<DbLockedHouseBankrollWithAvailable, "id" | "grossAmount" | "available">;
|
|
17
|
+
};
|
|
18
|
+
export type HandleDeadlineFn<RefType extends string> = (args: HandleDeadlineArgs<RefType>) => Promise<LiabilityResolved>;
|
|
19
|
+
export type LiabilityRefTypeConfig<RefType extends string> = {
|
|
20
|
+
handleDeadline: HandleDeadlineFn<RefType>;
|
|
21
|
+
};
|
|
22
|
+
export declare function createLiabilityConfig<RefType extends string>(refTypes: {
|
|
23
|
+
[K in RefType]: LiabilityRefTypeConfig<K>;
|
|
24
|
+
}, options?: LiabilityConfigOptions): LiabilityConfig<RefType>;
|
|
25
|
+
export type LiabilityConfig<RefType extends string> = {
|
|
26
|
+
dbCreateLiability: typeof dbCreateLiability<RefType>;
|
|
27
|
+
dbResolveLiabilityByRef: typeof dbResolveLiabilityByRef<RefType>;
|
|
28
|
+
};
|
|
29
|
+
type LiabilityConfigOptions = never;
|
|
30
|
+
export type LiabilityConfigInternal<RefType extends string> = LiabilityConfig<RefType> & {
|
|
31
|
+
_refTypes: {
|
|
32
|
+
[K in RefType]: LiabilityRefTypeConfig<K>;
|
|
33
|
+
};
|
|
34
|
+
_options: LiabilityConfigOptions;
|
|
35
|
+
};
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { dbCreateLiability, dbResolveLiabilityByRef } from "./db.js";
|
|
2
|
+
export const LIABILITY_RESOLVED = {
|
|
3
|
+
resolved: true,
|
|
4
|
+
};
|
|
5
|
+
export function createLiabilityConfig(refTypes, options) {
|
|
6
|
+
return {
|
|
7
|
+
_refTypes: refTypes,
|
|
8
|
+
_options: options,
|
|
9
|
+
dbCreateLiability,
|
|
10
|
+
dbResolveLiabilityByRef,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { PgClientInTransaction } from "../../db/transaction.js";
|
|
2
|
+
import { DbLiability, NewLiability } from "../types.js";
|
|
3
|
+
export declare function dbCreateLiability<RefType extends string>(pgClient: PgClientInTransaction, { lockedBankrollId, casinoId, experienceId, userId, currencyKey, refId, refType, maxPayout, deadline, }: NewLiability<RefType>): Promise<DbLiability & {
|
|
4
|
+
ref_type: RefType;
|
|
5
|
+
}>;
|
|
6
|
+
export declare function dbResolveLiabilityByRef<RefType extends string>(pgClient: PgClientInTransaction, { refType, refId, lockedBankrollId, }: {
|
|
7
|
+
refType: RefType;
|
|
8
|
+
refId: string;
|
|
9
|
+
lockedBankrollId: string;
|
|
10
|
+
}): Promise<boolean>;
|