@voltro/plugin-progression 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 +478 -2
- package/SECURITY.md +56 -0
- package/THIRD-PARTY-NOTICES.md +9189 -0
- package/dist/achievementComponents.js +92 -0
- package/dist/achievementStore.js +603 -0
- package/dist/achievementTables.entity.js +98 -0
- package/dist/achievements.d.ts +188 -0
- package/dist/achievements.js +205 -0
- package/dist/capture.js +64 -0
- package/dist/cmsQuests.d.ts +91 -0
- package/dist/cmsQuests.js +83 -0
- package/dist/components.d.ts +172 -0
- package/dist/components.js +4 -0
- package/dist/curve.d.ts +27 -0
- package/dist/curve.js +38 -0
- package/dist/daily.d.ts +152 -0
- package/dist/daily.js +126 -0
- package/dist/dailyStore.js +421 -0
- package/dist/dailySystem.fixture.js +138 -0
- package/dist/dailyTables.entity.js +90 -0
- package/dist/declarationFixtures.js +143 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +20 -0
- package/dist/index.d.ts +1833 -0
- package/dist/index.js +19 -0
- package/dist/pass.d.ts +186 -0
- package/dist/pass.js +122 -0
- package/dist/passRules.js +96 -0
- package/dist/passStore.js +485 -0
- package/dist/passTables.entity.js +96 -0
- package/dist/plugin.js +1086 -0
- package/dist/port.js +5 -0
- package/dist/progressionStore.js +706 -0
- package/dist/questComponents.js +163 -0
- package/dist/questPort.d.ts +126 -0
- package/dist/questPort.js +5 -0
- package/dist/questRefresh.js +56 -0
- package/dist/questRefresh.server.js +126 -0
- package/dist/questRerollJournal.js +77 -0
- package/dist/questStore.js +592 -0
- package/dist/questTables.entity.js +117 -0
- package/dist/quests.d.ts +142 -0
- package/dist/quests.js +153 -0
- package/dist/rank-app/schedules/progression.dispatchRankClosures.cron.tsx +19 -0
- package/dist/rank-app/workflows/progression.closeRankSeason.workflow.server.tsx +10 -0
- package/dist/rank-app/workflows/progression.closeRankSeason.workflow.tsx +1 -0
- package/dist/rankClosure.d.ts +69 -0
- package/dist/rankClosure.js +35 -0
- package/dist/rankClosure.server.js +139 -0
- package/dist/rankClosureWorkflow.d.ts +34 -0
- package/dist/rankClosureWorkflow.js +16 -0
- package/dist/rankReader.server.js +57 -0
- package/dist/rankRewardParent.server.js +65 -0
- package/dist/rankSeasons.d.ts +144 -0
- package/dist/rankSeasons.js +91 -0
- package/dist/rankStore.js +521 -0
- package/dist/rankSystem.fixture.js +170 -0
- package/dist/rankTables.entity.js +101 -0
- package/dist/ranks.d.ts +167 -0
- package/dist/ranks.js +219 -0
- package/dist/resourceStore.js +464 -0
- package/dist/resourceTables.entity.js +89 -0
- package/dist/resources.d.ts +105 -0
- package/dist/resources.js +60 -0
- package/dist/reward-app/schedules/progression.dispatchRewards.cron.tsx +19 -0
- package/dist/reward-app/workflows/progression.deliverReward.workflow.server.tsx +10 -0
- package/dist/reward-app/workflows/progression.deliverReward.workflow.tsx +1 -0
- package/dist/reward.d.ts +27 -0
- package/dist/reward.js +39 -0
- package/dist/rewardDelivery.d.ts +163 -0
- package/dist/rewardDelivery.js +38 -0
- package/dist/rewardDelivery.server.js +430 -0
- package/dist/rewardDeliveryRecord.js +48 -0
- package/dist/rewardDeliveryWorkflow.d.ts +36 -0
- package/dist/rewardDeliveryWorkflow.js +16 -0
- package/dist/rpc.d.ts +880 -0
- package/dist/rpc.js +498 -0
- package/dist/server.d.ts +2812 -0
- package/dist/server.js +21 -0
- package/dist/stats.js +45 -0
- package/dist/strategies.d.ts +131 -0
- package/dist/strategies.js +4 -0
- package/dist/systemFixtures.js +474 -0
- package/dist/tables.entity.js +155 -0
- package/dist/testing.d.ts +1566 -0
- package/dist/testing.js +11 -0
- package/dist/track.js +72 -0
- package/dist/tutorial.d.ts +157 -0
- package/dist/tutorial.js +110 -0
- package/dist/tutorialStore.js +496 -0
- package/dist/tutorialSystem.fixture.js +100 -0
- package/dist/tutorialTables.entity.js +91 -0
- package/dist/web.d.ts +363 -0
- package/dist/web.js +7 -0
- package/package.json +167 -4
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ProgressionError as e, progressionError as t } from "./errors.js";
|
|
2
|
+
import { identifier as n, integer as r, object as i } from "./capture.js";
|
|
3
|
+
import { defineQuestPool as a } from "./quests.js";
|
|
4
|
+
import { questHash as o } from "./questStore.js";
|
|
5
|
+
import { Effect as s } from "effect";
|
|
6
|
+
import { Schema as c, cmsContext as l, contentTypeToEntities as u, defineContentType as d } from "@voltro/cms";
|
|
7
|
+
//#region src/cmsQuests.ts
|
|
8
|
+
var f = d({
|
|
9
|
+
name: "quest_pool",
|
|
10
|
+
displayName: "Quest pool",
|
|
11
|
+
pluralName: "Quest pools",
|
|
12
|
+
fields: {
|
|
13
|
+
poolId: c.String.pipe(c.maxLength(120), c.pattern(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/), c.unique()),
|
|
14
|
+
version: c.Number,
|
|
15
|
+
definitionJson: c.String.pipe(c.maxLength(65536))
|
|
16
|
+
},
|
|
17
|
+
list: {
|
|
18
|
+
columns: ["poolId", "version"],
|
|
19
|
+
defaultSort: {
|
|
20
|
+
field: "poolId",
|
|
21
|
+
dir: "asc"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}), p = u(f), m = (c, u) => {
|
|
25
|
+
let d = i(u, [
|
|
26
|
+
"site",
|
|
27
|
+
"environments",
|
|
28
|
+
"maxPools"
|
|
29
|
+
]), p = i(d.environments), m = (e) => {
|
|
30
|
+
let r = n(e);
|
|
31
|
+
if (r.length > 64) throw t("invalid_input");
|
|
32
|
+
return r;
|
|
33
|
+
}, h = m(d.site), g = Object.freeze(Object.fromEntries(Object.entries(p).map(([e, t]) => [n(e), m(t)]))), _ = r(d.maxPools ?? 64, 1, 64);
|
|
34
|
+
if (Object.keys(g).length < 1 || Object.keys(g).length > 32) throw t("invalid_input");
|
|
35
|
+
return Object.freeze({ read: (r) => {
|
|
36
|
+
try {
|
|
37
|
+
let u = i(r, [
|
|
38
|
+
"tenantId",
|
|
39
|
+
"channel",
|
|
40
|
+
"locale"
|
|
41
|
+
]), d = n(u.tenantId), p = n(u.channel), v = m(u.locale), y = g[p];
|
|
42
|
+
if (!y) throw t("not_found");
|
|
43
|
+
let b = Object.freeze({
|
|
44
|
+
site: h,
|
|
45
|
+
environment: y,
|
|
46
|
+
locale: v
|
|
47
|
+
});
|
|
48
|
+
return s.tryPromise({
|
|
49
|
+
try: async () => {
|
|
50
|
+
let e = await l(c, [f], { scope: b }).contentType("quest_pool").where("tenantId", "=", d).orderBy("poolId", "asc").limit(_ + 1).all();
|
|
51
|
+
if (e.length > _) throw t("limit_exceeded");
|
|
52
|
+
let n = Object.freeze(e.map((e) => {
|
|
53
|
+
if (e.tenantId !== d || e.site !== h || e.environment !== y || e.locale !== v || e.status !== "published" || typeof e.definitionJson != "string" || e.definitionJson.length > 65536) throw t("invalid_record");
|
|
54
|
+
let n;
|
|
55
|
+
try {
|
|
56
|
+
n = JSON.parse(e.definitionJson);
|
|
57
|
+
} catch {
|
|
58
|
+
throw t("invalid_record");
|
|
59
|
+
}
|
|
60
|
+
let r = a(n);
|
|
61
|
+
if (r.id !== e.poolId || r.version !== e.version) throw t("invalid_record");
|
|
62
|
+
return r;
|
|
63
|
+
}).sort((e, t) => e.id < t.id ? -1 : +(e.id > t.id)));
|
|
64
|
+
if (new Set(n.map((e) => e.id)).size !== n.length) throw t("conflict");
|
|
65
|
+
return Object.freeze({
|
|
66
|
+
revision: o({
|
|
67
|
+
site: h,
|
|
68
|
+
environment: y,
|
|
69
|
+
locale: v,
|
|
70
|
+
pools: n
|
|
71
|
+
}),
|
|
72
|
+
pools: n
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
catch: (n) => n instanceof e ? n : t("unavailable")
|
|
76
|
+
});
|
|
77
|
+
} catch (n) {
|
|
78
|
+
return s.fail(n instanceof e ? n : t("invalid_input"));
|
|
79
|
+
}
|
|
80
|
+
} });
|
|
81
|
+
};
|
|
82
|
+
//#endregion
|
|
83
|
+
export { m as cmsQuestSource, p as progressionQuestContentTables, f as progressionQuestContentType };
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { ActionProcedureDescriptor } from '@voltro/protocol';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { Schema } from 'effect';
|
|
4
|
+
import { YieldableError } from 'effect/Cause';
|
|
5
|
+
|
|
6
|
+
/** Exact decimal text plus bounded display ratios; client state never grants an unlock. */
|
|
7
|
+
export declare function AchievementCard({ achievement, title, locale }: AchievementCardProps): ReactNode;
|
|
8
|
+
|
|
9
|
+
export declare interface AchievementCardProps {
|
|
10
|
+
readonly achievement: PlayerAchievement;
|
|
11
|
+
readonly title?: ReactNode | undefined;
|
|
12
|
+
readonly locale?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Mount in a verified owner/API context; the opaque scope changes with that identity. */
|
|
16
|
+
export declare function AchievementList(props: AchievementListProps): ReactNode;
|
|
17
|
+
|
|
18
|
+
export declare interface AchievementListProps {
|
|
19
|
+
readonly api?: string;
|
|
20
|
+
readonly invocationScope: string;
|
|
21
|
+
readonly locale?: string;
|
|
22
|
+
readonly renderTitle?: (achievement: PlayerAchievement) => ReactNode;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export declare const browserQuestRerollJournal: QuestRerollJournal;
|
|
26
|
+
|
|
27
|
+
/** Only one bounded request/receipt per verified scope persists; no credentials or quest content. */
|
|
28
|
+
export declare const makeQuestRerollJournal: (ports: QuestRerollJournalPorts) => QuestRerollJournal;
|
|
29
|
+
|
|
30
|
+
/** The wire shape decoded below; optional platform IDs may be present-but-undefined. */
|
|
31
|
+
declare type PlayerAchievement = typeof PlayerAchievementSchema.Type;
|
|
32
|
+
|
|
33
|
+
declare const PlayerAchievementSchema: Schema.Struct<{
|
|
34
|
+
readonly achievementId: Schema.String;
|
|
35
|
+
readonly version: Schema.Number;
|
|
36
|
+
readonly tierId: Schema.String;
|
|
37
|
+
readonly unlocked: Schema.Boolean;
|
|
38
|
+
readonly unlockedAt: Schema.NullOr<Schema.Number>;
|
|
39
|
+
readonly progress: Schema.$Array<Schema.Struct<{
|
|
40
|
+
readonly stat: Schema.String;
|
|
41
|
+
readonly period: Schema.Literals<readonly ["lifetime", "season"]>;
|
|
42
|
+
readonly seasonId: Schema.NullOr<Schema.String>;
|
|
43
|
+
readonly value: Schema.String;
|
|
44
|
+
readonly target: Schema.String;
|
|
45
|
+
readonly met: Schema.Boolean;
|
|
46
|
+
}>>;
|
|
47
|
+
readonly platform: Schema.NullOr<Schema.Struct<{
|
|
48
|
+
readonly steam: Schema.optional<Schema.String>;
|
|
49
|
+
readonly gameCenter: Schema.optional<Schema.String>;
|
|
50
|
+
readonly playGames: Schema.optional<Schema.String>;
|
|
51
|
+
}>>;
|
|
52
|
+
}>;
|
|
53
|
+
|
|
54
|
+
/** The wire shape decoded from the quest query. */
|
|
55
|
+
declare type PlayerQuest = typeof PlayerQuestSchema.Type;
|
|
56
|
+
|
|
57
|
+
declare const PlayerQuestSchema: Schema.Struct<{
|
|
58
|
+
readonly id: Schema.String;
|
|
59
|
+
readonly poolId: Schema.String;
|
|
60
|
+
readonly windowId: Schema.String;
|
|
61
|
+
readonly quest: Schema.Struct<{
|
|
62
|
+
readonly id: Schema.String;
|
|
63
|
+
readonly titleKey: Schema.optional<Schema.String>;
|
|
64
|
+
readonly objectives: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
65
|
+
readonly stat: Schema.String;
|
|
66
|
+
readonly target: Schema.Number;
|
|
67
|
+
}>, Schema.Struct<{
|
|
68
|
+
readonly event: Schema.String;
|
|
69
|
+
readonly target: Schema.Number;
|
|
70
|
+
readonly where: Schema.optional<Schema.$Record<Schema.String, Schema.Union<readonly [Schema.String, Schema.Number, Schema.Boolean, Schema.Null]>>>;
|
|
71
|
+
}>]>>;
|
|
72
|
+
readonly reward: Schema.$Array<Schema.Union<readonly [Schema.Struct<{
|
|
73
|
+
readonly kind: Schema.Literal<"currency">;
|
|
74
|
+
readonly currency: Schema.String;
|
|
75
|
+
readonly amount: Schema.String;
|
|
76
|
+
}>, Schema.Struct<{
|
|
77
|
+
readonly kind: Schema.Literal<"item">;
|
|
78
|
+
readonly itemId: Schema.String;
|
|
79
|
+
readonly qty: Schema.Number;
|
|
80
|
+
}>, Schema.Struct<{
|
|
81
|
+
readonly kind: Schema.Literal<"xp">;
|
|
82
|
+
readonly trackId: Schema.String;
|
|
83
|
+
readonly amount: Schema.String;
|
|
84
|
+
}>]>>;
|
|
85
|
+
}>;
|
|
86
|
+
readonly progress: Schema.$Array<Schema.Number>;
|
|
87
|
+
readonly state: Schema.Literals<readonly ["active", "completed"]>;
|
|
88
|
+
readonly assignedAt: Schema.Number;
|
|
89
|
+
readonly expiresAt: Schema.Number;
|
|
90
|
+
readonly rerollsUsed: Schema.Number;
|
|
91
|
+
readonly rerollsAllowed: Schema.Number;
|
|
92
|
+
}>;
|
|
93
|
+
|
|
94
|
+
declare class ProgressionError extends ProgressionError_base {
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
|
|
98
|
+
readonly code: Schema.Literals<readonly ["invalid_input", "invalid_record", "invalid_curve", "invalid_reward", "invalid_track", "invalid_stat", "overflow", "insufficient_resource", "forbidden", "conflict", "erased", "not_found", "limit_exceeded", "unavailable"]>;
|
|
99
|
+
}>, YieldableError>;
|
|
100
|
+
|
|
101
|
+
/** Semantic, ejectable markup in the UI kit's existing unstyled component system. */
|
|
102
|
+
export declare function QuestCard({ quest, now, title, locale, pending, blocked, error, onReroll }: QuestCardProps): ReactNode;
|
|
103
|
+
|
|
104
|
+
export declare interface QuestCardProps {
|
|
105
|
+
readonly quest: PlayerQuest;
|
|
106
|
+
readonly now: number;
|
|
107
|
+
readonly title?: ReactNode | undefined;
|
|
108
|
+
readonly locale?: string | undefined;
|
|
109
|
+
readonly pending?: boolean | undefined;
|
|
110
|
+
readonly blocked?: boolean | undefined;
|
|
111
|
+
readonly error?: boolean | undefined;
|
|
112
|
+
readonly onReroll?: (() => Promise<void>) | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Own live Quest query plus durable, scope-locked reroll intentions; no optimistic progress or rewards. */
|
|
116
|
+
export declare function QuestList(props: QuestListProps): ReactNode;
|
|
117
|
+
|
|
118
|
+
export declare interface QuestListProps {
|
|
119
|
+
readonly api?: string;
|
|
120
|
+
/** Opaque, stable verified app/API/credential-owner scope; remount on identity changes. Never a secret. */
|
|
121
|
+
readonly invocationScope: string;
|
|
122
|
+
readonly locale?: string;
|
|
123
|
+
readonly journal?: QuestRerollJournal;
|
|
124
|
+
readonly renderTitle?: (quest: PlayerQuest) => ReactNode;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
declare const QuestReceiptSchema: Schema.Struct<{
|
|
128
|
+
readonly operationId: Schema.String;
|
|
129
|
+
readonly kind: Schema.Literals<readonly ["assign", "record", "reroll"]>;
|
|
130
|
+
readonly assignmentIds: Schema.$Array<Schema.String>;
|
|
131
|
+
readonly completedIds: Schema.$Array<Schema.String>;
|
|
132
|
+
readonly rewardsQueued: Schema.Number;
|
|
133
|
+
readonly at: Schema.Number;
|
|
134
|
+
}>;
|
|
135
|
+
|
|
136
|
+
export declare type QuestRerollIntent = typeof questsRerollDescriptor.input.Type;
|
|
137
|
+
|
|
138
|
+
export declare interface QuestRerollJournal {
|
|
139
|
+
/** Exclusive scope lock; write identity durably before invoking transport. */
|
|
140
|
+
readonly pending: (scope: string) => boolean;
|
|
141
|
+
readonly recover: (scope: string, send: (input: QuestRerollIntent) => Promise<QuestRerollReceipt>) => Promise<QuestRerollReceipt | undefined>;
|
|
142
|
+
readonly run: (scope: string, input: Omit<QuestRerollIntent, 'requestId'>, send: (input: QuestRerollIntent) => Promise<QuestRerollReceipt>) => Promise<QuestRerollReceipt>;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export declare interface QuestRerollJournalPorts {
|
|
146
|
+
readonly storage: QuestRerollJournalStorage;
|
|
147
|
+
readonly exclusive: <A>(name: string, run: () => Promise<A>) => Promise<A>;
|
|
148
|
+
readonly requestId: () => string;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export declare interface QuestRerollJournalStorage {
|
|
152
|
+
readonly getItem: (key: string) => string | null;
|
|
153
|
+
readonly setItem: (key: string, value: string) => void;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export declare type QuestRerollReceipt = typeof QuestReceiptSchema.Type;
|
|
157
|
+
|
|
158
|
+
declare const questsRerollDescriptor: ActionProcedureDescriptor<"quests.reroll", Schema.Struct<{
|
|
159
|
+
readonly poolId: Schema.String;
|
|
160
|
+
readonly windowId: Schema.String;
|
|
161
|
+
readonly assignmentId: Schema.String;
|
|
162
|
+
readonly requestId: Schema.String;
|
|
163
|
+
}>, Schema.Struct<{
|
|
164
|
+
readonly operationId: Schema.String;
|
|
165
|
+
readonly kind: Schema.Literals<readonly ["assign", "record", "reroll"]>;
|
|
166
|
+
readonly assignmentIds: Schema.$Array<Schema.String>;
|
|
167
|
+
readonly completedIds: Schema.$Array<Schema.String>;
|
|
168
|
+
readonly rewardsQueued: Schema.Number;
|
|
169
|
+
readonly at: Schema.Number;
|
|
170
|
+
}>, typeof ProgressionError>;
|
|
171
|
+
|
|
172
|
+
export { }
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { browserQuestRerollJournal as e, makeQuestRerollJournal as t } from "./questRerollJournal.js";
|
|
2
|
+
import { QuestCard as n, QuestList as r } from "./questComponents.js";
|
|
3
|
+
import { AchievementCard as i, AchievementList as a } from "./achievementComponents.js";
|
|
4
|
+
export { i as AchievementCard, a as AchievementList, n as QuestCard, r as QuestList, e as browserQuestRerollJournal, t as makeQuestRerollJournal };
|
package/dist/curve.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const defaultXpCurve: XpCurve;
|
|
2
|
+
|
|
3
|
+
export declare const explicitLevelCurve: (thresholds: readonly ProgressValue[]) => LevelCurve;
|
|
4
|
+
|
|
5
|
+
export declare interface LevelCurve {
|
|
6
|
+
readonly thresholds: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Recurrence rounds the next per-level requirement up using an exact decimal ratio. */
|
|
10
|
+
export declare const levelCurve: (input: {
|
|
11
|
+
readonly base: ProgressValue;
|
|
12
|
+
readonly growth: number;
|
|
13
|
+
readonly maxLevel: number;
|
|
14
|
+
}) => LevelCurve;
|
|
15
|
+
|
|
16
|
+
declare type ProgressValue = string | number | bigint;
|
|
17
|
+
|
|
18
|
+
export declare const resolveXpCurve: (xp: ProgressValue, curve: LevelCurve, slot?: XpCurve) => number;
|
|
19
|
+
|
|
20
|
+
export declare type XpCurve = (context: XpCurveContext) => number;
|
|
21
|
+
|
|
22
|
+
export declare interface XpCurveContext {
|
|
23
|
+
readonly xp: string;
|
|
24
|
+
readonly curve: LevelCurve;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { }
|
package/dist/curve.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { progressionError as e } from "./errors.js";
|
|
2
|
+
import { integer as t, list as n, object as r, progressValue as i } from "./capture.js";
|
|
3
|
+
//#region src/curve.ts
|
|
4
|
+
var a = (t) => {
|
|
5
|
+
let r = n(t, 1e4).map((e) => i(e));
|
|
6
|
+
if (r.length === 0 || r[0] !== 0n || r.some((e, t) => t > 0 && e <= r[t - 1])) throw e("invalid_curve");
|
|
7
|
+
return Object.freeze({ thresholds: Object.freeze(r.map(String)) });
|
|
8
|
+
}, o = (n) => {
|
|
9
|
+
let o = r(n, [
|
|
10
|
+
"base",
|
|
11
|
+
"growth",
|
|
12
|
+
"maxLevel"
|
|
13
|
+
]), s = t(o.maxLevel, 1, 1e4), c = o.growth;
|
|
14
|
+
if (typeof c != "number" || !Number.isFinite(c) || c < 1 || c > 100 || !/^[0-9]+(?:\.[0-9]{1,12})?$/.test(String(c))) throw e("invalid_curve");
|
|
15
|
+
let l = i(o.base);
|
|
16
|
+
if (l === 0n) throw e("invalid_curve");
|
|
17
|
+
let [u, d = ""] = String(c).split("."), f = 10n ** BigInt(d.length), p = BigInt(u + d), m = ["0"], h = 0n;
|
|
18
|
+
for (let t = 1; t < s; t++) {
|
|
19
|
+
if (h += l, h > 9223372036854775807n) throw e("overflow");
|
|
20
|
+
m.push(String(h)), l = (l * p + f - 1n) / f;
|
|
21
|
+
}
|
|
22
|
+
return a(m);
|
|
23
|
+
}, s = ({ xp: e, curve: t }) => {
|
|
24
|
+
let n = i(e), r = 0, a = t.thresholds.length;
|
|
25
|
+
for (; r + 1 < a;) {
|
|
26
|
+
let e = Math.floor((r + a) / 2);
|
|
27
|
+
BigInt(t.thresholds[e]) <= n ? r = e : a = e;
|
|
28
|
+
}
|
|
29
|
+
return r + 1;
|
|
30
|
+
}, c = (e, n, r = s) => {
|
|
31
|
+
let o = a(n.thresholds), c = Object.freeze({
|
|
32
|
+
xp: String(i(e)),
|
|
33
|
+
curve: o
|
|
34
|
+
});
|
|
35
|
+
return t(r(c), 1, o.thresholds.length);
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { s as defaultXpCurve, a as explicitLevelCurve, o as levelCurve, c as resolveXpCurve };
|
package/dist/daily.d.ts
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Context } from 'effect';
|
|
2
|
+
import { Effect } from 'effect';
|
|
3
|
+
import { PluginPrivacyTarget } from '@voltro/protocol';
|
|
4
|
+
import { Schema } from 'effect';
|
|
5
|
+
import { Subject } from '@voltro/protocol';
|
|
6
|
+
import { YieldableError } from 'effect/Cause';
|
|
7
|
+
|
|
8
|
+
export declare const captureDailyProfile: (input: unknown) => DailyProfile;
|
|
9
|
+
|
|
10
|
+
export declare const captureDailyState: (input: unknown, calendar: LoginCalendar) => DailyState;
|
|
11
|
+
|
|
12
|
+
export declare interface DailyClaimInput {
|
|
13
|
+
readonly calendarId: string;
|
|
14
|
+
readonly requestId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export declare interface DailyClaimPlan {
|
|
18
|
+
readonly dayIndex: number;
|
|
19
|
+
readonly streak: number;
|
|
20
|
+
readonly protectionRemaining: number;
|
|
21
|
+
readonly cycle: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export declare type DailyClaimPolicy = (input: DailyClaimPolicyContext) => DailyClaimPlan;
|
|
25
|
+
|
|
26
|
+
export declare interface DailyClaimPolicyContext {
|
|
27
|
+
readonly calendar: LoginCalendar;
|
|
28
|
+
readonly previous: DailyState | null;
|
|
29
|
+
readonly window: DailyWindow;
|
|
30
|
+
readonly at: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare interface DailyProfile {
|
|
34
|
+
readonly timezone: string;
|
|
35
|
+
readonly version: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export declare class DailyProfileSource extends DailyProfileSource_base {
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare const DailyProfileSource_base: Context.ServiceClass<DailyProfileSource, "@voltro/DailyProfileSource", DailyProfileSourceService>;
|
|
42
|
+
|
|
43
|
+
export declare interface DailyProfileSourceService {
|
|
44
|
+
readonly read: (subject: Subject, scope: {
|
|
45
|
+
readonly tenantId: string;
|
|
46
|
+
readonly ownerId: string;
|
|
47
|
+
}) => Effect.Effect<DailyProfile, ProgressionError>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export declare interface DailyReceipt {
|
|
51
|
+
readonly operationId: string;
|
|
52
|
+
readonly claimId: string;
|
|
53
|
+
readonly calendarId: string;
|
|
54
|
+
readonly dayKey: string;
|
|
55
|
+
readonly after: DailyState;
|
|
56
|
+
readonly rewardsQueued: number;
|
|
57
|
+
readonly at: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export declare interface DailyState extends DailyWindow {
|
|
61
|
+
readonly calendarId: string;
|
|
62
|
+
readonly version: number;
|
|
63
|
+
readonly dayIndex: number;
|
|
64
|
+
readonly streak: number;
|
|
65
|
+
readonly protectionRemaining: number;
|
|
66
|
+
readonly cycle: number;
|
|
67
|
+
readonly at: number;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export declare interface DailyWindow {
|
|
71
|
+
readonly dayKey: string;
|
|
72
|
+
readonly dayOrdinal: number;
|
|
73
|
+
readonly from: number;
|
|
74
|
+
readonly until: number;
|
|
75
|
+
readonly timezone: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export declare const defaultDailyClaimPolicy: DailyClaimPolicy;
|
|
79
|
+
|
|
80
|
+
/** Effect Cron owns the timezone/DST boundary; day ordinals count civil dates, not 24-hour intervals. */
|
|
81
|
+
export declare const defaultDailyWindow: (input: {
|
|
82
|
+
readonly timezone: string;
|
|
83
|
+
readonly at: number;
|
|
84
|
+
}) => DailyWindow;
|
|
85
|
+
|
|
86
|
+
export declare const defineLoginCalendar: (input: LoginCalendarDefinition) => LoginCalendar;
|
|
87
|
+
|
|
88
|
+
export declare interface LoginCalendar {
|
|
89
|
+
readonly id: string;
|
|
90
|
+
readonly version: number;
|
|
91
|
+
readonly days: readonly (readonly ProgressionReward[])[];
|
|
92
|
+
readonly streakProtection: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export declare interface LoginCalendarDefinition {
|
|
96
|
+
readonly id: string;
|
|
97
|
+
readonly version?: number;
|
|
98
|
+
readonly days: readonly (readonly ProgressionReward[])[];
|
|
99
|
+
readonly streakProtection?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export declare class LoginCalendarPort extends LoginCalendarPort_base {
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
declare const LoginCalendarPort_base: Context.ServiceClass<LoginCalendarPort, "@voltro/LoginCalendarPort", LoginCalendarService>;
|
|
106
|
+
|
|
107
|
+
export declare interface LoginCalendarService {
|
|
108
|
+
readonly claim: (subject: Subject, input: DailyClaimInput) => Effect.Effect<DailyReceipt, ProgressionError>;
|
|
109
|
+
readonly mine: (subject: Subject) => Effect.Effect<readonly PlayerDaily[], ProgressionError>;
|
|
110
|
+
readonly exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, ProgressionError>;
|
|
111
|
+
readonly eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, ProgressionError>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export declare interface PlayerDaily {
|
|
115
|
+
readonly calendarId: string;
|
|
116
|
+
readonly version: number;
|
|
117
|
+
readonly dayKey: string;
|
|
118
|
+
readonly timezone: string;
|
|
119
|
+
readonly from: number;
|
|
120
|
+
readonly until: number;
|
|
121
|
+
readonly observedAt: number;
|
|
122
|
+
readonly claimed: boolean;
|
|
123
|
+
readonly dayIndex: number;
|
|
124
|
+
readonly streak: number;
|
|
125
|
+
readonly protectionRemaining: number;
|
|
126
|
+
readonly reward: readonly ProgressionReward[];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
declare class ProgressionError extends ProgressionError_base {
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
|
|
133
|
+
readonly code: Schema.Literals<readonly ["invalid_input", "invalid_record", "invalid_curve", "invalid_reward", "invalid_track", "invalid_stat", "overflow", "insufficient_resource", "forbidden", "conflict", "erased", "not_found", "limit_exceeded", "unavailable"]>;
|
|
134
|
+
}>, YieldableError>;
|
|
135
|
+
|
|
136
|
+
declare type ProgressionReward = {
|
|
137
|
+
readonly kind: 'currency';
|
|
138
|
+
readonly currency: string;
|
|
139
|
+
readonly amount: string;
|
|
140
|
+
} | {
|
|
141
|
+
readonly kind: 'item';
|
|
142
|
+
readonly itemId: string;
|
|
143
|
+
readonly qty: number;
|
|
144
|
+
} | {
|
|
145
|
+
readonly kind: 'xp';
|
|
146
|
+
readonly trackId: string;
|
|
147
|
+
readonly amount: string;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export declare const resolveDailyClaim: (input: DailyClaimPolicyContext, slot?: DailyClaimPolicy) => DailyState;
|
|
151
|
+
|
|
152
|
+
export { }
|
package/dist/daily.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { progressionError as e } from "./errors.js";
|
|
2
|
+
import { identifier as t, integer as n, list as r, object as i } from "./capture.js";
|
|
3
|
+
import { captureRewards as a } from "./reward.js";
|
|
4
|
+
import { questTimezone as o } from "./quests.js";
|
|
5
|
+
import { Context as s, Cron as c } from "effect";
|
|
6
|
+
//#region src/daily.ts
|
|
7
|
+
var l = (o) => {
|
|
8
|
+
let s = i(o, [
|
|
9
|
+
"id",
|
|
10
|
+
"version",
|
|
11
|
+
"days",
|
|
12
|
+
"streakProtection"
|
|
13
|
+
]), c = r(s.days, 366).map((e) => a(e, 64));
|
|
14
|
+
if (!c.length) throw e("invalid_input");
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
id: t(s.id),
|
|
17
|
+
version: n(s.version ?? 1, 1, 1e6),
|
|
18
|
+
days: Object.freeze(c),
|
|
19
|
+
streakProtection: n(s.streakProtection ?? 0, 0, 30)
|
|
20
|
+
});
|
|
21
|
+
}, u = (t) => {
|
|
22
|
+
let r = i(t, ["timezone", "at"]), a = o(r.timezone), s = n(r.at, 0, 864e13), l = c.parse("0 0 * * *", a);
|
|
23
|
+
if (l._tag === "Failure") throw e("invalid_input");
|
|
24
|
+
let u = Math.floor(s / 1e3) * 1e3, d = c.match(l.success, new Date(u)) ? u : c.prev(l.success, new Date(u)).getTime(), f = c.next(l.success, new Date(d)).getTime();
|
|
25
|
+
n(d, 0, s), n(f, s + 1, 864e13);
|
|
26
|
+
let p = new Intl.DateTimeFormat("en-US", {
|
|
27
|
+
timeZone: a,
|
|
28
|
+
calendar: "iso8601",
|
|
29
|
+
numberingSystem: "latn",
|
|
30
|
+
year: "numeric",
|
|
31
|
+
month: "2-digit",
|
|
32
|
+
day: "2-digit"
|
|
33
|
+
}).formatToParts(new Date(s)), m = (e) => p.find((t) => t.type === e)?.value, h = Number(m("year")), g = Number(m("month")), _ = Number(m("day"));
|
|
34
|
+
n(h, 1970, 9999), n(g, 1, 12), n(_, 1, 31);
|
|
35
|
+
let v = `${String(h).padStart(4, "0")}-${String(g).padStart(2, "0")}-${String(_).padStart(2, "0")}`;
|
|
36
|
+
return Object.freeze({
|
|
37
|
+
dayKey: v,
|
|
38
|
+
dayOrdinal: Math.floor(Date.UTC(h, g - 1, _) / 864e5),
|
|
39
|
+
from: d,
|
|
40
|
+
until: f,
|
|
41
|
+
timezone: a
|
|
42
|
+
});
|
|
43
|
+
}, d = ({ calendar: t, previous: n, window: r }) => {
|
|
44
|
+
if (!n) return {
|
|
45
|
+
dayIndex: 0,
|
|
46
|
+
streak: 1,
|
|
47
|
+
protectionRemaining: t.streakProtection,
|
|
48
|
+
cycle: 0
|
|
49
|
+
};
|
|
50
|
+
let i = r.dayOrdinal - n.dayOrdinal - 1;
|
|
51
|
+
if (i < 0) throw e("conflict");
|
|
52
|
+
if (i > n.protectionRemaining) return {
|
|
53
|
+
dayIndex: 0,
|
|
54
|
+
streak: 1,
|
|
55
|
+
protectionRemaining: t.streakProtection,
|
|
56
|
+
cycle: n.cycle + 1
|
|
57
|
+
};
|
|
58
|
+
let a = (n.dayIndex + 1) % t.days.length, o = n.cycle + +(a === 0);
|
|
59
|
+
return {
|
|
60
|
+
dayIndex: a,
|
|
61
|
+
streak: n.streak + 1,
|
|
62
|
+
protectionRemaining: a === 0 ? t.streakProtection : n.protectionRemaining - i,
|
|
63
|
+
cycle: o
|
|
64
|
+
};
|
|
65
|
+
}, f = (t, r) => {
|
|
66
|
+
let a = i(t, [
|
|
67
|
+
"calendarId",
|
|
68
|
+
"version",
|
|
69
|
+
"dayKey",
|
|
70
|
+
"dayOrdinal",
|
|
71
|
+
"from",
|
|
72
|
+
"until",
|
|
73
|
+
"timezone",
|
|
74
|
+
"dayIndex",
|
|
75
|
+
"streak",
|
|
76
|
+
"protectionRemaining",
|
|
77
|
+
"cycle",
|
|
78
|
+
"at"
|
|
79
|
+
]), s = n(a.at, 0, 864e13), c = u({
|
|
80
|
+
at: s,
|
|
81
|
+
timezone: o(a.timezone)
|
|
82
|
+
});
|
|
83
|
+
if (a.calendarId !== r.id || a.version !== r.version || a.dayKey !== c.dayKey || a.dayOrdinal !== c.dayOrdinal || a.from !== c.from || a.until !== c.until) throw e("invalid_record");
|
|
84
|
+
return Object.freeze({
|
|
85
|
+
...c,
|
|
86
|
+
calendarId: r.id,
|
|
87
|
+
version: r.version,
|
|
88
|
+
dayIndex: n(a.dayIndex, 0, r.days.length - 1),
|
|
89
|
+
streak: n(a.streak, 1, 2147483647),
|
|
90
|
+
protectionRemaining: n(a.protectionRemaining, 0, r.streakProtection),
|
|
91
|
+
cycle: n(a.cycle, 0, 2147483647),
|
|
92
|
+
at: s
|
|
93
|
+
});
|
|
94
|
+
}, p = (t, r = d) => {
|
|
95
|
+
let a = l(t.calendar), o = n(t.at, 0, 864e13), s = u({
|
|
96
|
+
timezone: t.window.timezone,
|
|
97
|
+
at: o
|
|
98
|
+
}), c = t.previous === null ? null : f(t.previous, a);
|
|
99
|
+
if (c && (c.timezone !== s.timezone || c.dayOrdinal >= s.dayOrdinal || c.until > s.from || c.at > o)) throw e("conflict");
|
|
100
|
+
let p = i(r(Object.freeze({
|
|
101
|
+
calendar: a,
|
|
102
|
+
previous: c,
|
|
103
|
+
window: s,
|
|
104
|
+
at: o
|
|
105
|
+
})), [
|
|
106
|
+
"dayIndex",
|
|
107
|
+
"streak",
|
|
108
|
+
"protectionRemaining",
|
|
109
|
+
"cycle"
|
|
110
|
+
]);
|
|
111
|
+
return f({
|
|
112
|
+
...s,
|
|
113
|
+
...p,
|
|
114
|
+
calendarId: a.id,
|
|
115
|
+
version: a.version,
|
|
116
|
+
at: o
|
|
117
|
+
}, a);
|
|
118
|
+
}, m = class extends s.Service()("@voltro/DailyProfileSource") {}, h = class extends s.Service()("@voltro/LoginCalendarPort") {}, g = (e) => {
|
|
119
|
+
let n = i(e, ["timezone", "version"]);
|
|
120
|
+
return Object.freeze({
|
|
121
|
+
timezone: o(n.timezone),
|
|
122
|
+
version: t(n.version)
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
export { m as DailyProfileSource, h as LoginCalendarPort, g as captureDailyProfile, f as captureDailyState, d as defaultDailyClaimPolicy, u as defaultDailyWindow, l as defineLoginCalendar, p as resolveDailyClaim };
|