@moneypot/hub 1.3.0-dev.9 → 1.4.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 CHANGED
@@ -73,3 +73,9 @@ You'll need an api key from your hub database:
73
73
  ```sql
74
74
  insert into hub.api_key default values returning key;
75
75
  ```
76
+
77
+ ## Changelog
78
+
79
+ ### 1.4.0
80
+
81
+ - Added `hubPutAlert` subscription
@@ -2,6 +2,7 @@ import * as pg from "pg";
2
2
  import stream from "node:stream";
3
3
  import { DbCasino, DbExperience, DbSession, DbTransferStatusKind, DbUser, DbWithdrawal } from "./types.js";
4
4
  import { TransferStatusKind } from "../__generated__/graphql.js";
5
+ export * from "../hash-chain/db-hash-chain.js";
5
6
  export * from "./types.js";
6
7
  export * from "./public.js";
7
8
  export * from "./util.js";
@@ -5,6 +5,7 @@ import { exactlyOneRow, maybeOneRow } from "./util.js";
5
5
  import { logger } from "../logger.js";
6
6
  import { setTimeout } from "node:timers/promises";
7
7
  import { assert } from "tsafe";
8
+ export * from "../hash-chain/db-hash-chain.js";
8
9
  export * from "./types.js";
9
10
  export * from "./public.js";
10
11
  export * from "./util.js";
@@ -1,12 +1,12 @@
1
1
  import { DbCasino, DbExperience, DbHash, DbHashChain, DbUser } from "../db/types.js";
2
2
  import { PgClientInTransaction } from "../db/index.js";
3
- export declare function dbLockHashChain(pgClient: PgClientInTransaction, { userId, experienceId, casinoId, hashChainId, }: {
3
+ export declare function dbLockHubHashChain(pgClient: PgClientInTransaction, { userId, experienceId, casinoId, hashChainId, }: {
4
4
  userId: DbUser["id"];
5
5
  experienceId: DbExperience["id"];
6
6
  casinoId: DbCasino["id"];
7
7
  hashChainId: DbHashChain["id"];
8
8
  }): Promise<DbHashChain | null>;
9
- export declare function dbInsertHash(pgClient: PgClientInTransaction, { hashChainId, kind, digest, iteration, metadata, }: {
9
+ export declare function dbInsertHubHash(pgClient: PgClientInTransaction, { hashChainId, kind, digest, iteration, metadata, }: {
10
10
  hashChainId: DbHashChain["id"];
11
11
  kind: DbHash["kind"];
12
12
  digest: DbHash["digest"];
@@ -1,7 +1,7 @@
1
1
  import { exactlyOneRow, maybeOneRow, } from "../db/index.js";
2
2
  import { assert } from "tsafe";
3
- export async function dbLockHashChain(pgClient, { userId, experienceId, casinoId, hashChainId, }) {
4
- assert(pgClient._inTransaction, "dbLockHashChain must be called in a transaction");
3
+ export async function dbLockHubHashChain(pgClient, { userId, experienceId, casinoId, hashChainId, }) {
4
+ assert(pgClient._inTransaction, "dbLockHubHashChain must be called in a transaction");
5
5
  return pgClient
6
6
  .query(`
7
7
  SELECT *
@@ -17,7 +17,7 @@ export async function dbLockHashChain(pgClient, { userId, experienceId, casinoId
17
17
  .then(maybeOneRow)
18
18
  .then((row) => row ?? null);
19
19
  }
20
- export async function dbInsertHash(pgClient, { hashChainId, kind, digest, iteration, metadata = {}, }) {
20
+ export async function dbInsertHubHash(pgClient, { hashChainId, kind, digest, iteration, metadata = {}, }) {
21
21
  assert(pgClient._inTransaction, "dbInsertHash must be called in a transaction");
22
22
  return pgClient
23
23
  .query(`
@@ -0,0 +1,22 @@
1
+ -- Let experience know when user successfully puts money into their balance
2
+ -- A put is a successful deposit
3
+
4
+ CREATE OR REPLACE FUNCTION notify_put() RETURNS TRIGGER AS $$
5
+ BEGIN
6
+ PERFORM pg_notify('hub:user:' || NEW.user_id || ':put',
7
+ json_build_object(
8
+ 'currency_key', NEW.currency_key,
9
+ 'experience_id', NEW.experience_id,
10
+ 'mp_transfer_id', NEW.mp_transfer_id
11
+ )::text);
12
+ RETURN NEW;
13
+ END;
14
+ $$ LANGUAGE plpgsql;
15
+
16
+ DROP TRIGGER IF EXISTS notify_put_trigger ON hub.deposit;
17
+
18
+ CREATE TRIGGER notify_put_trigger
19
+ AFTER INSERT ON hub.deposit
20
+ FOR EACH ROW
21
+ EXECUTE FUNCTION notify_put();
22
+
@@ -4,11 +4,11 @@ declare const OutcomeSchema: z.ZodObject<{
4
4
  weight: z.ZodNumber;
5
5
  profit: z.ZodNumber;
6
6
  }, "strict", z.ZodTypeAny, {
7
- weight: number;
8
7
  profit: number;
9
- }, {
10
8
  weight: number;
9
+ }, {
11
10
  profit: number;
11
+ weight: number;
12
12
  }>;
13
13
  declare const InputSchema: z.ZodObject<{
14
14
  kind: z.ZodString;
@@ -18,53 +18,53 @@ declare const InputSchema: z.ZodObject<{
18
18
  weight: z.ZodNumber;
19
19
  profit: z.ZodNumber;
20
20
  }, "strict", z.ZodTypeAny, {
21
- weight: number;
22
21
  profit: number;
23
- }, {
24
22
  weight: number;
23
+ }, {
25
24
  profit: number;
26
- }>, "many">, {
27
25
  weight: number;
26
+ }>, "many">, {
28
27
  profit: number;
29
- }[], {
30
28
  weight: number;
29
+ }[], {
31
30
  profit: number;
32
- }[]>, {
33
31
  weight: number;
32
+ }[]>, {
34
33
  profit: number;
35
- }[], {
36
34
  weight: number;
35
+ }[], {
37
36
  profit: number;
37
+ weight: number;
38
38
  }[]>;
39
39
  hashChainId: z.ZodString;
40
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
40
+ metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
41
41
  }, "strict", z.ZodTypeAny, {
42
42
  currency: string;
43
- kind: string;
44
43
  hashChainId: string;
44
+ kind: string;
45
45
  wager: number;
46
46
  outcomes: {
47
- weight: number;
48
47
  profit: number;
48
+ weight: number;
49
49
  }[];
50
- metadata?: Record<string, any> | undefined;
50
+ metadata?: Record<string, unknown> | undefined;
51
51
  }, {
52
52
  currency: string;
53
- kind: string;
54
53
  hashChainId: string;
54
+ kind: string;
55
55
  wager: number;
56
56
  outcomes: {
57
- weight: number;
58
57
  profit: number;
58
+ weight: number;
59
59
  }[];
60
- metadata?: Record<string, any> | undefined;
60
+ metadata?: Record<string, unknown> | undefined;
61
61
  }>;
62
62
  type Input = z.infer<typeof InputSchema>;
63
- type InputWithoutMetadata = Omit<Input, "metadata"> & {
64
- metadata?: never;
65
- };
66
63
  type Outcome = z.infer<typeof OutcomeSchema>;
64
+ type Metadata = NonNullable<Input["metadata"]>;
67
65
  type FinalizeMetadataData = {
66
+ wager: number;
67
+ currencyKey: string;
68
68
  clientSeed: string;
69
69
  hash: Uint8Array;
70
70
  outcomes: Outcome[];
@@ -73,8 +73,8 @@ type FinalizeMetadataData = {
73
73
  export type OutcomeBetConfig = {
74
74
  houseEdge: number;
75
75
  saveOutcomes: boolean;
76
- validateUntrustedMetadata?: (input: Input) => Result<Record<string, any>, string>;
77
- finalizeMetadata?: (input: InputWithoutMetadata, validatedMetadata: Record<string, any>, data: FinalizeMetadataData) => Record<string, any>;
76
+ initializeMetadataFromUntrustedUserInput?: (input: Input) => Result<Metadata, string>;
77
+ finalizeMetadata?: (validatedMetadata: Metadata, data: FinalizeMetadataData) => Metadata;
78
78
  };
79
79
  export type OutcomeBetConfigMap<BetKind extends string> = {
80
80
  [betKind in BetKind]: OutcomeBetConfig;
@@ -4,8 +4,9 @@ import { z } from "zod";
4
4
  import { GraphQLError } from "graphql";
5
5
  import { DbHashKind, exactlyOneRow, maybeOneRow, superuserPool, withPgPoolTransaction, } from "../db/index.js";
6
6
  import { assert } from "tsafe";
7
- import { dbInsertHash, dbLockHashChain } from "../hash-chain/db-hash-chain.js";
7
+ import { dbInsertHubHash, dbLockHubHashChain, } from "../hash-chain/db-hash-chain.js";
8
8
  import { getIntermediateHash, getPreimageHash, } from "../hash-chain/get-hash.js";
9
+ import { createHmac } from "node:crypto";
9
10
  const FLOAT_EPSILON = 1e-10;
10
11
  function sum(ns) {
11
12
  return ns.reduce((a, b) => a + b, 0);
@@ -38,7 +39,7 @@ const InputSchema = z
38
39
  .refine((data) => data.some((o) => o.profit < 0), "At least one outcome should have profit < 0")
39
40
  .refine((data) => data.some((o) => o.profit > 0), "At least one outcome should have profit > 0"),
40
41
  hashChainId: z.string().uuid("Invalid hash chain ID"),
41
- metadata: z.record(z.string(), z.any()).optional(),
42
+ metadata: z.record(z.string(), z.unknown()).optional(),
42
43
  })
43
44
  .strict();
44
45
  const BetKindSchema = z
@@ -52,7 +53,7 @@ const BetConfigsSchema = z.record(BetKindSchema, z.object({
52
53
  .gte(0, "House edge must be >= 0")
53
54
  .lte(1, "House edge must be <= 1"),
54
55
  saveOutcomes: z.boolean(),
55
- validateUntrustedMetadata: z
56
+ initializeMetadataFromUntrustedUserInput: z
56
57
  .function()
57
58
  .optional(),
58
59
  finalizeMetadata: z
@@ -78,11 +79,11 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
78
79
  metadata: JSON
79
80
  }
80
81
 
81
- type HubMakeOutcomeBetOk {
82
+ type HubMakeOutcomeBetSuccess {
82
83
  bet: HubOutcomeBet!
83
84
  }
84
85
 
85
- union HubMakeOutcomeBetResult = HubMakeOutcomeBetOk | HubBadHashChainError
86
+ union HubMakeOutcomeBetResult = HubMakeOutcomeBetSuccess | HubBadHashChainError
86
87
 
87
88
  type HubMakeOutcomeBetPayload {
88
89
  result: HubMakeOutcomeBetResult!
@@ -118,17 +119,14 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
118
119
  if (!betConfig) {
119
120
  throw new GraphQLError(`Invalid bet kind`);
120
121
  }
121
- if (!betKinds.includes(rawInput.kind)) {
122
- throw new GraphQLError(`Invalid bet kind`);
123
- }
124
- let validatedMetadata;
125
- if (betConfig.validateUntrustedMetadata) {
126
- const result = betConfig.validateUntrustedMetadata(input);
122
+ let initializedMetadata;
123
+ if (betConfig.initializeMetadataFromUntrustedUserInput) {
124
+ const result = betConfig.initializeMetadataFromUntrustedUserInput(input);
127
125
  if (result.ok) {
128
- validatedMetadata = result.value;
126
+ initializedMetadata = result.value;
129
127
  }
130
128
  else {
131
- throw new GraphQLError(`Invalid metadata: ${result.error}`);
129
+ throw new GraphQLError(`Invalid input: ${result.error}`);
132
130
  }
133
131
  }
134
132
  const houseEV = calculateHouseEV(input.outcomes);
@@ -181,7 +179,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
181
179
  if (maxPotentialPayout > maxAllowablePayout) {
182
180
  throw new GraphQLError(`House risk limit exceeded. Max payout: ${maxPotentialPayout.toFixed(4)}`);
183
181
  }
184
- const dbHashChain = await dbLockHashChain(pgClient, {
182
+ const dbHashChain = await dbLockHubHashChain(pgClient, {
185
183
  userId: session.user_id,
186
184
  experienceId: session.experience_id,
187
185
  casinoId: session.casino_id,
@@ -225,7 +223,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
225
223
  throw new Error(`Unknown bet hash result: ${_exhaustiveCheck}`);
226
224
  }
227
225
  }
228
- await dbInsertHash(pgClient, {
226
+ await dbInsertHubHash(pgClient, {
229
227
  hashChainId: dbHashChain.id,
230
228
  kind: DbHashKind.INTERMEDIATE,
231
229
  digest: betHashResult.hash,
@@ -239,7 +237,18 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
239
237
  if (result.rowCount !== 1) {
240
238
  throw new GraphQLError("Failed to update hash chain iteration");
241
239
  }
242
- const { outcome, outcomeIdx } = pickRandomOutcome(input.outcomes, betHashResult.hash);
240
+ const finalHash = (() => {
241
+ const serverHash = betHashResult.hash;
242
+ const clientSeed = dbHashChain.client_seed;
243
+ const finalHash = createHmac("sha256", serverHash)
244
+ .update(clientSeed)
245
+ .digest();
246
+ return finalHash;
247
+ })();
248
+ const { outcome, outcomeIdx } = pickRandomOutcome({
249
+ outcomes: input.outcomes,
250
+ hash: finalHash,
251
+ });
243
252
  const netPlayerAmount = input.wager * outcome.profit;
244
253
  await pgClient.query({
245
254
  text: `
@@ -275,14 +284,16 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
275
284
  ],
276
285
  });
277
286
  const immutableData = structuredClone({
287
+ wager: input.wager,
288
+ currencyKey: dbCurrency.key,
278
289
  clientSeed: dbHashChain.client_seed,
279
290
  hash: betHashResult.hash,
280
291
  outcomes: input.outcomes,
281
292
  outcomeIdx,
282
293
  });
283
294
  const finalizedMetadata = betConfig.finalizeMetadata
284
- ? betConfig.finalizeMetadata(withoutKey(input, "metadata"), validatedMetadata, immutableData)
285
- : validatedMetadata;
295
+ ? betConfig.finalizeMetadata(initializedMetadata, immutableData)
296
+ : initializedMetadata;
286
297
  const newBet = {
287
298
  kind: rawInput.kind,
288
299
  wager: input.wager,
@@ -338,7 +349,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
338
349
  })
339
350
  .then(exactlyOneRow);
340
351
  return {
341
- __typename: "HubMakeOutcomeBetOk",
352
+ __typename: "HubMakeOutcomeBetSuccess",
342
353
  betId: bet.id,
343
354
  };
344
355
  });
@@ -348,7 +359,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
348
359
  });
349
360
  },
350
361
  },
351
- HubMakeOutcomeBetOk: {
362
+ HubMakeOutcomeBetSuccess: {
352
363
  __assertStep: ObjectStep,
353
364
  bet($data) {
354
365
  const $betId = access($data, "betId");
@@ -360,7 +371,7 @@ export function MakeOutcomeBetPlugin({ betConfigs }) {
360
371
  result($data) {
361
372
  const $result = $data.get("result");
362
373
  return polymorphicBranch($result, {
363
- HubMakeOutcomeBetOk: {},
374
+ HubMakeOutcomeBetSuccess: {},
364
375
  HubBadHashChainError: {},
365
376
  });
366
377
  },
@@ -375,7 +386,7 @@ function normalizeHash(hash) {
375
386
  const uint32Value = view.getUint32(0, false);
376
387
  return uint32Value / Math.pow(2, 32);
377
388
  }
378
- function pickRandomOutcome(outcomes, hash) {
389
+ function pickRandomOutcome({ outcomes, hash, }) {
379
390
  assert(outcomes.length >= 2, "Outcome count must be >= 2");
380
391
  const totalWeight = sum(outcomes.map((o) => o.weight));
381
392
  const outcomesWithProbability = outcomes.map((o) => ({
@@ -449,7 +460,7 @@ async function finishHashChainInBackground({ hashChainId, }) {
449
460
  iteration: 0,
450
461
  });
451
462
  await withPgPoolTransaction(superuserPool, async (pgClient) => {
452
- await dbInsertHash(pgClient, {
463
+ await dbInsertHubHash(pgClient, {
453
464
  hashChainId,
454
465
  kind: DbHashKind.PREIMAGE,
455
466
  digest: preimageHashResult.hash,
@@ -473,7 +484,3 @@ async function finishHashChainInBackground({ hashChainId, }) {
473
484
  });
474
485
  }
475
486
  }
476
- function withoutKey(obj, key) {
477
- const { [key]: _, ...rest } = obj;
478
- return rest;
479
- }
@@ -0,0 +1,18 @@
1
+ import { GraphQLInputFieldConfig } from "graphql";
2
+ export declare const HubOutcomeInputNonNullFieldsPlugin: {
3
+ name: string;
4
+ version: string;
5
+ description: string;
6
+ schema: {
7
+ hooks: {
8
+ GraphQLInputObjectType_fields_field: (field: GraphQLInputFieldConfig, build: any, context: any) => {
9
+ type: any;
10
+ description?: import("graphql/jsutils/Maybe.js").Maybe<string>;
11
+ defaultValue?: unknown;
12
+ deprecationReason?: import("graphql/jsutils/Maybe.js").Maybe<string>;
13
+ extensions?: import("graphql/jsutils/Maybe.js").Maybe<Readonly<import("graphql").GraphQLInputFieldExtensions>>;
14
+ astNode?: import("graphql/jsutils/Maybe.js").Maybe<import("graphql").InputValueDefinitionNode>;
15
+ };
16
+ };
17
+ };
18
+ };
@@ -0,0 +1,20 @@
1
+ export const HubOutcomeInputNonNullFieldsPlugin = {
2
+ name: "HubOutcomeInputNonNullFieldsPlugin",
3
+ version: "0.0.0",
4
+ description: "Specifies that HubOutcomeInput fields are non-null",
5
+ schema: {
6
+ hooks: {
7
+ GraphQLInputObjectType_fields_field: (field, build, context) => {
8
+ const { scope: { fieldName }, } = context;
9
+ if (context.scope.pgCodec?.name === "hubOutcome" &&
10
+ ["profit", "weight"].includes(fieldName)) {
11
+ return {
12
+ ...field,
13
+ type: new build.graphql.GraphQLNonNull(field.type),
14
+ };
15
+ }
16
+ return field;
17
+ },
18
+ },
19
+ },
20
+ };
@@ -0,0 +1 @@
1
+ export declare const HubPutAlertPlugin: GraphileConfig.Plugin;
@@ -0,0 +1,56 @@
1
+ import { context, lambda } from "postgraphile/grafast";
2
+ import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
3
+ import { jsonParse } from "postgraphile/@dataplan/json";
4
+ import { listenWithFilter } from "./listen-with-filter.js";
5
+ export const HubPutAlertPlugin = makeExtendSchemaPlugin(() => {
6
+ return {
7
+ typeDefs: gql `
8
+ extend type Subscription {
9
+ hubPutAlert: HubPutAlertPayload
10
+ }
11
+
12
+ type HubPutAlertPayload {
13
+ currencyKey: String!
14
+ mpTransferId: UUID!
15
+ }
16
+ `,
17
+ plans: {
18
+ Subscription: {
19
+ hubPutAlert: {
20
+ subscribePlan(_$root) {
21
+ const $pgSubscriber = context().get("pgSubscriber");
22
+ const $identity = context().get("identity");
23
+ const $channelKey = lambda($identity, (identity) => {
24
+ if (identity?.kind === "user") {
25
+ return `hub:user:${identity.session.user_id}:put`;
26
+ }
27
+ else {
28
+ return "";
29
+ }
30
+ });
31
+ return listenWithFilter($pgSubscriber, $channelKey, jsonParse, $identity, (identity, item) => {
32
+ if (identity?.kind !== "user") {
33
+ return false;
34
+ }
35
+ else {
36
+ return (identity.session.experience_id ===
37
+ JSON.parse(item).experience_id);
38
+ }
39
+ });
40
+ },
41
+ plan($event) {
42
+ return $event;
43
+ },
44
+ },
45
+ },
46
+ HubPutAlertPayload: {
47
+ currencyKey($event) {
48
+ return $event.get("currency_key");
49
+ },
50
+ mpTransferId($event) {
51
+ return $event.get("mp_transfer_id");
52
+ },
53
+ },
54
+ },
55
+ };
56
+ });
@@ -1,55 +1,30 @@
1
- import { access, context, loadOne, object, } from "postgraphile/grafast";
1
+ import { access, constant, context } from "postgraphile/grafast";
2
2
  import { gql, makeExtendSchemaPlugin } from "postgraphile/utils";
3
- import { superuserPool } from "../db/index.js";
4
- import { pgSelectSingleFromRecord, } from "postgraphile/@dataplan/pg";
3
+ import { TYPES } from "postgraphile/@dataplan/pg";
4
+ import { sql } from "postgraphile/pg-sql2";
5
5
  export const HubUserBalanceByCurrencyPlugin = makeExtendSchemaPlugin((build) => {
6
- const balances = build.input.pgRegistry.pgResources.hub_balance;
6
+ const balanceTable = build.input.pgRegistry.pgResources.hub_balance;
7
7
  return {
8
8
  typeDefs: gql `
9
9
  extend type HubUser {
10
- balanceByCurrency(currency: String!): HubBalance
10
+ hubBalanceByCurrency(currency: String!): HubBalance
11
11
  }
12
12
  `,
13
13
  plans: {
14
14
  HubUser: {
15
- balanceByCurrency: ($record, { $currency }) => {
15
+ hubBalanceByCurrency: ($record, { $currency }) => {
16
16
  const $identity = context().get("identity");
17
- const $params = object({
18
- currency: $currency,
19
- targetUserId: $record.get("id"),
20
- casino_id: access($identity, ["session", "casino_id"]),
21
- experience_id: access($identity, ["session", "experience_id"]),
22
- });
23
- const $balance = loadOne($params, batchGetUserBalanceByCurrency);
24
- return pgSelectSingleFromRecord(balances, $balance);
17
+ const $balances = balanceTable.find();
18
+ $balances.where(sql `
19
+ ${$balances}.currency_key = ${$balances.placeholder($currency, TYPES.text)}
20
+ AND ${$balances}.user_id = ${$balances.placeholder($record.get("id"), TYPES.uuid)}
21
+ AND ${$balances}.casino_id = ${$balances.placeholder(access($identity, ["session", "casino_id"]), TYPES.uuid)}
22
+ AND ${$balances}.experience_id = ${$balances.placeholder(access($identity, ["session", "experience_id"]), TYPES.uuid)}
23
+ `);
24
+ $balances.setFirst(constant(1));
25
+ return $balances.single();
25
26
  },
26
27
  },
27
28
  },
28
29
  };
29
30
  });
30
- async function batchGetUserBalanceByCurrency(paramsArray) {
31
- const values = [];
32
- const valuePlaceholders = [];
33
- paramsArray.forEach((p, index) => {
34
- const baseIndex = index * 4 + 1;
35
- valuePlaceholders.push(`($${baseIndex}, $${baseIndex + 1}::uuid, $${baseIndex + 2}::uuid, $${baseIndex + 3}::uuid)`);
36
- values.push(p.currency, p.targetUserId, p.casino_id, p.experience_id);
37
- });
38
- const sql = `
39
- SELECT b.*
40
- FROM hub.balance b
41
- JOIN (
42
- VALUES
43
- ${valuePlaceholders.join(",\n ")}
44
- ) AS vals(currency_key, user_id, casino_id, experience_id)
45
- ON b.currency_key = vals.currency_key
46
- AND b.user_id = vals.user_id
47
- AND b.casino_id = vals.casino_id
48
- AND b.experience_id = vals.experience_id
49
- `;
50
- const { rows } = await superuserPool.query(sql, values);
51
- return paramsArray.map((p) => rows.find((row) => row.currency_key === p.currency &&
52
- row.user_id === p.targetUserId &&
53
- row.casino_id === p.casino_id &&
54
- row.experience_id === p.experience_id));
55
- }
@@ -0,0 +1,22 @@
1
+ import { __ItemStep, ExecutionDetails, GrafastResultStreamList } from "postgraphile/grafast";
2
+ import { GrafastSubscriber } from "postgraphile/grafast";
3
+ import { Step } from "postgraphile/grafast";
4
+ export declare class ListenWithFilterStep<TTopics extends {
5
+ [topic: string]: any;
6
+ }, TTopic extends keyof TTopics, TPayloadStep extends Step, TFilterInput> extends Step<TTopics[TTopic][]> {
7
+ itemPlan: (itemPlan: __ItemStep<TTopics[TTopic]>) => TPayloadStep;
8
+ filterFn: (filterInput: TFilterInput, item: any) => boolean;
9
+ static $$export: {
10
+ moduleName: string;
11
+ exportName: string;
12
+ };
13
+ isSyncAndSafe: boolean;
14
+ private pubsubDep;
15
+ private topicDep;
16
+ private filterInputDep;
17
+ constructor(pubsubOrPlan: Step<GrafastSubscriber<TTopics> | null> | GrafastSubscriber<TTopics> | null, topicOrPlan: Step<TTopic> | string, itemPlan: ((itemPlan: __ItemStep<TTopics[TTopic]>) => TPayloadStep) | undefined, filterInputPlan: Step<TFilterInput>, filterFn: (filterInput: TFilterInput, item: any) => boolean);
18
+ execute({ indexMap, values, stream, }: ExecutionDetails<readonly [GrafastSubscriber<TTopics>, TTopic]>): GrafastResultStreamList<TTopics[TTopic]>;
19
+ }
20
+ export declare function listenWithFilter<TTopics extends {
21
+ [topic: string]: any;
22
+ }, TTopic extends keyof TTopics, TPayloadStep extends Step, TFilterInput>(pubsubOrPlan: Step<GrafastSubscriber<TTopics> | null> | GrafastSubscriber<TTopics> | null, topicOrPlan: Step<TTopic> | string, itemPlan: ((itemPlan: __ItemStep<TTopics[TTopic]>) => TPayloadStep) | undefined, filterInputPlan: Step<TFilterInput>, filterFn: (filterInput: TFilterInput, item: any) => boolean): ListenWithFilterStep<TTopics, TTopic, TPayloadStep, TFilterInput>;
@@ -0,0 +1,61 @@
1
+ import { constant, isDev, isExecutableStep, SafeError, } from "postgraphile/grafast";
2
+ import { Step } from "postgraphile/grafast";
3
+ export class ListenWithFilterStep extends Step {
4
+ itemPlan;
5
+ filterFn;
6
+ static $$export = {
7
+ moduleName: "grafast",
8
+ exportName: "ListenWithFilterStep",
9
+ };
10
+ isSyncAndSafe = true;
11
+ pubsubDep;
12
+ topicDep;
13
+ filterInputDep;
14
+ constructor(pubsubOrPlan, topicOrPlan, itemPlan = ($item) => $item, filterInputPlan, filterFn) {
15
+ super();
16
+ this.itemPlan = itemPlan;
17
+ this.filterFn = filterFn;
18
+ const $topic = typeof topicOrPlan === "string" ? constant(topicOrPlan) : topicOrPlan;
19
+ const $pubsub = isExecutableStep(pubsubOrPlan)
20
+ ? pubsubOrPlan
21
+ : constant(pubsubOrPlan, false);
22
+ this.pubsubDep = this.addDependency($pubsub);
23
+ this.topicDep = this.addDependency($topic);
24
+ this.filterInputDep = this.addDependency(filterInputPlan);
25
+ }
26
+ execute({ indexMap, values, stream, }) {
27
+ if (!stream) {
28
+ throw new Error("ListenWithFilterStep must be streamed, never merely executed");
29
+ }
30
+ const pubsubValue = values[this.pubsubDep];
31
+ const topicValue = values[this.topicDep];
32
+ const filterInputValue = values[this.filterInputDep];
33
+ return indexMap((i) => {
34
+ const pubsub = pubsubValue.at(i);
35
+ if (!pubsub) {
36
+ throw new SafeError("Subscription not supported", isDev
37
+ ? {
38
+ hint: `${this.dependencies[this.pubsubDep]} did not provide a GrafastSubscriber; perhaps you forgot to add the relevant property to context?`,
39
+ }
40
+ : {});
41
+ }
42
+ const topic = topicValue.at(i);
43
+ const filterInput = filterInputValue.at(i);
44
+ const origStream = pubsub.subscribe(topic);
45
+ const filterFn = this.filterFn;
46
+ return {
47
+ [Symbol.asyncIterator]: async function* () {
48
+ const iterator = await origStream;
49
+ for await (const item of iterator) {
50
+ if (filterFn(filterInput, item)) {
51
+ yield item;
52
+ }
53
+ }
54
+ },
55
+ };
56
+ });
57
+ }
58
+ }
59
+ export function listenWithFilter(pubsubOrPlan, topicOrPlan, itemPlan = ($item) => $item, filterInputPlan, filterFn) {
60
+ return new ListenWithFilterStep(pubsubOrPlan, topicOrPlan, itemPlan, filterInputPlan, filterFn);
61
+ }
@@ -23,6 +23,8 @@ import { HubCurrentXPlugin } from "../plugins/hub-current-x.js";
23
23
  import { HubCreateHashChainPlugin } from "../hash-chain/plugins/hub-create-hash-chain.js";
24
24
  import { HubBadHashChainErrorPlugin } from "../hash-chain/plugins/hub-bad-hash-chain-error.js";
25
25
  import { HubUserActiveHashChainPlugin } from "../hash-chain/plugins/hub-user-active-hash-chain.js";
26
+ import { HubOutcomeInputNonNullFieldsPlugin } from "../plugins/hub-outcome-input-non-null-fields.js";
27
+ import { HubPutAlertPlugin } from "../plugins/hub-put-alert.js";
26
28
  export const requiredPlugins = [
27
29
  SmartTagsPlugin,
28
30
  IdToNodeIdPlugin,
@@ -31,6 +33,7 @@ export const requiredPlugins = [
31
33
  HubCurrentXPlugin,
32
34
  HubWithdrawPlugin,
33
35
  HubBalanceAlertPlugin,
36
+ HubPutAlertPlugin,
34
37
  HubUserBalanceByCurrencyPlugin,
35
38
  HubAddCasinoPlugin,
36
39
  ValidateCasinoFieldsPlugin,
@@ -38,6 +41,7 @@ export const requiredPlugins = [
38
41
  export const defaultPlugins = [
39
42
  ...(config.NODE_ENV === "development" ? [DebugPlugin] : []),
40
43
  ...requiredPlugins,
44
+ HubOutcomeInputNonNullFieldsPlugin,
41
45
  HubBadHashChainErrorPlugin,
42
46
  HubCreateHashChainPlugin,
43
47
  HubUserActiveHashChainPlugin,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moneypot/hub",
3
- "version": "1.3.0-dev.9",
3
+ "version": "1.4.0",
4
4
  "author": "moneypot.com",
5
5
  "homepage": "https://moneypot.com/hub",
6
6
  "keywords": [