@voltro/plugin-progression 0.0.0-stage → 0.88.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.
Files changed (97) hide show
  1. package/CHANGELOG.md +24492 -0
  2. package/LICENSE +61 -0
  3. package/README.md +478 -2
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +9841 -0
  6. package/dist/achievementComponents.js +91 -0
  7. package/dist/achievementStore.js +602 -0
  8. package/dist/achievementTables.entity.js +97 -0
  9. package/dist/achievements.d.ts +188 -0
  10. package/dist/achievements.js +204 -0
  11. package/dist/capture.js +63 -0
  12. package/dist/cmsQuests.d.ts +91 -0
  13. package/dist/cmsQuests.js +82 -0
  14. package/dist/components.d.ts +172 -0
  15. package/dist/components.js +4 -0
  16. package/dist/curve.d.ts +27 -0
  17. package/dist/curve.js +37 -0
  18. package/dist/daily.d.ts +152 -0
  19. package/dist/daily.js +125 -0
  20. package/dist/dailyStore.js +420 -0
  21. package/dist/dailySystem.fixture.js +137 -0
  22. package/dist/dailyTables.entity.js +89 -0
  23. package/dist/declarationFixtures.js +142 -0
  24. package/dist/errors.d.ts +13 -0
  25. package/dist/errors.js +19 -0
  26. package/dist/index.d.ts +1833 -0
  27. package/dist/index.js +19 -0
  28. package/dist/pass.d.ts +186 -0
  29. package/dist/pass.js +121 -0
  30. package/dist/passRules.js +95 -0
  31. package/dist/passStore.js +484 -0
  32. package/dist/passTables.entity.js +95 -0
  33. package/dist/plugin.js +1085 -0
  34. package/dist/port.js +4 -0
  35. package/dist/progressionStore.js +705 -0
  36. package/dist/questComponents.js +162 -0
  37. package/dist/questPort.d.ts +126 -0
  38. package/dist/questPort.js +4 -0
  39. package/dist/questRefresh.js +55 -0
  40. package/dist/questRefresh.server.js +125 -0
  41. package/dist/questRerollJournal.js +76 -0
  42. package/dist/questStore.js +591 -0
  43. package/dist/questTables.entity.js +116 -0
  44. package/dist/quests.d.ts +142 -0
  45. package/dist/quests.js +152 -0
  46. package/dist/rank-app/schedules/progression.dispatchRankClosures.cron.tsx +19 -0
  47. package/dist/rank-app/workflows/progression.closeRankSeason.workflow.server.tsx +10 -0
  48. package/dist/rank-app/workflows/progression.closeRankSeason.workflow.tsx +1 -0
  49. package/dist/rankClosure.d.ts +69 -0
  50. package/dist/rankClosure.js +34 -0
  51. package/dist/rankClosure.server.js +138 -0
  52. package/dist/rankClosureWorkflow.d.ts +34 -0
  53. package/dist/rankClosureWorkflow.js +15 -0
  54. package/dist/rankReader.server.js +56 -0
  55. package/dist/rankRewardParent.server.js +64 -0
  56. package/dist/rankSeasons.d.ts +144 -0
  57. package/dist/rankSeasons.js +90 -0
  58. package/dist/rankStore.js +520 -0
  59. package/dist/rankSystem.fixture.js +169 -0
  60. package/dist/rankTables.entity.js +100 -0
  61. package/dist/ranks.d.ts +167 -0
  62. package/dist/ranks.js +218 -0
  63. package/dist/resourceStore.js +463 -0
  64. package/dist/resourceTables.entity.js +88 -0
  65. package/dist/resources.d.ts +105 -0
  66. package/dist/resources.js +59 -0
  67. package/dist/reward-app/schedules/progression.dispatchRewards.cron.tsx +19 -0
  68. package/dist/reward-app/workflows/progression.deliverReward.workflow.server.tsx +10 -0
  69. package/dist/reward-app/workflows/progression.deliverReward.workflow.tsx +1 -0
  70. package/dist/reward.d.ts +27 -0
  71. package/dist/reward.js +38 -0
  72. package/dist/rewardDelivery.d.ts +163 -0
  73. package/dist/rewardDelivery.js +37 -0
  74. package/dist/rewardDelivery.server.js +429 -0
  75. package/dist/rewardDeliveryRecord.js +47 -0
  76. package/dist/rewardDeliveryWorkflow.d.ts +36 -0
  77. package/dist/rewardDeliveryWorkflow.js +15 -0
  78. package/dist/rpc.d.ts +880 -0
  79. package/dist/rpc.js +497 -0
  80. package/dist/server.d.ts +2812 -0
  81. package/dist/server.js +21 -0
  82. package/dist/stats.js +44 -0
  83. package/dist/strategies.d.ts +131 -0
  84. package/dist/strategies.js +4 -0
  85. package/dist/systemFixtures.js +473 -0
  86. package/dist/tables.entity.js +154 -0
  87. package/dist/testing.d.ts +1566 -0
  88. package/dist/testing.js +11 -0
  89. package/dist/track.js +71 -0
  90. package/dist/tutorial.d.ts +157 -0
  91. package/dist/tutorial.js +109 -0
  92. package/dist/tutorialStore.js +495 -0
  93. package/dist/tutorialSystem.fixture.js +99 -0
  94. package/dist/tutorialTables.entity.js +90 -0
  95. package/dist/web.d.ts +363 -0
  96. package/dist/web.js +6 -0
  97. package/package.json +167 -4
@@ -0,0 +1,162 @@
1
+ import { PlayerQuestSchema as e } from "./rpc.js";
2
+ import { useQuestReroll as t, useQuests as n } from "./web.js";
3
+ import { browserQuestRerollJournal as r } from "./questRerollJournal.js";
4
+ import { Schema as i } from "effect";
5
+ import { useEffect as a, useId as o, useRef as s, useState as c } from "react";
6
+ import { useUiStrings as l } from "@voltro/ui";
7
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
8
+ // src/questComponents.tsx
9
+ function f({ quest: e, now: t, title: n, locale: r, pending: i = !1, blocked: a = !1, error: f = !1, onReroll: p }) {
10
+ let m = l().quests, h = o(), g = s(!1), [_, v] = c(!1), y = e.expiresAt <= t, b = !y && e.state === "active" && e.rerollsUsed < e.rerollsAllowed && p !== void 0;
11
+ return /* @__PURE__ */ d("article", {
12
+ "data-quest": e.id,
13
+ "data-state": y ? "expired" : e.state,
14
+ "aria-labelledby": h,
15
+ "aria-busy": i,
16
+ children: [
17
+ /* @__PURE__ */ d("header", { children: [/* @__PURE__ */ u("h3", {
18
+ id: h,
19
+ children: n ?? e.quest.titleKey ?? e.quest.id
20
+ }), /* @__PURE__ */ u("p", {
21
+ "data-quest-state": !0,
22
+ children: y ? m.expired : e.state === "completed" ? m.completed : m.active
23
+ })] }),
24
+ /* @__PURE__ */ u("ul", {
25
+ "data-quest-objectives": !0,
26
+ children: e.quest.objectives.map((t, n) => {
27
+ let r = "stat" in t ? t.stat : t.event, i = e.progress[n] ?? 0;
28
+ return /* @__PURE__ */ d("li", { children: [/* @__PURE__ */ u("span", { children: m.objective(r, i, t.target) }), /* @__PURE__ */ u("progress", {
29
+ "aria-label": r,
30
+ value: i,
31
+ max: t.target
32
+ })] }, n);
33
+ })
34
+ }),
35
+ /* @__PURE__ */ u("p", { children: /* @__PURE__ */ u("time", {
36
+ dateTime: new Date(e.expiresAt).toISOString(),
37
+ children: m.expires(new Date(e.expiresAt).toLocaleString(r))
38
+ }) }),
39
+ p ? /* @__PURE__ */ d("div", {
40
+ "data-quest-reroll": !0,
41
+ children: [
42
+ /* @__PURE__ */ u("p", { children: m.remaining(Math.max(0, e.rerollsAllowed - e.rerollsUsed)) }),
43
+ /* @__PURE__ */ u("button", {
44
+ type: "button",
45
+ "aria-disabled": !b || i || a,
46
+ onClick: () => {
47
+ !b || i || a || g.current || (g.current = !0, v(!1), Promise.resolve().then(p).catch(() => {
48
+ v(!0);
49
+ }).finally(() => {
50
+ g.current = !1;
51
+ }));
52
+ },
53
+ children: i ? m.rerollPending : m.reroll
54
+ }),
55
+ f || _ ? /* @__PURE__ */ u("p", {
56
+ role: "alert",
57
+ children: m.rerollError
58
+ }) : null
59
+ ]
60
+ }) : null
61
+ ]
62
+ });
63
+ }
64
+ var p = i.decodeUnknownSync(i.Array(e), { onExcessProperty: "error" }), m = (e) => {
65
+ try {
66
+ if (!Array.isArray(e) || e.length > 512) return;
67
+ let t = p(e);
68
+ return t.length > 512 || new Set(t.map((e) => e.id)).size !== t.length || t.some((e) => !Number.isSafeInteger(e.assignedAt) || !Number.isSafeInteger(e.expiresAt) || e.assignedAt < 0 || e.expiresAt <= e.assignedAt || e.expiresAt > 864e13 || !Number.isInteger(e.rerollsUsed) || !Number.isInteger(e.rerollsAllowed) || e.rerollsUsed < 0 || e.rerollsUsed > e.rerollsAllowed || e.rerollsAllowed > 8 || e.quest.objectives.length < 1 || e.quest.objectives.length > 16 || e.progress.length !== e.quest.objectives.length || e.quest.objectives.some((t, n) => !Number.isSafeInteger(t.target) || t.target < 1 || t.target > 1e6 || !Number.isSafeInteger(e.progress[n]) || e.progress[n] < 0 || e.progress[n] > t.target) || e.state === "completed" && !e.progress.every((t, n) => t === e.quest.objectives[n].target)) ? void 0 : t;
69
+ } catch {
70
+ return;
71
+ }
72
+ };
73
+ function h({ api: e = "app", invocationScope: i, locale: o, journal: p = r, renderTitle: h }) {
74
+ let g = l().quests, _ = n(e), v = t(e), [y, b] = c(() => Date.now()), [x, S] = c(), [C, w] = c(), T = s(!1), E = s(!1), [D, O] = c(!1), [k, A] = c(!1), [j, M] = c(!1);
75
+ a(() => {
76
+ try {
77
+ O(p.pending(i));
78
+ } catch {
79
+ M(!0);
80
+ }
81
+ }, [
82
+ p,
83
+ i,
84
+ x,
85
+ k
86
+ ]), a(() => (T.current = !0, () => {
87
+ T.current = !1;
88
+ }), []);
89
+ let N = _.data === void 0 ? void 0 : m(_.data), P = N?.filter((e) => e.expiresAt > y).reduce((e, t) => Math.min(e, t.expiresAt), Infinity);
90
+ if (a(() => {
91
+ if (P === void 0 || !Number.isFinite(P)) return;
92
+ let e = setTimeout(() => b(Date.now()), Math.min(2147483647, Math.max(1, P - Date.now())));
93
+ return () => clearTimeout(e);
94
+ }, [P, y]), _.error !== void 0) return /* @__PURE__ */ u("p", {
95
+ role: "alert",
96
+ children: g.unavailable
97
+ });
98
+ if (_.loading) return /* @__PURE__ */ u("p", {
99
+ role: "status",
100
+ children: g.loading
101
+ });
102
+ if (!N) return /* @__PURE__ */ u("p", {
103
+ role: "alert",
104
+ children: g.invalid
105
+ });
106
+ let F = N.filter((e) => e.expiresAt > y);
107
+ return /* @__PURE__ */ d("section", {
108
+ "data-quests": !0,
109
+ "aria-label": g.title,
110
+ children: [
111
+ D || j ? /* @__PURE__ */ u("button", {
112
+ type: "button",
113
+ "aria-disabled": x !== void 0 || k,
114
+ onClick: () => {
115
+ E.current || k || (E.current = !0, A(!0), M(!1), Promise.resolve().then(() => p.recover(i, (e) => v.run(e))).catch(() => {
116
+ T.current && M(!0);
117
+ }).finally(() => {
118
+ E.current = !1, T.current && A(!1);
119
+ }));
120
+ },
121
+ children: k ? g.recoverPending : g.recover
122
+ }) : null,
123
+ j ? /* @__PURE__ */ u("p", {
124
+ role: "alert",
125
+ children: g.rerollError
126
+ }) : null,
127
+ F.length === 0 ? /* @__PURE__ */ u("p", { children: g.empty }) : /* @__PURE__ */ u("ul", { children: F.map((e) => /* @__PURE__ */ u("li", { children: /* @__PURE__ */ u(f, {
128
+ quest: e,
129
+ now: y,
130
+ locale: o,
131
+ title: h?.(e),
132
+ blocked: x !== void 0 || k || D || j,
133
+ pending: x === e.id,
134
+ error: C === e.id,
135
+ onReroll: async () => {
136
+ if (!(E.current || e.expiresAt <= Date.now())) {
137
+ E.current = !0, S(e.id), w(void 0);
138
+ try {
139
+ await p.run(i, {
140
+ poolId: e.poolId,
141
+ windowId: e.windowId,
142
+ assignmentId: e.id
143
+ }, (e) => v.run(e));
144
+ } catch {
145
+ T.current && w(e.id);
146
+ } finally {
147
+ E.current = !1, T.current && S(void 0);
148
+ }
149
+ }
150
+ }
151
+ }) }, e.id)) })
152
+ ]
153
+ });
154
+ }
155
+ function g(e) {
156
+ let t = l().quests;
157
+ return /^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(e.invocationScope) ? /* @__PURE__ */ u(h, { ...e }, JSON.stringify([e.api ?? "app", e.invocationScope])) : /* @__PURE__ */ u("p", {
158
+ role: "alert",
159
+ children: t.invalid
160
+ });
161
+ }
162
+ export { f as QuestCard, g as QuestList };
@@ -0,0 +1,126 @@
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 interface AssignQuestsInput {
9
+ readonly tenantId: string;
10
+ readonly ownerId: string;
11
+ readonly poolId: string;
12
+ readonly requestId: string;
13
+ readonly expectedPoolDigest?: string;
14
+ readonly windowId: string;
15
+ readonly from: number;
16
+ readonly until: number;
17
+ readonly timezone: string;
18
+ readonly seed: number;
19
+ }
20
+
21
+ export declare interface PlayerQuest {
22
+ readonly id: string;
23
+ readonly poolId: string;
24
+ readonly windowId: string;
25
+ readonly quest: QuestDefinition;
26
+ readonly progress: readonly number[];
27
+ readonly state: 'active' | 'completed';
28
+ readonly assignedAt: number;
29
+ readonly expiresAt: number;
30
+ readonly rerollsUsed: number;
31
+ readonly rerollsAllowed: number;
32
+ }
33
+
34
+ declare class ProgressionError extends ProgressionError_base {
35
+ }
36
+
37
+ declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
38
+ 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"]>;
39
+ }>, YieldableError>;
40
+
41
+ declare type ProgressionReward = {
42
+ readonly kind: 'currency';
43
+ readonly currency: string;
44
+ readonly amount: string;
45
+ } | {
46
+ readonly kind: 'item';
47
+ readonly itemId: string;
48
+ readonly qty: number;
49
+ } | {
50
+ readonly kind: 'xp';
51
+ readonly trackId: string;
52
+ readonly amount: string;
53
+ };
54
+
55
+ declare type ProgressValue = string | number | bigint;
56
+
57
+ declare interface QuestDefinition {
58
+ readonly id: string;
59
+ readonly titleKey?: string;
60
+ readonly objectives: readonly QuestObjective[];
61
+ readonly reward: readonly ProgressionReward[];
62
+ }
63
+
64
+ declare type QuestObjective = {
65
+ readonly stat: string;
66
+ readonly target: number;
67
+ } | {
68
+ readonly event: string;
69
+ readonly target: number;
70
+ readonly where?: Readonly<Record<string, QuestScalar>>;
71
+ };
72
+
73
+ export declare class QuestPort extends QuestPort_base {
74
+ }
75
+
76
+ declare const QuestPort_base: Context.ServiceClass<QuestPort, "@voltro/QuestPort", QuestService>;
77
+
78
+ export declare interface QuestReceipt {
79
+ readonly operationId: string;
80
+ readonly kind: 'assign' | 'record' | 'reroll';
81
+ readonly assignmentIds: readonly string[];
82
+ readonly completedIds: readonly string[];
83
+ readonly rewardsQueued: number;
84
+ readonly at: number;
85
+ }
86
+
87
+ declare type QuestScalar = string | number | boolean | null;
88
+
89
+ export declare interface QuestService {
90
+ readonly assign: (subject: Subject, input: AssignQuestsInput) => Effect.Effect<QuestReceipt, ProgressionError>;
91
+ readonly record: (subject: Subject, input: RecordQuestInput) => Effect.Effect<QuestReceipt, ProgressionError>;
92
+ readonly reroll: (subject: Subject, input: RerollQuestInput) => Effect.Effect<QuestReceipt, ProgressionError>;
93
+ readonly mine: (subject: Subject) => Effect.Effect<readonly PlayerQuest[], ProgressionError>;
94
+ readonly exportSubject: (target: PluginPrivacyTarget) => Effect.Effect<unknown, ProgressionError>;
95
+ readonly eraseSubject: (target: PluginPrivacyTarget) => Effect.Effect<void, ProgressionError>;
96
+ }
97
+
98
+ declare type QuestSignalInput = {
99
+ readonly kind: 'stat';
100
+ readonly stat: string;
101
+ readonly delta: ProgressValue;
102
+ } | {
103
+ readonly kind: 'event';
104
+ readonly event: string;
105
+ readonly data?: Readonly<Record<string, QuestScalar>>;
106
+ };
107
+
108
+ export declare interface RecordQuestInput {
109
+ readonly tenantId: string;
110
+ readonly ownerId: string;
111
+ readonly poolId: string;
112
+ readonly windowId: string;
113
+ readonly requestId: string;
114
+ readonly source: string;
115
+ readonly origin: string;
116
+ readonly signal: QuestSignalInput;
117
+ }
118
+
119
+ export declare interface RerollQuestInput {
120
+ readonly poolId: string;
121
+ readonly windowId: string;
122
+ readonly assignmentId: string;
123
+ readonly requestId: string;
124
+ }
125
+
126
+ export { }
@@ -0,0 +1,4 @@
1
+ import { Context as e } from "effect";
2
+ // src/questPort.ts
3
+ var t = class extends e.Service()("@voltro/QuestPort") {};
4
+ export { t as QuestPort };
@@ -0,0 +1,55 @@
1
+ import { progressionError as e } from "./errors.js";
2
+ import { captureArguments as t, identifier as n, integer as r, list as i, object as a } from "./capture.js";
3
+ import { defineQuestPool as o, questTimezone as s } from "./quests.js";
4
+ import { Context as c, Cron as l, Effect as u } from "effect";
5
+ // src/questRefresh.ts
6
+ var d = class extends c.Service()("@voltro/QuestPlayerSource") {}, f = ({ pool: t, player: n, at: r }) => {
7
+ let i = t.refresh.timezone === "player" ? n.timezone : t.refresh.timezone, a = l.parse(t.refresh.cron, i);
8
+ if (a._tag === "Failure") throw e("invalid_input");
9
+ let o = Math.floor(r / 1e3) * 1e3, s = l.match(a.success, new Date(o)) ? o : l.prev(a.success, new Date(o)).getTime();
10
+ return {
11
+ from: s,
12
+ until: l.next(a.success, new Date(s)).getTime(),
13
+ timezone: i
14
+ };
15
+ }, p = (e) => {
16
+ let t = a(e, [
17
+ "tenantId",
18
+ "ownerId",
19
+ "timezone"
20
+ ]);
21
+ return Object.freeze({
22
+ tenantId: n(t.tenantId),
23
+ ownerId: n(t.ownerId),
24
+ timezone: s(t.timezone)
25
+ });
26
+ }, m = (t, n = f) => {
27
+ let i = o(t.pool), c = p(t.player), l = r(t.at, 0, 864e13), u = a(n(Object.freeze({
28
+ pool: i,
29
+ player: c,
30
+ at: l
31
+ })), [
32
+ "from",
33
+ "until",
34
+ "timezone"
35
+ ]), d = r(u.from, 0, l), m = r(u.until, l + 1, 864e13), h = s(u.timezone);
36
+ if (m - d < 6e4 || m - d > 316224e5 || h !== (i.refresh.timezone === "player" ? c.timezone : i.refresh.timezone)) throw e("invalid_input");
37
+ return Object.freeze({
38
+ from: d,
39
+ until: m,
40
+ timezone: h
41
+ });
42
+ }, h = class extends c.Service()("@voltro/QuestRefreshPort") {}, g = (t, n) => {
43
+ if (Array.isArray(t) && t.length > n) throw e("limit_exceeded");
44
+ let r = Object.freeze(i(t, n).map(p));
45
+ if (new Set(r.map((e) => JSON.stringify([e.tenantId, e.ownerId]))).size !== r.length) throw e("invalid_record");
46
+ return r;
47
+ }, _ = (n, r) => {
48
+ try {
49
+ let e = t(n);
50
+ return u.suspend(() => r(e));
51
+ } catch {
52
+ return u.fail(e("invalid_input"));
53
+ }
54
+ };
55
+ export { d as QuestPlayerSource, h as QuestRefreshPort, p as captureQuestPlayer, g as captureQuestPlayers, _ as capturedRefresh, f as defaultQuestWindow, m as resolveQuestWindow };
@@ -0,0 +1,125 @@
1
+ import { ProgressionError as e, progressionError as t } from "./errors.js";
2
+ import { identifier as n, integer as r, list as i, object as a } from "./capture.js";
3
+ import { defineQuestPool as o } from "./quests.js";
4
+ import { questHash as s } from "./questStore.js";
5
+ import { captureQuestPlayer as c, captureQuestPlayers as l, capturedRefresh as u, defaultQuestWindow as d, resolveQuestWindow as f } from "./questRefresh.js";
6
+ import { Effect as p, Result as m } from "effect";
7
+ import { createHash as h } from "node:crypto";
8
+ // src/questRefresh.server.ts
9
+ var g = async (e) => {
10
+ let t = await p.runPromise(p.result(e));
11
+ if (m.isFailure(t)) throw t.failure;
12
+ return t.success;
13
+ }, _ = (e) => h("sha256").update(JSON.stringify([
14
+ e.player.tenantId,
15
+ e.player.ownerId,
16
+ e.pool.id,
17
+ e.pool.version,
18
+ e.window.from,
19
+ e.window.until,
20
+ e.window.timezone
21
+ ])).digest().readUInt32BE(0) & 2147483647, v = (e) => {
22
+ let i = a(e, [
23
+ "players",
24
+ "intervalMs",
25
+ "maxPlayers",
26
+ "strategies",
27
+ "service"
28
+ ]), o = a(i.players, ["read"]), s = a(i.strategies ?? {}, [
29
+ "questWindow",
30
+ "questSeed",
31
+ "version"
32
+ ]), c = s.questWindow ?? d, l = s.questSeed ?? _;
33
+ if (typeof o.read != "function" || typeof c != "function" || typeof l != "function" || (c !== d || l !== _) && s.version === void 0) throw t("invalid_input");
34
+ let u = i.service === void 0 ? void 0 : a(i.service, ["refreshOwner", "refreshAll"]);
35
+ if (u && (typeof u.refreshOwner != "function" || typeof u.refreshAll != "function")) throw t("invalid_input");
36
+ return Object.freeze({
37
+ players: Object.freeze({ read: o.read }),
38
+ intervalMs: r(i.intervalMs ?? 6e4, 1e3, 36e5),
39
+ maxPlayers: r(i.maxPlayers ?? 1e3, 1, 1e4),
40
+ strategies: Object.freeze({
41
+ questWindow: c,
42
+ questSeed: l,
43
+ version: n(s.version ?? "default.v1")
44
+ }),
45
+ service: u
46
+ });
47
+ }, y = (i, o, d) => {
48
+ let m = v(d), h = o.now ?? Date.now, _ = new Set(o.allowedServices ?? []), y = (e) => {
49
+ if (e.type !== "system" && !(e.type === "service" && _.has(e.service))) throw t("forbidden");
50
+ }, x = (n) => p.tryPromise({
51
+ try: n,
52
+ catch: (n) => n instanceof e ? n : t("unavailable")
53
+ }), S = async (e, t) => {
54
+ let i = a(await g(o.source.read({
55
+ tenantId: e.tenantId,
56
+ channel: o.channel,
57
+ locale: o.locale
58
+ })), ["revision", "pools"]);
59
+ n(i.revision);
60
+ let c = b(i.pools);
61
+ return Object.freeze(c.map((n) => {
62
+ let i = f({
63
+ pool: n,
64
+ player: e,
65
+ at: t
66
+ }, m.strategies.questWindow), a = r(m.strategies.questSeed(Object.freeze({
67
+ pool: n,
68
+ player: e,
69
+ at: t,
70
+ window: i
71
+ })), 0, 2147483647), o = s([
72
+ e.tenantId,
73
+ e.ownerId,
74
+ n.id,
75
+ n.version,
76
+ i.from,
77
+ i.until,
78
+ i.timezone,
79
+ m.strategies.version
80
+ ]).slice(0, 56);
81
+ return Object.freeze({
82
+ tenantId: e.tenantId,
83
+ ownerId: e.ownerId,
84
+ poolId: n.id,
85
+ windowId: "refresh." + o,
86
+ requestId: "refresh." + o,
87
+ ...i,
88
+ seed: a,
89
+ expectedPoolDigest: s(n)
90
+ });
91
+ }));
92
+ };
93
+ return Object.freeze({
94
+ refreshOwner: (e, t) => u({
95
+ subject: e,
96
+ player: t
97
+ }, (e) => x(async () => {
98
+ y(e.subject);
99
+ let t = c(e.player), n = r(h(), 0, 864e13), a = await S(t, n), o = [];
100
+ for (let t of a) o.push(await g(i.assign(e.subject, t)));
101
+ return Object.freeze(o);
102
+ })),
103
+ refreshAll: (t) => u(t, (t) => x(async () => {
104
+ y(t);
105
+ let n = r(h(), 0, 864e13), a = l(await g(m.players.read({ limit: m.maxPlayers })), m.maxPlayers), o = 0, s = 0, c = [];
106
+ for (let r of a) try {
107
+ let e = await S(r, n);
108
+ for (let n of e) await g(i.assign(t, n)), o++;
109
+ } catch (t) {
110
+ t instanceof e && t.code === "erased" ? s++ : c.push(t instanceof e ? t.code : "unavailable");
111
+ }
112
+ return Object.freeze({
113
+ players: a.length,
114
+ assignments: o,
115
+ erased: s,
116
+ failures: Object.freeze(c)
117
+ });
118
+ }))
119
+ });
120
+ }, b = (e) => {
121
+ let n = Object.freeze(i(e, 64).map((e) => o(e)));
122
+ if (new Set(n.map((e) => e.id)).size !== n.length) throw t("invalid_record");
123
+ return n;
124
+ };
125
+ export { _ as defaultQuestSeed, y as makeQuestRefreshService, v as resolveQuestRefreshOptions };
@@ -0,0 +1,76 @@
1
+ import { QuestReceiptSchema as e, questsRerollDescriptor as t } from "./rpc.js";
2
+ import { Schema as n } from "effect";
3
+ // src/questRerollJournal.ts
4
+ var r = (e) => {
5
+ if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/.test(e)) throw Error("Invalid quest request scope");
6
+ return e;
7
+ }, i = n.decodeUnknownSync(t.input, { onExcessProperty: "error" }), a = (t, i) => {
8
+ let a = n.decodeUnknownSync(e, { onExcessProperty: "error" })(t);
9
+ if (a.kind !== "reroll" || !Number.isSafeInteger(a.at) || a.at < 0 || a.at > 864e13 || a.assignmentIds.length !== 1 || a.assignmentIds[0] === i.assignmentId || a.completedIds.length !== 0 || a.rewardsQueued !== 0) throw Error("Unconfirmed quest reroll receipt");
10
+ return r(a.operationId), r(a.assignmentIds[0]), a;
11
+ }, o = (e) => {
12
+ let t = (e) => "voltro.quest-reroll." + r(e), n = (t) => {
13
+ let n = e.storage.getItem(t);
14
+ if (n === null) return;
15
+ if (n.length > 4096) throw Error("Invalid quest reroll journal");
16
+ let o = JSON.parse(n);
17
+ if (!o || typeof o != "object" || Array.isArray(o) || Object.keys(o).length !== 2 || !Object.hasOwn(o, "intent") || !Object.hasOwn(o, "receipt")) throw Error("Invalid quest reroll journal");
18
+ let s = o, c = i(s.intent);
19
+ return Object.values(c).forEach(r), {
20
+ intent: c,
21
+ receipt: s.receipt === null ? null : a(s.receipt, c)
22
+ };
23
+ }, o = (t, n) => {
24
+ let r = JSON.stringify(n);
25
+ if (e.storage.setItem(t, r), e.storage.getItem(t) !== r) throw Error("Quest request identity was not acknowledged");
26
+ }, s = async (e, t, n) => {
27
+ o(e, {
28
+ intent: t,
29
+ receipt: null
30
+ });
31
+ let r = a(await n(Object.freeze(t)), t);
32
+ return o(e, {
33
+ intent: t,
34
+ receipt: r
35
+ }), r;
36
+ };
37
+ return {
38
+ pending: (e) => n(t(e))?.receipt === null,
39
+ recover: (r, i) => {
40
+ let a = t(r);
41
+ return e.exclusive(a, async () => {
42
+ let e = n(a);
43
+ return e ? e.receipt ?? s(a, e.intent, i) : void 0;
44
+ });
45
+ },
46
+ run: async (a, o, c) => {
47
+ let l = Object.freeze({
48
+ poolId: r(o.poolId),
49
+ windowId: r(o.windowId),
50
+ assignmentId: r(o.assignmentId)
51
+ }), u = t(a);
52
+ return e.exclusive(u, async () => {
53
+ let t = n(u), a = t && t.intent.poolId === l.poolId && t.intent.windowId === l.windowId && t.intent.assignmentId === l.assignmentId;
54
+ if (t && a && t.receipt) return t.receipt;
55
+ if (t && !a && t.receipt === null) throw Error("Another quest reroll is unconfirmed");
56
+ let o = t && a ? t.intent : i({
57
+ ...l,
58
+ requestId: r(e.requestId())
59
+ });
60
+ return s(u, o, c);
61
+ });
62
+ }
63
+ };
64
+ }, s = () => {
65
+ if (typeof navigator > "u" || !navigator.locks || typeof localStorage > "u" || typeof crypto > "u" || typeof crypto.randomUUID != "function") throw Error("Durable quest reroll storage and Web Locks are required");
66
+ return o({
67
+ storage: localStorage,
68
+ exclusive: (e, t) => navigator.locks.request(e, { mode: "exclusive" }, t),
69
+ requestId: () => crypto.randomUUID()
70
+ });
71
+ }, c = {
72
+ run: (e, t, n) => s().run(e, t, n),
73
+ pending: (e) => s().pending(e),
74
+ recover: (e, t) => s().recover(e, t)
75
+ };
76
+ export { c as browserQuestRerollJournal, o as makeQuestRerollJournal };