@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,142 @@
1
+ import { Context } from 'effect';
2
+ import { Effect } from 'effect';
3
+ import { Schema } from 'effect';
4
+ import { YieldableError } from 'effect/Cause';
5
+
6
+ export declare const captureQuestSignal: (input: unknown) => QuestSignal;
7
+
8
+ export declare const defaultQuestProgressResolver: QuestProgressResolver;
9
+
10
+ /** Deterministic weighted selection without replacement; a trusted seed is not a security RNG. */
11
+ export declare const defaultQuestSelector: QuestSelector;
12
+
13
+ export declare const defineQuest: (input: QuestDefinition) => QuestDefinition;
14
+
15
+ export declare const defineQuestPool: (input: QuestPoolDefinition) => QuestPool;
16
+
17
+ declare class ProgressionError extends ProgressionError_base {
18
+ }
19
+
20
+ declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
21
+ 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"]>;
22
+ }>, YieldableError>;
23
+
24
+ declare type ProgressionReward = {
25
+ readonly kind: 'currency';
26
+ readonly currency: string;
27
+ readonly amount: string;
28
+ } | {
29
+ readonly kind: 'item';
30
+ readonly itemId: string;
31
+ readonly qty: number;
32
+ } | {
33
+ readonly kind: 'xp';
34
+ readonly trackId: string;
35
+ readonly amount: string;
36
+ };
37
+
38
+ declare type ProgressValue = string | number | bigint;
39
+
40
+ export declare interface QuestDefinition {
41
+ readonly id: string;
42
+ readonly titleKey?: string;
43
+ readonly objectives: readonly QuestObjective[];
44
+ readonly reward: readonly ProgressionReward[];
45
+ }
46
+
47
+ export declare const questFields: (input: unknown) => Readonly<Record<string, QuestScalar>>;
48
+
49
+ export declare type QuestObjective = {
50
+ readonly stat: string;
51
+ readonly target: number;
52
+ } | {
53
+ readonly event: string;
54
+ readonly target: number;
55
+ readonly where?: Readonly<Record<string, QuestScalar>>;
56
+ };
57
+
58
+ export declare interface QuestPool extends QuestPoolDefinition {
59
+ readonly version: number;
60
+ readonly rerolls: number;
61
+ readonly weights: Readonly<Record<string, number>>;
62
+ }
63
+
64
+ export declare interface QuestPoolDefinition {
65
+ readonly id: string;
66
+ readonly version?: number;
67
+ readonly slots: number;
68
+ readonly rerolls?: number;
69
+ readonly refresh: {
70
+ readonly cron: string;
71
+ readonly timezone: string;
72
+ };
73
+ readonly quests: readonly QuestDefinition[];
74
+ readonly weights?: Readonly<Record<string, number>>;
75
+ }
76
+
77
+ export declare interface QuestProgressContext {
78
+ readonly quest: QuestDefinition;
79
+ readonly progress: readonly number[];
80
+ readonly signal: QuestSignal;
81
+ }
82
+
83
+ export declare type QuestProgressResolver = (context: QuestProgressContext) => readonly number[];
84
+
85
+ export declare type QuestScalar = string | number | boolean | null;
86
+
87
+ export declare type QuestSelector = (context: QuestSelectorContext) => readonly string[];
88
+
89
+ export declare interface QuestSelectorContext {
90
+ readonly pool: QuestPool;
91
+ readonly count: number;
92
+ readonly seed: number;
93
+ readonly excluded: readonly string[];
94
+ }
95
+
96
+ export declare type QuestSignal = {
97
+ readonly kind: 'stat';
98
+ readonly stat: string;
99
+ readonly delta: string;
100
+ } | {
101
+ readonly kind: 'event';
102
+ readonly event: string;
103
+ readonly data: Readonly<Record<string, QuestScalar>>;
104
+ };
105
+
106
+ export declare type QuestSignalInput = {
107
+ readonly kind: 'stat';
108
+ readonly stat: string;
109
+ readonly delta: ProgressValue;
110
+ } | {
111
+ readonly kind: 'event';
112
+ readonly event: string;
113
+ readonly data?: Readonly<Record<string, QuestScalar>>;
114
+ };
115
+
116
+ export declare class QuestSource extends QuestSource_base {
117
+ }
118
+
119
+ declare const QuestSource_base: Context.ServiceClass<QuestSource, "@voltro/QuestSource", QuestSourceService>;
120
+
121
+ export declare interface QuestSourceScope {
122
+ readonly tenantId: string;
123
+ readonly channel: string;
124
+ readonly locale: string;
125
+ }
126
+
127
+ export declare interface QuestSourceService {
128
+ readonly read: (scope: QuestSourceScope) => Effect.Effect<{
129
+ readonly revision: string;
130
+ readonly pools: readonly QuestPool[];
131
+ }, ProgressionError>;
132
+ }
133
+
134
+ export declare const questTimezone: (input: unknown) => string;
135
+
136
+ export declare const resolveQuestProgress: (input: QuestProgressContext, slot?: QuestProgressResolver) => readonly number[];
137
+
138
+ export declare const selectQuests: (input: QuestSelectorContext, slot?: QuestSelector) => readonly string[];
139
+
140
+ export declare const staticQuestSource: (input: readonly QuestPoolDefinition[], revision?: string) => QuestSourceService;
141
+
142
+ export { }
package/dist/quests.js ADDED
@@ -0,0 +1,152 @@
1
+ import { progressionError as e } from "./errors.js";
2
+ import { identifier as t, integer as n, list as r, object as i, progressValue as a } from "./capture.js";
3
+ import { captureRewards as o } from "./reward.js";
4
+ import { Context as s, Cron as c, Effect as l } from "effect";
5
+ // src/quests.ts
6
+ var u = (n) => {
7
+ let r = i(n), a = Object.entries(r);
8
+ if (a.length > 32) throw e("invalid_input");
9
+ return Object.freeze(Object.fromEntries(a.map(([n, r]) => {
10
+ if (t(n), r !== null && typeof r != "boolean" && !(typeof r == "string" && r.length <= 256) && (typeof r != "number" || !Number.isFinite(r) || Object.is(r, -0))) throw e("invalid_input");
11
+ return [n, r];
12
+ })));
13
+ }, d = (t) => {
14
+ if (typeof t != "string" || t.length < 1 || t.length > 64) throw e("invalid_input");
15
+ try {
16
+ new Intl.DateTimeFormat("en", { timeZone: t }).format(0);
17
+ } catch {
18
+ throw e("invalid_input");
19
+ }
20
+ return t;
21
+ }, f = (a) => {
22
+ let s = i(a, [
23
+ "id",
24
+ "titleKey",
25
+ "objectives",
26
+ "reward"
27
+ ]), c = Object.freeze(r(s.objectives, 16).map((r) => {
28
+ let a = i(r, [
29
+ "stat",
30
+ "event",
31
+ "where",
32
+ "target"
33
+ ]), o = n(a.target, 1, 1e6);
34
+ if (a.stat !== void 0 && a.event === void 0 && a.where === void 0) return Object.freeze({
35
+ stat: t(a.stat),
36
+ target: o
37
+ });
38
+ if (a.event !== void 0 && a.stat === void 0) return Object.freeze({
39
+ event: t(a.event),
40
+ target: o,
41
+ ...a.where === void 0 ? {} : { where: u(a.where) }
42
+ });
43
+ throw e("invalid_input");
44
+ }));
45
+ if (c.length === 0) throw e("invalid_input");
46
+ return Object.freeze({
47
+ id: t(s.id),
48
+ ...s.titleKey === void 0 ? {} : { titleKey: t(s.titleKey) },
49
+ objectives: c,
50
+ reward: o(s.reward, 64)
51
+ });
52
+ }, p = (a) => {
53
+ let o = i(a, [
54
+ "id",
55
+ "version",
56
+ "slots",
57
+ "rerolls",
58
+ "refresh",
59
+ "quests",
60
+ "weights"
61
+ ]), s = i(o.refresh, ["cron", "timezone"]), l = Object.freeze(r(o.quests, 128).map((e) => f(e))), u = n(o.slots, 1, 8);
62
+ if (l.length < u || new Set(l.map((e) => e.id)).size !== l.length || typeof s.cron != "string" || s.cron.length < 1 || s.cron.length > 120 || /[\r\n]/.test(s.cron)) throw e("invalid_input");
63
+ let p = s.timezone === "player" ? "player" : d(s.timezone);
64
+ if (c.parse(s.cron, p === "player" ? "UTC" : p)._tag === "Failure") throw e("invalid_input");
65
+ let m = i(o.weights ?? {});
66
+ if (Object.keys(m).some((e) => !l.some((t) => t.id === e))) throw e("invalid_input");
67
+ let h = Object.freeze(Object.fromEntries(l.map((e) => [e.id, n(m[e.id] ?? 1, 1, 1e6)])));
68
+ return Object.freeze({
69
+ id: t(o.id),
70
+ version: n(o.version ?? 1, 1, 1e6),
71
+ slots: u,
72
+ rerolls: n(o.rerolls ?? 0, 0, 8),
73
+ refresh: Object.freeze({
74
+ cron: s.cron,
75
+ timezone: p
76
+ }),
77
+ quests: l,
78
+ weights: h
79
+ });
80
+ }, m = ({ pool: t, count: n, seed: r, excluded: i }) => {
81
+ let a = r >>> 0, o = t.quests.filter((e) => !i.includes(e.id)), s = [];
82
+ for (let r = 0; r < n; r++) {
83
+ if (o.length === 0) throw e("not_found");
84
+ a = Math.imul(a, 1664525) + 1013904223 >>> 0;
85
+ let n = o.reduce((e, n) => e + t.weights[n.id], 0), r = a / 4294967296 * n, i = 0;
86
+ for (; i < o.length - 1 && (r -= t.weights[o[i].id], !(r < 0)); i++);
87
+ s.push(o.splice(i, 1)[0].id);
88
+ }
89
+ return Object.freeze(s);
90
+ }, h = (i, a = m) => {
91
+ let o = p(i.pool), s = n(i.count, 1, o.slots), c = n(i.seed, 0, 4294967295), l = Object.freeze(r(i.excluded, 128).map(t));
92
+ if (new Set(l).size !== l.length || l.some((e) => !o.quests.some((t) => t.id === e))) throw e("invalid_input");
93
+ let u = Object.freeze(r(a(Object.freeze({
94
+ pool: o,
95
+ count: s,
96
+ seed: c,
97
+ excluded: l
98
+ })), s).map(t));
99
+ if (u.length !== s || new Set(u).size !== u.length || u.some((e) => l.includes(e) || !o.quests.some((t) => t.id === e))) throw e("invalid_input");
100
+ return u;
101
+ }, g = (n) => {
102
+ let r = i(n, [
103
+ "kind",
104
+ "stat",
105
+ "delta",
106
+ "event",
107
+ "data"
108
+ ]);
109
+ if (r.kind === "stat" && r.event === void 0 && r.data === void 0) return Object.freeze({
110
+ kind: "stat",
111
+ stat: t(r.stat),
112
+ delta: String(a(r.delta))
113
+ });
114
+ if (r.kind === "event" && r.stat === void 0 && r.delta === void 0) return Object.freeze({
115
+ kind: "event",
116
+ event: t(r.event),
117
+ data: u(r.data ?? {})
118
+ });
119
+ throw e("invalid_input");
120
+ }, _ = ({ quest: e, progress: t, signal: n }) => Object.freeze(e.objectives.map((e, r) => {
121
+ let i = t[r];
122
+ return "stat" in e && n.kind === "stat" && e.stat === n.stat ? Number(BigInt(i) + BigInt(n.delta) > BigInt(e.target) ? BigInt(e.target) : BigInt(i) + BigInt(n.delta)) : "event" in e && n.kind === "event" && e.event === n.event && Object.entries(e.where ?? {}).every(([e, t]) => Object.hasOwn(n.data, e) && n.data[e] === t) ? Math.min(e.target, i + 1) : i;
123
+ })), v = (t, i = _) => {
124
+ let a = f(t.quest), o = g(t.signal), s = Object.freeze(r(t.progress, 16).map((e, t) => n(e, 0, a.objectives[t]?.target ?? -1)));
125
+ if (s.length !== a.objectives.length) throw e("invalid_record");
126
+ let c = Object.freeze(r(i(Object.freeze({
127
+ quest: a,
128
+ signal: o,
129
+ progress: s
130
+ })), 16).map((e, t) => n(e, s[t] ?? 0, a.objectives[t]?.target ?? -1)));
131
+ if (c.length !== s.length) throw e("invalid_input");
132
+ return c;
133
+ }, y = class extends s.Service()("@voltro/QuestSource") {}, b = (n, a = "static.v1") => {
134
+ let o = Object.freeze(r(n, 64).map((e) => p(e))), s = t(a);
135
+ if (new Set(o.map((e) => e.id)).size !== o.length) throw e("invalid_input");
136
+ return Object.freeze({ read: (n) => {
137
+ try {
138
+ let e = i(n, [
139
+ "tenantId",
140
+ "channel",
141
+ "locale"
142
+ ]);
143
+ return t(e.tenantId), t(e.channel), t(e.locale), l.succeed(Object.freeze({
144
+ revision: s,
145
+ pools: o
146
+ }));
147
+ } catch {
148
+ return l.fail(e("invalid_input"));
149
+ }
150
+ } });
151
+ };
152
+ export { y as QuestSource, g as captureQuestSignal, _ as defaultQuestProgressResolver, m as defaultQuestSelector, f as defineQuest, p as defineQuestPool, u as questFields, d as questTimezone, v as resolveQuestProgress, h as selectQuests, b as staticQuestSource };
@@ -0,0 +1,19 @@
1
+ import { Effect } from 'effect'
2
+ import { defineSchedule } from '@voltro/runtime'
3
+ import { systemSubject } from '@voltro/protocol'
4
+ import { RankClosurePort } from '@voltro/plugin-progression/rank-seasons/closure'
5
+ import { ProgressionError } from '@voltro/plugin-progression/errors'
6
+ export default defineSchedule({ name: 'progression.dispatchRankClosures', cron: '* * * * *', timezone: 'UTC', handler: ctx => Effect.gen(function* () {
7
+ const service = yield* RankClosurePort, workflows = ctx.app.workflows
8
+ const failure = () => new ProgressionError({ code: 'unavailable' })
9
+ if (!workflows) return yield* Effect.fail(failure())
10
+ let after: string | undefined, failed = false
11
+ for (let page = 0; page < 100; page++) {
12
+ const batch = yield* service.dispatch(systemSubject('progression.rank.dispatch'), { limit: 100, ...(after ? { after } : {}) }, payload => Effect.tryPromise(() => workflows.start('progression.closeRankSeason', payload)))
13
+ failed ||= batch.failed.length > 0
14
+ if (batch.next === null) { if (failed) return yield* Effect.fail(failure()); return }
15
+ after = batch.next
16
+ }
17
+ // Accepted SQL units remain due. Exhausting the bounded scan makes the schedule red.
18
+ return yield* Effect.fail(failure())
19
+ }) })
@@ -0,0 +1,10 @@
1
+ import { Effect } from 'effect'
2
+ import { step } from '@voltro/workflow'
3
+ import { systemSubject } from '@voltro/protocol'
4
+ import { RankClosurePort, RankClosureReceiptSchema, type RankClosureRequest } from '@voltro/plugin-progression/rank-seasons/closure'
5
+ import { ProgressionError } from '@voltro/plugin-progression/errors'
6
+ export default () => (payload: RankClosureRequest) => Effect.gen(function* () {
7
+ const service = yield* RankClosurePort
8
+ return yield* step({ name: 'close-accepted-rank-season', success: RankClosureReceiptSchema, error: ProgressionError,
9
+ retry: { maxAttempts: 5 }, execute: service.execute(systemSubject('progression.rank.workflow'), payload) })
10
+ })
@@ -0,0 +1 @@
1
+ export { ProgressionRankClosure } from '@voltro/plugin-progression/rank-seasons/workflow'
@@ -0,0 +1,69 @@
1
+ import { Context } from 'effect';
2
+ import { Effect } from 'effect';
3
+ import { Schema } from 'effect';
4
+ import { Subject } from '@voltro/protocol';
5
+ import { WorkflowRunHandle } from '@voltro/protocol';
6
+ import { YieldableError } from 'effect/Cause';
7
+
8
+ export declare const captureRankClosureReceipt: (input: unknown) => RankClosureReceipt;
9
+
10
+ declare class ProgressionError extends ProgressionError_base {
11
+ }
12
+
13
+ declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
14
+ 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"]>;
15
+ }>, YieldableError>;
16
+
17
+ export declare interface RankClosureDispatchInput {
18
+ readonly limit?: number;
19
+ readonly after?: string;
20
+ }
21
+
22
+ export declare interface RankClosureDispatchResult {
23
+ readonly scanned: number;
24
+ readonly admitted: number;
25
+ readonly failed: readonly string[];
26
+ readonly next: string | null;
27
+ }
28
+
29
+ export declare interface RankClosureOptions {
30
+ readonly service?: RankClosureService | undefined;
31
+ readonly now?: (() => number) | undefined;
32
+ readonly allowedServices?: readonly string[] | undefined;
33
+ }
34
+
35
+ export declare class RankClosurePort extends RankClosurePort_base {
36
+ }
37
+
38
+ declare const RankClosurePort_base: Context.ServiceClass<RankClosurePort, "@voltro/RankClosurePort", RankClosureService>;
39
+
40
+ export declare interface RankClosureReceipt {
41
+ readonly unitId: string;
42
+ readonly status: 'closed' | 'erased';
43
+ readonly operationId: string | null;
44
+ readonly rewardsQueued: number;
45
+ readonly at: number;
46
+ }
47
+
48
+ export declare const RankClosureReceiptSchema: Schema.Struct<{
49
+ readonly unitId: Schema.String;
50
+ readonly status: Schema.Literals<readonly ["closed", "erased"]>;
51
+ readonly operationId: Schema.NullOr<Schema.String>;
52
+ readonly rewardsQueued: Schema.Number;
53
+ readonly at: Schema.Number;
54
+ }>;
55
+
56
+ export declare interface RankClosureRequest {
57
+ readonly unitId: string;
58
+ }
59
+
60
+ export declare const RankClosureRequestSchema: Schema.Struct<{
61
+ readonly unitId: Schema.String;
62
+ }>;
63
+
64
+ export declare interface RankClosureService {
65
+ readonly execute: (subject: Subject, input: RankClosureRequest) => Effect.Effect<RankClosureReceipt, ProgressionError>;
66
+ readonly dispatch: (subject: Subject, input: RankClosureDispatchInput, start: (payload: RankClosureRequest) => Effect.Effect<WorkflowRunHandle, unknown>) => Effect.Effect<RankClosureDispatchResult, ProgressionError>;
67
+ }
68
+
69
+ export { }
@@ -0,0 +1,34 @@
1
+ import { progressionError as e } from "./errors.js";
2
+ import { identifier as t, integer as n, object as r } from "./capture.js";
3
+ import { Context as i, Schema as a } from "effect";
4
+ // src/rankClosure.ts
5
+ var o = a.String.pipe(a.check(a.isPattern(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,119}$/))), s = a.Struct({ unitId: o }), c = a.Struct({
6
+ unitId: o,
7
+ status: a.Literals(["closed", "erased"]),
8
+ operationId: a.NullOr(o),
9
+ rewardsQueued: a.Number.pipe(a.check(a.isInt()), a.check(a.isBetween({
10
+ minimum: 0,
11
+ maximum: 64
12
+ }))),
13
+ at: a.Number.pipe(a.check(a.isInt()), a.check(a.isBetween({
14
+ minimum: 0,
15
+ maximum: 864e13
16
+ })))
17
+ }), l = (i) => {
18
+ let a = r(i, [
19
+ "unitId",
20
+ "status",
21
+ "operationId",
22
+ "rewardsQueued",
23
+ "at"
24
+ ]), o = t(a.unitId), s = n(a.at, 0, 864e13), c = n(a.rewardsQueued, 0, 64);
25
+ if (a.status !== "closed" && a.status !== "erased" || a.status === "erased" && (a.operationId !== null || c !== 0) || a.status === "closed" && a.operationId === null) throw e("invalid_record");
26
+ return Object.freeze({
27
+ unitId: o,
28
+ status: a.status,
29
+ operationId: a.operationId === null ? null : t(a.operationId),
30
+ rewardsQueued: c,
31
+ at: s
32
+ });
33
+ }, u = class extends i.Service()("@voltro/RankClosurePort") {};
34
+ export { u as RankClosurePort, c as RankClosureReceiptSchema, s as RankClosureRequestSchema, l as captureRankClosureReceipt };
@@ -0,0 +1,138 @@
1
+ import { ProgressionError as e, progressionError as t } from "./errors.js";
2
+ import { captureArguments as n, identifier as r, integer as i, list as a, object as o } from "./capture.js";
3
+ import { captureRankClosureReceipt as s } from "./rankClosure.js";
4
+ import { questHash as c } from "./questStore.js";
5
+ import { rankClosureUnitsTable as l } from "./rankTables.entity.js";
6
+ import { resolveRankStoreOptions as u } from "./rankStore.js";
7
+ import { Effect as d, Result as f, Schema as p } from "effect";
8
+ import { WorkflowRunHandleSchema as m } from "@voltro/protocol";
9
+ import { and as h, eq as g, gt as _, lte as v, queryFor as y } from "@voltro/database";
10
+ // src/rankClosure.server.ts
11
+ var b = (e = {}) => {
12
+ let n = o(e, [
13
+ "service",
14
+ "now",
15
+ "allowedServices"
16
+ ]), i = Object.freeze(a(n.allowedServices ?? [], 256).map(r)), s = n.now ?? Date.now;
17
+ if (typeof s != "function" || new Set(i).size !== i.length) throw t("invalid_input");
18
+ let c = n.service === void 0 ? void 0 : o(n.service);
19
+ if (c && (typeof c.execute != "function" || typeof c.dispatch != "function")) throw t("invalid_input");
20
+ let l = c ? Object.freeze({
21
+ execute: c.execute.bind(n.service),
22
+ dispatch: c.dispatch.bind(n.service)
23
+ }) : void 0;
24
+ return Object.freeze({
25
+ service: l,
26
+ now: s,
27
+ allowedServices: i
28
+ });
29
+ }, x = (e, ...t) => e + c(t).slice(0, 56), S = (e) => i(e instanceof Date ? e.getTime() : typeof e == "string" ? Date.parse(e) : NaN, 0, 864e13), C = (n) => d.tryPromise({
30
+ try: n,
31
+ catch: (n) => n instanceof e ? n : t("unavailable")
32
+ }), w = (a, w, T, E = {}) => {
33
+ let D = u(T), O = b(E), k = new Map(D.seasons.map((e) => [e.id, e])), A = new Set(O.allowedServices);
34
+ if (O.service) throw t("invalid_input");
35
+ let j = (e) => {
36
+ if (e.type !== "system" && !(e.type === "service" && A.has(e.service))) throw t("forbidden");
37
+ }, M = async (e) => {
38
+ let n = (await a.query(y(l).where(g("id", e)).limit(1).descriptor))[0];
39
+ if (!n) throw t("not_found");
40
+ let o = r(n.stateId);
41
+ if (n.id !== e || e !== x("prc_", o)) throw t("invalid_record");
42
+ if (r(n.tenantId), r(n.seasonId), i(n.version, 1, 1e6), S(n.until), S(n.createdAt), n.state === "erased") {
43
+ if (n.ownerId !== null || n.ownerKey !== null || n.receipt !== null) throw t("invalid_record");
44
+ return n;
45
+ }
46
+ let c = r(n.tenantId), u = r(n.ownerId), d = k.get(r(n.seasonId));
47
+ if (!d || d.version !== n.version || d.until !== S(n.until)) throw t("conflict");
48
+ if (o !== x("prv_", c, u, d.id, String(d.version)) || n.ownerKey !== x("prs_", c, u) || n.state !== "open" && n.state !== "closed" || n.state === "open" && n.receipt !== null) throw t("invalid_record");
49
+ if (n.state === "closed") {
50
+ let r = s(n.receipt);
51
+ if (r.unitId !== e || r.status !== "closed" || r.at < d.until || r.at < S(n.createdAt)) throw t("invalid_record");
52
+ }
53
+ return n;
54
+ }, N = async (e, n) => {
55
+ if (n.state === "erased") return s({
56
+ unitId: n.id,
57
+ status: "erased",
58
+ operationId: null,
59
+ rewardsQueued: 0,
60
+ at: S(n.createdAt)
61
+ });
62
+ if (n.state !== "closed") return;
63
+ let i = await d.runPromise(d.result(w.auditRanks(e, r(n.tenantId), r(n.ownerId))));
64
+ if (f.isFailure(i)) {
65
+ if (i.failure.code === "erased") {
66
+ let t = await M(r(n.id));
67
+ if (t.state === "erased") return N(e, t);
68
+ }
69
+ throw i.failure;
70
+ }
71
+ if (!i.success.matches) throw t("invalid_record");
72
+ let a = await M(r(n.id));
73
+ if (a.state === "erased") return N(e, a);
74
+ if (a.state !== "closed" || c(a.receipt) !== c(n.receipt)) throw t("conflict");
75
+ return s(a.receipt);
76
+ };
77
+ return Object.freeze({
78
+ execute: (i, a) => {
79
+ try {
80
+ let e = n(i);
81
+ j(e);
82
+ let s = o(a, ["unitId"]), c = r(s.unitId);
83
+ return C(async () => {
84
+ let n = await M(c), i = await N(e, n);
85
+ if (i) return i;
86
+ let a = await d.runPromise(d.result(w.close(e, {
87
+ tenantId: r(n.tenantId),
88
+ ownerId: r(n.ownerId),
89
+ seasonId: r(n.seasonId),
90
+ requestId: "rank.close:" + c
91
+ })));
92
+ if (f.isFailure(a)) {
93
+ if (a.failure.code === "erased") {
94
+ let t = await M(c), n = await N(e, t);
95
+ if (n?.status === "erased") return n;
96
+ }
97
+ throw a.failure;
98
+ }
99
+ let o = await N(e, await M(c));
100
+ if (!o || o.status === "closed" && (o.operationId !== a.success.operationId || o.rewardsQueued !== a.success.rewardsQueued || o.at !== a.success.at)) throw t("invalid_record");
101
+ return o;
102
+ });
103
+ } catch (n) {
104
+ return d.fail(n instanceof e ? n : t("invalid_input"));
105
+ }
106
+ },
107
+ dispatch: (s, c, u) => {
108
+ try {
109
+ let e = n(s);
110
+ j(e);
111
+ let f = o(c, ["limit", "after"]), b = i(f.limit ?? 100, 1, 1e3), x = f.after === void 0 ? void 0 : r(f.after);
112
+ if (typeof u != "function") throw t("invalid_input");
113
+ return C(async () => {
114
+ let e = i(O.now(), 0, 864e13), o = h(g("state", "open"), v("until", new Date(e))), s = x ? h(o, _("id", x)) : o, c = await a.query(y(l).where(s).orderBy("id", "asc").select("id").limit(b + 1).descriptor), f = c.slice(0, b), S = [], C = 0;
115
+ for (let e of f) {
116
+ let i = r(e.id);
117
+ try {
118
+ let e = p.decodeUnknownSync(m, { onExcessProperty: "error" })(n(await d.runPromise(u(Object.freeze({ unitId: i })))));
119
+ if (e.workflowName !== "progression.closeRankSeason" || !e.id.trim() || e.status === "dropped" || e.status !== "queued" && (e.executionId === null || !e.executionId.trim())) throw t("invalid_record");
120
+ C++;
121
+ } catch {
122
+ S.push(i);
123
+ }
124
+ }
125
+ return Object.freeze({
126
+ scanned: f.length,
127
+ admitted: C,
128
+ failed: Object.freeze(S),
129
+ next: c.length > b ? r(f.at(-1).id) : null
130
+ });
131
+ });
132
+ } catch (n) {
133
+ return d.fail(n instanceof e ? n : t("invalid_input"));
134
+ }
135
+ }
136
+ });
137
+ };
138
+ export { w as dataStoreRankClosure, b as resolveRankClosureOptions };
@@ -0,0 +1,34 @@
1
+ import { Literals } from 'effect/Schema';
2
+ import { NullOr } from 'effect/Schema';
3
+ import { Number as Number_2 } from 'effect/Schema';
4
+ import { Schema } from 'effect';
5
+ import { String as String_2 } from 'effect/Schema';
6
+ import { Struct } from 'effect/Schema';
7
+ import { Workflow } from 'effect/workflow/Workflow';
8
+ import { WorkflowMessagesCarrier } from '@voltro/workflow';
9
+ import { YieldableError } from 'effect/Cause';
10
+
11
+ declare class ProgressionError extends ProgressionError_base {
12
+ }
13
+
14
+ declare const ProgressionError_base: Schema.Class<ProgressionError, Schema.TaggedStruct<"ProgressionError", {
15
+ 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"]>;
16
+ }>, YieldableError>;
17
+
18
+ /** Only accepted opaque SQL unit identities enter the durable workflow journal. */
19
+ export declare const ProgressionRankClosure: Workflow<"progression.closeRankSeason", Struct<{
20
+ readonly unitId: String_2;
21
+ }>, Struct<{
22
+ readonly unitId: String_2;
23
+ readonly status: Literals<readonly ["closed", "erased"]>;
24
+ readonly operationId: NullOr<String_2>;
25
+ readonly rewardsQueued: Number_2;
26
+ readonly at: Number_2;
27
+ }>, typeof ProgressionError> & {
28
+ readonly name: "progression.closeRankSeason";
29
+ } & WorkflowMessagesCarrier<{
30
+ readonly signals: {};
31
+ readonly updates: {};
32
+ }>;
33
+
34
+ export { }
@@ -0,0 +1,15 @@
1
+ import { ProgressionError as e } from "./errors.js";
2
+ import { RankClosureReceiptSchema as t, RankClosureRequestSchema as n } from "./rankClosure.js";
3
+ import { workflow as r } from "@voltro/workflow/define";
4
+ // src/rankClosureWorkflow.ts
5
+ var i = r({
6
+ name: "progression.closeRankSeason",
7
+ payload: n,
8
+ success: t,
9
+ error: e,
10
+ idempotencyKey: ({ unitId: e }) => `progression.rank.close:${e}`,
11
+ concurrency: { limit: 4 },
12
+ suspendOnFailure: !0,
13
+ internal: !0
14
+ });
15
+ export { i as ProgressionRankClosure };
@@ -0,0 +1,56 @@
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 { captureRankRating as o, defineRankLadder as s, resolveRank as c, resolveRankStrategies as l } from "./ranks.js";
4
+ import { Effect as u } from "effect";
5
+ // src/rankReader.server.ts
6
+ var d = (t) => {
7
+ let n = a(t);
8
+ if (typeof n.myRating != "function") throw e("invalid_input");
9
+ let r = n.myRating;
10
+ return Object.freeze({ read: (n, i) => u.try({
11
+ try: () => r.call(t, n, i),
12
+ catch: () => e("unavailable")
13
+ }).pipe(u.flatten, u.mapError(() => e("unavailable")), u.flatMap((t) => u.try({
14
+ try: () => o(t, i),
15
+ catch: () => e("invalid_record")
16
+ }))) });
17
+ }, f = (d) => {
18
+ let f = a(d, [
19
+ "ladders",
20
+ "source",
21
+ "strategies",
22
+ "now"
23
+ ]), p = Object.freeze(i(f.ladders, 64).map((e) => s(e))), m = a(f.source), h = f.now ?? Date.now, g = l(f.strategies);
24
+ if (!p.length || new Set(p.map((e) => e.id)).size !== p.length || typeof m.read != "function" || typeof h != "function") throw e("invalid_input");
25
+ let _ = m.read, v = h;
26
+ return Object.freeze({ mine: (i) => {
27
+ try {
28
+ let a = t(i);
29
+ if (a.type !== "user") throw e("forbidden");
30
+ return n(a.id), n(a.tenantId), u.gen(function* () {
31
+ let t = /* @__PURE__ */ new Map();
32
+ for (let n of p) t.has(n.rating) || t.set(n.rating, yield* u.flatMap(u.try({
33
+ try: () => _.call(m, a, n.rating),
34
+ catch: () => e("unavailable")
35
+ }).pipe(u.flatten), (t) => u.try({
36
+ try: () => o(t, n.rating),
37
+ catch: () => e("invalid_record")
38
+ })));
39
+ return yield* u.try({
40
+ try: () => {
41
+ let e = r(v(), 0, 864e13);
42
+ return Object.freeze(p.map((n) => c({
43
+ ladder: n,
44
+ rating: t.get(n.rating),
45
+ at: e
46
+ }, g)));
47
+ },
48
+ catch: () => e("invalid_record")
49
+ });
50
+ });
51
+ } catch {
52
+ return u.fail(e("forbidden"));
53
+ }
54
+ } });
55
+ };
56
+ export { f as makeRankReader, d as ratingSourceFromLeaderboards };