Skip to content

⭐ feat(core): Add usage billing #268

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
203 changes: 157 additions & 46 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
"@instantdb/admin": "^0.17.7",
"@openauthjs/openauth": "*",
"@openauthjs/openevent": "^0.0.27",
"@polar-sh/sdk": "^0.26.1",
"@polar-sh/ingestion": "^0.2.2",
"@polar-sh/sdk": "^0.32.10",
Comment on lines +35 to +36
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify dependency versions.

Adding the ingestion package and updating the SDK is aligned with adding billing functionality. However, it's worth verifying these are the most appropriate versions for your needs.


🏁 Script executed:

#!/bin/bash
# Description: Check for the latest versions of @polar-sh packages being used

echo "Checking latest versions of polar-sh packages on npm..."
npm view @polar-sh/ingestion version
npm view @polar-sh/sdk version

echo "Checking if there are any breaking changes in the changelog or release notes..."
curl -s "https://api.github.com/repos/polarsource/polar/releases" | grep -A 3 "\"name\": \"v0.3" || echo "No v0.3+ releases found"

Length of output: 526


Update @polar-sh/sdk to the latest patch release

Based on the npm registry and GitHub releases:

  • @polar-sh/ingestion @^0.2.2 is already at the published latest (0.2.2).
  • @polar-sh/sdk @^0.32.10 is one patch behind; the latest is 0.32.11.
  • No v0.3+ releases (major bumps) are available yet.

Please bump the SDK dependency to pick up the newest patch:

--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ lines 35-36
-        "@polar-sh/ingestion": "^0.2.2",
-        "@polar-sh/sdk":        "^0.32.10",
+        "@polar-sh/ingestion": "^0.2.2",
+        "@polar-sh/sdk":        "^0.32.11",
📝 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
"@polar-sh/ingestion": "^0.2.2",
"@polar-sh/sdk": "^0.32.10",
"@polar-sh/ingestion": "^0.2.2",
"@polar-sh/sdk": "^0.32.11",

"drizzle-kit": "^0.30.5",
"drizzle-orm": "^0.40.0",
"postgres": "^3.4.5"
Expand Down
15 changes: 15 additions & 0 deletions packages/core/src/billing/billing.sql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { bigint, pgEnum, pgTable } from "drizzle-orm/pg-core";
import { teamID, timestamps } from "../drizzle/types";

export const CreditsType = ["gpu", "bandwidth", "storage"] as const;
export const creditsEnum = pgEnum('credits_type', CreditsType);

export const usage = pgTable(
"usage",
{
...teamID,
...timestamps,
type: creditsEnum("type").notNull(),
creditsUsed: bigint("credits_used", { mode: "number" }).notNull(),
}
)
Comment on lines +7 to +15
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding references to related entities

The usage table doesn't include references to the user, game, or session that generated the usage. Consider adding foreign key columns to establish these relationships.

export const usage = pgTable(
    "usage",
    {
        ...teamID,
        ...timestamps,
        type: creditsEnum("type").notNull(),
        creditsUsed: bigint("credits_used", { mode: "number" }).notNull(),
+        // Reference to the user who generated this usage
+        userID: text("user_id").notNull().references(() => user.id),
+        // References to related entities (uncomment when implemented)
+        // gameID: text("game_id").references(() => game.id),
+        // sessionID: text("session_id").references(() => session.id),
    }
)

⚠️ Potential issue

Add a primary key to the usage table

The usage table is missing a primary key column. Every table should have a unique identifier to ensure data integrity and efficient querying.

export const usage = pgTable(
    "usage",
    {
+        id: text("id").primaryKey(),
        ...teamID,
        ...timestamps,
        type: creditsEnum("type").notNull(),
        creditsUsed: bigint("credits_used", { mode: "number" }).notNull(),
    }
)
📝 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 usage = pgTable(
"usage",
{
...teamID,
...timestamps,
type: creditsEnum("type").notNull(),
creditsUsed: bigint("credits_used", { mode: "number" }).notNull(),
}
)
export const usage = pgTable(
"usage",
{
id: text("id").primaryKey(),
...teamID,
...timestamps,
type: creditsEnum("type").notNull(),
creditsUsed: bigint("credits_used", { mode: "number" }).notNull(),
}
)

27 changes: 27 additions & 0 deletions packages/core/src/billing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { z } from "zod";
import { Common } from "../common";
import { Examples } from "../examples";
import { CreditsType } from "./billing.sql";

export namespace Billing {
export const Info = z.object({
id: z.string().openapi({
description: Common.IdDescription,
example: Examples.Usage.id,
}),
creditsUsed: z.number().openapi({
description: "The credits used",
example: Examples.Usage.creditsUsed
}),
type: z.enum(CreditsType).openapi({
description: "The type of credits this was billed on"
}),
// game:
// session:
})
.openapi({
ref: "Billing",
description: "Represents a usage billing",
example: Examples.Usage,
});
}
16 changes: 16 additions & 0 deletions packages/core/src/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ export namespace Examples {
steamAccounts: [Steam]
};

export const game = {
id: Id("game")
}

export const session = {
id: Id("session")
}

export const Usage = {
id: Id("usage"),
creditsUsed: 20,
type: "gpu" as const, //or bandwidth, storage
game: [game],
session: [session]
}

export const Product = {
id: Id("product"),
name: "RTX 4090",
Expand Down
5 changes: 3 additions & 2 deletions packages/core/src/member/member.sql.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { teamIndexes } from "../team/team.sql";
import { timestamps, utc, teamID } from "../drizzle/types";
import { index, pgTable, text, uniqueIndex, varchar } from "drizzle-orm/pg-core";
import { index, pgEnum, pgTable, text, uniqueIndex, varchar } from "drizzle-orm/pg-core";
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Good improvement using PostgreSQL native enum type.

Converting from a text column with validation to a native PostgreSQL enum type improves type safety and database integrity. However, this change requires a database migration strategy to handle existing data.

Make sure you have a migration plan in place for this schema change. Existing records with role column values will need to be converted to use the new enum type.

Also applies to: 6-6, 13-13


🏁 Script executed:

#!/bin/bash
# Description: Check if migration files exist for the member table role column change

echo "Looking for migration files related to member table:"
fd -t f "member.*migration" -E "*.md"

echo "Checking if there are any drizzle migration files:"
fd -t f "migration" -E "*.md" | grep -i "member\|role" || echo "No specific member role migration files found"

Length of output: 421


Add migration for member.role enum change

We didn’t find any existing migration files handling the conversion of the role column from text to a native PostgreSQL enum. To ensure data integrity and a smooth rollout, please add a migration that:

  • Creates the new enum type (e.g. member_role_enum) if it doesn’t already exist
  • Converts existing string values in member.role to the new enum type
  • Alters the member table’s role column to use the enum type
  • (Optionally) Drops or renames the old text column once the migration is complete

Once you’ve added and tested this migration, the enum change in packages/core/src/member/member.sql.ts can be safely deployed.


export const role = ["admin", "member", "owner"] as const;
const pgRole = pgEnum("role", role)

export const memberTable = pgTable(
"member",
{
...teamID,
...timestamps,
role: text("role", { enum: role }).notNull(),
role: pgRole().notNull(),
timeSeen: utc("time_seen"),
email: varchar("email", { length: 255 }).notNull(),
},
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/utils/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ export const prefixes = {
subscription: "sub",
invite: "inv",
product: "prd",
usage: "usg",
game: "gme",
session: "ssn"
} as const;

/**
Expand Down