@playcademy/vite-plugin 0.2.24-beta.1 → 0.2.24-beta.3
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.js +478 -392
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25335,7 +25335,7 @@ var package_default;
|
|
|
25335
25335
|
var init_package = __esm(() => {
|
|
25336
25336
|
package_default = {
|
|
25337
25337
|
name: "@playcademy/sandbox",
|
|
25338
|
-
version: "0.3.17-beta.
|
|
25338
|
+
version: "0.3.17-beta.6",
|
|
25339
25339
|
description: "Local development server for Playcademy game development",
|
|
25340
25340
|
type: "module",
|
|
25341
25341
|
exports: {
|
|
@@ -50498,453 +50498,539 @@ var init_developer_service = __esm(() => {
|
|
|
50498
50498
|
init_errors();
|
|
50499
50499
|
logger4 = log.scope("DeveloperService");
|
|
50500
50500
|
});
|
|
50501
|
-
|
|
50502
|
-
|
|
50503
|
-
|
|
50504
|
-
static MANIFEST_FETCH_TIMEOUT_MS = 5000;
|
|
50505
|
-
static MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
50506
|
-
constructor(deps) {
|
|
50507
|
-
this.deps = deps;
|
|
50508
|
-
}
|
|
50509
|
-
static getManifestHost(manifestUrl) {
|
|
50510
|
-
try {
|
|
50511
|
-
return new URL(manifestUrl).host;
|
|
50512
|
-
} catch {
|
|
50513
|
-
return manifestUrl;
|
|
50514
|
-
}
|
|
50501
|
+
function sleep(ms) {
|
|
50502
|
+
if (ms <= 0) {
|
|
50503
|
+
return Promise.resolve();
|
|
50515
50504
|
}
|
|
50516
|
-
|
|
50517
|
-
|
|
50518
|
-
|
|
50519
|
-
|
|
50520
|
-
|
|
50521
|
-
|
|
50522
|
-
|
|
50523
|
-
|
|
50524
|
-
|
|
50505
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
50506
|
+
}
|
|
50507
|
+
var logger5;
|
|
50508
|
+
var inFlightManifestFetches;
|
|
50509
|
+
var GameService;
|
|
50510
|
+
var init_game_service = __esm(() => {
|
|
50511
|
+
init_drizzle_orm();
|
|
50512
|
+
init_tables_index();
|
|
50513
|
+
init_src2();
|
|
50514
|
+
init_errors();
|
|
50515
|
+
init_deployment_util();
|
|
50516
|
+
logger5 = log.scope("GameService");
|
|
50517
|
+
inFlightManifestFetches = new Map;
|
|
50518
|
+
GameService = class GameService2 {
|
|
50519
|
+
deps;
|
|
50520
|
+
static MANIFEST_FETCH_ATTEMPT_TIMEOUT_MS = 1e4;
|
|
50521
|
+
static MANIFEST_FETCH_MAX_RETRIES = 2;
|
|
50522
|
+
static MANIFEST_FETCH_RETRY_BACKOFF_MS = [250, 750];
|
|
50523
|
+
static MANIFEST_CACHE_TTL_SECONDS = 60;
|
|
50524
|
+
static MANIFEST_CACHE_KEY_PREFIX = "game:manifest";
|
|
50525
|
+
static MAX_FETCH_ERROR_MESSAGE_LENGTH = 512;
|
|
50526
|
+
constructor(deps) {
|
|
50527
|
+
this.deps = deps;
|
|
50528
|
+
}
|
|
50529
|
+
static getManifestHost(manifestUrl) {
|
|
50530
|
+
try {
|
|
50531
|
+
return new URL(manifestUrl).host;
|
|
50532
|
+
} catch {
|
|
50533
|
+
return manifestUrl;
|
|
50534
|
+
}
|
|
50525
50535
|
}
|
|
50526
|
-
|
|
50527
|
-
|
|
50528
|
-
|
|
50536
|
+
static getFetchErrorMessage(error) {
|
|
50537
|
+
let raw;
|
|
50538
|
+
if (error instanceof Error) {
|
|
50539
|
+
raw = error.message;
|
|
50540
|
+
} else if (typeof error === "string") {
|
|
50541
|
+
raw = error;
|
|
50542
|
+
}
|
|
50543
|
+
if (!raw) {
|
|
50544
|
+
return;
|
|
50545
|
+
}
|
|
50546
|
+
const normalized = raw.replace(/\s+/g, " ").trim();
|
|
50547
|
+
if (!normalized) {
|
|
50548
|
+
return;
|
|
50549
|
+
}
|
|
50550
|
+
return normalized.slice(0, GameService2.MAX_FETCH_ERROR_MESSAGE_LENGTH);
|
|
50529
50551
|
}
|
|
50530
|
-
|
|
50531
|
-
|
|
50532
|
-
static isRetryableStatus(status) {
|
|
50533
|
-
return status === 429 || status >= 500;
|
|
50534
|
-
}
|
|
50535
|
-
async list(caller) {
|
|
50536
|
-
const db2 = this.deps.db;
|
|
50537
|
-
const isAdmin = caller?.role === "admin";
|
|
50538
|
-
const isDeveloper = caller?.role === "developer";
|
|
50539
|
-
let whereClause;
|
|
50540
|
-
if (isAdmin) {
|
|
50541
|
-
whereClause = undefined;
|
|
50542
|
-
} else if (isDeveloper && caller?.id) {
|
|
50543
|
-
whereClause = or(ne(games.visibility, "internal"), eq(games.developerId, caller.id));
|
|
50544
|
-
} else {
|
|
50545
|
-
whereClause = ne(games.visibility, "internal");
|
|
50552
|
+
static isRetryableStatus(status) {
|
|
50553
|
+
return status === 429 || status >= 500;
|
|
50546
50554
|
}
|
|
50547
|
-
|
|
50548
|
-
|
|
50549
|
-
|
|
50550
|
-
|
|
50551
|
-
}
|
|
50552
|
-
async listManageable(user) {
|
|
50553
|
-
this.validateDeveloperStatus(user);
|
|
50554
|
-
const db2 = this.deps.db;
|
|
50555
|
-
return db2.query.games.findMany({
|
|
50556
|
-
where: user.role === "admin" ? undefined : eq(games.developerId, user.id),
|
|
50557
|
-
orderBy: [desc(games.createdAt)]
|
|
50558
|
-
});
|
|
50559
|
-
}
|
|
50560
|
-
async getSubjects() {
|
|
50561
|
-
const db2 = this.deps.db;
|
|
50562
|
-
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
50563
|
-
columns: { gameId: true, subject: true },
|
|
50564
|
-
orderBy: [asc(gameTimebackIntegrations.createdAt)]
|
|
50565
|
-
});
|
|
50566
|
-
const subjectMap = {};
|
|
50567
|
-
for (const integration of integrations) {
|
|
50568
|
-
if (!(integration.gameId in subjectMap)) {
|
|
50569
|
-
subjectMap[integration.gameId] = integration.subject;
|
|
50555
|
+
static getRetryBackoffMs(attemptIndex) {
|
|
50556
|
+
const backoff = GameService2.MANIFEST_FETCH_RETRY_BACKOFF_MS;
|
|
50557
|
+
if (backoff.length === 0) {
|
|
50558
|
+
return 0;
|
|
50570
50559
|
}
|
|
50560
|
+
return backoff[Math.min(attemptIndex, backoff.length - 1)] ?? 0;
|
|
50571
50561
|
}
|
|
50572
|
-
|
|
50573
|
-
|
|
50574
|
-
async getById(gameId, caller) {
|
|
50575
|
-
const db2 = this.deps.db;
|
|
50576
|
-
const game = await db2.query.games.findFirst({
|
|
50577
|
-
where: eq(games.id, gameId)
|
|
50578
|
-
});
|
|
50579
|
-
if (!game) {
|
|
50580
|
-
throw new NotFoundError("Game", gameId);
|
|
50562
|
+
static normalizeDeploymentUrl(deploymentUrl) {
|
|
50563
|
+
return deploymentUrl.replace(/\/$/, "");
|
|
50581
50564
|
}
|
|
50582
|
-
|
|
50583
|
-
|
|
50584
|
-
}
|
|
50585
|
-
async getBySlug(slug, caller) {
|
|
50586
|
-
const db2 = this.deps.db;
|
|
50587
|
-
const game = await db2.query.games.findFirst({
|
|
50588
|
-
where: eq(games.slug, slug)
|
|
50589
|
-
});
|
|
50590
|
-
if (!game) {
|
|
50591
|
-
throw new NotFoundError("Game", slug);
|
|
50565
|
+
static getManifestCacheKey(deploymentUrl) {
|
|
50566
|
+
return `${GameService2.MANIFEST_CACHE_KEY_PREFIX}:${deploymentUrl}`;
|
|
50592
50567
|
}
|
|
50593
|
-
|
|
50594
|
-
|
|
50595
|
-
|
|
50596
|
-
|
|
50597
|
-
|
|
50598
|
-
|
|
50599
|
-
|
|
50600
|
-
|
|
50601
|
-
|
|
50602
|
-
|
|
50603
|
-
|
|
50604
|
-
|
|
50605
|
-
|
|
50606
|
-
|
|
50607
|
-
|
|
50608
|
-
|
|
50609
|
-
manifestUrl,
|
|
50610
|
-
manifestHost,
|
|
50611
|
-
deploymentUrl,
|
|
50612
|
-
fetchOutcome,
|
|
50613
|
-
retryCount: 0,
|
|
50614
|
-
durationMs: Date.now() - startedAt,
|
|
50615
|
-
manifestErrorKind,
|
|
50616
|
-
...extra
|
|
50617
|
-
};
|
|
50568
|
+
async list(caller) {
|
|
50569
|
+
const db2 = this.deps.db;
|
|
50570
|
+
const isAdmin = caller?.role === "admin";
|
|
50571
|
+
const isDeveloper = caller?.role === "developer";
|
|
50572
|
+
let whereClause;
|
|
50573
|
+
if (isAdmin) {
|
|
50574
|
+
whereClause = undefined;
|
|
50575
|
+
} else if (isDeveloper && caller?.id) {
|
|
50576
|
+
whereClause = or(ne(games.visibility, "internal"), eq(games.developerId, caller.id));
|
|
50577
|
+
} else {
|
|
50578
|
+
whereClause = ne(games.visibility, "internal");
|
|
50579
|
+
}
|
|
50580
|
+
return db2.query.games.findMany({
|
|
50581
|
+
where: whereClause,
|
|
50582
|
+
orderBy: [desc(games.createdAt)]
|
|
50583
|
+
});
|
|
50618
50584
|
}
|
|
50619
|
-
|
|
50620
|
-
|
|
50621
|
-
|
|
50622
|
-
|
|
50623
|
-
|
|
50624
|
-
|
|
50625
|
-
},
|
|
50626
|
-
signal: controller.signal
|
|
50585
|
+
async listManageable(user) {
|
|
50586
|
+
this.validateDeveloperStatus(user);
|
|
50587
|
+
const db2 = this.deps.db;
|
|
50588
|
+
return db2.query.games.findMany({
|
|
50589
|
+
where: user.role === "admin" ? undefined : eq(games.developerId, user.id),
|
|
50590
|
+
orderBy: [desc(games.createdAt)]
|
|
50627
50591
|
});
|
|
50628
|
-
}
|
|
50629
|
-
|
|
50630
|
-
const
|
|
50631
|
-
const
|
|
50632
|
-
|
|
50633
|
-
|
|
50634
|
-
manifestUrl,
|
|
50635
|
-
error,
|
|
50636
|
-
details
|
|
50592
|
+
}
|
|
50593
|
+
async getSubjects() {
|
|
50594
|
+
const db2 = this.deps.db;
|
|
50595
|
+
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
50596
|
+
columns: { gameId: true, subject: true },
|
|
50597
|
+
orderBy: [asc(gameTimebackIntegrations.createdAt)]
|
|
50637
50598
|
});
|
|
50638
|
-
|
|
50639
|
-
|
|
50599
|
+
const subjectMap = {};
|
|
50600
|
+
for (const integration of integrations) {
|
|
50601
|
+
if (!(integration.gameId in subjectMap)) {
|
|
50602
|
+
subjectMap[integration.gameId] = integration.subject;
|
|
50603
|
+
}
|
|
50640
50604
|
}
|
|
50641
|
-
|
|
50642
|
-
} finally {
|
|
50643
|
-
clearTimeout(timeout);
|
|
50605
|
+
return subjectMap;
|
|
50644
50606
|
}
|
|
50645
|
-
|
|
50646
|
-
const
|
|
50647
|
-
const
|
|
50648
|
-
|
|
50649
|
-
const details = buildDetails("bad_status", manifestErrorKind, {
|
|
50650
|
-
manifestUrl: resolvedManifestUrl,
|
|
50651
|
-
manifestHost: resolvedManifestHost,
|
|
50652
|
-
status: response.status,
|
|
50653
|
-
contentType: response.headers.get("content-type") ?? undefined,
|
|
50654
|
-
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50655
|
-
redirected: response.redirected,
|
|
50656
|
-
...response.redirected ? {
|
|
50657
|
-
originalManifestUrl: manifestUrl,
|
|
50658
|
-
originalManifestHost: manifestHost
|
|
50659
|
-
} : {}
|
|
50660
|
-
});
|
|
50661
|
-
const message = `Failed to fetch manifest: ${response.status} ${response.statusText}`;
|
|
50662
|
-
logger5.error("Game manifest returned non-ok response", {
|
|
50663
|
-
gameId,
|
|
50664
|
-
manifestUrl,
|
|
50665
|
-
status: response.status,
|
|
50666
|
-
details
|
|
50607
|
+
async getById(gameId, caller) {
|
|
50608
|
+
const db2 = this.deps.db;
|
|
50609
|
+
const game = await db2.query.games.findFirst({
|
|
50610
|
+
where: eq(games.id, gameId)
|
|
50667
50611
|
});
|
|
50668
|
-
if (
|
|
50669
|
-
throw new
|
|
50612
|
+
if (!game) {
|
|
50613
|
+
throw new NotFoundError("Game", gameId);
|
|
50670
50614
|
}
|
|
50671
|
-
|
|
50615
|
+
this.enforceVisibility(game, caller, gameId);
|
|
50616
|
+
return game;
|
|
50672
50617
|
}
|
|
50673
|
-
|
|
50674
|
-
|
|
50675
|
-
|
|
50676
|
-
|
|
50677
|
-
const resolvedManifestHost = GameService.getManifestHost(resolvedManifestUrl);
|
|
50678
|
-
const details = buildDetails("invalid_body", "permanent", {
|
|
50679
|
-
manifestUrl: resolvedManifestUrl,
|
|
50680
|
-
manifestHost: resolvedManifestHost,
|
|
50681
|
-
status: response.status,
|
|
50682
|
-
contentType: response.headers.get("content-type") ?? undefined,
|
|
50683
|
-
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50684
|
-
redirected: response.redirected,
|
|
50685
|
-
...response.redirected ? {
|
|
50686
|
-
originalManifestUrl: manifestUrl,
|
|
50687
|
-
originalManifestHost: manifestHost
|
|
50688
|
-
} : {}
|
|
50689
|
-
});
|
|
50690
|
-
logger5.error("Failed to parse game manifest", {
|
|
50691
|
-
gameId,
|
|
50692
|
-
manifestUrl,
|
|
50693
|
-
error,
|
|
50694
|
-
details
|
|
50618
|
+
async getBySlug(slug, caller) {
|
|
50619
|
+
const db2 = this.deps.db;
|
|
50620
|
+
const game = await db2.query.games.findFirst({
|
|
50621
|
+
where: eq(games.slug, slug)
|
|
50695
50622
|
});
|
|
50696
|
-
|
|
50697
|
-
|
|
50698
|
-
}
|
|
50699
|
-
enforceVisibility(game, caller, lookupIdentifier) {
|
|
50700
|
-
if (game.visibility !== "internal") {
|
|
50701
|
-
return;
|
|
50702
|
-
}
|
|
50703
|
-
const isAdmin = caller?.role === "admin";
|
|
50704
|
-
const isOwner = caller?.id != null && caller.id === game.developerId;
|
|
50705
|
-
if (!isAdmin && !isOwner) {
|
|
50706
|
-
throw new NotFoundError("Game", lookupIdentifier);
|
|
50707
|
-
}
|
|
50708
|
-
}
|
|
50709
|
-
async upsertBySlug(slug, data, user) {
|
|
50710
|
-
const db2 = this.deps.db;
|
|
50711
|
-
const existingGame = await db2.query.games.findFirst({
|
|
50712
|
-
where: eq(games.slug, slug)
|
|
50713
|
-
});
|
|
50714
|
-
const isUpdate = Boolean(existingGame);
|
|
50715
|
-
const gameId = existingGame?.id ?? crypto.randomUUID();
|
|
50716
|
-
if (isUpdate) {
|
|
50717
|
-
await this.validateDeveloperAccess(user, gameId);
|
|
50718
|
-
} else {
|
|
50719
|
-
this.validateDeveloperStatus(user);
|
|
50720
|
-
}
|
|
50721
|
-
const gameDataForDb = {
|
|
50722
|
-
displayName: data.displayName,
|
|
50723
|
-
platform: data.platform,
|
|
50724
|
-
metadata: data.metadata,
|
|
50725
|
-
mapElementId: data.mapElementId,
|
|
50726
|
-
gameType: data.gameType,
|
|
50727
|
-
...data.visibility && { visibility: data.visibility },
|
|
50728
|
-
externalUrl: data.externalUrl || null,
|
|
50729
|
-
updatedAt: new Date
|
|
50730
|
-
};
|
|
50731
|
-
let gameResponse;
|
|
50732
|
-
if (isUpdate) {
|
|
50733
|
-
const [updatedGame] = await db2.update(games).set(gameDataForDb).where(eq(games.id, gameId)).returning();
|
|
50734
|
-
if (!updatedGame) {
|
|
50735
|
-
logger5.error("Game update returned no rows", { gameId, slug });
|
|
50736
|
-
throw new InternalError("DB update failed to return result for existing game");
|
|
50737
|
-
}
|
|
50738
|
-
gameResponse = updatedGame;
|
|
50739
|
-
} else {
|
|
50740
|
-
const insertData = {
|
|
50741
|
-
...gameDataForDb,
|
|
50742
|
-
id: gameId,
|
|
50743
|
-
slug,
|
|
50744
|
-
developerId: user.id,
|
|
50745
|
-
metadata: data.metadata || {},
|
|
50746
|
-
version: data.gameType === "external" ? "external" : "",
|
|
50747
|
-
deploymentUrl: null,
|
|
50748
|
-
createdAt: new Date
|
|
50749
|
-
};
|
|
50750
|
-
const [createdGame] = await db2.insert(games).values(insertData).returning();
|
|
50751
|
-
if (!createdGame) {
|
|
50752
|
-
logger5.error("Game insert returned no rows", { slug, developerId: user.id });
|
|
50753
|
-
throw new InternalError("DB insert failed to return result for new game");
|
|
50623
|
+
if (!game) {
|
|
50624
|
+
throw new NotFoundError("Game", slug);
|
|
50754
50625
|
}
|
|
50755
|
-
|
|
50626
|
+
this.enforceVisibility(game, caller, slug);
|
|
50627
|
+
return game;
|
|
50756
50628
|
}
|
|
50757
|
-
|
|
50758
|
-
|
|
50759
|
-
|
|
50760
|
-
|
|
50761
|
-
|
|
50762
|
-
|
|
50763
|
-
|
|
50764
|
-
|
|
50765
|
-
|
|
50766
|
-
|
|
50629
|
+
async getManifest(gameId, caller) {
|
|
50630
|
+
const game = await this.getById(gameId, caller);
|
|
50631
|
+
if (game.gameType !== "hosted" || !game.deploymentUrl) {
|
|
50632
|
+
throw new BadRequestError("Game does not have a deployment manifest");
|
|
50633
|
+
}
|
|
50634
|
+
const deploymentUrl = GameService2.normalizeDeploymentUrl(game.deploymentUrl);
|
|
50635
|
+
const cacheKey2 = GameService2.getManifestCacheKey(deploymentUrl);
|
|
50636
|
+
const cached = await this.deps.cache.get(cacheKey2);
|
|
50637
|
+
if (cached) {
|
|
50638
|
+
return cached;
|
|
50639
|
+
}
|
|
50640
|
+
const inFlight = inFlightManifestFetches.get(deploymentUrl);
|
|
50641
|
+
if (inFlight) {
|
|
50642
|
+
return inFlight;
|
|
50643
|
+
}
|
|
50644
|
+
const promise = this.fetchManifestFromOrigin({ gameId, deploymentUrl }).then(async (manifest) => {
|
|
50645
|
+
try {
|
|
50646
|
+
await this.deps.cache.set(cacheKey2, manifest, GameService2.MANIFEST_CACHE_TTL_SECONDS);
|
|
50647
|
+
} catch (cacheError) {
|
|
50648
|
+
logger5.warn("Failed to cache game manifest", {
|
|
50649
|
+
gameId,
|
|
50650
|
+
deploymentUrl,
|
|
50651
|
+
cacheKey: cacheKey2,
|
|
50652
|
+
error: cacheError
|
|
50653
|
+
});
|
|
50654
|
+
}
|
|
50655
|
+
return manifest;
|
|
50656
|
+
}).finally(() => {
|
|
50657
|
+
inFlightManifestFetches.delete(deploymentUrl);
|
|
50658
|
+
});
|
|
50659
|
+
inFlightManifestFetches.set(deploymentUrl, promise);
|
|
50660
|
+
return promise;
|
|
50661
|
+
}
|
|
50662
|
+
async fetchManifestFromOrigin(args2) {
|
|
50663
|
+
const { gameId, deploymentUrl } = args2;
|
|
50664
|
+
const manifestUrl = `${deploymentUrl}/playcademy.manifest.json`;
|
|
50665
|
+
const manifestHost = GameService2.getManifestHost(manifestUrl);
|
|
50666
|
+
const startedAt = Date.now();
|
|
50667
|
+
const maxAttempts = GameService2.MANIFEST_FETCH_MAX_RETRIES + 1;
|
|
50668
|
+
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
50669
|
+
const isLastAttempt = attempt === maxAttempts - 1;
|
|
50670
|
+
const outcome = await this.attemptManifestFetch({
|
|
50671
|
+
manifestUrl,
|
|
50672
|
+
manifestHost,
|
|
50673
|
+
deploymentUrl,
|
|
50674
|
+
startedAt,
|
|
50675
|
+
retryCount: attempt
|
|
50676
|
+
});
|
|
50677
|
+
if (outcome.kind === "success") {
|
|
50678
|
+
return outcome.manifest;
|
|
50679
|
+
}
|
|
50680
|
+
if (!outcome.retryable || isLastAttempt) {
|
|
50681
|
+
logger5.error("Failed to fetch game manifest", {
|
|
50682
|
+
gameId,
|
|
50683
|
+
manifestUrl,
|
|
50684
|
+
attempt: attempt + 1,
|
|
50685
|
+
maxAttempts,
|
|
50686
|
+
retryable: outcome.retryable,
|
|
50687
|
+
details: outcome.details,
|
|
50688
|
+
throwable: outcome.throwable,
|
|
50689
|
+
cause: outcome.cause
|
|
50690
|
+
});
|
|
50691
|
+
throw outcome.throwable;
|
|
50692
|
+
}
|
|
50693
|
+
const backoffMs = GameService2.getRetryBackoffMs(attempt);
|
|
50694
|
+
logger5.warn("Retrying game manifest fetch after transient failure", {
|
|
50695
|
+
gameId,
|
|
50696
|
+
manifestUrl,
|
|
50697
|
+
attempt: attempt + 1,
|
|
50698
|
+
maxAttempts,
|
|
50699
|
+
backoffMs,
|
|
50700
|
+
details: outcome.details,
|
|
50701
|
+
cause: outcome.cause
|
|
50767
50702
|
});
|
|
50703
|
+
await sleep(backoffMs);
|
|
50768
50704
|
}
|
|
50705
|
+
throw new InternalError("Exhausted manifest fetch retries without result");
|
|
50769
50706
|
}
|
|
50770
|
-
|
|
50771
|
-
|
|
50772
|
-
|
|
50773
|
-
|
|
50774
|
-
|
|
50775
|
-
|
|
50776
|
-
|
|
50777
|
-
|
|
50778
|
-
|
|
50779
|
-
|
|
50780
|
-
|
|
50781
|
-
|
|
50782
|
-
|
|
50783
|
-
|
|
50784
|
-
|
|
50785
|
-
|
|
50786
|
-
|
|
50787
|
-
}
|
|
50788
|
-
const activeDeployment = await db2.query.gameDeployments.findFirst({
|
|
50789
|
-
where: and(eq(gameDeployments.gameId, gameId), eq(gameDeployments.isActive, true)),
|
|
50790
|
-
columns: { deploymentId: true, provider: true, resources: true }
|
|
50791
|
-
});
|
|
50792
|
-
const customHostnames = await db2.select({
|
|
50793
|
-
hostname: gameCustomHostnames.hostname,
|
|
50794
|
-
cloudflareId: gameCustomHostnames.cloudflareId,
|
|
50795
|
-
environment: gameCustomHostnames.environment
|
|
50796
|
-
}).from(gameCustomHostnames).where(eq(gameCustomHostnames.gameId, gameId));
|
|
50797
|
-
const result = await db2.delete(games).where(eq(games.id, gameId)).returning({ id: games.id });
|
|
50798
|
-
if (result.length === 0) {
|
|
50799
|
-
throw new NotFoundError("Game", gameId);
|
|
50800
|
-
}
|
|
50801
|
-
logger5.info("Deleted game", {
|
|
50802
|
-
gameId: result[0].id,
|
|
50803
|
-
slug: gameToDelete.slug,
|
|
50804
|
-
hadActiveDeployment: Boolean(activeDeployment),
|
|
50805
|
-
customDomainsCount: customHostnames.length
|
|
50806
|
-
});
|
|
50807
|
-
this.deps.alerts.notifyGameDeletion({
|
|
50808
|
-
slug: gameToDelete.slug,
|
|
50809
|
-
displayName: gameToDelete.displayName,
|
|
50810
|
-
developer: { id: user.id, email: user.email }
|
|
50811
|
-
}).catch((error) => {
|
|
50812
|
-
logger5.warn("Failed to send deletion alert", { error });
|
|
50813
|
-
});
|
|
50814
|
-
if (activeDeployment?.provider === "cloudflare" && this.deps.cloudflare) {
|
|
50707
|
+
async attemptManifestFetch(args2) {
|
|
50708
|
+
const { manifestUrl, manifestHost, deploymentUrl, startedAt, retryCount } = args2;
|
|
50709
|
+
function buildDetails(fetchOutcome, manifestErrorKind, extra = {}) {
|
|
50710
|
+
return {
|
|
50711
|
+
manifestUrl,
|
|
50712
|
+
manifestHost,
|
|
50713
|
+
deploymentUrl,
|
|
50714
|
+
fetchOutcome,
|
|
50715
|
+
retryCount,
|
|
50716
|
+
durationMs: Date.now() - startedAt,
|
|
50717
|
+
manifestErrorKind,
|
|
50718
|
+
...extra
|
|
50719
|
+
};
|
|
50720
|
+
}
|
|
50721
|
+
const controller = new AbortController;
|
|
50722
|
+
const timeout = setTimeout(() => controller.abort(), GameService2.MANIFEST_FETCH_ATTEMPT_TIMEOUT_MS);
|
|
50723
|
+
let response;
|
|
50815
50724
|
try {
|
|
50816
|
-
await
|
|
50817
|
-
|
|
50818
|
-
|
|
50819
|
-
|
|
50820
|
-
|
|
50821
|
-
|
|
50822
|
-
logger5.info("Cleaned up Cloudflare resources", {
|
|
50823
|
-
gameId,
|
|
50824
|
-
deploymentId: activeDeployment.deploymentId,
|
|
50825
|
-
customDomainsDeleted: customHostnames.length
|
|
50725
|
+
response = await fetch(manifestUrl, {
|
|
50726
|
+
method: "GET",
|
|
50727
|
+
headers: {
|
|
50728
|
+
Accept: "application/json"
|
|
50729
|
+
},
|
|
50730
|
+
signal: controller.signal
|
|
50826
50731
|
});
|
|
50827
|
-
} catch (
|
|
50828
|
-
|
|
50829
|
-
|
|
50830
|
-
|
|
50831
|
-
|
|
50732
|
+
} catch (error) {
|
|
50733
|
+
const fetchErrorMessage = GameService2.getFetchErrorMessage(error);
|
|
50734
|
+
const details = buildDetails("network_error", "temporary", fetchErrorMessage ? { fetchErrorMessage } : {});
|
|
50735
|
+
const throwable = error instanceof Error && error.name === "AbortError" ? new TimeoutError("Timed out loading game manifest", details) : new ServiceUnavailableError("Failed to load game manifest", details);
|
|
50736
|
+
return { kind: "failure", retryable: true, throwable, details, cause: error };
|
|
50737
|
+
} finally {
|
|
50738
|
+
clearTimeout(timeout);
|
|
50739
|
+
}
|
|
50740
|
+
if (!response.ok) {
|
|
50741
|
+
const resolvedManifestUrl = response.url || manifestUrl;
|
|
50742
|
+
const resolvedManifestHost = GameService2.getManifestHost(resolvedManifestUrl);
|
|
50743
|
+
const manifestErrorKind = GameService2.isRetryableStatus(response.status) ? "temporary" : "permanent";
|
|
50744
|
+
const details = buildDetails("bad_status", manifestErrorKind, {
|
|
50745
|
+
manifestUrl: resolvedManifestUrl,
|
|
50746
|
+
manifestHost: resolvedManifestHost,
|
|
50747
|
+
status: response.status,
|
|
50748
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
50749
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50750
|
+
redirected: response.redirected,
|
|
50751
|
+
...response.redirected ? {
|
|
50752
|
+
originalManifestUrl: manifestUrl,
|
|
50753
|
+
originalManifestHost: manifestHost
|
|
50754
|
+
} : {}
|
|
50832
50755
|
});
|
|
50756
|
+
const message = `Failed to fetch manifest: ${response.status} ${response.statusText}`;
|
|
50757
|
+
const throwable = manifestErrorKind === "temporary" ? new ServiceUnavailableError(message, details) : new BadRequestError(message, details);
|
|
50758
|
+
return {
|
|
50759
|
+
kind: "failure",
|
|
50760
|
+
retryable: manifestErrorKind === "temporary",
|
|
50761
|
+
throwable,
|
|
50762
|
+
details
|
|
50763
|
+
};
|
|
50833
50764
|
}
|
|
50834
50765
|
try {
|
|
50835
|
-
const
|
|
50836
|
-
|
|
50837
|
-
|
|
50838
|
-
|
|
50839
|
-
|
|
50840
|
-
|
|
50766
|
+
const manifest = await response.json();
|
|
50767
|
+
return { kind: "success", manifest };
|
|
50768
|
+
} catch (error) {
|
|
50769
|
+
const resolvedManifestUrl = response.url || manifestUrl;
|
|
50770
|
+
const resolvedManifestHost = GameService2.getManifestHost(resolvedManifestUrl);
|
|
50771
|
+
const details = buildDetails("invalid_body", "permanent", {
|
|
50772
|
+
manifestUrl: resolvedManifestUrl,
|
|
50773
|
+
manifestHost: resolvedManifestHost,
|
|
50774
|
+
status: response.status,
|
|
50775
|
+
contentType: response.headers.get("content-type") ?? undefined,
|
|
50776
|
+
cfRay: response.headers.get("cf-ray") ?? undefined,
|
|
50777
|
+
redirected: response.redirected,
|
|
50778
|
+
...response.redirected ? {
|
|
50779
|
+
originalManifestUrl: manifestUrl,
|
|
50780
|
+
originalManifestHost: manifestHost
|
|
50781
|
+
} : {}
|
|
50782
|
+
});
|
|
50783
|
+
return {
|
|
50784
|
+
kind: "failure",
|
|
50785
|
+
retryable: false,
|
|
50786
|
+
throwable: new BadRequestError("Failed to parse game manifest", details),
|
|
50787
|
+
details,
|
|
50788
|
+
cause: error
|
|
50789
|
+
};
|
|
50790
|
+
}
|
|
50791
|
+
}
|
|
50792
|
+
enforceVisibility(game, caller, lookupIdentifier) {
|
|
50793
|
+
if (game.visibility !== "internal") {
|
|
50794
|
+
return;
|
|
50795
|
+
}
|
|
50796
|
+
const isAdmin = caller?.role === "admin";
|
|
50797
|
+
const isOwner = caller?.id != null && caller.id === game.developerId;
|
|
50798
|
+
if (!isAdmin && !isOwner) {
|
|
50799
|
+
throw new NotFoundError("Game", lookupIdentifier);
|
|
50800
|
+
}
|
|
50801
|
+
}
|
|
50802
|
+
async upsertBySlug(slug, data, user) {
|
|
50803
|
+
const db2 = this.deps.db;
|
|
50804
|
+
const existingGame = await db2.query.games.findFirst({
|
|
50805
|
+
where: eq(games.slug, slug)
|
|
50806
|
+
});
|
|
50807
|
+
const isUpdate = Boolean(existingGame);
|
|
50808
|
+
const gameId = existingGame?.id ?? crypto.randomUUID();
|
|
50809
|
+
if (isUpdate) {
|
|
50810
|
+
await this.validateDeveloperAccess(user, gameId);
|
|
50811
|
+
} else {
|
|
50812
|
+
this.validateDeveloperStatus(user);
|
|
50813
|
+
}
|
|
50814
|
+
const gameDataForDb = {
|
|
50815
|
+
displayName: data.displayName,
|
|
50816
|
+
platform: data.platform,
|
|
50817
|
+
metadata: data.metadata,
|
|
50818
|
+
mapElementId: data.mapElementId,
|
|
50819
|
+
gameType: data.gameType,
|
|
50820
|
+
...data.visibility && { visibility: data.visibility },
|
|
50821
|
+
externalUrl: data.externalUrl || null,
|
|
50822
|
+
updatedAt: new Date
|
|
50823
|
+
};
|
|
50824
|
+
let gameResponse;
|
|
50825
|
+
if (isUpdate) {
|
|
50826
|
+
const [updatedGame] = await db2.update(games).set(gameDataForDb).where(eq(games.id, gameId)).returning();
|
|
50827
|
+
if (!updatedGame) {
|
|
50828
|
+
logger5.error("Game update returned no rows", { gameId, slug });
|
|
50829
|
+
throw new InternalError("DB update failed to return result for existing game");
|
|
50830
|
+
}
|
|
50831
|
+
gameResponse = updatedGame;
|
|
50832
|
+
} else {
|
|
50833
|
+
const insertData = {
|
|
50834
|
+
...gameDataForDb,
|
|
50835
|
+
id: gameId,
|
|
50836
|
+
slug,
|
|
50837
|
+
developerId: user.id,
|
|
50838
|
+
metadata: data.metadata || {},
|
|
50839
|
+
version: data.gameType === "external" ? "external" : "",
|
|
50840
|
+
deploymentUrl: null,
|
|
50841
|
+
createdAt: new Date
|
|
50842
|
+
};
|
|
50843
|
+
const [createdGame] = await db2.insert(games).values(insertData).returning();
|
|
50844
|
+
if (!createdGame) {
|
|
50845
|
+
logger5.error("Game insert returned no rows", { slug, developerId: user.id });
|
|
50846
|
+
throw new InternalError("DB insert failed to return result for new game");
|
|
50847
|
+
}
|
|
50848
|
+
gameResponse = createdGame;
|
|
50849
|
+
}
|
|
50850
|
+
if (data.mapElementId) {
|
|
50851
|
+
try {
|
|
50852
|
+
await db2.update(mapElements).set({
|
|
50853
|
+
interactionType: "game_entry",
|
|
50854
|
+
gameId: gameResponse.id
|
|
50855
|
+
}).where(eq(mapElements.id, data.mapElementId));
|
|
50856
|
+
} catch (mapError) {
|
|
50857
|
+
logger5.warn("Failed to update map element", {
|
|
50858
|
+
mapElementId: data.mapElementId,
|
|
50859
|
+
error: mapError
|
|
50841
50860
|
});
|
|
50842
50861
|
}
|
|
50843
|
-
} catch (keyError) {
|
|
50844
|
-
logger5.warn("Failed to cleanup API key", { gameId, error: keyError });
|
|
50845
50862
|
}
|
|
50863
|
+
logger5.info("Upserted game", {
|
|
50864
|
+
gameId: gameResponse.id,
|
|
50865
|
+
slug: gameResponse.slug,
|
|
50866
|
+
operation: isUpdate ? "update" : "create",
|
|
50867
|
+
displayName: gameResponse.displayName
|
|
50868
|
+
});
|
|
50869
|
+
return gameResponse;
|
|
50846
50870
|
}
|
|
50847
|
-
|
|
50848
|
-
|
|
50849
|
-
|
|
50850
|
-
|
|
50851
|
-
}
|
|
50852
|
-
async validateOwnership(user, gameId) {
|
|
50853
|
-
if (user.role === "admin") {
|
|
50854
|
-
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50871
|
+
async delete(gameId, user) {
|
|
50872
|
+
await this.validateDeveloperAccess(user, gameId);
|
|
50873
|
+
const db2 = this.deps.db;
|
|
50874
|
+
const gameToDelete = await db2.query.games.findFirst({
|
|
50855
50875
|
where: eq(games.id, gameId),
|
|
50856
|
-
columns: { id: true }
|
|
50876
|
+
columns: { id: true, slug: true, displayName: true }
|
|
50857
50877
|
});
|
|
50858
|
-
if (!
|
|
50878
|
+
if (!gameToDelete?.slug) {
|
|
50859
50879
|
throw new NotFoundError("Game", gameId);
|
|
50860
50880
|
}
|
|
50861
|
-
|
|
50881
|
+
const activeDeployment = await db2.query.gameDeployments.findFirst({
|
|
50882
|
+
where: and(eq(gameDeployments.gameId, gameId), eq(gameDeployments.isActive, true)),
|
|
50883
|
+
columns: { deploymentId: true, provider: true, resources: true }
|
|
50884
|
+
});
|
|
50885
|
+
const customHostnames = await db2.select({
|
|
50886
|
+
hostname: gameCustomHostnames.hostname,
|
|
50887
|
+
cloudflareId: gameCustomHostnames.cloudflareId,
|
|
50888
|
+
environment: gameCustomHostnames.environment
|
|
50889
|
+
}).from(gameCustomHostnames).where(eq(gameCustomHostnames.gameId, gameId));
|
|
50890
|
+
const result = await db2.delete(games).where(eq(games.id, gameId)).returning({ id: games.id });
|
|
50891
|
+
if (result.length === 0) {
|
|
50892
|
+
throw new NotFoundError("Game", gameId);
|
|
50893
|
+
}
|
|
50894
|
+
logger5.info("Deleted game", {
|
|
50895
|
+
gameId: result[0].id,
|
|
50896
|
+
slug: gameToDelete.slug,
|
|
50897
|
+
hadActiveDeployment: Boolean(activeDeployment),
|
|
50898
|
+
customDomainsCount: customHostnames.length
|
|
50899
|
+
});
|
|
50900
|
+
this.deps.alerts.notifyGameDeletion({
|
|
50901
|
+
slug: gameToDelete.slug,
|
|
50902
|
+
displayName: gameToDelete.displayName,
|
|
50903
|
+
developer: { id: user.id, email: user.email }
|
|
50904
|
+
}).catch((error) => {
|
|
50905
|
+
logger5.warn("Failed to send deletion alert", { error });
|
|
50906
|
+
});
|
|
50907
|
+
if (activeDeployment?.provider === "cloudflare" && this.deps.cloudflare) {
|
|
50908
|
+
try {
|
|
50909
|
+
await this.deps.cloudflare.delete(activeDeployment.deploymentId, {
|
|
50910
|
+
deleteBindings: true,
|
|
50911
|
+
resources: activeDeployment.resources ?? undefined,
|
|
50912
|
+
customDomains: customHostnames.length > 0 ? customHostnames : undefined,
|
|
50913
|
+
gameSlug: gameToDelete.slug
|
|
50914
|
+
});
|
|
50915
|
+
logger5.info("Cleaned up Cloudflare resources", {
|
|
50916
|
+
gameId,
|
|
50917
|
+
deploymentId: activeDeployment.deploymentId,
|
|
50918
|
+
customDomainsDeleted: customHostnames.length
|
|
50919
|
+
});
|
|
50920
|
+
} catch (cfError) {
|
|
50921
|
+
logger5.warn("Failed to cleanup Cloudflare resources", {
|
|
50922
|
+
gameId,
|
|
50923
|
+
deploymentId: activeDeployment.deploymentId,
|
|
50924
|
+
error: cfError
|
|
50925
|
+
});
|
|
50926
|
+
}
|
|
50927
|
+
try {
|
|
50928
|
+
const deletedKeyId = await this.deps.deleteApiKeyByName(getGameWorkerApiKeyName(gameToDelete.slug), user.id);
|
|
50929
|
+
if (deletedKeyId) {
|
|
50930
|
+
logger5.info("Cleaned up API key for deleted game", {
|
|
50931
|
+
gameId,
|
|
50932
|
+
slug: gameToDelete.slug,
|
|
50933
|
+
keyId: deletedKeyId
|
|
50934
|
+
});
|
|
50935
|
+
}
|
|
50936
|
+
} catch (keyError) {
|
|
50937
|
+
logger5.warn("Failed to cleanup API key", { gameId, error: keyError });
|
|
50938
|
+
}
|
|
50939
|
+
}
|
|
50940
|
+
return {
|
|
50941
|
+
slug: gameToDelete.slug,
|
|
50942
|
+
displayName: gameToDelete.displayName
|
|
50943
|
+
};
|
|
50862
50944
|
}
|
|
50863
|
-
|
|
50864
|
-
|
|
50865
|
-
|
|
50866
|
-
|
|
50867
|
-
|
|
50868
|
-
|
|
50869
|
-
|
|
50870
|
-
|
|
50945
|
+
async validateOwnership(user, gameId) {
|
|
50946
|
+
if (user.role === "admin") {
|
|
50947
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50948
|
+
where: eq(games.id, gameId),
|
|
50949
|
+
columns: { id: true }
|
|
50950
|
+
});
|
|
50951
|
+
if (!gameExists) {
|
|
50952
|
+
throw new NotFoundError("Game", gameId);
|
|
50953
|
+
}
|
|
50954
|
+
return;
|
|
50955
|
+
}
|
|
50956
|
+
const db2 = this.deps.db;
|
|
50957
|
+
const gameOwnership = await db2.query.games.findFirst({
|
|
50958
|
+
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50871
50959
|
columns: { id: true }
|
|
50872
50960
|
});
|
|
50873
|
-
if (!
|
|
50874
|
-
|
|
50961
|
+
if (!gameOwnership) {
|
|
50962
|
+
const gameExists = await db2.query.games.findFirst({
|
|
50963
|
+
where: eq(games.id, gameId),
|
|
50964
|
+
columns: { id: true }
|
|
50965
|
+
});
|
|
50966
|
+
if (!gameExists) {
|
|
50967
|
+
throw new NotFoundError("Game", gameId);
|
|
50968
|
+
}
|
|
50969
|
+
throw new AccessDeniedError("You do not own this game");
|
|
50875
50970
|
}
|
|
50876
|
-
throw new AccessDeniedError("You do not own this game");
|
|
50877
50971
|
}
|
|
50878
|
-
|
|
50879
|
-
|
|
50880
|
-
|
|
50881
|
-
|
|
50882
|
-
|
|
50883
|
-
|
|
50972
|
+
async validateDeveloperAccess(user, gameId) {
|
|
50973
|
+
this.validateDeveloperStatus(user);
|
|
50974
|
+
if (user.role === "admin") {
|
|
50975
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
50976
|
+
where: eq(games.id, gameId),
|
|
50977
|
+
columns: { id: true }
|
|
50978
|
+
});
|
|
50979
|
+
if (!gameExists) {
|
|
50980
|
+
throw new NotFoundError("Game", gameId);
|
|
50981
|
+
}
|
|
50982
|
+
return;
|
|
50983
|
+
}
|
|
50984
|
+
const db2 = this.deps.db;
|
|
50985
|
+
const existingGame = await db2.query.games.findFirst({
|
|
50986
|
+
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50884
50987
|
columns: { id: true }
|
|
50885
50988
|
});
|
|
50886
|
-
if (!
|
|
50989
|
+
if (!existingGame) {
|
|
50887
50990
|
throw new NotFoundError("Game", gameId);
|
|
50888
50991
|
}
|
|
50889
|
-
return;
|
|
50890
|
-
}
|
|
50891
|
-
const db2 = this.deps.db;
|
|
50892
|
-
const existingGame = await db2.query.games.findFirst({
|
|
50893
|
-
where: and(eq(games.id, gameId), eq(games.developerId, user.id)),
|
|
50894
|
-
columns: { id: true }
|
|
50895
|
-
});
|
|
50896
|
-
if (!existingGame) {
|
|
50897
|
-
throw new NotFoundError("Game", gameId);
|
|
50898
50992
|
}
|
|
50899
|
-
|
|
50900
|
-
|
|
50901
|
-
|
|
50902
|
-
|
|
50903
|
-
|
|
50904
|
-
|
|
50905
|
-
|
|
50993
|
+
async validateDeveloperAccessBySlug(user, slug) {
|
|
50994
|
+
this.validateDeveloperStatus(user);
|
|
50995
|
+
const db2 = this.deps.db;
|
|
50996
|
+
if (user.role === "admin") {
|
|
50997
|
+
const game2 = await db2.query.games.findFirst({
|
|
50998
|
+
where: eq(games.slug, slug)
|
|
50999
|
+
});
|
|
51000
|
+
if (!game2) {
|
|
51001
|
+
throw new NotFoundError("Game", slug);
|
|
51002
|
+
}
|
|
51003
|
+
return game2;
|
|
51004
|
+
}
|
|
51005
|
+
const game = await db2.query.games.findFirst({
|
|
51006
|
+
where: and(eq(games.slug, slug), eq(games.developerId, user.id))
|
|
50906
51007
|
});
|
|
50907
|
-
if (!
|
|
51008
|
+
if (!game) {
|
|
50908
51009
|
throw new NotFoundError("Game", slug);
|
|
50909
51010
|
}
|
|
50910
|
-
return
|
|
50911
|
-
}
|
|
50912
|
-
const game = await db2.query.games.findFirst({
|
|
50913
|
-
where: and(eq(games.slug, slug), eq(games.developerId, user.id))
|
|
50914
|
-
});
|
|
50915
|
-
if (!game) {
|
|
50916
|
-
throw new NotFoundError("Game", slug);
|
|
50917
|
-
}
|
|
50918
|
-
return game;
|
|
50919
|
-
}
|
|
50920
|
-
validateDeveloperStatus(user) {
|
|
50921
|
-
if (user.role === "admin") {
|
|
50922
|
-
return;
|
|
51011
|
+
return game;
|
|
50923
51012
|
}
|
|
50924
|
-
|
|
50925
|
-
|
|
50926
|
-
|
|
50927
|
-
|
|
50928
|
-
|
|
50929
|
-
|
|
51013
|
+
validateDeveloperStatus(user) {
|
|
51014
|
+
if (user.role === "admin") {
|
|
51015
|
+
return;
|
|
51016
|
+
}
|
|
51017
|
+
if (user.developerStatus !== "approved") {
|
|
51018
|
+
const status = user.developerStatus || "none";
|
|
51019
|
+
if (status === "pending") {
|
|
51020
|
+
throw new AccessDeniedError("Developer application is pending approval. You will be notified when approved.");
|
|
51021
|
+
} else {
|
|
51022
|
+
throw new AccessDeniedError("Developer status required. Apply for developer access to create and manage games.");
|
|
51023
|
+
}
|
|
50930
51024
|
}
|
|
50931
51025
|
}
|
|
50932
|
-
}
|
|
50933
|
-
}
|
|
50934
|
-
var logger5;
|
|
50935
|
-
var init_game_service = __esm(() => {
|
|
50936
|
-
init_drizzle_orm();
|
|
50937
|
-
init_tables_index();
|
|
50938
|
-
init_src2();
|
|
50939
|
-
init_errors();
|
|
50940
|
-
init_deployment_util();
|
|
50941
|
-
logger5 = log.scope("GameService");
|
|
51026
|
+
};
|
|
50942
51027
|
});
|
|
50943
51028
|
function createGameServices(deps) {
|
|
50944
51029
|
const { db: db2, config: config2, cloudflare, auth: auth2, storage, cache, alerts } = deps;
|
|
50945
51030
|
const game = new GameService({
|
|
50946
51031
|
db: db2,
|
|
50947
51032
|
alerts,
|
|
51033
|
+
cache,
|
|
50948
51034
|
cloudflare,
|
|
50949
51035
|
deleteApiKeyByName: auth2.deleteApiKeyByName.bind(auth2)
|
|
50950
51036
|
});
|
|
@@ -125200,7 +125286,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
|
125200
125286
|
// package.json
|
|
125201
125287
|
var package_default2 = {
|
|
125202
125288
|
name: "@playcademy/vite-plugin",
|
|
125203
|
-
version: "0.2.24-beta.
|
|
125289
|
+
version: "0.2.24-beta.3",
|
|
125204
125290
|
type: "module",
|
|
125205
125291
|
exports: {
|
|
125206
125292
|
".": {
|