@voltro/plugin-economy 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 +2719 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +9189 -0
- package/dist/amount.js +21 -0
- package/dist/appleIap.server.js +263 -0
- package/dist/appleIapNotifications.server.js +163 -0
- package/dist/appleIapTrust.server.js +31 -0
- package/dist/bundleValue.server.js +113 -0
- package/dist/canonicalDataJson.js +11 -0
- package/dist/capture.js +54 -0
- package/dist/codePort.fixture.js +124 -0
- package/dist/codeStore.server.js +440 -0
- package/dist/codeTables.entity.js +44 -0
- package/dist/codes.d.ts +150 -0
- package/dist/codes.js +83 -0
- package/dist/components.d.ts +397 -0
- package/dist/components.js +284 -0
- package/dist/consistency.server.js +116 -0
- package/dist/containerStore.server.js +688 -0
- package/dist/containerStore.testFixtures.js +76 -0
- package/dist/containerTables.entity.js +46 -0
- package/dist/containers.d.ts +311 -0
- package/dist/containers.fixture.js +129 -0
- package/dist/containers.js +215 -0
- package/dist/containers.server.js +40 -0
- package/dist/containersPort.fixture.js +75 -0
- package/dist/currency.js +33 -0
- package/dist/declarationFixtures.js +62 -0
- package/dist/economy-app/events/economy.refundReversed.event.ts +1 -0
- package/dist/economy-app/events/economy.refunded.event.ts +1 -0
- package/dist/economyPort.fixture.js +125 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +22 -0
- package/dist/events.d.ts +46 -0
- package/dist/events.js +1 -0
- package/dist/googleIap.server.js +392 -0
- package/dist/googleIapNotifications.server.js +131 -0
- package/dist/googleIapSubscription.server.js +184 -0
- package/dist/iap.d.ts +309 -0
- package/dist/iap.js +304 -0
- package/dist/iap.testFixtures.js +145 -0
- package/dist/iapAccountTables.entity.js +40 -0
- package/dist/iapAccounts.server.js +201 -0
- package/dist/iapBinding.server.js +524 -0
- package/dist/iapCheckout.server.js +98 -0
- package/dist/iapCompletion.server.js +56 -0
- package/dist/iapCurrentState.server.js +108 -0
- package/dist/iapCurrentState.testFixtures.js +23 -0
- package/dist/iapCurrentStatePort.fixture.js +64 -0
- package/dist/iapEntitlements.d.ts +79 -0
- package/dist/iapEntitlements.js +58 -0
- package/dist/iapEntitlements.server.js +49 -0
- package/dist/iapEntitlementsBinding.server.js +106 -0
- package/dist/iapEntitlementsPort.fixture.js +81 -0
- package/dist/iapInstalledEntitlements.testFixtures.js +76 -0
- package/dist/iapInstalledNotifications.testFixtures.js +94 -0
- package/dist/iapInventoryRefunds.server.js +262 -0
- package/dist/iapNotificationCustodyPort.fixture.js +68 -0
- package/dist/iapNotificationPort.fixture.js +74 -0
- package/dist/iapNotificationProcessor.server.js +135 -0
- package/dist/iapNotificationProcessor.testFixtures.js +64 -0
- package/dist/iapNotificationProcessorPort.fixture.js +56 -0
- package/dist/iapNotificationSchedule.server.js +70 -0
- package/dist/iapNotificationStore.server.js +353 -0
- package/dist/iapNotificationStore.testFixtures.js +32 -0
- package/dist/iapNotificationTables.entity.js +49 -0
- package/dist/iapNotificationTarget.server.js +37 -0
- package/dist/iapNotificationTarget.testFixtures.js +30 -0
- package/dist/iapNotifications.d.ts +644 -0
- package/dist/iapNotifications.js +6 -0
- package/dist/iapNotifications.server.js +96 -0
- package/dist/iapNotificationsBinding.server.js +233 -0
- package/dist/iapNotificationsSystemPort.fixture.js +51 -0
- package/dist/iapPlugin.testFixtures.js +60 -0
- package/dist/iapPort.fixture.js +75 -0
- package/dist/iapProviderCapture.server.js +18 -0
- package/dist/iapRecovery.d.ts +146 -0
- package/dist/iapRecovery.js +90 -0
- package/dist/iapRefundEventDelivery.server.js +83 -0
- package/dist/iapRefundEvents.js +45 -0
- package/dist/iapRefundEventsPort.fixture.js +60 -0
- package/dist/iapRefundPort.fixture.js +55 -0
- package/dist/iapRefundTables.entity.js +56 -0
- package/dist/iapRefunds.d.ts +755 -0
- package/dist/iapRefunds.js +6 -0
- package/dist/iapRefunds.server.js +554 -0
- package/dist/iapRefunds.testFixtures.js +65 -0
- package/dist/iapSpendAdmission.server.js +99 -0
- package/dist/iapStore.server.js +741 -0
- package/dist/iapStore.testFixtures.js +128 -0
- package/dist/iapTables.entity.js +81 -0
- package/dist/iapVerification.server.js +121 -0
- package/dist/index.d.ts +2984 -0
- package/dist/index.js +15 -0
- package/dist/intentionRecovery.js +134 -0
- package/dist/ledger.js +78 -0
- package/dist/loyalty.d.ts +274 -0
- package/dist/loyalty.fixture.js +93 -0
- package/dist/loyalty.js +312 -0
- package/dist/loyaltyBinding.server.js +204 -0
- package/dist/loyaltyPort.fixture.js +87 -0
- package/dist/loyaltyShopSource.server.js +91 -0
- package/dist/loyaltyStore.server.js +553 -0
- package/dist/loyaltyStore.testFixtures.js +83 -0
- package/dist/loyaltyTables.entity.js +50 -0
- package/dist/offerMerchandising.fixture.js +115 -0
- package/dist/offerMerchandising.js +283 -0
- package/dist/offerPolicies.d.ts +441 -0
- package/dist/offerPolicies.fixture.js +54 -0
- package/dist/offerPolicies.js +268 -0
- package/dist/piggyBanks.d.ts +272 -0
- package/dist/piggyBanks.fixture.js +71 -0
- package/dist/piggyBanks.js +201 -0
- package/dist/piggyBinding.server.js +205 -0
- package/dist/piggyPort.fixture.js +73 -0
- package/dist/piggyStore.server.js +611 -0
- package/dist/piggyStore.testFixtures.js +98 -0
- package/dist/piggyTables.entity.js +41 -0
- package/dist/plugin.js +1048 -0
- package/dist/port.js +13 -0
- package/dist/price.d.ts +99 -0
- package/dist/price.js +144 -0
- package/dist/purchaseRecovery.js +56 -0
- package/dist/reports.server.js +115 -0
- package/dist/rpc.d.ts +3201 -0
- package/dist/rpc.js +1201 -0
- package/dist/sanctionsTradeAuthority.server.js +80 -0
- package/dist/server.d.ts +3856 -0
- package/dist/server.js +43 -0
- package/dist/shop.d.ts +279 -0
- package/dist/shop.js +196 -0
- package/dist/shop.testFixtures.js +114 -0
- package/dist/shopPort.fixture.js +72 -0
- package/dist/shopPresentation.js +68 -0
- package/dist/shopStore.server.js +624 -0
- package/dist/shopTables.entity.js +63 -0
- package/dist/shopUiCopy.js +57 -0
- package/dist/spendLimits.d.ts +140 -0
- package/dist/spendLimits.fixture.js +53 -0
- package/dist/spendLimits.js +155 -0
- package/dist/spendLimitsPort.fixture.js +120 -0
- package/dist/subscriptionBinding.server.js +211 -0
- package/dist/subscriptionStore.server.js +643 -0
- package/dist/subscriptionStore.testFixtures.js +72 -0
- package/dist/subscriptionTables.entity.js +37 -0
- package/dist/subscriptions.d.ts +394 -0
- package/dist/subscriptions.fixture.js +97 -0
- package/dist/subscriptions.js +213 -0
- package/dist/subscriptionsPort.fixture.js +76 -0
- package/dist/systemFixtures.js +89 -0
- package/dist/tables.entity.js +83 -0
- package/dist/testing.d.ts +4615 -0
- package/dist/testing.js +29 -0
- package/dist/tradeAuthority.server.js +172 -0
- package/dist/tradeBinding.server.js +201 -0
- package/dist/tradeComponents.js +342 -0
- package/dist/tradeEscrow.server.js +232 -0
- package/dist/tradeEscrow.testFixtures.js +133 -0
- package/dist/tradeExpiry.server.js +43 -0
- package/dist/tradeRecovery.js +48 -0
- package/dist/tradeStore.server.js +750 -0
- package/dist/tradeStore.testFixtures.js +73 -0
- package/dist/tradeTables.entity.js +66 -0
- package/dist/tradeUiCopy.js +79 -0
- package/dist/trades.d.ts +246 -0
- package/dist/trades.fixture.js +103 -0
- package/dist/trades.js +289 -0
- package/dist/tradesPort.fixture.js +94 -0
- package/dist/walletEscrow.js +106 -0
- package/dist/walletEscrow.server.js +376 -0
- package/dist/walletKeys.server.js +5 -0
- package/dist/walletStore.js +571 -0
- package/dist/web.d.ts +2257 -0
- package/dist/web.js +9 -0
- package/package.json +161 -4
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
//#region src/shopUiCopy.ts
|
|
2
|
+
var e = Object.freeze({
|
|
3
|
+
en: Object.freeze({
|
|
4
|
+
shop: "Shop",
|
|
5
|
+
loading: "Loading offers…",
|
|
6
|
+
unavailable: "Offers are unavailable. Your saved purchase stays available for recovery.",
|
|
7
|
+
empty: "No offers are available.",
|
|
8
|
+
choose: "Choose offer",
|
|
9
|
+
free: "Free",
|
|
10
|
+
oneTime: "One purchase per account",
|
|
11
|
+
closed: "Offer is outside its availability window.",
|
|
12
|
+
providersMissing: "Real-money checkout is unavailable.",
|
|
13
|
+
confirm: "Confirm purchase",
|
|
14
|
+
cancel: "Close",
|
|
15
|
+
sending: "Sending the saved purchase…",
|
|
16
|
+
uncertain: "This purchase is awaiting confirmation. Keep its original request; closing this view does not cancel it.",
|
|
17
|
+
retry: "Retry original purchase",
|
|
18
|
+
complete: "Purchase confirmed",
|
|
19
|
+
invalid: "This selection has changed. Review the current offer before purchasing.",
|
|
20
|
+
originalRequest: "Original request",
|
|
21
|
+
originalOffer: "Original offer",
|
|
22
|
+
pricePolicy: "The current discount can change before acceptance. The server can charge at most the declared maximum for this saved offer. Limits and eligibility are checked on purchase.",
|
|
23
|
+
currentPrice: "Currently displayed",
|
|
24
|
+
maximumPrice: "Maximum charge",
|
|
25
|
+
quantity: "Quantity",
|
|
26
|
+
endsIn: (e) => `Ends in ${e} seconds`,
|
|
27
|
+
discount: (e) => `${e}% below the declared price`
|
|
28
|
+
}),
|
|
29
|
+
de: Object.freeze({
|
|
30
|
+
shop: "Shop",
|
|
31
|
+
loading: "Angebote werden geladen…",
|
|
32
|
+
unavailable: "Angebote sind nicht verfügbar. Dein gespeicherter Kauf bleibt zur Wiederaufnahme erhalten.",
|
|
33
|
+
empty: "Keine Angebote verfügbar.",
|
|
34
|
+
choose: "Angebot auswählen",
|
|
35
|
+
free: "Kostenlos",
|
|
36
|
+
oneTime: "Ein Kauf pro Konto",
|
|
37
|
+
closed: "Das Angebot liegt außerhalb seines Verfügbarkeitsfensters.",
|
|
38
|
+
providersMissing: "Echtgeld-Checkout ist nicht verfügbar.",
|
|
39
|
+
confirm: "Kauf bestätigen",
|
|
40
|
+
cancel: "Schließen",
|
|
41
|
+
sending: "Der gespeicherte Kauf wird gesendet…",
|
|
42
|
+
uncertain: "Dieser Kauf wartet auf Bestätigung. Behalte die ursprüngliche Anfrage; Schließen dieser Ansicht storniert ihn nicht.",
|
|
43
|
+
retry: "Ursprünglichen Kauf erneut senden",
|
|
44
|
+
complete: "Kauf bestätigt",
|
|
45
|
+
invalid: "Diese Auswahl hat sich geändert. Prüfe vor dem Kauf das aktuelle Angebot.",
|
|
46
|
+
originalRequest: "Ursprüngliche Anfrage",
|
|
47
|
+
originalOffer: "Ursprüngliches Angebot",
|
|
48
|
+
pricePolicy: "Der aktuelle Rabatt kann sich vor der Annahme ändern. Der Server kann höchstens den deklarierten Maximalpreis für das gespeicherte Angebot berechnen. Limits und Zulassung werden beim Kauf geprüft.",
|
|
49
|
+
currentPrice: "Aktuell angezeigt",
|
|
50
|
+
maximumPrice: "Maximale Belastung",
|
|
51
|
+
quantity: "Menge",
|
|
52
|
+
endsIn: (e) => `Endet in ${e} Sekunden`,
|
|
53
|
+
discount: (e) => `${e}% unter dem deklarierten Preis`
|
|
54
|
+
})
|
|
55
|
+
});
|
|
56
|
+
//#endregion
|
|
57
|
+
export { e as shopUiCopy };
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Effect } from 'effect';
|
|
2
|
+
import { Schema } from 'effect';
|
|
3
|
+
import { YieldableError } from 'effect/Cause';
|
|
4
|
+
|
|
5
|
+
export declare const assertSpendBudget: (projection: ReturnType<typeof projectSpend>, input: EconomyAmountInput, budget: SpendBudget) => void;
|
|
6
|
+
|
|
7
|
+
export declare const captureSpendLimitsOptions: (input: SpendLimitsOptions, currencies: readonly CurrencyDefinition[]) => Readonly<{
|
|
8
|
+
limits: readonly SpendLimit[];
|
|
9
|
+
source: SpendFactsSource;
|
|
10
|
+
version: string;
|
|
11
|
+
policy: SpendLimitPolicy;
|
|
12
|
+
}>;
|
|
13
|
+
|
|
14
|
+
declare interface CurrencyDefinition {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly kind: 'soft' | 'hard';
|
|
17
|
+
readonly cap?: EconomyAmountInput;
|
|
18
|
+
readonly premium?: boolean;
|
|
19
|
+
readonly display?: {
|
|
20
|
+
readonly nameKey: string;
|
|
21
|
+
} | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Currency-specific virtual limits, not an exchange rate, legal age source,
|
|
25
|
+
* provider payment limit or consent/withdrawal implementation. */
|
|
26
|
+
export declare const defaultSpendLimitPolicy: SpendLimitPolicy;
|
|
27
|
+
|
|
28
|
+
export declare const defineSpendLimits: (input: readonly (SpendLimitDefinition | SpendLimit)[], declarations: readonly CurrencyDefinition[]) => readonly SpendLimit[];
|
|
29
|
+
|
|
30
|
+
declare type EconomyAmountInput = string | number | bigint;
|
|
31
|
+
|
|
32
|
+
/** Redacted codes; never carries owners, amounts or provider payloads. */
|
|
33
|
+
declare class EconomyError extends EconomyError_base {
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare const EconomyError_base: Schema.Class<EconomyError, Schema.TaggedStruct<"EconomyError", {
|
|
37
|
+
readonly code: Schema.Literals<readonly ["invalid_currency", "invalid_amount", "overflow", "invalid_price", "real_money_disabled", "invalid_input", "invalid_record", "currency_cap_reached", "insufficient_funds", "conflict", "forbidden", "unavailable", "not_found", "erased", "limit_exceeded", "already_redeemed"]>;
|
|
38
|
+
}>, YieldableError>;
|
|
39
|
+
|
|
40
|
+
/** Complete owner/currency history is supplied and validated by SQL custody.
|
|
41
|
+
* Credits/refunds never silently replenish an already consumed spend allowance. */
|
|
42
|
+
export declare const projectSpend: (input: readonly SpendPosting[], at: number) => Readonly<{
|
|
43
|
+
windows: Readonly<{
|
|
44
|
+
day: Readonly<{
|
|
45
|
+
from: number;
|
|
46
|
+
until: number;
|
|
47
|
+
}>;
|
|
48
|
+
month: Readonly<{
|
|
49
|
+
from: number;
|
|
50
|
+
until: number;
|
|
51
|
+
}>;
|
|
52
|
+
}>;
|
|
53
|
+
daily: string;
|
|
54
|
+
monthly: string;
|
|
55
|
+
balance: string;
|
|
56
|
+
}>;
|
|
57
|
+
|
|
58
|
+
export declare const resolveSpendBudget: (input: SpendLimitContext, policy?: SpendLimitPolicy) => SpendBudget;
|
|
59
|
+
|
|
60
|
+
export declare interface SpendBudget {
|
|
61
|
+
readonly daily: string | null;
|
|
62
|
+
readonly monthly: string | null;
|
|
63
|
+
readonly allowed: boolean;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export declare interface SpendFacts extends SpendFactsInput {
|
|
67
|
+
readonly allowed: boolean;
|
|
68
|
+
readonly minor: boolean;
|
|
69
|
+
readonly observedAt: number;
|
|
70
|
+
readonly daily?: EconomyAmountInput | null | undefined;
|
|
71
|
+
readonly monthly?: EconomyAmountInput | null;
|
|
72
|
+
readonly blockedUntil?: number | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export declare interface SpendFactsInput {
|
|
76
|
+
readonly tenantId: string;
|
|
77
|
+
readonly ownerId: string;
|
|
78
|
+
readonly ownerKind: 'player' | 'guild';
|
|
79
|
+
readonly currency: string;
|
|
80
|
+
readonly at: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export declare interface SpendFactsSource {
|
|
84
|
+
readonly read: (input: SpendFactsInput) => Effect.Effect<SpendFacts, EconomyError>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export declare interface SpendLimit {
|
|
88
|
+
readonly currency: string;
|
|
89
|
+
readonly daily: string | null;
|
|
90
|
+
readonly monthly: string | null;
|
|
91
|
+
readonly minors: {
|
|
92
|
+
readonly daily: string | null;
|
|
93
|
+
readonly monthly: string | null;
|
|
94
|
+
} | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export declare interface SpendLimitContext {
|
|
98
|
+
readonly limit: SpendLimit;
|
|
99
|
+
readonly facts: SpendFacts;
|
|
100
|
+
readonly version: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export declare interface SpendLimitDefinition {
|
|
104
|
+
readonly currency: string;
|
|
105
|
+
readonly daily?: EconomyAmountInput | null | undefined;
|
|
106
|
+
readonly monthly?: EconomyAmountInput | null;
|
|
107
|
+
readonly minors?: {
|
|
108
|
+
readonly daily?: EconomyAmountInput | null | undefined;
|
|
109
|
+
readonly monthly?: EconomyAmountInput | null;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export declare type SpendLimitPolicy = (input: SpendLimitContext) => SpendBudget;
|
|
114
|
+
|
|
115
|
+
export declare interface SpendLimitsOptions {
|
|
116
|
+
readonly limits: readonly (SpendLimitDefinition | SpendLimit)[];
|
|
117
|
+
readonly source: SpendFactsSource;
|
|
118
|
+
readonly version: string;
|
|
119
|
+
readonly policy?: SpendLimitPolicy;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export declare interface SpendPosting {
|
|
123
|
+
readonly id: string;
|
|
124
|
+
readonly debit: string;
|
|
125
|
+
readonly credit: string;
|
|
126
|
+
readonly at: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare const utcSpendWindows: (at: number) => Readonly<{
|
|
130
|
+
day: Readonly<{
|
|
131
|
+
from: number;
|
|
132
|
+
until: number;
|
|
133
|
+
}>;
|
|
134
|
+
month: Readonly<{
|
|
135
|
+
from: number;
|
|
136
|
+
until: number;
|
|
137
|
+
}>;
|
|
138
|
+
}>;
|
|
139
|
+
|
|
140
|
+
export { }
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defaultSpendLimitPolicy as e, defineSpendLimits as t, resolveSpendBudget as n } from "./spendLimits.js";
|
|
2
|
+
import { Effect as r } from "effect";
|
|
3
|
+
//#region src/spendLimits.fixture.ts
|
|
4
|
+
var i = () => Object.freeze({
|
|
5
|
+
limit: t([{
|
|
6
|
+
currency: "test.gold",
|
|
7
|
+
daily: "100",
|
|
8
|
+
monthly: "1000",
|
|
9
|
+
minors: {
|
|
10
|
+
daily: "10",
|
|
11
|
+
monthly: "100"
|
|
12
|
+
}
|
|
13
|
+
}], [{
|
|
14
|
+
id: "test.gold",
|
|
15
|
+
kind: "soft"
|
|
16
|
+
}])[0],
|
|
17
|
+
facts: Object.freeze({
|
|
18
|
+
tenantId: "fixture",
|
|
19
|
+
ownerId: "a",
|
|
20
|
+
ownerKind: "player",
|
|
21
|
+
currency: "test.gold",
|
|
22
|
+
at: 100,
|
|
23
|
+
observedAt: 100,
|
|
24
|
+
allowed: !0,
|
|
25
|
+
minor: !0
|
|
26
|
+
}),
|
|
27
|
+
version: "test.spend.v1"
|
|
28
|
+
}), a = i(), o = Object.freeze({
|
|
29
|
+
kind: "slot",
|
|
30
|
+
defaultImplementation: e,
|
|
31
|
+
defaultProbe: {
|
|
32
|
+
run: () => r.sync(() => n(a)),
|
|
33
|
+
expected: {
|
|
34
|
+
daily: "10",
|
|
35
|
+
monthly: "100",
|
|
36
|
+
allowed: !0
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
overrideProbe: {
|
|
40
|
+
run: () => r.sync(() => n(a, () => ({
|
|
41
|
+
daily: "5",
|
|
42
|
+
monthly: "50",
|
|
43
|
+
allowed: !0
|
|
44
|
+
}))),
|
|
45
|
+
expected: {
|
|
46
|
+
daily: "5",
|
|
47
|
+
monthly: "50",
|
|
48
|
+
allowed: !0
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
//#endregion
|
|
53
|
+
export { i as spendLimitFixture, o as spendLimitPolicySystemFixture };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { economyError as e } from "./errors.js";
|
|
2
|
+
import { captureBoundMethods as t, dataList as n, dataObject as r, identifier as i } from "./capture.js";
|
|
3
|
+
import { nonNegativeAmount as a, positiveAmount as o } from "./amount.js";
|
|
4
|
+
import { captureCurrencies as s } from "./currency.js";
|
|
5
|
+
import { shopInteger as c } from "./shop.js";
|
|
6
|
+
//#region src/spendLimits.ts
|
|
7
|
+
var l = (e) => e == null ? null : a(e).toString(), u = (e, t) => e === null || t !== null && BigInt(t) <= BigInt(e), d = (e, t) => e === null ? t : t === null || BigInt(e) < BigInt(t) ? e : t, f = (t, a) => {
|
|
8
|
+
let o = s(a), c = Object.freeze(n(t, 128).map((t) => {
|
|
9
|
+
let n = r(t, [
|
|
10
|
+
"currency",
|
|
11
|
+
"daily",
|
|
12
|
+
"monthly",
|
|
13
|
+
"minors"
|
|
14
|
+
]), a = i(n.currency);
|
|
15
|
+
if (!o.some((e) => e.id === a)) throw e("invalid_currency");
|
|
16
|
+
let s = l(n.daily), c = l(n.monthly), u = n.minors == null ? null : r(n.minors, ["daily", "monthly"]), f = u ? Object.freeze({
|
|
17
|
+
daily: d(s, l(u.daily)),
|
|
18
|
+
monthly: d(c, l(u.monthly))
|
|
19
|
+
}) : null;
|
|
20
|
+
if (s === null && c === null && (f === null || f.daily === null && f.monthly === null)) throw e("invalid_input");
|
|
21
|
+
return Object.freeze({
|
|
22
|
+
currency: a,
|
|
23
|
+
daily: s,
|
|
24
|
+
monthly: c,
|
|
25
|
+
minors: f
|
|
26
|
+
});
|
|
27
|
+
}).sort((e, t) => e.currency < t.currency ? -1 : +(e.currency > t.currency)));
|
|
28
|
+
if (!c.length || new Set(c.map((e) => e.currency)).size !== c.length) throw e("invalid_input");
|
|
29
|
+
return c;
|
|
30
|
+
}, p = ({ limit: e, facts: t }) => {
|
|
31
|
+
let n = t.minor && e.minors ? e.minors : e;
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
daily: d(n.daily, l(t.daily)),
|
|
34
|
+
monthly: d(n.monthly, l(t.monthly)),
|
|
35
|
+
allowed: t.allowed && (t.blockedUntil == null || t.at >= t.blockedUntil)
|
|
36
|
+
});
|
|
37
|
+
}, m = (t, n = p) => {
|
|
38
|
+
let a = r(t, [
|
|
39
|
+
"limit",
|
|
40
|
+
"facts",
|
|
41
|
+
"version"
|
|
42
|
+
]), o = r(a.limit, [
|
|
43
|
+
"currency",
|
|
44
|
+
"daily",
|
|
45
|
+
"monthly",
|
|
46
|
+
"minors"
|
|
47
|
+
]), s = o.minors == null ? null : r(o.minors, ["daily", "monthly"]), f = l(o.daily), m = l(o.monthly), h = Object.freeze({
|
|
48
|
+
currency: i(o.currency),
|
|
49
|
+
daily: f,
|
|
50
|
+
monthly: m,
|
|
51
|
+
minors: s ? Object.freeze({
|
|
52
|
+
daily: d(f, l(s.daily)),
|
|
53
|
+
monthly: d(m, l(s.monthly))
|
|
54
|
+
}) : null
|
|
55
|
+
}), g = r(a.facts, [
|
|
56
|
+
"tenantId",
|
|
57
|
+
"ownerId",
|
|
58
|
+
"ownerKind",
|
|
59
|
+
"currency",
|
|
60
|
+
"at",
|
|
61
|
+
"allowed",
|
|
62
|
+
"minor",
|
|
63
|
+
"observedAt",
|
|
64
|
+
"daily",
|
|
65
|
+
"monthly",
|
|
66
|
+
"blockedUntil"
|
|
67
|
+
]);
|
|
68
|
+
if (typeof g.allowed != "boolean" || typeof g.minor != "boolean" || g.ownerKind !== "player" && g.ownerKind !== "guild") throw e("invalid_record");
|
|
69
|
+
let _ = c(g.at, 0, 8639999999999999), v = c(g.observedAt, 0, _), y = Object.freeze({
|
|
70
|
+
tenantId: i(g.tenantId),
|
|
71
|
+
ownerId: i(g.ownerId),
|
|
72
|
+
ownerKind: g.ownerKind,
|
|
73
|
+
currency: i(g.currency),
|
|
74
|
+
at: _,
|
|
75
|
+
observedAt: v,
|
|
76
|
+
allowed: g.allowed,
|
|
77
|
+
minor: g.minor,
|
|
78
|
+
daily: l(g.daily),
|
|
79
|
+
monthly: l(g.monthly),
|
|
80
|
+
blockedUntil: g.blockedUntil == null ? null : c(g.blockedUntil, 0, 864e13)
|
|
81
|
+
}), b = i(a.version);
|
|
82
|
+
if (y.currency !== h.currency || typeof n != "function" || b === "default.v1") throw e("invalid_input");
|
|
83
|
+
let x = Object.freeze({
|
|
84
|
+
limit: h,
|
|
85
|
+
facts: y,
|
|
86
|
+
version: b
|
|
87
|
+
}), S = p(x), C = r(n(x), [
|
|
88
|
+
"daily",
|
|
89
|
+
"monthly",
|
|
90
|
+
"allowed"
|
|
91
|
+
]), w = l(C.daily), T = l(C.monthly);
|
|
92
|
+
if (typeof C.allowed != "boolean" || C.allowed && !S.allowed || !u(S.daily, w) || !u(S.monthly, T)) throw e("invalid_input");
|
|
93
|
+
return Object.freeze({
|
|
94
|
+
daily: w,
|
|
95
|
+
monthly: T,
|
|
96
|
+
allowed: C.allowed
|
|
97
|
+
});
|
|
98
|
+
}, h = (n, a) => {
|
|
99
|
+
let o = r(n, [
|
|
100
|
+
"limits",
|
|
101
|
+
"source",
|
|
102
|
+
"version",
|
|
103
|
+
"policy"
|
|
104
|
+
]), s = f(o.limits, a), c = t(o.source, ["read"]), l = i(o.version), u = o.policy ?? p;
|
|
105
|
+
if (l === "default.v1" || typeof u != "function") throw e("invalid_input");
|
|
106
|
+
return Object.freeze({
|
|
107
|
+
limits: s,
|
|
108
|
+
source: c,
|
|
109
|
+
version: l,
|
|
110
|
+
policy: u
|
|
111
|
+
});
|
|
112
|
+
}, g = (e) => {
|
|
113
|
+
c(e, 0, 8639999999999999);
|
|
114
|
+
let t = Math.floor(e / 864e5) * 864e5, n = new Date(e);
|
|
115
|
+
n.setUTCDate(1), n.setUTCHours(0, 0, 0, 0);
|
|
116
|
+
let r = new Date(n);
|
|
117
|
+
return r.setUTCMonth(r.getUTCMonth() + 1), Object.freeze({
|
|
118
|
+
day: Object.freeze({
|
|
119
|
+
from: t,
|
|
120
|
+
until: Math.min(t + 864e5, 864e13)
|
|
121
|
+
}),
|
|
122
|
+
month: Object.freeze({
|
|
123
|
+
from: n.getTime(),
|
|
124
|
+
until: Number.isFinite(r.getTime()) ? Math.min(r.getTime(), 864e13) : 864e13
|
|
125
|
+
})
|
|
126
|
+
});
|
|
127
|
+
}, _ = (t, o) => {
|
|
128
|
+
let s = g(o), l = /* @__PURE__ */ new Set(), u = 0n, d = 0n, f = 0n, p = (t) => {
|
|
129
|
+
if (typeof t != "string" || a(t).toString() !== t) throw e("invalid_record");
|
|
130
|
+
return BigInt(t);
|
|
131
|
+
};
|
|
132
|
+
for (let a of n(t, 1e5, "invalid_record")) {
|
|
133
|
+
let t = r(a, [
|
|
134
|
+
"id",
|
|
135
|
+
"debit",
|
|
136
|
+
"credit",
|
|
137
|
+
"at"
|
|
138
|
+
], "invalid_record"), n = i(t.id), m = p(t.debit), h = p(t.credit), g = c(t.at, 0, o);
|
|
139
|
+
if (l.has(n) || m === 0n == (h === 0n)) throw e("invalid_record");
|
|
140
|
+
l.add(n), f += h - m, g >= s.day.from && (u += m), g >= s.month.from && (d += m);
|
|
141
|
+
}
|
|
142
|
+
if (f < 0n) throw e("invalid_record");
|
|
143
|
+
return Object.freeze({
|
|
144
|
+
windows: s,
|
|
145
|
+
daily: u.toString(),
|
|
146
|
+
monthly: d.toString(),
|
|
147
|
+
balance: f.toString()
|
|
148
|
+
});
|
|
149
|
+
}, v = (t, n, r) => {
|
|
150
|
+
let i = o(n);
|
|
151
|
+
if (!r.allowed) throw e("forbidden");
|
|
152
|
+
if (r.daily !== null && BigInt(t.daily) + i > BigInt(r.daily) || r.monthly !== null && BigInt(t.monthly) + i > BigInt(r.monthly)) throw e("limit_exceeded");
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
export { v as assertSpendBudget, h as captureSpendLimitsOptions, p as defaultSpendLimitPolicy, f as defineSpendLimits, _ as projectSpend, m as resolveSpendBudget, g as utcSpendWindows };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { economyTables as e } from "./tables.entity.js";
|
|
2
|
+
import { EconomyPort as t } from "./port.js";
|
|
3
|
+
import { economyPlugin as n } from "./plugin.js";
|
|
4
|
+
import { shopAt as r, shopMachine as i, shopUser as a } from "./shop.testFixtures.js";
|
|
5
|
+
import { Effect as o, Layer as s, Result as c } from "effect";
|
|
6
|
+
import { ensureTableRegistered as l } from "@voltro/database";
|
|
7
|
+
import { InMemoryDataStore as u } from "@voltro/runtime";
|
|
8
|
+
//#region src/spendLimitsPort.fixture.ts
|
|
9
|
+
var d = {
|
|
10
|
+
app: {
|
|
11
|
+
name: "spend-fixture",
|
|
12
|
+
type: "api",
|
|
13
|
+
voltroVersion: "0.83.1"
|
|
14
|
+
},
|
|
15
|
+
config: {},
|
|
16
|
+
env: {},
|
|
17
|
+
logger: {
|
|
18
|
+
info() {},
|
|
19
|
+
warn() {},
|
|
20
|
+
error() {},
|
|
21
|
+
debug() {}
|
|
22
|
+
}
|
|
23
|
+
}, f = o.gen(function* () {
|
|
24
|
+
e.forEach(l);
|
|
25
|
+
let t = new u(), s = [{
|
|
26
|
+
id: "test.gold",
|
|
27
|
+
kind: "soft"
|
|
28
|
+
}], c = n({
|
|
29
|
+
currencies: s,
|
|
30
|
+
now: () => r,
|
|
31
|
+
spendLimits: {
|
|
32
|
+
limits: [{
|
|
33
|
+
currency: "test.gold",
|
|
34
|
+
daily: "10",
|
|
35
|
+
monthly: "100"
|
|
36
|
+
}],
|
|
37
|
+
version: "fixture.spend.v1",
|
|
38
|
+
source: { read: (e) => o.succeed({
|
|
39
|
+
...e,
|
|
40
|
+
observedAt: e.at,
|
|
41
|
+
allowed: !0,
|
|
42
|
+
minor: !1
|
|
43
|
+
}) }
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
yield* o.promise(async () => {
|
|
47
|
+
await c.bindDataStore?.(t);
|
|
48
|
+
});
|
|
49
|
+
let f = a("a", "fixture");
|
|
50
|
+
return yield* c.economy.service().transfer(i, {
|
|
51
|
+
tenantId: "fixture",
|
|
52
|
+
requestId: "fund",
|
|
53
|
+
currency: "test.gold",
|
|
54
|
+
amount: "100",
|
|
55
|
+
from: {
|
|
56
|
+
kind: "system",
|
|
57
|
+
id: "fixture"
|
|
58
|
+
},
|
|
59
|
+
to: {
|
|
60
|
+
kind: "wallet",
|
|
61
|
+
ownerKind: "player",
|
|
62
|
+
ownerId: "a"
|
|
63
|
+
},
|
|
64
|
+
reason: "fixture.fund",
|
|
65
|
+
ref: {
|
|
66
|
+
kind: "fixture",
|
|
67
|
+
id: "fund"
|
|
68
|
+
}
|
|
69
|
+
}), Object.freeze({
|
|
70
|
+
store: t,
|
|
71
|
+
plugin: c,
|
|
72
|
+
currencies: s,
|
|
73
|
+
who: f,
|
|
74
|
+
close: o.suspend(() => c.onDeactivate?.(d))
|
|
75
|
+
});
|
|
76
|
+
}), p = s.effectContext(o.acquireRelease(f, (e) => e.close).pipe(o.flatMap((e) => s.build(e.plugin.services)))), m = Object.freeze({
|
|
77
|
+
kind: "port",
|
|
78
|
+
tag: t,
|
|
79
|
+
fakeLayer: p,
|
|
80
|
+
probe: {
|
|
81
|
+
run: () => o.scoped(o.gen(function* () {
|
|
82
|
+
let e = yield* t, n = a("a", "fixture"), r = {
|
|
83
|
+
tenantId: "fixture",
|
|
84
|
+
requestId: "spend",
|
|
85
|
+
currency: "test.gold",
|
|
86
|
+
amount: "7",
|
|
87
|
+
from: {
|
|
88
|
+
kind: "wallet",
|
|
89
|
+
ownerKind: "player",
|
|
90
|
+
ownerId: "a"
|
|
91
|
+
},
|
|
92
|
+
to: {
|
|
93
|
+
kind: "system",
|
|
94
|
+
id: "fixture"
|
|
95
|
+
},
|
|
96
|
+
reason: "fixture.spend",
|
|
97
|
+
ref: {
|
|
98
|
+
kind: "fixture",
|
|
99
|
+
id: "spend"
|
|
100
|
+
}
|
|
101
|
+
}, s = yield* e.transfer(i, r), l = yield* e.transfer(i, r), u = yield* o.result(e.transfer(i, {
|
|
102
|
+
...r,
|
|
103
|
+
requestId: "excess",
|
|
104
|
+
amount: "4"
|
|
105
|
+
})), d = yield* e.mine(n);
|
|
106
|
+
return {
|
|
107
|
+
same: s.txId === l.txId,
|
|
108
|
+
refusal: c.isFailure(u) ? u.failure.code : "unexpected",
|
|
109
|
+
balance: d[0].balance
|
|
110
|
+
};
|
|
111
|
+
}).pipe(o.provide(p))),
|
|
112
|
+
expected: {
|
|
113
|
+
same: !0,
|
|
114
|
+
refusal: "limit_exceeded",
|
|
115
|
+
balance: "93"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
//#endregion
|
|
120
|
+
export { p as spendLimitsFakeLayer, f as spendLimitsPortFixture, m as spendLimitsPortSystemFixture };
|