@moneypot/hub 1.18.0-dev.2 → 1.18.0-dev.4

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 CHANGED
@@ -77,6 +77,14 @@ insert into hub.api_key default values returning key;
77
77
 
78
78
  You should always keep your hub server up to date as soon as possible. Never install an old, intermediate version: it may leave you without an automatic upgrade path.
79
79
 
80
+ ### 1.18.x
81
+
82
+ - `MakeOutcomeBetPlugin` replaced by `HubGameConfigPlugin({ outcomeBetConfigs,
83
+ customGameConfigs })`
84
+ - Added graphql query `hubRiskLimits(gameKinds: [DICE])` to get risk limits for
85
+ the game kinds configured in `HubGameConfigPlugin`.
86
+ - This lets experiences display the max payout for each kind of bet.
87
+
80
88
  ### 1.17.x
81
89
 
82
90
  - Postgres transactions now default to read committed (postgres default)
@@ -0,0 +1,7 @@
1
+ import { RiskPolicy } from "./risk-policy.js";
2
+ export type CustomGameConfig = {
3
+ riskPolicy: RiskPolicy;
4
+ };
5
+ export type CustomGameConfigMap<CustomGameKind extends string> = {
6
+ [kind in CustomGameKind]: CustomGameConfig;
7
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -17,22 +17,25 @@ declare global {
17
17
  }
18
18
  }
19
19
  }
20
- export { MakeOutcomeBetPlugin, type OutcomeBetConfigMap, type OutcomeBetConfig, } from "./plugins/hub-make-outcome-bet.js";
20
+ export { HubGameConfigPlugin, type OutcomeBetConfigMap, type OutcomeBetConfig, } from "./plugins/hub-game-config-plugin.js";
21
+ export { type CustomGameConfig, type CustomGameConfigMap, } from "./custom-game-config.js";
21
22
  export { validateRisk, type RiskPolicy, type RiskPolicyArgs, type RiskLimits, } from "./risk-policy.js";
22
23
  export type PluginContext = Grafast.Context;
23
- export { defaultPlugins, type PluginIdentity, type UserSessionContext, } from "./server/graphile.config.js";
24
+ export { defaultPlugins, type PluginIdentity, type UserSessionContext, type HubPlugin, } from "./server/graphile.config.js";
24
25
  export type ConfigureAppArgs = {
25
26
  app: Express;
26
27
  superuserPool: ServerContext["superuserPool"];
27
28
  };
29
+ import type { HubPlugin } from "./server/graphile.config.js";
28
30
  export type ServerOptions = {
29
31
  configureApp?: (args: ConfigureAppArgs) => void;
30
- plugins?: readonly GraphileConfig.Plugin[];
32
+ plugins?: readonly HubPlugin[];
31
33
  extraPgSchemas?: string[];
32
34
  exportSchemaSDLPath?: string;
33
35
  userDatabaseMigrationsPath?: string;
34
36
  enableChat?: boolean;
35
37
  enablePlayground?: boolean;
38
+ port?: number;
36
39
  };
37
40
  export declare function runMigrations(options: {
38
41
  userDatabaseMigrationsPath?: string;
package/dist/src/index.js CHANGED
@@ -6,7 +6,7 @@ import { initializeTransferProcessors } from "./process-transfers/index.js";
6
6
  import { join } from "path";
7
7
  import { logger } from "./logger.js";
8
8
  import { createServerContext, closeServerContext, } from "./context.js";
9
- export { MakeOutcomeBetPlugin, } from "./plugins/hub-make-outcome-bet.js";
9
+ export { HubGameConfigPlugin, } from "./plugins/hub-game-config-plugin.js";
10
10
  export { validateRisk, } from "./risk-policy.js";
11
11
  export { defaultPlugins, } from "./server/graphile.config.js";
12
12
  export async function runMigrations(options) {
@@ -28,6 +28,7 @@ export async function runMigrations(options) {
28
28
  pgClient,
29
29
  dirname: join(import.meta.dirname, "pg-versions"),
30
30
  schemaName: "hub_core_versions",
31
+ silent: process.env.NODE_ENV === "test",
31
32
  });
32
33
  }
33
34
  catch (e) {
@@ -43,6 +44,7 @@ export async function runMigrations(options) {
43
44
  pgClient,
44
45
  dirname: options.userDatabaseMigrationsPath,
45
46
  schemaName: "hub_user_versions",
47
+ silent: process.env.NODE_ENV === "test",
46
48
  });
47
49
  }
48
50
  }
@@ -71,7 +73,7 @@ async function initialize(options) {
71
73
  }
72
74
  export async function startAndListen(options) {
73
75
  if (options.plugins && options.plugins.some((p) => typeof p === "function")) {
74
- throw new Error("`plugins` should be an array of GraphileConfig.Plugin but one of the items is a function. Did you forget to call it?");
76
+ throw new Error("`plugins` should be an array of HubPlugin but one of the items is a function. Did you forget to call it?");
75
77
  }
76
78
  if (options.userDatabaseMigrationsPath &&
77
79
  !options.userDatabaseMigrationsPath.startsWith("/")) {
@@ -98,6 +100,7 @@ export async function startAndListen(options) {
98
100
  context,
99
101
  enableChat: options.enableChat ?? true,
100
102
  enablePlayground: options.enablePlayground ?? true,
103
+ port: options.port ?? config.PORT,
101
104
  });
102
105
  const gracefulShutdown = async ({ exit = true } = {}) => {
103
106
  if (isShuttingDown) {
@@ -128,9 +131,9 @@ export async function startAndListen(options) {
128
131
  process.on("SIGINT", gracefulShutdown);
129
132
  process.on("SIGTERM", gracefulShutdown);
130
133
  }
131
- return hubServer.listen().then(() => {
134
+ return hubServer.listen().then(({ port }) => {
132
135
  return {
133
- port: config.PORT,
136
+ port,
134
137
  stop: async () => {
135
138
  await gracefulShutdown({ exit: false });
136
139
  },
@@ -0,0 +1,15 @@
1
+ import { type OutcomeBetConfig, type OutcomeBetConfigMap } from "./hub-make-outcome-bet.js";
2
+ import { CustomGameConfigMap } from "../custom-game-config.js";
3
+ export type { OutcomeBetConfig, OutcomeBetConfigMap };
4
+ declare const HUB_GAME_CONFIG_PLUGIN_BRAND: unique symbol;
5
+ export type HubGameConfigPlugin = {
6
+ readonly [HUB_GAME_CONFIG_PLUGIN_BRAND]: true;
7
+ readonly outcomeBetConfigs: OutcomeBetConfigMap<string>;
8
+ readonly customGameConfigs?: CustomGameConfigMap<string>;
9
+ };
10
+ export declare function isHubGameConfigPlugin(p: unknown): p is HubGameConfigPlugin;
11
+ export declare function expandHubGameConfigPlugin(config: HubGameConfigPlugin): GraphileConfig.Plugin[];
12
+ export declare function HubGameConfigPlugin<OutcomeBetKind extends string, CustomGameKind extends string = never>({ outcomeBetConfigs, customGameConfigs, }: {
13
+ outcomeBetConfigs: OutcomeBetConfigMap<OutcomeBetKind>;
14
+ customGameConfigs?: CustomGameConfigMap<CustomGameKind>;
15
+ }): HubGameConfigPlugin;
@@ -0,0 +1,25 @@
1
+ import { MakeOutcomeBetPlugin, } from "./hub-make-outcome-bet.js";
2
+ import { HubRiskLimitsPlugin } from "./hub-risk-limits.js";
3
+ const HUB_GAME_CONFIG_PLUGIN_BRAND = Symbol("HubGameConfigPlugin");
4
+ export function isHubGameConfigPlugin(p) {
5
+ return (typeof p === "object" &&
6
+ p !== null &&
7
+ HUB_GAME_CONFIG_PLUGIN_BRAND in p &&
8
+ p[HUB_GAME_CONFIG_PLUGIN_BRAND] === true);
9
+ }
10
+ export function expandHubGameConfigPlugin(config) {
11
+ return [
12
+ MakeOutcomeBetPlugin({ outcomeBetConfigs: config.outcomeBetConfigs }),
13
+ HubRiskLimitsPlugin({
14
+ outcomeBetConfigs: config.outcomeBetConfigs,
15
+ customGameConfigs: config.customGameConfigs,
16
+ }),
17
+ ];
18
+ }
19
+ export function HubGameConfigPlugin({ outcomeBetConfigs, customGameConfigs, }) {
20
+ return {
21
+ [HUB_GAME_CONFIG_PLUGIN_BRAND]: true,
22
+ outcomeBetConfigs,
23
+ customGameConfigs,
24
+ };
25
+ }
@@ -33,10 +33,10 @@ export type OutcomeBetConfig = {
33
33
  initializeMetadataFromUntrustedUserInput?: (input: Input) => Result<Metadata, string>;
34
34
  finalizeMetadata?: (validatedMetadata: Metadata, data: FinalizeMetadataData) => Metadata;
35
35
  };
36
- export type OutcomeBetConfigMap<BetKind extends string> = {
37
- [betKind in BetKind]: OutcomeBetConfig;
36
+ export type OutcomeBetConfigMap<OutcomeBetKind extends string> = {
37
+ [kind in OutcomeBetKind]: OutcomeBetConfig;
38
38
  };
39
- export declare function MakeOutcomeBetPlugin<BetKind extends string>({ betConfigs }: {
40
- betConfigs: OutcomeBetConfigMap<BetKind>;
39
+ export declare function MakeOutcomeBetPlugin<OutcomeBetKind extends string>({ outcomeBetConfigs, }: {
40
+ outcomeBetConfigs: OutcomeBetConfigMap<OutcomeBetKind>;
41
41
  }): GraphileConfig.Plugin;
42
42
  export {};
@@ -2,7 +2,7 @@ import { access, context, object, ObjectStep, sideEffect, } from "postgraphile/g
2
2
  import { gql, extendSchema } from "postgraphile/utils";
3
3
  import * as z from "zod/v4";
4
4
  import { GraphQLError } from "graphql";
5
- import { DbHashKind, dbGetHouseBankrolls, dbLockHouseBankroll, dbLockPlayerBalance, exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
5
+ import { DbHashKind, dbLockHouseBankroll, dbLockPlayerBalance, exactlyOneRow, maybeOneRow, withPgPoolTransaction, } from "../db/index.js";
6
6
  import { assert } from "tsafe";
7
7
  import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chain.js";
8
8
  import { getIntermediateHash } from "../hash-chain/get-hash.js";
@@ -68,18 +68,19 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
68
68
  .function()
69
69
  .optional(),
70
70
  }));
71
- export function MakeOutcomeBetPlugin({ betConfigs }) {
72
- BetConfigsSchema.parse(betConfigs);
73
- const betKinds = Object.keys(betConfigs);
71
+ export function MakeOutcomeBetPlugin({ outcomeBetConfigs, }) {
72
+ BetConfigsSchema.parse(outcomeBetConfigs);
73
+ const outcomeBetKinds = Object.keys(outcomeBetConfigs);
74
+ const outcomeBetKindEnumValues = outcomeBetKinds.length > 0 ? outcomeBetKinds : ["_NONE"];
74
75
  return extendSchema((build) => {
75
76
  const outcomeBetTable = build.input.pgRegistry.pgResources.hub_outcome_bet;
76
77
  const typeDefs = gql `
77
- enum BetKind {
78
- ${betKinds.join("\n")}
78
+ enum OutcomeBetKind {
79
+ ${outcomeBetKindEnumValues.join("\n")}
79
80
  }
80
81
 
81
82
  input HubMakeOutcomeBetInput {
82
- kind: BetKind!
83
+ kind: OutcomeBetKind!
83
84
  wager: Int!
84
85
  currency: String!
85
86
  outcomes: [HubOutcomeInput!]!
@@ -106,70 +107,10 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
106
107
  extend type Mutation {
107
108
  hubMakeOutcomeBet(input: HubMakeOutcomeBetInput!): HubMakeOutcomeBetPayload
108
109
  }
109
-
110
- type HubRiskLimit {
111
- maxPayout: Float!
112
- maxWager: Float
113
- }
114
-
115
- type HubBulkRiskLimit {
116
- betKind: BetKind!
117
- currency: String!
118
- maxPayout: Float!
119
- maxWager: Float
120
- }
121
-
122
- extend type Query {
123
- hubRiskLimits(betKinds: [BetKind!]!): [HubBulkRiskLimit!]!
124
- }
125
110
  `;
126
111
  return {
127
112
  typeDefs,
128
113
  objects: {
129
- Query: {
130
- plans: {
131
- hubRiskLimits: (_, { $betKinds }) => {
132
- const $identity = context().get("identity");
133
- const $superuserPool = context().get("superuserPool");
134
- const $result = sideEffect([$identity, $superuserPool, $betKinds], async ([identity, superuserPool, inputBetKinds]) => {
135
- if (identity?.kind !== "user") {
136
- throw new GraphQLError("Unauthorized");
137
- }
138
- if (inputBetKinds.length > 5) {
139
- throw new GraphQLError("Maximum 5 bet kinds allowed");
140
- }
141
- if (new Set(inputBetKinds).size !== inputBetKinds.length) {
142
- throw new GraphQLError("Duplicate bet kinds not allowed");
143
- }
144
- for (const kind of inputBetKinds) {
145
- if (!betConfigs[kind]) {
146
- throw new GraphQLError(`Invalid bet kind: ${kind}`);
147
- }
148
- }
149
- const dbHouseBankrolls = await dbGetHouseBankrolls(superuserPool, {
150
- casinoId: identity.session.casino_id,
151
- });
152
- const limits = inputBetKinds.flatMap((betKind) => {
153
- const betConfig = betConfigs[betKind];
154
- return dbHouseBankrolls.map((bankroll) => {
155
- const riskLimits = betConfig.riskPolicy({
156
- type: "get-limits",
157
- currency: bankroll.currency_key,
158
- bankroll: bankroll.amount,
159
- });
160
- return {
161
- betKind,
162
- currency: bankroll.currency_key,
163
- ...riskLimits,
164
- };
165
- });
166
- });
167
- return limits;
168
- });
169
- return $result;
170
- },
171
- },
172
- },
173
114
  Mutation: {
174
115
  plans: {
175
116
  hubMakeOutcomeBet: (_, { $input }) => {
@@ -180,10 +121,10 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
180
121
  throw new GraphQLError("Unauthorized");
181
122
  }
182
123
  let input;
183
- let betKind;
124
+ let outcomeBetKind;
184
125
  try {
185
126
  input = InputSchema.parse(rawInput);
186
- betKind = BetKindSchema.parse(rawInput.kind);
127
+ outcomeBetKind = BetKindSchema.parse(rawInput.kind);
187
128
  }
188
129
  catch (e) {
189
130
  if (e instanceof z.ZodError) {
@@ -195,9 +136,9 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
195
136
  input.currency !== "HOUSE") {
196
137
  throw new GraphQLError("Playground users can only bet with HOUSE currency");
197
138
  }
198
- const betConfig = betConfigs[betKind];
139
+ const betConfig = outcomeBetConfigs[outcomeBetKind];
199
140
  if (!betConfig) {
200
- throw new GraphQLError(`Invalid bet kind`);
141
+ throw new GraphQLError(`Invalid outcome bet kind`);
201
142
  }
202
143
  if (!betConfig.allowLossBeyondWager) {
203
144
  const minProfit = Math.min(...input.outcomes.map((o) => o.profit));
@@ -508,17 +449,6 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
508
449
  },
509
450
  },
510
451
  },
511
- HubRiskLimit: {
512
- assertStep: ObjectStep,
513
- plans: {
514
- maxWager($data) {
515
- return access($data, "maxWager");
516
- },
517
- maxPayout($data) {
518
- return access($data, "maxPayout");
519
- },
520
- },
521
- },
522
452
  HubMakeOutcomeBetPayload: {
523
453
  assertStep: ObjectStep,
524
454
  plans: {
@@ -0,0 +1,6 @@
1
+ import { OutcomeBetConfigMap } from "./hub-make-outcome-bet.js";
2
+ import { CustomGameConfigMap } from "../custom-game-config.js";
3
+ export declare function HubRiskLimitsPlugin<OutcomeBetKind extends string, CustomGameKind extends string = never>({ outcomeBetConfigs, customGameConfigs, }: {
4
+ outcomeBetConfigs: OutcomeBetConfigMap<OutcomeBetKind>;
5
+ customGameConfigs?: CustomGameConfigMap<CustomGameKind>;
6
+ }): GraphileConfig.Plugin;
@@ -0,0 +1,128 @@
1
+ import { context, sideEffect, access } from "postgraphile/grafast";
2
+ import { gql, extendSchema } from "postgraphile/utils";
3
+ import { GraphQLError } from "graphql";
4
+ import { dbGetHouseBankrolls } from "../db/index.js";
5
+ export function HubRiskLimitsPlugin({ outcomeBetConfigs, customGameConfigs, }) {
6
+ const outcomeBetKinds = Object.keys(outcomeBetConfigs);
7
+ const customGameKinds = customGameConfigs
8
+ ? Object.keys(customGameConfigs)
9
+ : [];
10
+ if (customGameConfigs) {
11
+ const outcomeBetKindSet = new Set(outcomeBetKinds);
12
+ for (const kind of customGameKinds) {
13
+ if (outcomeBetKindSet.has(kind)) {
14
+ throw new Error(`Key collision: "${kind}" exists in both outcomeBetConfigs and customGameConfigs`);
15
+ }
16
+ }
17
+ }
18
+ const anyGameKinds = [...outcomeBetKinds, ...customGameKinds];
19
+ const anyGameKindEnumValues = anyGameKinds.length > 0 ? anyGameKinds : ["_NONE"];
20
+ const mergedConfigs = {};
21
+ for (const kind of outcomeBetKinds) {
22
+ mergedConfigs[kind] = outcomeBetConfigs[kind];
23
+ }
24
+ if (customGameConfigs) {
25
+ for (const kind of customGameKinds) {
26
+ mergedConfigs[kind] = customGameConfigs[kind];
27
+ }
28
+ }
29
+ return extendSchema((build) => {
30
+ const typeDefs = gql `
31
+ enum AnyGameKind {
32
+ ${anyGameKindEnumValues.join("\n")}
33
+ }
34
+
35
+ ${customGameKinds.length > 0
36
+ ? `enum CustomGameKind {
37
+ ${customGameKinds.join("\n")}
38
+ }`
39
+ : ""}
40
+
41
+ type HubRiskLimit {
42
+ maxPayout: Float!
43
+ }
44
+
45
+ type HubBulkRiskLimit {
46
+ gameKind: AnyGameKind!
47
+ currency: String!
48
+ maxPayout: Float!
49
+ }
50
+
51
+ extend type Query {
52
+ hubRiskLimits(gameKinds: [AnyGameKind!]!): [HubBulkRiskLimit!]!
53
+ }
54
+ `;
55
+ return {
56
+ typeDefs,
57
+ objects: {
58
+ Query: {
59
+ plans: {
60
+ hubRiskLimits: (_, { $gameKinds }) => {
61
+ const $identity = context().get("identity");
62
+ const $superuserPool = context().get("superuserPool");
63
+ const $result = sideEffect([$identity, $superuserPool, $gameKinds], async ([identity, superuserPool, inputGameKinds]) => {
64
+ if (identity?.kind !== "user") {
65
+ throw new GraphQLError("Unauthorized");
66
+ }
67
+ if (inputGameKinds.length > 10) {
68
+ throw new GraphQLError("Maximum 10 game kinds allowed");
69
+ }
70
+ if (new Set(inputGameKinds).size !== inputGameKinds.length) {
71
+ throw new GraphQLError("Duplicate game kinds not allowed");
72
+ }
73
+ for (const kind of inputGameKinds) {
74
+ if (kind === "_NONE" || !mergedConfigs[kind]) {
75
+ throw new GraphQLError(`Invalid game kind: ${kind}`);
76
+ }
77
+ }
78
+ if (anyGameKinds.length === 0) {
79
+ return [];
80
+ }
81
+ const dbHouseBankrolls = await dbGetHouseBankrolls(superuserPool, {
82
+ casinoId: identity.session.casino_id,
83
+ });
84
+ const limits = inputGameKinds.flatMap((gameKind) => {
85
+ const config = mergedConfigs[gameKind];
86
+ return dbHouseBankrolls.map((bankroll) => {
87
+ const riskLimits = config.riskPolicy({
88
+ type: "get-limits",
89
+ currency: bankroll.currency_key,
90
+ bankroll: bankroll.amount,
91
+ });
92
+ return {
93
+ gameKind,
94
+ currency: bankroll.currency_key,
95
+ maxPayout: riskLimits.maxPayout,
96
+ };
97
+ });
98
+ });
99
+ return limits;
100
+ });
101
+ return $result;
102
+ },
103
+ },
104
+ },
105
+ HubRiskLimit: {
106
+ plans: {
107
+ maxPayout($data) {
108
+ return access($data, "maxPayout");
109
+ },
110
+ },
111
+ },
112
+ HubBulkRiskLimit: {
113
+ plans: {
114
+ gameKind($data) {
115
+ return access($data, "gameKind");
116
+ },
117
+ currency($data) {
118
+ return access($data, "currency");
119
+ },
120
+ maxPayout($data) {
121
+ return access($data, "maxPayout");
122
+ },
123
+ },
124
+ },
125
+ },
126
+ };
127
+ }, "HubRiskLimitsPlugin");
128
+ }
@@ -1,7 +1 @@
1
- import * as pg from "pg";
2
- import { GraphQLClient } from "graphql-request";
3
- export declare function processWithdrawalRequests({ casinoId, graphqlClient, pool, }: {
4
- casinoId: string;
5
- graphqlClient: GraphQLClient;
6
- pool: pg.Pool;
7
- }): Promise<void>;
1
+ export {};
@@ -1,146 +1 @@
1
- import { assert } from "tsafe";
2
- import { TransferStatusKind } from "./__generated__/graphql.js";
3
- import { withPgPoolTransaction } from "./db/index.js";
4
- import { maybeOneRow, exactlyOneRow } from "./db/util.js";
5
- import { START_PENDING_EXPERIENCE_TRANSFER } from "./graphql-queries.js";
6
- import { logger } from "./logger.js";
7
- async function processWithdrawalRequest({ requestId, graphqlClient, pool, }) {
8
- return withPgPoolTransaction(pool, async (pgClient) => {
9
- const dbWithdrawalRequest = await pgClient
10
- .query({
11
- text: `
12
- select *
13
- from hub.withdrawal_request
14
- where id = $1
15
-
16
- FOR UPDATE
17
- `,
18
- values: [requestId],
19
- })
20
- .then(maybeOneRow);
21
- if (!dbWithdrawalRequest) {
22
- throw new Error("Withdrawal request not found");
23
- }
24
- if (dbWithdrawalRequest.mp_transfer_id) {
25
- return;
26
- }
27
- const dbUser = await pgClient
28
- .query({
29
- text: `select * from hub.user where id = $1`,
30
- values: [dbWithdrawalRequest.user_id],
31
- })
32
- .then(exactlyOneRow);
33
- const dbExperience = await pgClient
34
- .query({
35
- text: `select * from hub.experience where id = $1`,
36
- values: [dbWithdrawalRequest.experience_id],
37
- })
38
- .then(exactlyOneRow);
39
- const mpResult = await graphqlClient
40
- .request({
41
- document: START_PENDING_EXPERIENCE_TRANSFER,
42
- variables: {
43
- mpUserId: dbUser.mp_user_id,
44
- mpExperienceId: dbExperience.mp_experience_id,
45
- amount: dbWithdrawalRequest.amount,
46
- currency: dbWithdrawalRequest.currency_key,
47
- metadata: JSON.stringify({
48
- id: `withdrawal_request:${dbWithdrawalRequest.id}`,
49
- }),
50
- },
51
- })
52
- .then((res) => res.transferCurrencyExperienceToUser?.result);
53
- if (!mpResult?.__typename) {
54
- throw new Error(`Failed to start MP transfer: ${mpResult}`);
55
- }
56
- if (mpResult.__typename === "TakeRequestAlreadyTerminal") {
57
- if (mpResult.takeRequest.experienceTransfer) {
58
- const mpTransferId = mpResult.takeRequest.experienceTransfer.id;
59
- await pgClient.query({
60
- text: `
61
- update hub.withdrawal_request
62
- set mp_transfer_id = $2
63
- where id = $1
64
- `,
65
- values: [dbWithdrawalRequest.id, mpTransferId],
66
- });
67
- }
68
- }
69
- if (mpResult?.__typename !== "TransferSuccess" &&
70
- mpResult?.__typename !== "TransferMetadataIdExists") {
71
- throw new Error(`Failed to start MP transfer: ${mpResult}`);
72
- }
73
- const mpTransferId = mpResult.transfer.id;
74
- const mpStatus = mpResult.transfer.status;
75
- await pgClient
76
- .query({
77
- text: `
78
- update hub.withdrawal_request
79
- set mp_transfer_id = $2
80
- where id = $1
81
- `,
82
- values: [dbWithdrawalRequest.id, mpTransferId],
83
- })
84
- .then((res) => {
85
- assert(res.rowCount === 1, `Expected to update a hub.withdrawal_request.mp_transfer_id`);
86
- });
87
- if (mpStatus === TransferStatusKind.Pending ||
88
- mpStatus === TransferStatusKind.Completed) {
89
- }
90
- else {
91
- logger.warn(`Expected status to be PENDING or COMPLETED, but got ${mpStatus}`);
92
- return;
93
- }
94
- await pgClient.query({
95
- text: `
96
- insert into hub.withdrawal(
97
- id,
98
- casino_id,
99
- mp_transfer_id,
100
- user_id,
101
- experience_id,
102
- amount,
103
- currency_key,
104
- status,
105
- withdrawal_request_id
106
- )
107
- values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
108
- `,
109
- values: [
110
- dbWithdrawalRequest.id,
111
- dbWithdrawalRequest.casino_id,
112
- mpTransferId,
113
- dbWithdrawalRequest.user_id,
114
- dbWithdrawalRequest.experience_id,
115
- dbWithdrawalRequest.amount,
116
- dbWithdrawalRequest.currency_key,
117
- mpResult.transfer.status,
118
- dbWithdrawalRequest.id,
119
- ],
120
- });
121
- });
122
- }
123
- export async function processWithdrawalRequests({ casinoId, graphqlClient, pool, }) {
124
- const pendingRequests = await pool.query({
125
- text: `
126
- SELECT wr.*
127
- FROM hub.withdrawal_request wr
128
- WHERE wr.casino_id = $1
129
- AND wr.mp_transfer_id IS NULL
130
- LIMIT 10
131
- `,
132
- values: [casinoId],
133
- });
134
- for (const request of pendingRequests.rows) {
135
- try {
136
- await processWithdrawalRequest({
137
- requestId: request.id,
138
- graphqlClient,
139
- pool,
140
- });
141
- }
142
- catch (error) {
143
- logger.error(error, `Failed to process withdrawal request ${request.id}`);
144
- }
145
- }
146
- }
1
+ export {};
@@ -14,7 +14,6 @@ export type BetLimitPolicyArgs = {
14
14
  bankroll: number;
15
15
  };
16
16
  export type RiskLimits = {
17
- maxWager?: number;
18
17
  maxPayout: number;
19
18
  };
20
19
  export type RiskPolicy = (args: RiskPolicyArgs | BetLimitPolicyArgs) => RiskLimits;
@@ -2,7 +2,6 @@ import { formatCurrency } from "./format-currency.js";
2
2
  import { z } from "zod/v4";
3
3
  const RiskLimitsSchema = z
4
4
  .object({
5
- maxWager: z.number().positive().optional(),
6
5
  maxPayout: z.number().positive(),
7
6
  })
8
7
  .strict();
@@ -21,20 +20,6 @@ export function validateRisk(options) {
21
20
  error: { message, riskLimits: options.riskLimits },
22
21
  };
23
22
  }
24
- if (options.riskLimits.maxWager !== undefined &&
25
- wager > options.riskLimits.maxWager) {
26
- const message = `Wager (${formatCurrency(wager, {
27
- displayUnitName: options.displayUnitName,
28
- displayUnitScale: options.displayUnitScale,
29
- })}) exceeds limit (${formatCurrency(options.riskLimits.maxWager, {
30
- displayUnitName: options.displayUnitName,
31
- displayUnitScale: options.displayUnitScale,
32
- })})`;
33
- return {
34
- ok: false,
35
- error: { message, riskLimits: options.riskLimits },
36
- };
37
- }
38
23
  if (maxPotentialPayout > options.riskLimits.maxPayout) {
39
24
  const message = `Payout (${formatCurrency(maxPotentialPayout, {
40
25
  displayUnitName: options.displayUnitName,
@@ -27,8 +27,10 @@ export type OurPgSettings = {
27
27
  };
28
28
  export declare const requiredPlugins: readonly GraphileConfig.Plugin[];
29
29
  export declare const defaultPlugins: readonly GraphileConfig.Plugin[];
30
+ import { HubGameConfigPlugin } from "../plugins/hub-game-config-plugin.js";
31
+ export type HubPlugin = GraphileConfig.Plugin | HubGameConfigPlugin;
30
32
  export declare function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, }: {
31
- plugins: readonly GraphileConfig.Plugin[];
33
+ plugins: readonly HubPlugin[];
32
34
  exportSchemaSDLPath?: string;
33
35
  extraPgSchemas: string[];
34
36
  abortSignal: AbortSignal;
@@ -56,6 +56,7 @@ export const defaultPlugins = [
56
56
  HubPreimageHashFieldPlugin,
57
57
  customPgOmitArchivedPlugin("deleted"),
58
58
  ];
59
+ import { HubGameConfigPlugin, isHubGameConfigPlugin, expandHubGameConfigPlugin, } from "../plugins/hub-game-config-plugin.js";
59
60
  export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, }) {
60
61
  if (exportSchemaSDLPath) {
61
62
  if (!exportSchemaSDLPath.startsWith("/")) {
@@ -66,9 +67,20 @@ export function createPreset({ plugins, exportSchemaSDLPath, extraPgSchemas, abo
66
67
  }
67
68
  logger.info(`Will save generated graphql schema to ${exportSchemaSDLPath}`);
68
69
  }
69
- const mutablePlugins = [...plugins];
70
+ const pluginsWithDefault = plugins.some(isHubGameConfigPlugin)
71
+ ? plugins
72
+ : [...plugins, HubGameConfigPlugin({ outcomeBetConfigs: {} })];
73
+ const mutablePlugins = [];
74
+ for (const p of pluginsWithDefault) {
75
+ if (isHubGameConfigPlugin(p)) {
76
+ mutablePlugins.push(...expandHubGameConfigPlugin(p));
77
+ }
78
+ else {
79
+ mutablePlugins.push(p);
80
+ }
81
+ }
70
82
  for (const requiredPlugin of requiredPlugins) {
71
- if (!plugins.some((plugin) => plugin === requiredPlugin)) {
83
+ if (!mutablePlugins.some((plugin) => plugin === requiredPlugin)) {
72
84
  logger.warn(`Adding required plugin "${requiredPlugin.name}"`);
73
85
  mutablePlugins.unshift(requiredPlugin);
74
86
  }
@@ -1,12 +1,15 @@
1
1
  import { ServerOptions } from "../index.js";
2
2
  import { ServerContext } from "../context.js";
3
3
  export type HubServer = {
4
- listen: () => Promise<void>;
4
+ listen: () => Promise<{
5
+ port: number;
6
+ }>;
5
7
  shutdown: () => Promise<void>;
6
8
  };
7
- export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, }: Pick<ServerOptions, "plugins" | "exportSchemaSDLPath" | "extraPgSchemas" | "configureApp"> & {
9
+ export declare function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, port, }: Pick<ServerOptions, "plugins" | "exportSchemaSDLPath" | "extraPgSchemas" | "configureApp"> & {
8
10
  abortSignal: AbortSignal;
9
11
  context: ServerContext;
10
12
  enableChat: boolean;
11
13
  enablePlayground: boolean;
14
+ port: number;
12
15
  }): HubServer;
@@ -3,7 +3,6 @@ import { grafserv } from "postgraphile/grafserv/express/v4";
3
3
  import postgraphile from "postgraphile";
4
4
  import { createPreset, defaultPlugins } from "./graphile.config.js";
5
5
  import express from "express";
6
- import * as config from "../config.js";
7
6
  import { logger } from "../logger.js";
8
7
  import cors from "./middleware/cors.js";
9
8
  import authentication from "./middleware/authentication.js";
@@ -45,7 +44,7 @@ function createExpressServer(context) {
45
44
  });
46
45
  return app;
47
46
  }
48
- export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, }) {
47
+ export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, extraPgSchemas, abortSignal, context, enableChat, enablePlayground, port, }) {
49
48
  const expressServer = createExpressServer(context);
50
49
  const preset = createPreset({
51
50
  plugins: plugins ?? defaultPlugins,
@@ -72,7 +71,11 @@ export function createHubServer({ configureApp, plugins, exportSchemaSDLPath, ex
72
71
  return {
73
72
  listen: () => {
74
73
  return new Promise((resolve) => {
75
- nodeServer.listen(config.PORT, resolve);
74
+ nodeServer.listen(port, () => {
75
+ const addr = nodeServer.address();
76
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
77
+ resolve({ port: actualPort });
78
+ });
76
79
  });
77
80
  },
78
81
  shutdown: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneypot/hub",
3
- "version": "1.18.0-dev.2",
3
+ "version": "1.18.0-dev.4",
4
4
  "author": "moneypot.com",
5
5
  "homepage": "https://moneypot.com/hub",
6
6
  "keywords": [
@@ -51,7 +51,7 @@
51
51
  "dependencies": {
52
52
  "@graphile-contrib/pg-omit-archived": "^4.0.0-beta.4",
53
53
  "@moneypot/hash-herald": "^1.0.0",
54
- "@moneypot/pg-upgrade-schema": "^2.0.4",
54
+ "@moneypot/pg-upgrade-schema": "^2.1.0",
55
55
  "@noble/curves": "^1.5.0",
56
56
  "dotenv": "^16.4.5",
57
57
  "express": "^5.0.1",