@magicweave/core 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1561 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ CORE_VERSION: () => CORE_VERSION,
34
+ EnvTokenProvider: () => EnvTokenProvider,
35
+ MagicweaveClient: () => MagicweaveClient,
36
+ MagicweaveError: () => MagicweaveError,
37
+ NotFoundError: () => NotFoundError,
38
+ PROD_API_BASE: () => PROD_API_BASE,
39
+ ProjectGraph: () => ProjectGraph,
40
+ STAGING_API_BASE: () => STAGING_API_BASE,
41
+ StaticTokenProvider: () => StaticTokenProvider,
42
+ UnauthorizedError: () => UnauthorizedError,
43
+ ValidationError: () => ValidationError,
44
+ buildReferenceGraph: () => buildReferenceGraph,
45
+ createClient: () => createClient,
46
+ currencies: () => currencies,
47
+ decodeJwt: () => decodeJwt,
48
+ didYouMean: () => didYouMean,
49
+ environments: () => environments,
50
+ exportDefinitions: () => exportDefinitions,
51
+ impactScan: () => impactScan,
52
+ isExpired: () => isExpired,
53
+ iterManifestRefs: () => iterManifestRefs,
54
+ iterSegmentRefs: () => iterSegmentRefs,
55
+ leaderboards: () => leaderboards,
56
+ manifests: () => manifests,
57
+ orgs: () => orgs,
58
+ projects: () => projects,
59
+ releases: () => releases,
60
+ requestLoginOtp: () => requestLoginOtp,
61
+ resolveBaseUrl: () => resolveBaseUrl,
62
+ segments: () => segments,
63
+ spec: () => spec_exports,
64
+ stats: () => stats,
65
+ unwrap: () => unwrap,
66
+ validateSpec: () => validateSpec,
67
+ verifyLoginOtp: () => verifyLoginOtp,
68
+ wheels: () => wheels
69
+ });
70
+ module.exports = __toCommonJS(index_exports);
71
+
72
+ // src/client.ts
73
+ var import_openapi_fetch = __toESM(require("openapi-fetch"), 1);
74
+ var STAGING_API_BASE = "https://console.staging.magicweave.xyz/api";
75
+ var PROD_API_BASE = "https://console.magicweave.xyz/api";
76
+ function resolveBaseUrl(explicit) {
77
+ return explicit ?? process.env.MW_API_BASE ?? STAGING_API_BASE;
78
+ }
79
+ var MagicweaveError = class extends Error {
80
+ constructor(message, status, code, detail) {
81
+ super(message);
82
+ this.status = status;
83
+ this.code = code;
84
+ this.detail = detail;
85
+ this.name = "MagicweaveError";
86
+ }
87
+ status;
88
+ code;
89
+ detail;
90
+ };
91
+ var UnauthorizedError = class extends MagicweaveError {
92
+ constructor(detail) {
93
+ super(
94
+ "Not authenticated or your session expired. Run `mw login`.",
95
+ 401,
96
+ "unauthorized",
97
+ detail
98
+ );
99
+ this.name = "UnauthorizedError";
100
+ }
101
+ };
102
+ var NotFoundError = class extends MagicweaveError {
103
+ constructor(detail) {
104
+ super("Not found.", 404, "not_found", detail);
105
+ this.name = "NotFoundError";
106
+ }
107
+ };
108
+ var ValidationError = class extends MagicweaveError {
109
+ constructor(detail) {
110
+ super(formatValidationDetail(detail), 422, "validation_error", detail);
111
+ this.name = "ValidationError";
112
+ }
113
+ };
114
+ function formatValidationDetail(detail) {
115
+ const list = detail?.detail;
116
+ if (Array.isArray(list) && list.length) {
117
+ return list.map((e) => {
118
+ const field = Array.isArray(e.loc) ? e.loc.filter((p) => p !== "body").join(".") : "";
119
+ return field ? `${field}: ${e.msg}` : e.msg ?? "invalid";
120
+ }).join("; ");
121
+ }
122
+ if (typeof detail?.detail === "string") {
123
+ return detail.detail;
124
+ }
125
+ return "Request validation failed.";
126
+ }
127
+ function errorFromResponse(response, body) {
128
+ switch (response.status) {
129
+ case 401:
130
+ return new UnauthorizedError(body);
131
+ case 404:
132
+ return new NotFoundError(body);
133
+ case 422:
134
+ return new ValidationError(body);
135
+ default: {
136
+ const msg = body?.detail ?? `Request failed: ${response.status} ${response.statusText}`;
137
+ return new MagicweaveError(
138
+ typeof msg === "string" ? msg : `Request failed: ${response.status}`,
139
+ response.status,
140
+ "http_error",
141
+ body
142
+ );
143
+ }
144
+ }
145
+ }
146
+ async function unwrap(promise) {
147
+ const { data, error, response } = await promise;
148
+ if (response.ok) return data;
149
+ throw errorFromResponse(response, error ?? data);
150
+ }
151
+ var MagicweaveClient = class {
152
+ raw;
153
+ baseUrl;
154
+ constructor(opts = {}) {
155
+ this.baseUrl = resolveBaseUrl(opts.baseUrl);
156
+ const tokenProvider = opts.tokenProvider;
157
+ const auth = {
158
+ async onRequest({ request }) {
159
+ const token = tokenProvider ? await tokenProvider.getToken() : null;
160
+ if (token) request.headers.set("Authorization", `Bearer ${token}`);
161
+ return request;
162
+ }
163
+ };
164
+ this.raw = (0, import_openapi_fetch.default)({
165
+ baseUrl: this.baseUrl,
166
+ fetch: opts.fetch
167
+ });
168
+ this.raw.use(auth);
169
+ }
170
+ };
171
+ function createClient(opts = {}) {
172
+ return new MagicweaveClient(opts);
173
+ }
174
+
175
+ // src/auth.ts
176
+ async function requestLoginOtp(baseUrl, email, opts = {}) {
177
+ const f = opts.fetch ?? globalThis.fetch;
178
+ const res = await f(`${baseUrl}/auth/login-email/`, {
179
+ method: "POST",
180
+ headers: { "Content-Type": "application/json" },
181
+ body: JSON.stringify({ email })
182
+ });
183
+ if (!res.ok) {
184
+ throw new MagicweaveError(
185
+ `Could not send a login code to ${email} (HTTP ${res.status}).`,
186
+ res.status,
187
+ "otp_request_failed"
188
+ );
189
+ }
190
+ }
191
+ async function verifyLoginOtp(baseUrl, email, otp, opts = {}) {
192
+ const f = opts.fetch ?? globalThis.fetch;
193
+ const res = await f(`${baseUrl}/auth/verify-email-otp/`, {
194
+ method: "POST",
195
+ headers: { "Content-Type": "application/json" },
196
+ body: JSON.stringify({ email, otp })
197
+ });
198
+ const body = await res.json().catch(() => ({}));
199
+ if (!res.ok || !body.access_token || !body.refresh_token) {
200
+ throw new MagicweaveError(
201
+ body.detail ?? "That code was not accepted. Check it and try again.",
202
+ res.status,
203
+ "otp_verify_failed",
204
+ body
205
+ );
206
+ }
207
+ return { access_token: body.access_token, refresh_token: body.refresh_token };
208
+ }
209
+ function decodeJwt(token) {
210
+ const part = token.split(".")[1];
211
+ if (!part) return null;
212
+ try {
213
+ return JSON.parse(Buffer.from(part, "base64url").toString("utf8"));
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+ function isExpired(token, skewSeconds = 30) {
219
+ const exp = decodeJwt(token)?.exp;
220
+ if (!exp) return true;
221
+ return exp * 1e3 <= Date.now() + skewSeconds * 1e3;
222
+ }
223
+ var StaticTokenProvider = class {
224
+ constructor(token) {
225
+ this.token = token;
226
+ }
227
+ token;
228
+ getToken() {
229
+ return this.token;
230
+ }
231
+ };
232
+ var EnvTokenProvider = class {
233
+ getToken() {
234
+ return process.env.MW_ACCESS_TOKEN ?? null;
235
+ }
236
+ };
237
+
238
+ // src/resources/orgs.ts
239
+ function orgs(client) {
240
+ return {
241
+ list: () => unwrap(client.raw.GET("/org/")),
242
+ create: (name) => unwrap(client.raw.POST("/org/", { body: { name } })),
243
+ remove: (orgId) => unwrap(client.raw.DELETE("/org/{org_id}/", { params: { path: { org_id: orgId } } }))
244
+ };
245
+ }
246
+
247
+ // src/resources/projects.ts
248
+ function projects(client) {
249
+ return {
250
+ list: (orgId) => unwrap(client.raw.GET("/project/{org_id}", { params: { path: { org_id: orgId } } })),
251
+ create: (orgId, name) => unwrap(
252
+ client.raw.POST("/project/{org_id}", {
253
+ params: { path: { org_id: orgId } },
254
+ body: { name }
255
+ })
256
+ ),
257
+ remove: (orgId, projectId) => unwrap(
258
+ client.raw.DELETE("/project/{org_id}/{project_id}/", {
259
+ params: { path: { org_id: orgId, project_id: projectId } }
260
+ })
261
+ ),
262
+ enableNetwork: (orgId, projectId) => unwrap(
263
+ client.raw.POST("/project/{org_id}/{project_id}/enable-network", {
264
+ params: { path: { org_id: orgId, project_id: projectId } }
265
+ })
266
+ )
267
+ };
268
+ }
269
+
270
+ // src/resources/environments.ts
271
+ function environments(client) {
272
+ const base = (orgId, projectId) => ({
273
+ path: { org_id: orgId, project_id: projectId }
274
+ });
275
+ const withEnv = (orgId, projectId, environmentId) => ({
276
+ path: { org_id: orgId, project_id: projectId, environment_id: environmentId }
277
+ });
278
+ return {
279
+ list: (orgId, projectId) => unwrap(
280
+ client.raw.GET("/project/{org_id}/{project_id}/environments/", {
281
+ params: base(orgId, projectId)
282
+ })
283
+ ),
284
+ create: (orgId, projectId, name) => unwrap(
285
+ client.raw.POST("/project/{org_id}/{project_id}/environments/", {
286
+ params: base(orgId, projectId),
287
+ body: { name }
288
+ })
289
+ ),
290
+ remove: (orgId, projectId, environmentId) => unwrap(
291
+ client.raw.DELETE("/project/{org_id}/{project_id}/environments/{environment_id}", {
292
+ params: withEnv(orgId, projectId, environmentId)
293
+ })
294
+ ),
295
+ rotateSecret: (orgId, projectId, environmentId) => unwrap(
296
+ client.raw.POST(
297
+ "/project/{org_id}/{project_id}/environments/{environment_id}/rotate-secret",
298
+ {
299
+ params: withEnv(orgId, projectId, environmentId)
300
+ }
301
+ )
302
+ ),
303
+ rotatePreviewSecret: (orgId, projectId, environmentId) => unwrap(
304
+ client.raw.POST(
305
+ "/project/{org_id}/{project_id}/environments/{environment_id}/rotate-preview-secret",
306
+ { params: withEnv(orgId, projectId, environmentId) }
307
+ )
308
+ )
309
+ };
310
+ }
311
+
312
+ // src/resources/releases.ts
313
+ function releases(client) {
314
+ const rel = (orgId, projectId, releaseId) => ({
315
+ path: { org_id: orgId, project_id: projectId, release_id: releaseId }
316
+ });
317
+ return {
318
+ list: (orgId, projectId, environmentId) => unwrap(
319
+ client.raw.GET("/project/{org_id}/{project_id}/environments/{environment_id}/releases/", {
320
+ params: { path: { org_id: orgId, project_id: projectId, environment_id: environmentId } }
321
+ })
322
+ ),
323
+ create: (orgId, projectId, environmentId, body = { release_notes: "" }) => unwrap(
324
+ client.raw.POST("/project/{org_id}/{project_id}/environments/{environment_id}/releases/", {
325
+ params: { path: { org_id: orgId, project_id: projectId, environment_id: environmentId } },
326
+ body
327
+ })
328
+ ),
329
+ update: (orgId, projectId, releaseId, body) => unwrap(
330
+ client.raw.PATCH("/project/{org_id}/{project_id}/releases/{release_id}", {
331
+ params: rel(orgId, projectId, releaseId),
332
+ body
333
+ })
334
+ ),
335
+ remove: (orgId, projectId, releaseId) => unwrap(
336
+ client.raw.DELETE("/project/{org_id}/{project_id}/releases/{release_id}", {
337
+ params: rel(orgId, projectId, releaseId)
338
+ })
339
+ ),
340
+ pushItem: (orgId, projectId, releaseId, body) => unwrap(
341
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/items/", {
342
+ params: rel(orgId, projectId, releaseId),
343
+ body
344
+ })
345
+ ),
346
+ removeItem: (orgId, projectId, releaseId, itemId) => unwrap(
347
+ client.raw.DELETE("/project/{org_id}/{project_id}/releases/{release_id}/items/{item_id}", {
348
+ params: {
349
+ path: { org_id: orgId, project_id: projectId, release_id: releaseId, item_id: itemId }
350
+ }
351
+ })
352
+ ),
353
+ lock: (orgId, projectId, releaseId) => unwrap(
354
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/lock", {
355
+ params: rel(orgId, projectId, releaseId)
356
+ })
357
+ ),
358
+ activate: (orgId, projectId, releaseId) => unwrap(
359
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/activate", {
360
+ params: rel(orgId, projectId, releaseId)
361
+ })
362
+ ),
363
+ deactivate: (orgId, projectId, releaseId) => unwrap(
364
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/deactivate", {
365
+ params: rel(orgId, projectId, releaseId)
366
+ })
367
+ ),
368
+ move: (orgId, projectId, releaseId, targetEnvironmentId, note = "") => unwrap(
369
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/move", {
370
+ params: rel(orgId, projectId, releaseId),
371
+ body: { target_environment_id: targetEnvironmentId, note }
372
+ })
373
+ ),
374
+ copy: (orgId, projectId, releaseId, targetEnvironmentId, note = "") => unwrap(
375
+ client.raw.POST("/project/{org_id}/{project_id}/releases/{release_id}/copy", {
376
+ params: rel(orgId, projectId, releaseId),
377
+ body: { target_environment_id: targetEnvironmentId, note }
378
+ })
379
+ )
380
+ };
381
+ }
382
+
383
+ // src/resources/currencies.ts
384
+ function currencies(client) {
385
+ const proj = (orgId, projectId) => ({
386
+ path: { org_id: orgId, project_id: projectId }
387
+ });
388
+ const one = (orgId, projectId, definitionId) => ({
389
+ path: { org_id: orgId, project_id: projectId, definition_id: definitionId }
390
+ });
391
+ return {
392
+ list: (orgId, projectId) => unwrap(
393
+ client.raw.GET("/currency-definitions/{org_id}/{project_id}/", {
394
+ params: proj(orgId, projectId)
395
+ })
396
+ ),
397
+ overview: (orgId, projectId) => unwrap(
398
+ client.raw.GET("/currency-definitions/{org_id}/{project_id}/overview/", {
399
+ params: proj(orgId, projectId)
400
+ })
401
+ ),
402
+ get: (orgId, projectId, definitionId) => unwrap(
403
+ client.raw.GET("/currency-definitions/{org_id}/{project_id}/{definition_id}/", {
404
+ params: one(orgId, projectId, definitionId)
405
+ })
406
+ ),
407
+ create: (orgId, projectId, body) => unwrap(
408
+ client.raw.POST("/currency-definitions/{org_id}/{project_id}/", {
409
+ params: proj(orgId, projectId),
410
+ body
411
+ })
412
+ ),
413
+ update: (orgId, projectId, definitionId, body) => unwrap(
414
+ client.raw.PATCH("/currency-definitions/{org_id}/{project_id}/{definition_id}/", {
415
+ params: one(orgId, projectId, definitionId),
416
+ body
417
+ })
418
+ ),
419
+ remove: (orgId, projectId, definitionId) => unwrap(
420
+ client.raw.DELETE("/currency-definitions/{org_id}/{project_id}/{definition_id}/", {
421
+ params: one(orgId, projectId, definitionId)
422
+ })
423
+ )
424
+ };
425
+ }
426
+
427
+ // src/resources/stats.ts
428
+ function stats(client) {
429
+ const proj = (orgId, projectId) => ({
430
+ path: { org_id: orgId, project_id: projectId }
431
+ });
432
+ const one = (orgId, projectId, definitionId) => ({
433
+ path: { org_id: orgId, project_id: projectId, definition_id: definitionId }
434
+ });
435
+ return {
436
+ list: (orgId, projectId) => unwrap(
437
+ client.raw.GET("/stat-definitions/{org_id}/{project_id}/", {
438
+ params: proj(orgId, projectId)
439
+ })
440
+ ),
441
+ overview: (orgId, projectId) => unwrap(
442
+ client.raw.GET("/stat-definitions/{org_id}/{project_id}/overview/", {
443
+ params: proj(orgId, projectId)
444
+ })
445
+ ),
446
+ get: (orgId, projectId, definitionId) => unwrap(
447
+ client.raw.GET("/stat-definitions/{org_id}/{project_id}/{definition_id}/", {
448
+ params: one(orgId, projectId, definitionId)
449
+ })
450
+ ),
451
+ create: (orgId, projectId, body) => unwrap(
452
+ client.raw.POST("/stat-definitions/{org_id}/{project_id}/", {
453
+ params: proj(orgId, projectId),
454
+ body
455
+ })
456
+ ),
457
+ update: (orgId, projectId, definitionId, body) => unwrap(
458
+ client.raw.PATCH("/stat-definitions/{org_id}/{project_id}/{definition_id}/", {
459
+ params: one(orgId, projectId, definitionId),
460
+ body
461
+ })
462
+ ),
463
+ remove: (orgId, projectId, definitionId) => unwrap(
464
+ client.raw.DELETE("/stat-definitions/{org_id}/{project_id}/{definition_id}/", {
465
+ params: one(orgId, projectId, definitionId)
466
+ })
467
+ )
468
+ };
469
+ }
470
+
471
+ // src/resources/manifests.ts
472
+ function manifests(client) {
473
+ const proj = (orgId, projectId) => ({
474
+ path: { org_id: orgId, project_id: projectId }
475
+ });
476
+ const one = (orgId, projectId, manifestId) => ({
477
+ path: { org_id: orgId, project_id: projectId, manifest_id: manifestId }
478
+ });
479
+ return {
480
+ list: (orgId, projectId) => unwrap(
481
+ client.raw.GET("/game-manifests/{org_id}/{project_id}/", {
482
+ params: proj(orgId, projectId)
483
+ })
484
+ ),
485
+ overview: (orgId, projectId) => unwrap(
486
+ client.raw.GET("/game-manifests/{org_id}/{project_id}/overview/", {
487
+ params: proj(orgId, projectId)
488
+ })
489
+ ),
490
+ get: (orgId, projectId, manifestId) => unwrap(
491
+ client.raw.GET("/game-manifests/{org_id}/{project_id}/{manifest_id}/", {
492
+ params: one(orgId, projectId, manifestId)
493
+ })
494
+ ),
495
+ create: (orgId, projectId, body) => unwrap(
496
+ client.raw.POST("/game-manifests/{org_id}/{project_id}/", {
497
+ params: proj(orgId, projectId),
498
+ body
499
+ })
500
+ ),
501
+ update: (orgId, projectId, manifestId, body) => unwrap(
502
+ client.raw.PATCH("/game-manifests/{org_id}/{project_id}/{manifest_id}/", {
503
+ params: one(orgId, projectId, manifestId),
504
+ body
505
+ })
506
+ ),
507
+ remove: (orgId, projectId, manifestId) => unwrap(
508
+ client.raw.DELETE("/game-manifests/{org_id}/{project_id}/{manifest_id}/", {
509
+ params: one(orgId, projectId, manifestId)
510
+ })
511
+ )
512
+ };
513
+ }
514
+
515
+ // src/resources/leaderboards.ts
516
+ function leaderboards(client) {
517
+ const proj = (orgId, projectId) => ({
518
+ path: { org_id: orgId, project_id: projectId }
519
+ });
520
+ const one = (orgId, projectId, leaderboardId) => ({
521
+ path: { org_id: orgId, project_id: projectId, leaderboard_id: leaderboardId }
522
+ });
523
+ return {
524
+ list: (orgId, projectId) => unwrap(
525
+ client.raw.GET("/leaderboards/{org_id}/{project_id}/", { params: proj(orgId, projectId) })
526
+ ),
527
+ overview: (orgId, projectId) => unwrap(
528
+ client.raw.GET("/leaderboards/{org_id}/{project_id}/overview/", {
529
+ params: proj(orgId, projectId)
530
+ })
531
+ ),
532
+ get: (orgId, projectId, leaderboardId) => unwrap(
533
+ client.raw.GET("/leaderboards/{org_id}/{project_id}/{leaderboard_id}/", {
534
+ params: one(orgId, projectId, leaderboardId)
535
+ })
536
+ ),
537
+ create: (orgId, projectId, body) => unwrap(
538
+ client.raw.POST("/leaderboards/{org_id}/{project_id}/", {
539
+ params: proj(orgId, projectId),
540
+ body
541
+ })
542
+ ),
543
+ update: (orgId, projectId, leaderboardId, body) => unwrap(
544
+ client.raw.PATCH("/leaderboards/{org_id}/{project_id}/{leaderboard_id}/", {
545
+ params: one(orgId, projectId, leaderboardId),
546
+ body
547
+ })
548
+ ),
549
+ remove: (orgId, projectId, leaderboardId) => unwrap(
550
+ client.raw.DELETE("/leaderboards/{org_id}/{project_id}/{leaderboard_id}/", {
551
+ params: one(orgId, projectId, leaderboardId)
552
+ })
553
+ ),
554
+ /** Irreversible: archive scores and reset the board. Confirm-gate in the CLI. */
555
+ processReset: (orgId, projectId, leaderboardId) => unwrap(
556
+ client.raw.POST("/leaderboards/{org_id}/{project_id}/{leaderboard_id}/process-reset/", {
557
+ params: one(orgId, projectId, leaderboardId)
558
+ })
559
+ )
560
+ };
561
+ }
562
+
563
+ // src/resources/segments.ts
564
+ function segments(client) {
565
+ const proj = (orgId, projectId) => ({
566
+ path: { org_id: orgId, project_id: projectId }
567
+ });
568
+ const one = (orgId, projectId, segmentId) => ({
569
+ path: { org_id: orgId, project_id: projectId, segment_id: segmentId }
570
+ });
571
+ return {
572
+ list: (orgId, projectId) => unwrap(
573
+ client.raw.GET("/spin-wheel/{org_id}/{project_id}/segments/", {
574
+ params: proj(orgId, projectId)
575
+ })
576
+ ),
577
+ catalog: (orgId, projectId) => unwrap(
578
+ client.raw.GET("/spin-wheel/{org_id}/{project_id}/segments/catalog/", {
579
+ params: proj(orgId, projectId)
580
+ })
581
+ ),
582
+ get: (orgId, projectId, segmentId) => unwrap(
583
+ client.raw.GET("/spin-wheel/{org_id}/{project_id}/segments/{segment_id}/", {
584
+ params: one(orgId, projectId, segmentId)
585
+ })
586
+ ),
587
+ create: (orgId, projectId, body) => unwrap(
588
+ client.raw.POST("/spin-wheel/{org_id}/{project_id}/segments/", {
589
+ params: proj(orgId, projectId),
590
+ body
591
+ })
592
+ ),
593
+ update: (orgId, projectId, segmentId, body) => unwrap(
594
+ client.raw.PATCH("/spin-wheel/{org_id}/{project_id}/segments/{segment_id}/", {
595
+ params: one(orgId, projectId, segmentId),
596
+ body
597
+ })
598
+ ),
599
+ remove: (orgId, projectId, segmentId) => unwrap(
600
+ client.raw.DELETE("/spin-wheel/{org_id}/{project_id}/segments/{segment_id}/", {
601
+ params: one(orgId, projectId, segmentId)
602
+ })
603
+ )
604
+ };
605
+ }
606
+
607
+ // src/resources/wheels.ts
608
+ function wheels(client) {
609
+ const proj = (orgId, projectId) => ({
610
+ path: { org_id: orgId, project_id: projectId }
611
+ });
612
+ const one = (orgId, projectId, wheelConfigId) => ({
613
+ path: { org_id: orgId, project_id: projectId, wheel_config_id: wheelConfigId }
614
+ });
615
+ return {
616
+ list: (orgId, projectId) => unwrap(
617
+ client.raw.GET("/spin-wheel/{org_id}/{project_id}/wheels/", {
618
+ params: proj(orgId, projectId)
619
+ })
620
+ ),
621
+ overview: (orgId, projectId) => unwrap(
622
+ client.raw.GET("/spin-wheel/{org_id}/{project_id}/wheels/overview/", {
623
+ params: proj(orgId, projectId)
624
+ })
625
+ ),
626
+ create: (orgId, projectId, body) => unwrap(
627
+ client.raw.POST("/spin-wheel/{org_id}/{project_id}/wheels/", {
628
+ params: proj(orgId, projectId),
629
+ body
630
+ })
631
+ ),
632
+ update: (orgId, projectId, wheelConfigId, body) => unwrap(
633
+ client.raw.PATCH("/spin-wheel/{org_id}/{project_id}/wheels/{wheel_config_id}/", {
634
+ params: one(orgId, projectId, wheelConfigId),
635
+ body
636
+ })
637
+ ),
638
+ remove: (orgId, projectId, wheelConfigId) => unwrap(
639
+ client.raw.DELETE("/spin-wheel/{org_id}/{project_id}/wheels/{wheel_config_id}/", {
640
+ params: one(orgId, projectId, wheelConfigId)
641
+ })
642
+ )
643
+ };
644
+ }
645
+
646
+ // src/schemas/spec.ts
647
+ var spec_exports = {};
648
+ __export(spec_exports, {
649
+ Condition: () => Condition,
650
+ ConditionSet: () => ConditionSet,
651
+ CurrencySpec: () => CurrencySpec,
652
+ EconomySpec: () => EconomySpec,
653
+ EntryRequirement: () => EntryRequirement,
654
+ Formula: () => Formula,
655
+ LeaderboardSpec: () => LeaderboardSpec,
656
+ ManifestSpec: () => ManifestSpec,
657
+ Outcome: () => Outcome,
658
+ Reward: () => Reward,
659
+ SegmentSpec: () => SegmentSpec,
660
+ StatSpec: () => StatSpec,
661
+ Step: () => Step,
662
+ WheelSegmentLink: () => WheelSegmentLink,
663
+ WheelSpec: () => WheelSpec
664
+ });
665
+ var import_zod = require("zod");
666
+ var CurrencySpec = import_zod.z.object({
667
+ key: import_zod.z.string(),
668
+ name: import_zod.z.string(),
669
+ symbol: import_zod.z.string().default(""),
670
+ initial_balance: import_zod.z.number().int().default(0),
671
+ max_balance: import_zod.z.number().int().nullable().default(null),
672
+ usable_as_reward: import_zod.z.boolean().default(true),
673
+ usable_in_conditions: import_zod.z.boolean().default(true)
674
+ });
675
+ var StatSpec = import_zod.z.object({
676
+ key: import_zod.z.string(),
677
+ name: import_zod.z.string(),
678
+ stat_type: import_zod.z.string(),
679
+ // integer | float | boolean
680
+ default_value: import_zod.z.union([import_zod.z.number(), import_zod.z.boolean()]),
681
+ min_value: import_zod.z.number().nullable().default(null),
682
+ max_value: import_zod.z.number().nullable().default(null),
683
+ usable_in_conditions: import_zod.z.boolean().default(true),
684
+ usable_as_reward: import_zod.z.boolean().default(true)
685
+ });
686
+ var Formula = import_zod.z.object({
687
+ formula: import_zod.z.string().nullish(),
688
+ max: import_zod.z.number().nullish()
689
+ });
690
+ var Outcome = import_zod.z.object({
691
+ reward_type: import_zod.z.string().nullish(),
692
+ // fixed | variable
693
+ reward_target: import_zod.z.string().nullish(),
694
+ // currency | stat | gem
695
+ currency_key: import_zod.z.string().nullish(),
696
+ stat_key: import_zod.z.string().nullish(),
697
+ operation: import_zod.z.string().nullish(),
698
+ // add | subtract | set | multiply
699
+ value: import_zod.z.number().nullish(),
700
+ formula: Formula.nullish()
701
+ });
702
+ var Condition = import_zod.z.object({
703
+ payload_key: import_zod.z.string().nullish(),
704
+ operator: import_zod.z.string().nullish(),
705
+ // eq | neq | gt | gte | lt | lte
706
+ expected_value: import_zod.z.union([import_zod.z.number(), import_zod.z.boolean(), import_zod.z.string()]).nullish()
707
+ });
708
+ var ConditionSet = import_zod.z.object({
709
+ conditions: import_zod.z.array(Condition).default([]),
710
+ outcomes: import_zod.z.array(Outcome).default([])
711
+ });
712
+ var Step = import_zod.z.object({
713
+ key: import_zod.z.string().nullish(),
714
+ // optional so the validator, not Zod, reports "missing key"
715
+ name: import_zod.z.string().default(""),
716
+ is_entry: import_zod.z.boolean().default(false),
717
+ is_terminal: import_zod.z.boolean().default(false),
718
+ next_step_keys: import_zod.z.array(import_zod.z.string()).default([]),
719
+ condition_sets: import_zod.z.array(ConditionSet).default([])
720
+ });
721
+ var EntryRequirement = import_zod.z.object({
722
+ requirement_type: import_zod.z.string().nullish(),
723
+ // entry_cost | eligibility
724
+ value_source: import_zod.z.string().nullish(),
725
+ // currency | stat
726
+ currency_key: import_zod.z.string().nullish(),
727
+ stat_key: import_zod.z.string().nullish(),
728
+ operator: import_zod.z.string().nullish(),
729
+ // gte | lte | eq (eligibility)
730
+ threshold_value: import_zod.z.union([import_zod.z.number(), import_zod.z.boolean()]).nullish(),
731
+ cost_amount: import_zod.z.number().int().nullish()
732
+ // REQUIRED by the admin API for entry_cost
733
+ });
734
+ var ManifestSpec = import_zod.z.object({
735
+ slug: import_zod.z.string(),
736
+ name: import_zod.z.string(),
737
+ entry_requirements: import_zod.z.array(EntryRequirement).default([]),
738
+ steps: import_zod.z.array(Step)
739
+ });
740
+ var LeaderboardSpec = import_zod.z.object({
741
+ slug: import_zod.z.string(),
742
+ name: import_zod.z.string(),
743
+ stat_key: import_zod.z.string(),
744
+ // authored by key; resolved to stat_id at create time
745
+ sort_order: import_zod.z.string().default("desc"),
746
+ leaderboard_type: import_zod.z.string().default("relative"),
747
+ reset_type: import_zod.z.string().nullable().default(null),
748
+ soft_reset_percentage: import_zod.z.number().nullish()
749
+ });
750
+ var Reward = import_zod.z.object({
751
+ reward_type: import_zod.z.string().nullish(),
752
+ // currency | stat | gem
753
+ currency_key: import_zod.z.string().nullish(),
754
+ stat_key: import_zod.z.string().nullish(),
755
+ amount: import_zod.z.number().int().nullish()
756
+ });
757
+ var SegmentSpec = import_zod.z.object({
758
+ label: import_zod.z.string(),
759
+ reward: Reward,
760
+ probability: import_zod.z.number().int(),
761
+ can_override: import_zod.z.boolean().default(false)
762
+ });
763
+ var WheelSegmentLink = import_zod.z.object({
764
+ segment_ref: import_zod.z.string(),
765
+ // label of a segment defined in this spec
766
+ probability_override: import_zod.z.number().int().nullish()
767
+ });
768
+ var WheelSpec = import_zod.z.object({
769
+ key_slug: import_zod.z.string(),
770
+ name: import_zod.z.string(),
771
+ daily_free_spins: import_zod.z.number().int().default(1),
772
+ allow_paid_spins: import_zod.z.boolean().default(false),
773
+ paid_spin_currency_key: import_zod.z.string().default(""),
774
+ paid_spin_currency_amount: import_zod.z.number().int().nullish(),
775
+ segments: import_zod.z.array(WheelSegmentLink).default([])
776
+ });
777
+ var EconomySpec = import_zod.z.object({
778
+ currencies: import_zod.z.array(CurrencySpec).default([]),
779
+ stats: import_zod.z.array(StatSpec).default([]),
780
+ manifests: import_zod.z.array(ManifestSpec).default([]),
781
+ leaderboards: import_zod.z.array(LeaderboardSpec).default([]),
782
+ segments: import_zod.z.array(SegmentSpec).default([]),
783
+ wheels: import_zod.z.array(WheelSpec).default([])
784
+ });
785
+
786
+ // src/smart/export.ts
787
+ async function exportDefinitions(client, orgId, projectId) {
788
+ const [currencyList, statList, manifestList, leaderboardList, segmentList, wheelList] = await Promise.all([
789
+ currencies(client).list(orgId, projectId),
790
+ stats(client).list(orgId, projectId),
791
+ manifests(client).list(orgId, projectId),
792
+ leaderboards(client).list(orgId, projectId),
793
+ segments(client).list(orgId, projectId),
794
+ wheels(client).list(orgId, projectId)
795
+ ]);
796
+ return {
797
+ org_id: orgId,
798
+ project_id: projectId,
799
+ currencies: currencyList,
800
+ stats: statList,
801
+ manifests: manifestList,
802
+ leaderboards: leaderboardList,
803
+ segments: segmentList,
804
+ wheels: wheelList
805
+ };
806
+ }
807
+
808
+ // src/smart/refgraph.ts
809
+ function didYouMean(needle, candidates) {
810
+ let best = null;
811
+ let bestRatio = 0;
812
+ for (const c of candidates) {
813
+ const r = similarity(needle, c);
814
+ if (r > bestRatio) {
815
+ bestRatio = r;
816
+ best = c;
817
+ }
818
+ }
819
+ return bestRatio >= 0.6 ? best : null;
820
+ }
821
+ function similarity(a, b) {
822
+ if (a === b) return 1;
823
+ if (!a.length || !b.length) return 0;
824
+ return 2 * lcsLength(a, b) / (a.length + b.length);
825
+ }
826
+ function lcsLength(a, b) {
827
+ const m = a.length;
828
+ const n = b.length;
829
+ const row = new Array(n + 1).fill(0);
830
+ for (let i = 1; i <= m; i++) {
831
+ let prevDiag = 0;
832
+ for (let j = 1; j <= n; j++) {
833
+ const tmp = row[j];
834
+ row[j] = a[i - 1] === b[j - 1] ? prevDiag + 1 : Math.max(row[j], row[j - 1]);
835
+ prevDiag = tmp;
836
+ }
837
+ }
838
+ return row[n];
839
+ }
840
+ function str(v) {
841
+ return typeof v === "string" && v ? v : void 0;
842
+ }
843
+ function* iterManifestRefs(manifest) {
844
+ for (const req of manifest.entry_requirements ?? []) {
845
+ if (typeof req !== "object" || req === null) continue;
846
+ const ck = str(req.currency_key);
847
+ if (ck) yield ["currency", ck, "entry requirement"];
848
+ const sk = str(req.stat_key);
849
+ if (sk) yield ["stat", sk, "entry requirement"];
850
+ }
851
+ for (const step of manifest.steps ?? []) {
852
+ if (typeof step !== "object" || step === null) continue;
853
+ const skey = str(step.key) ?? "?";
854
+ for (const cs of step.condition_sets ?? []) {
855
+ if (typeof cs !== "object" || cs === null) continue;
856
+ for (const out of cs.outcomes ?? []) {
857
+ if (typeof out !== "object" || out === null) continue;
858
+ const target = out.reward_target;
859
+ if (target === "currency") {
860
+ const ck = str(out.currency_key);
861
+ if (ck) yield ["currency", ck, `outcome in step '${skey}'`];
862
+ } else if (target === "stat") {
863
+ const sk = str(out.stat_key);
864
+ if (sk) yield ["stat", sk, `outcome in step '${skey}'`];
865
+ }
866
+ }
867
+ }
868
+ }
869
+ }
870
+ function* iterSegmentRefs(segment) {
871
+ const reward = segment.reward ?? {};
872
+ if (reward.reward_type === "currency") {
873
+ const ck = str(reward.currency_key);
874
+ if (ck) yield ["currency", ck, "segment reward"];
875
+ } else if (reward.reward_type === "stat") {
876
+ const sk = str(reward.stat_key);
877
+ if (sk) yield ["stat", sk, "segment reward"];
878
+ }
879
+ }
880
+ function wheelViews(wheel) {
881
+ const eff = wheel.effective ?? {};
882
+ const cfg = wheel.wheel_config ?? {};
883
+ if (Object.keys(eff).length === 0 && Object.keys(cfg).length === 0) return [wheel, wheel];
884
+ return [eff, cfg];
885
+ }
886
+ var ProjectGraph = class {
887
+ constructor(currencies2, stats2, manifests2, leaderboards2, segments2, wheels2) {
888
+ this.currencies = currencies2;
889
+ this.stats = stats2;
890
+ this.manifests = manifests2;
891
+ this.leaderboards = leaderboards2;
892
+ this.segments = segments2;
893
+ this.wheels = wheels2;
894
+ }
895
+ currencies;
896
+ stats;
897
+ manifests;
898
+ leaderboards;
899
+ segments;
900
+ wheels;
901
+ currencyKeys() {
902
+ return this.currencies.map((c) => str(c.key)).filter((k) => !!k);
903
+ }
904
+ statKeys() {
905
+ return this.stats.map((s) => str(s.key)).filter((k) => !!k);
906
+ }
907
+ segmentIds() {
908
+ return this.segments.map((s) => s.id).filter((v) => typeof v === "number");
909
+ }
910
+ statIdForKey(key) {
911
+ for (const s of this.stats) if (s.key === key) return s.id ?? null;
912
+ return null;
913
+ }
914
+ /** Inbound references (blast radius) to a currency/stat (by key) or segment (by id). */
915
+ referencesTo(kind, ref) {
916
+ if (kind === "segment") return this.refsToSegment(Number(ref));
917
+ return this.refsToDefinition(kind, String(ref));
918
+ }
919
+ refsToDefinition(targetKind, key) {
920
+ const found = [];
921
+ for (const m of this.manifests) {
922
+ for (const [tk, refKey, locus] of iterManifestRefs(m)) {
923
+ if (tk === targetKind && refKey === key) {
924
+ found.push({
925
+ referencingKind: "manifest",
926
+ id: m.id ?? null,
927
+ name: str(m.slug) ?? "?",
928
+ locus
929
+ });
930
+ }
931
+ }
932
+ }
933
+ for (const seg of this.segments) {
934
+ for (const [tk, refKey] of iterSegmentRefs(seg)) {
935
+ if (tk === targetKind && refKey === key) {
936
+ found.push({
937
+ referencingKind: "segment",
938
+ id: seg.id ?? null,
939
+ name: str(seg.label) ?? "?",
940
+ locus: "segment reward"
941
+ });
942
+ }
943
+ }
944
+ }
945
+ if (targetKind === "currency") {
946
+ for (const w of this.wheels) {
947
+ const [eff, cfg] = wheelViews(w);
948
+ const paid = str(cfg.paid_spin_currency_key) ?? str(eff.paid_spin_currency_key);
949
+ if (paid === key) {
950
+ found.push({
951
+ referencingKind: "wheel",
952
+ id: cfg.id ?? null,
953
+ name: str(cfg.key_slug) ?? str(eff.key_slug) ?? "?",
954
+ locus: "paid-spin currency"
955
+ });
956
+ }
957
+ }
958
+ }
959
+ if (targetKind === "stat") {
960
+ const statId = this.statIdForKey(key);
961
+ if (statId !== null) {
962
+ for (const lb of this.leaderboards) {
963
+ if (lb.stat_id === statId) {
964
+ found.push({
965
+ referencingKind: "leaderboard",
966
+ id: lb.id ?? null,
967
+ name: str(lb.slug) ?? "?",
968
+ locus: "ranked stat"
969
+ });
970
+ }
971
+ }
972
+ }
973
+ }
974
+ return found;
975
+ }
976
+ refsToSegment(segmentId) {
977
+ const found = [];
978
+ for (const w of this.wheels) {
979
+ const [eff, cfg] = wheelViews(w);
980
+ const links = eff.segments ?? cfg.segments ?? [];
981
+ if (links.some((l) => l.segment_id === segmentId)) {
982
+ found.push({
983
+ referencingKind: "wheel",
984
+ id: cfg.id ?? null,
985
+ name: str(cfg.key_slug) ?? str(eff.key_slug) ?? "?",
986
+ locus: "wheel segment link"
987
+ });
988
+ }
989
+ }
990
+ return found;
991
+ }
992
+ };
993
+ async function buildReferenceGraph(client, orgId, projectId) {
994
+ const path = { org_id: orgId, project_id: projectId };
995
+ const pick = (obj, key) => {
996
+ const arr = obj?.[key];
997
+ return Array.isArray(arr) ? arr : [];
998
+ };
999
+ const [currencies2, stats2, manifests2, leaderboards2, segments2, wheels2] = await Promise.all([
1000
+ unwrap(client.raw.GET("/currency-definitions/{org_id}/{project_id}/", { params: { path } })),
1001
+ unwrap(client.raw.GET("/stat-definitions/{org_id}/{project_id}/", { params: { path } })),
1002
+ unwrap(client.raw.GET("/game-manifests/{org_id}/{project_id}/", { params: { path } })),
1003
+ unwrap(client.raw.GET("/leaderboards/{org_id}/{project_id}/", { params: { path } })),
1004
+ unwrap(client.raw.GET("/spin-wheel/{org_id}/{project_id}/segments/", { params: { path } })),
1005
+ unwrap(client.raw.GET("/spin-wheel/{org_id}/{project_id}/wheels/", { params: { path } }))
1006
+ ]);
1007
+ return new ProjectGraph(
1008
+ pick(currencies2, "currency_definitions"),
1009
+ pick(stats2, "stat_definitions"),
1010
+ pick(manifests2, "game_manifests"),
1011
+ pick(leaderboards2, "leaderboards"),
1012
+ pick(segments2, "segments"),
1013
+ pick(wheels2, "wheels")
1014
+ );
1015
+ }
1016
+
1017
+ // src/smart/validate.ts
1018
+ var STAT_TYPES = ["integer", "float", "boolean"];
1019
+ var SORT_ORDERS = ["desc", "asc"];
1020
+ var LEADERBOARD_TYPES = ["relative", "absolute"];
1021
+ var RESET_TYPES = ["soft", "hard"];
1022
+ var REWARD_TYPES = ["currency", "stat", "gem"];
1023
+ var REQUIREMENT_TYPES = ["entry_cost", "eligibility"];
1024
+ var VALUE_SOURCES = ["currency", "stat"];
1025
+ var OUTCOME_REWARD_TYPES = ["fixed", "variable"];
1026
+ var REWARD_TARGETS = ["currency", "stat", "gem"];
1027
+ var OPERATIONS = ["add", "subtract", "set", "multiply"];
1028
+ var OPERATORS = ["eq", "neq", "gt", "gte", "lt", "lte"];
1029
+ var EXPR_IN_VALUE = /\s[-+*/]\s/;
1030
+ var FORMULA_TOKEN = /[A-Za-z_][A-Za-z0-9_]*/g;
1031
+ var FORMULA_NOISE = /* @__PURE__ */ new Set([
1032
+ "max",
1033
+ "min",
1034
+ "abs",
1035
+ "round",
1036
+ "floor",
1037
+ "ceil",
1038
+ "sqrt",
1039
+ "pow",
1040
+ "if",
1041
+ "else",
1042
+ "and",
1043
+ "or",
1044
+ "not",
1045
+ "true",
1046
+ "false"
1047
+ ]);
1048
+ function validateSpec(input) {
1049
+ const parsed = EconomySpec.safeParse(input);
1050
+ if (!parsed.success) {
1051
+ const violations = parsed.error.issues.map((issue) => ({
1052
+ path: issue.path.join("."),
1053
+ message: issue.message,
1054
+ severity: "error",
1055
+ rule: "schema"
1056
+ }));
1057
+ return report(violations);
1058
+ }
1059
+ return report(validateEconomy(parsed.data));
1060
+ }
1061
+ function report(violations) {
1062
+ const errors = violations.filter((v) => v.severity === "error").length;
1063
+ const warnings = violations.length - errors;
1064
+ return { ok: errors === 0, violations, summary: { errors, warnings } };
1065
+ }
1066
+ function validateEconomy(spec) {
1067
+ const out = [];
1068
+ const currencyKeys = new Set(spec.currencies.map((c) => c.key));
1069
+ const statKeys = new Set(spec.stats.map((s) => s.key));
1070
+ const segmentLabels = new Set(spec.segments.map((s) => s.label));
1071
+ const bad = (path, message, severity = "error", rule) => out.push({ path, message, severity, rule });
1072
+ const refOk = (kind, key, path) => {
1073
+ const pool = kind === "currency" ? currencyKeys : statKeys;
1074
+ if (!pool.has(key)) {
1075
+ const hint = didYouMean(key, [...pool].sort());
1076
+ bad(
1077
+ path,
1078
+ `references unknown ${kind} '${key}'.${hint ? ` Did you mean '${hint}'?` : ""}`,
1079
+ "error",
1080
+ "unknown-ref"
1081
+ );
1082
+ }
1083
+ };
1084
+ for (const [poolName, items] of [
1085
+ ["currencies", spec.currencies],
1086
+ ["stats", spec.stats]
1087
+ ]) {
1088
+ const seen = /* @__PURE__ */ new Set();
1089
+ items.forEach((item, i) => {
1090
+ if (seen.has(item.key))
1091
+ bad(`${poolName}[${i}].key`, `duplicate key '${item.key}'.`, "error", "duplicate-key");
1092
+ seen.add(item.key);
1093
+ });
1094
+ }
1095
+ spec.stats.forEach((s, i) => {
1096
+ if (!STAT_TYPES.includes(s.stat_type)) {
1097
+ bad(
1098
+ `stats[${i}].stat_type`,
1099
+ `must be one of ${STAT_TYPES.join(", ")}.`,
1100
+ "error",
1101
+ "stat-type"
1102
+ );
1103
+ }
1104
+ });
1105
+ spec.manifests.forEach((m, i) => {
1106
+ const stepsErr = validateSteps(m);
1107
+ if (stepsErr) bad(`manifests[${i}].steps`, stepsErr, "error", "steps-shape");
1108
+ for (const [tk, key, locus] of iterManifestRefs(m)) {
1109
+ refOk(tk, key, `manifests[${i}] (${locus})`);
1110
+ }
1111
+ validateManifestInternals(bad, i, m);
1112
+ validateEntryRequirements(bad, i, m);
1113
+ lintReachability(bad, i, m);
1114
+ lintFormulaKeys(bad, i, m);
1115
+ });
1116
+ spec.leaderboards.forEach((lb, i) => {
1117
+ const lbErr = validateLeaderboardFields(lb);
1118
+ if (lbErr) bad(`leaderboards[${i}]`, lbErr, "error", "leaderboard-fields");
1119
+ if (!statKeys.has(lb.stat_key)) {
1120
+ const hint = didYouMean(lb.stat_key, [...statKeys].sort());
1121
+ bad(
1122
+ `leaderboards[${i}].stat_key`,
1123
+ `unknown stat '${lb.stat_key}'.${hint ? ` Did you mean '${hint}'?` : ""}`,
1124
+ "error",
1125
+ "unknown-ref"
1126
+ );
1127
+ }
1128
+ });
1129
+ spec.segments.forEach((seg, i) => {
1130
+ const rErr = validateReward(seg.reward);
1131
+ if (rErr) bad(`segments[${i}].reward`, rErr, "error", "reward-shape");
1132
+ for (const [tk, key] of iterSegmentRefs({ reward: seg.reward })) {
1133
+ refOk(tk, key, `segments[${i}].reward`);
1134
+ }
1135
+ if (seg.reward.reward_type === "gem") {
1136
+ bad(
1137
+ `segments[${i}].reward.reward_type`,
1138
+ "gem rewards are only allowed on network catalog segments \u2014 project segments may reward currency only. Link network gem segments into the wheel at authoring time (get_segment_catalog).",
1139
+ "error",
1140
+ "gem-project-segment"
1141
+ );
1142
+ }
1143
+ });
1144
+ spec.wheels.forEach((w, i) => validateWheelSpec(out, i, w, spec, currencyKeys, segmentLabels));
1145
+ lintPhantomStats(bad, spec);
1146
+ lintOrphanCurrencies(bad, spec);
1147
+ return out;
1148
+ }
1149
+ function validateSteps(m) {
1150
+ const steps = m.steps ?? [];
1151
+ if (steps.length === 0) return "manifest has no steps.";
1152
+ const keys = steps.map((s) => s.key).filter((k) => !!k);
1153
+ if (keys.length !== steps.length) return "every step must have a key.";
1154
+ if (new Set(keys).size !== keys.length) return "step keys must be unique.";
1155
+ const entries = steps.filter((s) => s.is_entry);
1156
+ if (entries.length !== 1) return `exactly one step must be is_entry (found ${entries.length}).`;
1157
+ if (!steps.some((s) => s.is_terminal)) return "at least one step must be is_terminal.";
1158
+ const keySet = new Set(keys);
1159
+ for (const s of steps) {
1160
+ for (const nk of s.next_step_keys ?? []) {
1161
+ if (!keySet.has(nk)) return `step '${s.key}' points to unknown next step '${nk}'.`;
1162
+ }
1163
+ }
1164
+ return null;
1165
+ }
1166
+ function validateLeaderboardFields(lb) {
1167
+ if (!SORT_ORDERS.includes(lb.sort_order))
1168
+ return `sort_order must be one of ${SORT_ORDERS.join(", ")}.`;
1169
+ if (!LEADERBOARD_TYPES.includes(lb.leaderboard_type))
1170
+ return `leaderboard_type must be one of ${LEADERBOARD_TYPES.join(", ")}.`;
1171
+ if (lb.reset_type != null && !RESET_TYPES.includes(lb.reset_type))
1172
+ return `reset_type must be one of ${RESET_TYPES.join(", ")} or null.`;
1173
+ if (lb.reset_type === "soft") {
1174
+ const p = lb.soft_reset_percentage;
1175
+ if (p == null || p <= 0 || p > 100)
1176
+ return "soft reset requires soft_reset_percentage in (0, 100].";
1177
+ }
1178
+ return null;
1179
+ }
1180
+ function validateReward(reward) {
1181
+ if (!reward.reward_type) return "reward is missing reward_type (currency | stat | gem).";
1182
+ if (!REWARD_TYPES.includes(reward.reward_type))
1183
+ return `reward_type must be one of ${REWARD_TYPES.join(", ")}.`;
1184
+ if (reward.reward_type === "currency" && !reward.currency_key)
1185
+ return "currency reward must set currency_key.";
1186
+ if (reward.reward_type === "stat" && !reward.stat_key) return "stat reward must set stat_key.";
1187
+ if (reward.amount == null || reward.amount < 1) return "reward amount must be an integer >= 1.";
1188
+ return null;
1189
+ }
1190
+ function validateEntryRequirements(bad, i, m) {
1191
+ m.entry_requirements.forEach((req, j) => {
1192
+ const base = `manifests[${i}].entry_requirements[${j}]`;
1193
+ if (!req.requirement_type || !REQUIREMENT_TYPES.includes(req.requirement_type)) {
1194
+ bad(
1195
+ `${base}.requirement_type`,
1196
+ `must be one of ${REQUIREMENT_TYPES.join(", ")} (got ${JSON.stringify(req.requirement_type)}).`,
1197
+ "error",
1198
+ "entry-requirement"
1199
+ );
1200
+ }
1201
+ if (!req.value_source || !VALUE_SOURCES.includes(req.value_source)) {
1202
+ bad(
1203
+ `${base}.value_source`,
1204
+ `is required and must be one of ${VALUE_SOURCES.join(", ")}.`,
1205
+ "error",
1206
+ "entry-requirement"
1207
+ );
1208
+ }
1209
+ if (req.requirement_type === "entry_cost" && (req.cost_amount == null || req.cost_amount < 1)) {
1210
+ bad(
1211
+ `${base}.cost_amount`,
1212
+ "cost_amount (integer >= 1) is required for entry_cost.",
1213
+ "error",
1214
+ "entry-requirement"
1215
+ );
1216
+ }
1217
+ });
1218
+ }
1219
+ function validateManifestInternals(bad, i, m) {
1220
+ m.steps.forEach((step, j) => {
1221
+ step.condition_sets.forEach((cs, k) => {
1222
+ const baseCs = `manifests[${i}].steps[${j}].condition_sets[${k}]`;
1223
+ cs.conditions.forEach((cond, c) => {
1224
+ if (cond.operator != null && !OPERATORS.includes(cond.operator)) {
1225
+ bad(
1226
+ `${baseCs}.conditions[${c}].operator`,
1227
+ `must be one of ${OPERATORS.join(", ")}.`,
1228
+ "error",
1229
+ "condition-operator"
1230
+ );
1231
+ }
1232
+ if (typeof cond.expected_value === "string" && EXPR_IN_VALUE.test(cond.expected_value)) {
1233
+ bad(
1234
+ `${baseCs}.conditions[${c}].expected_value`,
1235
+ `'${cond.expected_value}' looks like an expression \u2014 expected_value must be a literal; compute the value client-side and send the result in the record payload.`,
1236
+ "warning",
1237
+ "expected-value-literal"
1238
+ );
1239
+ }
1240
+ });
1241
+ cs.outcomes.forEach((oc, o) => {
1242
+ const base = `${baseCs}.outcomes[${o}]`;
1243
+ if (!oc.reward_type)
1244
+ bad(base, "outcome is missing reward_type (fixed | variable).", "error", "outcome-enum");
1245
+ else if (!OUTCOME_REWARD_TYPES.includes(oc.reward_type))
1246
+ bad(
1247
+ `${base}.reward_type`,
1248
+ `must be one of ${OUTCOME_REWARD_TYPES.join(", ")}.`,
1249
+ "error",
1250
+ "outcome-enum"
1251
+ );
1252
+ if (!oc.reward_target)
1253
+ bad(
1254
+ base,
1255
+ "outcome is missing reward_target (currency | stat | gem).",
1256
+ "error",
1257
+ "outcome-enum"
1258
+ );
1259
+ else if (!REWARD_TARGETS.includes(oc.reward_target))
1260
+ bad(
1261
+ `${base}.reward_target`,
1262
+ `must be one of ${REWARD_TARGETS.join(", ")}.`,
1263
+ "error",
1264
+ "outcome-enum"
1265
+ );
1266
+ if (oc.operation != null && !OPERATIONS.includes(oc.operation))
1267
+ bad(
1268
+ `${base}.operation`,
1269
+ `must be one of ${OPERATIONS.join(", ")}.`,
1270
+ "error",
1271
+ "outcome-enum"
1272
+ );
1273
+ const hasFormula = oc.formula != null && !!oc.formula.formula;
1274
+ if (oc.reward_type === "fixed") {
1275
+ if (oc.value == null) bad(base, "fixed outcome must set value.", "error", "pairing");
1276
+ if (hasFormula)
1277
+ bad(
1278
+ base,
1279
+ "fixed outcome must not carry a formula \u2014 use reward_type 'variable' to evaluate a formula.",
1280
+ "error",
1281
+ "pairing"
1282
+ );
1283
+ } else if (oc.reward_type === "variable") {
1284
+ if (!hasFormula)
1285
+ bad(base, "variable outcome must set formula.formula.", "error", "pairing");
1286
+ if (oc.value != null)
1287
+ bad(
1288
+ base,
1289
+ "variable outcome must not set value \u2014 use reward_type 'fixed' to award a fixed value.",
1290
+ "error",
1291
+ "pairing"
1292
+ );
1293
+ }
1294
+ if (oc.reward_target === "gem" && oc.reward_type === "variable" && oc.formula != null && oc.formula.max == null) {
1295
+ bad(
1296
+ `${base}.formula.max`,
1297
+ "uncapped variable gem award \u2014 set formula.max (gems are the network-wide premium wallet).",
1298
+ "warning",
1299
+ "uncapped-gem"
1300
+ );
1301
+ }
1302
+ });
1303
+ });
1304
+ });
1305
+ }
1306
+ function validateWheelSpec(out, i, wheel, spec, currencyKeys, segmentLabels) {
1307
+ const probByLabel = new Map(spec.segments.map((s) => [s.label, s.probability]));
1308
+ let total = 0;
1309
+ wheel.segments.forEach((link, j) => {
1310
+ if (!segmentLabels.has(link.segment_ref)) {
1311
+ const hint = didYouMean(link.segment_ref, [...segmentLabels].sort());
1312
+ out.push({
1313
+ path: `wheels[${i}].segments[${j}].segment_ref`,
1314
+ message: `unknown segment '${link.segment_ref}'.${hint ? ` Did you mean '${hint}'?` : ""}`,
1315
+ severity: "error",
1316
+ rule: "segment-ref"
1317
+ });
1318
+ return;
1319
+ }
1320
+ total += link.probability_override ?? probByLabel.get(link.segment_ref) ?? 0;
1321
+ });
1322
+ if (wheel.segments.length && total !== 100) {
1323
+ out.push({
1324
+ path: `wheels[${i}].segments`,
1325
+ message: `effective probabilities total ${total}, must be exactly 100.`,
1326
+ severity: "error",
1327
+ rule: "probability-total"
1328
+ });
1329
+ }
1330
+ if (wheel.allow_paid_spins) {
1331
+ if (!currencyKeys.has(wheel.paid_spin_currency_key)) {
1332
+ const hint = didYouMean(wheel.paid_spin_currency_key, [...currencyKeys].sort());
1333
+ out.push({
1334
+ path: `wheels[${i}].paid_spin_currency_key`,
1335
+ message: `unknown currency '${wheel.paid_spin_currency_key}'.${hint ? ` Did you mean '${hint}'?` : ""}`,
1336
+ severity: "error",
1337
+ rule: "unknown-ref"
1338
+ });
1339
+ }
1340
+ if (wheel.paid_spin_currency_amount == null || wheel.paid_spin_currency_amount < 1) {
1341
+ out.push({
1342
+ path: `wheels[${i}].paid_spin_currency_amount`,
1343
+ message: "must be an integer >= 1 when allow_paid_spins is true.",
1344
+ severity: "error",
1345
+ rule: "paid-spin"
1346
+ });
1347
+ }
1348
+ const rewardByLabel = new Map(spec.segments.map((s) => [s.label, s.reward]));
1349
+ const amt = wheel.paid_spin_currency_amount;
1350
+ if (currencyKeys.has(wheel.paid_spin_currency_key) && amt && amt >= 1 && wheel.segments.length) {
1351
+ let ev = 0;
1352
+ for (const link of wheel.segments) {
1353
+ const reward = rewardByLabel.get(link.segment_ref);
1354
+ if (!reward || reward.reward_type !== "currency" || !reward.amount) continue;
1355
+ if (reward.currency_key !== wheel.paid_spin_currency_key) continue;
1356
+ const prob = link.probability_override ?? probByLabel.get(link.segment_ref) ?? 0;
1357
+ ev += prob / 100 * reward.amount;
1358
+ }
1359
+ if (ev >= amt) {
1360
+ out.push({
1361
+ path: `wheels[${i}].paid_spin_currency_amount`,
1362
+ message: `money printer: expected payout per paid spin is ${ev.toFixed(1)} '${wheel.paid_spin_currency_key}' but a spin costs ${amt} \u2014 raise the cost or lower the payouts.`,
1363
+ severity: "error",
1364
+ rule: "money-printer"
1365
+ });
1366
+ }
1367
+ }
1368
+ }
1369
+ }
1370
+ function payloadKeyCounts(m) {
1371
+ const counts = /* @__PURE__ */ new Map();
1372
+ const inc = (k) => counts.set(k, (counts.get(k) ?? 0) + 1);
1373
+ for (const step of m.steps) {
1374
+ for (const cs of step.condition_sets) {
1375
+ for (const cond of cs.conditions) if (cond.payload_key) inc(cond.payload_key);
1376
+ for (const oc of cs.outcomes) {
1377
+ if (oc.formula?.formula) {
1378
+ for (const tok of oc.formula.formula.match(FORMULA_TOKEN) ?? []) {
1379
+ if (!FORMULA_NOISE.has(tok.toLowerCase())) inc(tok);
1380
+ }
1381
+ }
1382
+ }
1383
+ }
1384
+ }
1385
+ return counts;
1386
+ }
1387
+ function lintReachability(bad, i, m) {
1388
+ const stepsByKey = new Map(m.steps.filter((s) => s.key).map((s) => [s.key, s]));
1389
+ const entries = m.steps.filter((s) => s.is_entry && s.key).map((s) => s.key);
1390
+ if (entries.length !== 1) return;
1391
+ const seen = /* @__PURE__ */ new Set([entries[0]]);
1392
+ const frontier = [entries[0]];
1393
+ while (frontier.length) {
1394
+ const step = stepsByKey.get(frontier.pop());
1395
+ for (const nk of step?.next_step_keys ?? []) {
1396
+ if (stepsByKey.has(nk) && !seen.has(nk)) {
1397
+ seen.add(nk);
1398
+ frontier.push(nk);
1399
+ }
1400
+ }
1401
+ }
1402
+ m.steps.forEach((s, j) => {
1403
+ if (s.key && !seen.has(s.key)) {
1404
+ bad(
1405
+ `manifests[${i}].steps[${j}]`,
1406
+ `step '${s.key}' is unreachable from the entry step.`,
1407
+ "warning",
1408
+ "unreachable-step"
1409
+ );
1410
+ }
1411
+ });
1412
+ }
1413
+ function lintFormulaKeys(bad, i, m) {
1414
+ const counts = payloadKeyCounts(m);
1415
+ for (const [key, n] of counts) {
1416
+ if (n !== 1) continue;
1417
+ const others = [...counts.keys()].filter((k) => k !== key);
1418
+ const match = didYouMean(key, others);
1419
+ if (match && similarityCloseEnough(key, match)) {
1420
+ bad(
1421
+ `manifests[${i}]`,
1422
+ `payload key '${key}' is used once \u2014 did you mean '${match}'?`,
1423
+ "warning",
1424
+ "payload-typo"
1425
+ );
1426
+ }
1427
+ }
1428
+ }
1429
+ function similarityCloseEnough(a, b) {
1430
+ const maxLen = Math.max(a.length, b.length) || 1;
1431
+ let same = 0;
1432
+ const bl = [...b];
1433
+ for (const ch of a) {
1434
+ const idx = bl.indexOf(ch);
1435
+ if (idx !== -1) {
1436
+ same++;
1437
+ bl.splice(idx, 1);
1438
+ }
1439
+ }
1440
+ return 2 * same / (a.length + b.length) >= 0.85 && a !== b && Math.abs(a.length - b.length) <= maxLen;
1441
+ }
1442
+ function lintPhantomStats(bad, spec) {
1443
+ const written = /* @__PURE__ */ new Set();
1444
+ for (const m of spec.manifests)
1445
+ for (const s of m.steps)
1446
+ for (const cs of s.condition_sets)
1447
+ for (const oc of cs.outcomes) if (oc.stat_key) written.add(oc.stat_key);
1448
+ const ranked = new Set(spec.leaderboards.map((lb) => lb.stat_key));
1449
+ const rewarded = new Set(
1450
+ spec.segments.map((s) => s.reward.stat_key).filter((k) => !!k)
1451
+ );
1452
+ const payloadKeys = /* @__PURE__ */ new Set();
1453
+ for (const m of spec.manifests) for (const k of payloadKeyCounts(m).keys()) payloadKeys.add(k);
1454
+ const referenced = /* @__PURE__ */ new Set([...written, ...ranked, ...rewarded]);
1455
+ spec.stats.forEach((st, i) => {
1456
+ if (!referenced.has(st.key) && payloadKeys.has(st.key)) {
1457
+ bad(
1458
+ `stats[${i}]`,
1459
+ `'${st.key}' looks like a runtime payload key, not a stat \u2014 no outcome writes it and no leaderboard ranks it. Payload keys are read from the record payload and need no definition.`,
1460
+ "warning",
1461
+ "phantom-stat"
1462
+ );
1463
+ }
1464
+ });
1465
+ }
1466
+ function lintOrphanCurrencies(bad, spec) {
1467
+ const sources = /* @__PURE__ */ new Set();
1468
+ const sinks = /* @__PURE__ */ new Set();
1469
+ for (const m of spec.manifests) {
1470
+ for (const req of m.entry_requirements) if (req.currency_key) sinks.add(req.currency_key);
1471
+ for (const s of m.steps)
1472
+ for (const cs of s.condition_sets)
1473
+ for (const oc of cs.outcomes)
1474
+ if (oc.reward_target === "currency" && oc.currency_key)
1475
+ (oc.operation === "subtract" ? sinks : sources).add(oc.currency_key);
1476
+ }
1477
+ for (const seg of spec.segments)
1478
+ if (seg.reward.reward_type === "currency" && seg.reward.currency_key)
1479
+ sources.add(seg.reward.currency_key);
1480
+ for (const w of spec.wheels)
1481
+ if (w.allow_paid_spins && w.paid_spin_currency_key) sinks.add(w.paid_spin_currency_key);
1482
+ spec.currencies.forEach((c, i) => {
1483
+ if (!sources.has(c.key))
1484
+ bad(
1485
+ `currencies[${i}]`,
1486
+ `'${c.key}' has no source \u2014 nothing awards it.`,
1487
+ "warning",
1488
+ "orphan-currency"
1489
+ );
1490
+ if (!sinks.has(c.key))
1491
+ bad(
1492
+ `currencies[${i}]`,
1493
+ `'${c.key}' has no sink \u2014 add an entry cost, a subtract outcome, or paid wheel spins.`,
1494
+ "warning",
1495
+ "orphan-currency"
1496
+ );
1497
+ });
1498
+ }
1499
+ async function impactScan(client, orgId, projectId, kind, ref) {
1500
+ const graph = await buildReferenceGraph(client, orgId, projectId);
1501
+ const exists = kind === "currency" ? graph.currencyKeys().includes(String(ref)) : kind === "stat" ? graph.statKeys().includes(String(ref)) : graph.segmentIds().includes(Number(ref));
1502
+ const refs = graph.referencesTo(kind, ref);
1503
+ const byKind = {};
1504
+ for (const r of refs) byKind[r.referencingKind] = (byKind[r.referencingKind] ?? 0) + 1;
1505
+ return {
1506
+ target: { kind, ref },
1507
+ exists,
1508
+ referenceCount: refs.length,
1509
+ byKind,
1510
+ references: refs.map((r) => ({
1511
+ kind: r.referencingKind,
1512
+ id: r.id,
1513
+ name: r.name,
1514
+ locus: r.locus
1515
+ })),
1516
+ breaking: refs.length > 0
1517
+ };
1518
+ }
1519
+
1520
+ // src/index.ts
1521
+ var CORE_VERSION = "0.0.0";
1522
+ // Annotate the CommonJS export names for ESM import in node:
1523
+ 0 && (module.exports = {
1524
+ CORE_VERSION,
1525
+ EnvTokenProvider,
1526
+ MagicweaveClient,
1527
+ MagicweaveError,
1528
+ NotFoundError,
1529
+ PROD_API_BASE,
1530
+ ProjectGraph,
1531
+ STAGING_API_BASE,
1532
+ StaticTokenProvider,
1533
+ UnauthorizedError,
1534
+ ValidationError,
1535
+ buildReferenceGraph,
1536
+ createClient,
1537
+ currencies,
1538
+ decodeJwt,
1539
+ didYouMean,
1540
+ environments,
1541
+ exportDefinitions,
1542
+ impactScan,
1543
+ isExpired,
1544
+ iterManifestRefs,
1545
+ iterSegmentRefs,
1546
+ leaderboards,
1547
+ manifests,
1548
+ orgs,
1549
+ projects,
1550
+ releases,
1551
+ requestLoginOtp,
1552
+ resolveBaseUrl,
1553
+ segments,
1554
+ spec,
1555
+ stats,
1556
+ unwrap,
1557
+ validateSpec,
1558
+ verifyLoginOtp,
1559
+ wheels
1560
+ });
1561
+ //# sourceMappingURL=index.cjs.map