Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/backend/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
*/

import type { ApiFromModules, FilterApi, FunctionReference } from "convex/server";
import type * as common from "../common.js";
import type * as healthCheck from "../healthCheck.js";
import type * as http from "../http.js";
import type * as kv from "../kv.js";
import type * as players from "../players.js";
import type * as schemas from "../schemas.js";

/**
* A utility for referencing Convex functions in your app's API.
Expand All @@ -23,10 +25,12 @@ import type * as players from "../players.js";
* ```
*/
declare const fullApi: ApiFromModules<{
common: typeof common;
healthCheck: typeof healthCheck;
http: typeof http;
kv: typeof kv;
players: typeof players;
schemas: typeof schemas;
}>;
export declare const api: FilterApi<typeof fullApi, FunctionReference<any, "public">>;
export declare const internal: FilterApi<typeof fullApi, FunctionReference<any, "internal">>;
11 changes: 11 additions & 0 deletions packages/backend/convex/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { NoOp } from "convex-helpers/server/customFunctions";
import { zCustomMutation, zCustomQuery } from "convex-helpers/server/zod";
import { internalMutation, internalQuery, mutation, query } from "./_generated/server";

export const zQuery = zCustomQuery(query, NoOp);

export const zMutation = zCustomMutation(mutation, NoOp);

export const zInternalQuery = zCustomQuery(internalQuery, NoOp);

export const zInternalMutation = zCustomMutation(internalMutation, NoOp);
72 changes: 60 additions & 12 deletions packages/backend/convex/http.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { zValidator } from "@hono/zod-validator";
import { HonoWithConvex, HttpRouterWithHono } from "convex-helpers/server/hono";
import { Hono } from "hono";
import { bearerAuth } from "hono/bearer-auth";
import { timing } from "hono/timing";
import { api, internal } from "./_generated/api";
import { ActionCtx } from "./_generated/server";
import { PlayerDocument, playerDocumentSchema } from "./schemas";

const app: HonoWithConvex<ActionCtx> = new Hono();

Expand Down Expand Up @@ -56,26 +58,72 @@ app.put("/kv/:key", async (c) => {
return c.json({ message: "Key-Value pair updated successfully", key, value });
});

app.get("/players/:uuid/joinData", async (c) => {
app.get("/players/:uuid/document", async (c) => {
const uuid = c.req.param("uuid");
const isJoinEvent = c.req.query("joinEvent") === "true";

const playerData = await c.env.runQuery(api.players.getPlayerByUuid, { uuid });
let firstTimeJoin = false;
let playerData = await c.env.runQuery(internal.players.getPlayerByUuid, { uuid });

if (!playerData) {
firstTimeJoin = true;
const isFirstTimeOnServer = !playerData;

if (isFirstTimeOnServer) {
try {
await c.env.runAction(internal.players.createPlayer, {
uuid,
});
} catch (error) {
if (error instanceof Error && error.message.includes("Player not found on playerdb.co")) {
return c.json({ message: `"${uuid}" is not a valid minecraft uuid` }, { status: 404 });
}

await c.env.runAction(internal.players.createPlayer, {
uuid,
});
} else {
return c.json({ message: "Player data was not able to be created" }, { status: 500 });
}
} else if (isJoinEvent) {
await c.env.runMutation(internal.players.updatePlayerJoin, { uuid });
}

return c.json({
firstTimeJoin,
playerData: firstTimeJoin ? null : playerData,
playerData ??= await c.env.runQuery(internal.players.getPlayerByUuid, { uuid });

if (!playerData) {
return c.json({ message: "Player data was not able to be created" }, { status: 500 });
}

const banData = await c.env.runQuery(internal.players.getPlayerBanByUuid, { uuid });

const document: PlayerDocument = {
avatarUrl: playerData.avatarUrl,
isFirstTimeOnServer,
lastJoinTime: playerData.lastJoinedAt,
stats: playerData.stats,
banData: banData
? {
isBanned: true,
reason: banData.reason,
expiresAt: banData.expiresAt,
bannedAt: banData.bannedAt,
bannedBy: banData.bannedBy,
}
: null,
};

return c.json(document);
});

app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => {
const uuid = c.req.param("uuid");
const document = c.req.valid("json");

await c.env.runMutation(internal.players.savePlayerDocument, {
uuid: uuid as any,
document,
});
});
Comment on lines +112 to 154
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unsafe casts; return a response on PUT

as any masks a real type mismatch. Also, the handler returns nothing; send a success response.

-app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => {
+app.put("/players/:uuid/document", zValidator("json", playerDocumentSchema), async (c) => {
   const uuid = c.req.param("uuid");
   const document = c.req.valid("json");
 
   await c.env.runMutation(internal.players.savePlayerDocument, {
-    uuid: uuid as any,
+    uuid,
     document,
   });
+  return c.json({ message: "Player document saved" });
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/backend/convex/http.ts around lines 112 to 120, remove the unsafe
"as any" cast on uuid and ensure the types passed to runMutation match the
mutation signature (convert/validate uuid to the expected type, e.g., string or
UUID type) by explicitly typing or parsing the param and using the validated
document type from zValidator; after awaiting runMutation return an appropriate
HTTP response (e.g., JSON success or 204) with correct status so the PUT handler
does not return nothing.


app.delete("/players/:uuid/document", async (c) => {
const uuid = c.req.param("uuid");
await c.env.runMutation(internal.players.deletePlayerDocument, { uuid: uuid as any });

return c.json({ message: "Player data deleted successfully" });
});
Comment on lines +122 to +171
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unsafe cast on DELETE

Aligns with delete mutation accepting external UUID.

 app.delete("/players/:uuid/document", async (c) => {
   const uuid = c.req.param("uuid");
-  await c.env.runMutation(internal.players.deletePlayerDocument, { uuid: uuid as any });
+  await c.env.runMutation(internal.players.deletePlayerDocument, { uuid });
 
   return c.json({ message: "Player data deleted successfully" });
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.delete("/players/:uuid/document", async (c) => {
const uuid = c.req.param("uuid");
await c.env.runMutation(internal.players.deletePlayerDocument, { uuid: uuid as any });
return c.json({ message: "Player data deleted successfully" });
});
app.delete("/players/:uuid/document", async (c) => {
const uuid = c.req.param("uuid");
await c.env.runMutation(internal.players.deletePlayerDocument, { uuid });
return c.json({ message: "Player data deleted successfully" });
});
🤖 Prompt for AI Agents
In packages/backend/convex/http.ts around lines 122 to 127, the DELETE handler
currently casts uuid using "as any" before passing it to
internal.players.deletePlayerDocument; remove the unsafe cast and pass the uuid
with the correct type expected by the mutation (e.g., string or ExternalUuid
type). If necessary, perform a runtime null/undefined check and/or
convert/validate the param to the expected type before calling runMutation so
the call is type-safe and aligns with the delete mutation's external UUID
parameter.


export default new HttpRouterWithHono(app);
79 changes: 74 additions & 5 deletions packages/backend/convex/players.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
import { zid } from "convex-helpers/server/zod";
import { v } from "convex/values";
import { z } from "zod";
import { z } from "zod/v3";
import { internal } from "./_generated/api";
import { Id } from "./_generated/dataModel";
import { internalAction, internalMutation, query } from "./_generated/server";
import { internalAction, internalMutation, internalQuery } from "./_generated/server";
import { zInternalMutation } from "./common";
import { playerDocumentSchema } from "./schemas";

export const getPlayerByUuid = query({
export const savePlayerDocument = zInternalMutation({
args: {
uuid: zid("players"),
document: playerDocumentSchema,
},
handler: async (ctx, args) => {
if (args.document.banData) {
const existingBan = await ctx.db
.query("playerBans")
.withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
.unique();

if (existingBan) {
await ctx.db.patch(existingBan._id, {
reason: args.document.banData.reason,
expiresAt: args.document.banData.expiresAt,
});
} else {
await ctx.db.insert("playerBans", {
uuid: args.uuid,
reason: args.document.banData.reason,
expiresAt: args.document.banData.expiresAt,
bannedAt: args.document.banData.bannedAt,
bannedBy: args.document.banData.bannedBy,
});
}
}

return await ctx.db.patch(args.uuid, {
lastJoinedAt: args.document.lastJoinTime,
stats: args.document.stats,
avatarUrl: args.document.avatarUrl,
});
},
});
Comment on lines +10 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Fix uuid type mismatch (external UUID), patch by internal _id, add returns, and handle unban.

Current code treats external UUID as a Convex Id and never unbans.

-export const savePlayerDocument = zInternalMutation({
-  args: {
-    uuid: zid("players"),
-    document: playerDocumentSchema,
-  },
-  handler: async (ctx, args) => {
-    if (args.document.banData) {
-      const existingBan = await ctx.db
-        .query("playerBans")
-        .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
-        .unique();
-
-      if (existingBan) {
-        await ctx.db.patch(existingBan._id, {
-          reason: args.document.banData.reason,
-          expiresAt: args.document.banData.expiresAt,
-        });
-      } else {
-        await ctx.db.insert("playerBans", {
-          uuid: args.uuid,
-          reason: args.document.banData.reason,
-          expiresAt: args.document.banData.expiresAt,
-          bannedAt: args.document.banData.bannedAt,
-          bannedBy: args.document.banData.bannedBy,
-        });
-      }
-    }
-
-    return await ctx.db.patch(args.uuid, {
-      lastJoinedAt: args.document.lastJoinTime,
-      stats: args.document.stats,
-      avatarUrl: args.document.avatarUrl,
-    });
-  },
-});
+export const savePlayerDocument = zInternalMutation({
+  args: {
+    uuid: v.string(), // external Minecraft UUID
+    document: playerDocumentSchema,
+  },
+  returns: v.null(),
+  handler: async (ctx, args) => {
+    const player = await ctx.db
+      .query("players")
+      .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
+      .unique();
+    if (!player) throw new Error("Player not found");
+
+    const existingBan = await ctx.db
+      .query("playerBans")
+      .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
+      .unique();
+
+    if (args.document.banData) {
+      if (existingBan) {
+        await ctx.db.patch(existingBan._id, {
+          reason: args.document.banData.reason,
+          expiresAt: args.document.banData.expiresAt,
+        });
+      } else {
+        await ctx.db.insert("playerBans", {
+          uuid: args.uuid,
+          reason: args.document.banData.reason,
+          expiresAt: args.document.banData.expiresAt,
+          bannedAt: args.document.banData.bannedAt,
+          bannedBy: args.document.banData.bannedBy,
+        });
+      }
+    } else if (existingBan) {
+      await ctx.db.delete(existingBan._id); // unban
+    }
+
+    await ctx.db.patch(player._id, {
+      lastJoinedAt: args.document.lastJoinTime,
+      stats: args.document.stats,
+      avatarUrl: args.document.avatarUrl,
+    });
+    return null;
+  },
+});

🏁 Script executed:

#!/bin/bash
# Show type mismatch usage across HTTP and convex layers.
rg -n 'savePlayerDocument' -C2
rg -n 'zid\("players"\)' packages/backend/convex/players.ts

Length of output: 683


Fix UUID type mismatch: accept external UUID, patch by internal _id, return null, and handle unban.

http.ts is calling internal.players.savePlayerDocument with an external UUID (packages/backend/convex/http.ts:116–118 — uuid: uuid as any) while players.ts validates uuid as a Convex id (zid("players")) and calls ctx.db.patch(args.uuid); this treats an external UUID as an internal Id and prevents unban logic. Change players.ts to accept an external string UUID (e.g. uuid: v.string()), add returns: v.null(), resolve the player by the external UUID (.query("players").withIndex("by_uuid", q => q.eq("uuid", args.uuid)).unique()), patch the found player’s internal _id, and when banData is falsy delete the existing playerBans row (ctx.db.delete(existingBan._id)). Also remove the uuid as any cast in http.ts and pass a proper string.

🤖 Prompt for AI Agents
In packages/backend/convex/players.ts around lines 10 to 44, fix the UUID type
mismatch and unban handling by changing the mutation to accept an external UUID
string (uuid: v.string()) and declare returns: v.null(), then resolve the
internal player row via ctx.db.query("players").withIndex("by_uuid", q =>
q.eq("uuid", args.uuid)).unique(); if no player is found return null; when a
player is found use ctx.db.patch(player._id, ...) to update
lastJoinedAt/stats/avatarUrl; for ban handling, if args.document.banData is
truthy insert or patch the playerBans row (by querying by uuid) as before, but
if args.document.banData is falsy delete the existing playerBans row via
ctx.db.delete(existingBan._id); finally remove the uuid as any cast in
packages/backend/convex/http.ts and pass the actual external UUID string.


export const deletePlayerDocument = internalMutation({
args: {
uuid: v.id("players"),
},
handler: async (ctx, args) => {
return await ctx.db.delete(args.uuid);
},
});

export const getPlayerByUuid = internalQuery({
args: {
uuid: v.string(),
},
Expand All @@ -16,7 +64,19 @@ export const getPlayerByUuid = query({
},
});

const playerDbSchema = z.object({
export const getPlayerBanByUuid = internalQuery({
args: {
uuid: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db
.query("playerBans")
.withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
.unique();
},
});
Comment on lines +78 to +88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add returns validator to ban query

Same rationale as above.

 export const getPlayerBanByUuid = internalQuery({
   args: {
     uuid: v.string(),
   },
+  returns: v.optional(v.any()),
   handler: async (ctx, args) => {
     return await ctx.db
       .query("playerBans")
       .withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
       .unique();
   },
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const getPlayerBanByUuid = internalQuery({
args: {
uuid: v.string(),
},
handler: async (ctx, args) => {
return await ctx.db
.query("playerBans")
.withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
.unique();
},
});
export const getPlayerBanByUuid = internalQuery({
args: {
uuid: v.string(),
},
returns: v.optional(v.any()),
handler: async (ctx, args) => {
return await ctx.db
.query("playerBans")
.withIndex("by_uuid", (q) => q.eq("uuid", args.uuid))
.unique();
},
});
🤖 Prompt for AI Agents
In packages/backend/convex/players.ts around lines 67 to 77, the internalQuery
definition for getPlayerBanByUuid is missing a returns validator; add a returns
validator that reflects that unique() can return either a playerBan object or
null (e.g., returns: v.nullable(v.type("playerBan")) or the appropriate nullable
object schema used elsewhere) so the query validator matches the actual return
shape.


const playerDbApiSchema = z.object({
data: z.object({
player: z.object({
username: z.string(),
Expand All @@ -34,9 +94,18 @@ export const createPlayer = internalAction({
returns: v.id("players"),
handler: async (ctx, args) => {
const response = await fetch(`https://playerdb.co/api/player/minecraft/${args.uuid}`);

Comment on lines +107 to +113
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Good timeout/abort handling around fetch

The AbortController with a 10s timeout and cleanup via finally(clearTimeout) is solid. 4xx vs 5xx handling is also clear.

Check for any lingering casts like uuid as any in the HTTP layer that depended on the old validator:

Also applies to: 114-121


🏁 Script executed:

#!/bin/bash
rg -nP 'uuid\s+as\s+any|playersSaveDocument|savePlayerDocument' -S packages

Length of output: 382


Good timeout/abort handling around fetch — remove uuid as any casts in HTTP layer

AbortController/timeout are fine; the HTTP layer still bypasses the new validator with uuid as any—remove those casts and pass a properly-validated/typed uuid to the internal mutations.

  • packages/backend/convex/http.ts — lines 139–140: call to internal.players.savePlayerDocument using uuid: uuid as any.
  • packages/backend/convex/http.ts — line 159: call to internal.players.deletePlayerDocument using uuid: uuid as any.
  • packages/backend/convex/players.ts — line 10: export const savePlayerDocument = zInternalMutation({ ... }) (verify mutation signature/validator).

Remove the as any casts and align the HTTP inputs with the mutation's validated types.

if (!response.ok) {
if (response.status >= 400 && response.status < 500) {
throw new Error("Player not found on playerdb.co");
}

throw new Error("Failed to fetch player data from playerdb.co");
}

const json = await response.json();

const { data } = playerDbSchema.parse(json);
const { data } = playerDbApiSchema.parse(json);

const minecraftPlayerData: Id<"players"> = await ctx.runMutation(
internal.players.insertPlayer,
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@ export default defineSchema({
})
.index("by_username", ["username"])
.index("by_uuid", ["uuid"]),
playerBans: defineTable({
uuid: v.string(),
reason: v.string(),
expiresAt: v.optional(v.string()),
bannedAt: v.string(),
bannedBy: v.string(),
}).index("by_uuid", ["uuid"]),
});
19 changes: 19 additions & 0 deletions packages/backend/convex/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import z from "zod/v3";

export const playerDocumentSchema = z.object({
isFirstTimeOnServer: z.boolean(),
avatarUrl: z.string(),
lastJoinTime: z.string(),
stats: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
banData: z.nullable(
z.object({
isBanned: z.boolean(),
reason: z.string(),
expiresAt: z.optional(z.string()),
bannedAt: z.string(),
bannedBy: z.string(),
}),
),
});

export type PlayerDocument = z.infer<typeof playerDocumentSchema>;
Comment on lines +1 to +19
Copy link

@coderabbitai coderabbitai bot Sep 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Import Zod v4 correctly and use v4 helpers for stronger validation.

Current import targets v3 while package.json installs v4. Switch to v4 and use helpers like z.url() and z.dateTime().

-import z from "zod/v3";
+import { z } from "zod";
 
 export const playerDocumentSchema = z.object({
   isFirstTimeOnServer: z.boolean(),
-  avatarUrl: z.string(),
-  lastJoinTime: z.string(),
+  avatarUrl: z.url(),
+  lastJoinTime: z.dateTime(), // RFC 3339 timestamp string
   stats: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])),
   banData: z.nullable(
     z.object({
       isBanned: z.boolean(),
       reason: z.string(),
-      expiresAt: z.optional(z.string()),
-      bannedAt: z.string(),
+      expiresAt: z.optional(z.dateTime()),
+      bannedAt: z.dateTime(),
       bannedBy: z.string(),
     }),
   ),
 });
 
 export type PlayerDocument = z.infer<typeof playerDocumentSchema>;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In packages/backend/convex/schemas.ts around lines 1 to 19, the file imports Zod
v3 but the project uses Zod v4 and you should use v4 helpers: change the import
from "zod/v3" to "zod", replace avatarUrl's z.string() with z.string().url(),
replace lastJoinTime, banData.bannedAt and banData.expiresAt (optional) with
z.string().datetime() (or z.date() if you prefer Date objects) to enforce proper
datetime validation, and keep the rest of the schema types as-is.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convex only support zod v3 currently, so we have to import zod/v3

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BetrixDev Thank you for the clarification! You're absolutely right - I wasn't aware of Convex's current limitation to Zod v3 support. That explains why the import is from "zod/v3" and why my suggestion to use v4 features like z.url() and z.dateTime() wouldn't be applicable here.

I appreciate you correcting me on this technical constraint. The schema structure you have is appropriate for Zod v3, and the import path is correct given Convex's current compatibility requirements.


✏️ Learnings added
Learnt from: BetrixDev
PR: BetrixDev/ssm-brawl#136
File: packages/backend/convex/schemas.ts:1-19
Timestamp: 2025-09-21T01:47:24.597Z
Learning: Convex projects must use Zod v3 (imported from "zod/v3") rather than Zod v4, as Convex currently only supports Zod v3. Do not suggest upgrading to Zod v4 features for Convex codebases.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
PR: BetrixDev/ssm-brawl#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-09-20T02:04:15.025Z
Learning: Applies to **/convex/schema.ts : Always define your schema in `convex/schema.ts` and always import schema definition functions from `convex/server`.

Learnt from: CR
PR: BetrixDev/ssm-brawl#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-09-20T02:04:15.025Z
Learning: Applies to **/convex/schema.ts : Use `v.record()` for defining record types in schema; `v.map()` and `v.set()` are not supported.

Learnt from: CR
PR: BetrixDev/ssm-brawl#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-09-20T02:04:15.025Z
Learning: Applies to **/convex/**/*.ts : Define all Convex database tables using `defineTable` and specify indexes using `.index()` in schema definition files.

Learnt from: CR
PR: BetrixDev/ssm-brawl#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-09-20T02:04:15.025Z
Learning: Applies to **/convex/**/*.ts : When using the helper TypeScript type `Id`, always import it from './_generated/dataModel' and use it in place of plain `string` for Convex document IDs.

Learnt from: CR
PR: BetrixDev/ssm-brawl#0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-09-20T02:04:15.025Z
Learning: Applies to **/convex/**/*.ts : Use the Convex-supported set of validators for argument and schema validation; avoid unsupported or deprecated ones as listed.

1 change: 1 addition & 0 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"typescript": "^5.9.2"
},
"dependencies": {
"@hono/zod-validator": "^0.7.3",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Align Zod v4 usage and verify @hono/zod-validator compatibility.

Repo depends on zod ^4, but backend/schemas.ts imports "zod/v3". Please unify on Zod v4 to avoid dual runtime types. Also confirm @hono/zod-validator@0.7.3 compatibility with hono@4 and zod@4.

Run to locate v3 imports and confirm versions:

And sanity‑check library compatibility:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Zod deps in package.json files:"
fd package.json -tf -X jq -r '.dependencies.zod? // empty + " @ " + (.name // input_filename)'
echo
echo "Any v3 subpath imports:"
rg -nP "from\\s+['\"]zod/v3['\"]|require\\(['\"]zod/v3['\"]\\)"

Length of output: 196


🌐 Web query:

Is @hono/zod-validator@0.7.3 compatible with hono@4.x and zod@4.x?

💡 Result:

Short answer: No official published 0.7.3 appears on npm; @hono/zod-validator v0.7.x has added Zod v4 support (via a PR/issue) and is intended for use with Hono 4.x, but you should use the latest 0.7.x release on npm (0.7.2 at the time of my check) or confirm a 0.7.3 publish before depending on that exact tag.

Sources:

  • @hono/zod-validator npm listing (shows latest 0.7.2). [1]
  • GitHub issue/PR to add Zod v4 support to @hono/zod-validator (feature/PR #1173). [2]
  • Zod v4 versioning / upgrade notes. [3]

Unify Zod to v4 and fix zod/v3 imports

  • package.json declares zod ^4.0.2 but code imports "zod/v3" — update imports to "zod" and migrate any v3-specific APIs. Affected files: packages/backend/convex/schemas.ts (import z from "zod/v3") and packages/backend/convex/players.ts (import { z } from "zod/v3").
  • @hono/zod-validator@0.7.3 does not appear published (latest seen 0.7.2). Pin to a published 0.7.x that explicitly supports Zod v4 + Hono v4 or confirm 0.7.3 is published before depending on it; re-run CI/tests after changes.
🤖 Prompt for AI Agents
In packages/backend/package.json around line 16 and related files
packages/backend/convex/schemas.ts and packages/backend/convex/players.ts, the
repo declares zod ^4.0.2 but the code imports from "zod/v3" and depends on
@hono/zod-validator@0.7.3 which may not be published; update the imports to use
"zod" (e.g., import z or { z } from "zod") and refactor any v3-specific API
usages to their Zod v4 equivalents in those two files, then change the
@hono/zod-validator dependency to a published 0.7.x release (e.g., 0.7.2) or
confirm 0.7.3 exists before pinning; after making these edits run CI/tests to
validate and fix any remaining type or API mismatches.

"convex": "^1.25.4",
"convex-helpers": "^0.1.104",
"hono": "^4.9.8",
Expand Down
4 changes: 3 additions & 1 deletion plugin/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
AXIOM_API_TOKEN=""
AXIOM_DATASET_NAME=""
AXIOM_DATASET_NAME=""
HTTP_AUTH_TOKEN=""
HTTP_BASE_URL=""
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
package dev.betrix.superSmashMobsBrawl

import com.github.shynixn.mccoroutine.bukkit.SuspendingJavaPlugin
import dev.betrix.superSmashMobsBrawl.commands.DebugCommand
import dev.betrix.superSmashMobsBrawl.commands.KitCommand
import dev.betrix.superSmashMobsBrawl.commands.LeaveCommand
import dev.betrix.superSmashMobsBrawl.commands.QueueCommand
import dev.betrix.superSmashMobsBrawl.commands.resolvers.KitDefArgument
import dev.betrix.superSmashMobsBrawl.commands.resolvers.MinigameDefinitionArgument
import dev.betrix.superSmashMobsBrawl.commands.*
import dev.betrix.superSmashMobsBrawl.commands.resolvers.*
import dev.betrix.superSmashMobsBrawl.extensions.hasPassive
import dev.betrix.superSmashMobsBrawl.models.brawlData.KitDef
import dev.betrix.superSmashMobsBrawl.models.brawlData.MinigameDef
import dev.betrix.superSmashMobsBrawl.services.DataService
import dev.betrix.superSmashMobsBrawl.services.DebugService
import dev.betrix.superSmashMobsBrawl.services.HubProtectionService
import dev.betrix.superSmashMobsBrawl.services.HubService
import dev.betrix.superSmashMobsBrawl.services.KitService
import dev.betrix.superSmashMobsBrawl.services.LangService
import dev.betrix.superSmashMobsBrawl.services.MinigameService
import dev.betrix.superSmashMobsBrawl.services.WorldService
import dev.betrix.superSmashMobsBrawl.services.*
import dev.rollczi.litecommands.LiteCommands
import dev.rollczi.litecommands.bukkit.LiteBukkitFactory
import gg.flyte.twilight.Twilight
Expand Down Expand Up @@ -57,6 +46,8 @@ class SuperSmashMobsBrawl : SuspendingJavaPlugin(), KoinComponent {
single { this@SuperSmashMobsBrawl }
single<JavaPlugin> { this@SuperSmashMobsBrawl }
single { this@SuperSmashMobsBrawl.logger }
single { ApiService }
single { PlayerDocumentService }
single(createdAtStart = true) { DataService() }
single { MinigameService() }
single { KitService }
Expand All @@ -69,7 +60,6 @@ class SuperSmashMobsBrawl : SuspendingJavaPlugin(), KoinComponent {

StatisticsBroadcaster()

// Initialize services
HubService.initialize(this)
HubProtectionService.registerEvents()
DebugService.initialize(this)
Expand All @@ -86,8 +76,6 @@ class SuperSmashMobsBrawl : SuspendingJavaPlugin(), KoinComponent {
.commands(DebugCommand())
.build()

// Player join events are now handled by HubService

event<PlayerDropItemEvent> {
if (player.gameMode == GameMode.CREATIVE) {
return@event
Expand Down
Loading