use standalone functions to reduce bundle size

This commit is contained in:
Shawn Erquhart
2025-07-25 09:41:11 -04:00
parent 61cbd6246a
commit edeedbfac9
9 changed files with 74 additions and 40 deletions

View File

@@ -17,6 +17,7 @@ import type {
FilterApi, FilterApi,
FunctionReference, FunctionReference,
} from "convex/server"; } from "convex/server";
/** /**
* A utility for referencing Convex functions in your app's API. * A utility for referencing Convex functions in your app's API.
* *

View File

@@ -1,10 +1,12 @@
import { Polar } from "@polar-sh/sdk"; import { PolarCore } from "@polar-sh/sdk/core.js";
import { productsCreate } from "@polar-sh/sdk/funcs/productsCreate.js";
import { productsList } from "@polar-sh/sdk/funcs/productsList.js";
import { internalAction, internalMutation } from "./_generated/server"; import { internalAction, internalMutation } from "./_generated/server";
import { internal } from "./_generated/api"; import { internal } from "./_generated/api";
const accessToken = process.env.POLAR_ORGANIZATION_TOKEN; const accessToken = process.env.POLAR_ORGANIZATION_TOKEN;
const polar = new Polar({ const polar = new PolarCore({
accessToken, accessToken,
server: "sandbox", server: "sandbox",
}); });
@@ -36,7 +38,7 @@ const seed = internalAction({
return items.length > 0; return items.length > 0;
} }
} }
const result = await polar.products.list({ const result = await productsList(polar, {
isArchived: false, isArchived: false,
limit: 1, limit: 1,
}); });
@@ -52,7 +54,7 @@ const seed = internalAction({
// Create example products. In a real app you would likely create your // Create example products. In a real app you would likely create your
// products in the Polar dashboard and reference them by id in your application. // products in the Polar dashboard and reference them by id in your application.
await Promise.all([ await Promise.all([
polar.products.create({ productsCreate(polar, {
name: PREMIUM_PLAN_NAME, name: PREMIUM_PLAN_NAME,
description: "All the things for one low monthly price.", description: "All the things for one low monthly price.",
recurringInterval: "month", recurringInterval: "month",
@@ -63,7 +65,7 @@ const seed = internalAction({
}, },
], ],
}), }),
polar.products.create({ productsCreate(polar, {
name: PREMIUM_PLAN_NAME, name: PREMIUM_PLAN_NAME,
description: "All the things for one low annual price.", description: "All the things for one low annual price.",
recurringInterval: "year", recurringInterval: "year",
@@ -74,7 +76,7 @@ const seed = internalAction({
}, },
], ],
}), }),
polar.products.create({ productsCreate(polar, {
name: PREMIUM_PLUS_PLAN_NAME, name: PREMIUM_PLUS_PLAN_NAME,
description: "All the things for one low monthly price.", description: "All the things for one low monthly price.",
recurringInterval: "month", recurringInterval: "month",
@@ -85,7 +87,7 @@ const seed = internalAction({
}, },
], ],
}), }),
polar.products.create({ productsCreate(polar, {
name: PREMIUM_PLUS_PLAN_NAME, name: PREMIUM_PLUS_PLAN_NAME,
description: "All the things for one low annual price.", description: "All the things for one low annual price.",
recurringInterval: "year", recurringInterval: "year",

View File

@@ -48,11 +48,9 @@
}, },
"..": { "..": {
"name": "@convex-dev/polar", "name": "@convex-dev/polar",
"version": "0.4.4", "version": "0.5.0",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@polar-sh/checkout": "0.1.10",
"@polar-sh/sdk": "0.32.11",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"convex-helpers": "^0.1.63", "convex-helpers": "^0.1.63",
"remeda": "^2.20.2", "remeda": "^2.20.2",
@@ -66,12 +64,14 @@
"eslint": "^9.9.1", "eslint": "^9.9.1",
"globals": "^15.9.0", "globals": "^15.9.0",
"prettier": "3.2.5", "prettier": "3.2.5",
"typescript": "~5.0.3", "typescript": "^5.5.0",
"typescript-eslint": "^8.4.0", "typescript-eslint": "^8.4.0",
"vitest": "^2.1.4" "vitest": "^2.1.4"
}, },
"peerDependencies": { "peerDependencies": {
"convex": "^1.19.2 || >=1.17.0 <1.25.0", "@polar-sh/checkout": ">=0.1.10",
"@polar-sh/sdk": ">=0.32.11",
"convex": "^1.25.4",
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19" "react-dom": "^18 || ^19"
} }

View File

@@ -41,14 +41,16 @@ export default function TodoList() {
const getButtonText = (targetProductId: string) => { const getButtonText = (targetProductId: string) => {
if (!user?.subscription) return "Upgrade"; if (!user?.subscription) return "Upgrade";
const currentAmount = user.subscription.amount ?? 0; const isPremium =
const targetProduct = Object.values(products ?? {}).find( user.subscription.productId === premiumMonthly?.id ||
(p) => p?.id === targetProductId user.subscription.productId === premiumYearly?.id;
); const targetIsPremiumPlus =
const targetAmount = targetProduct?.prices[0].priceAmount ?? 0; targetProductId === premiumPlusMonthly?.id ||
if (targetAmount > currentAmount) return "Upgrade"; targetProductId === premiumPlusYearly?.id;
if (targetAmount < currentAmount) return "Downgrade"; if (isPremium && targetIsPremiumPlus) {
return "Switch"; return "Upgrade";
}
return "Downgrade";
}; };
const handlePlanChange = async (productId: string) => { const handlePlanChange = async (productId: string) => {

View File

@@ -73,7 +73,9 @@
} }
}, },
"peerDependencies": { "peerDependencies": {
"convex": "^1.19.2 || >=1.17.0 <1.35.0", "@polar-sh/checkout": ">=0.1.10",
"@polar-sh/sdk": ">=0.32.11",
"convex": "^1.25.4",
"react": "^18 || ^19", "react": "^18 || ^19",
"react-dom": "^18 || ^19" "react-dom": "^18 || ^19"
}, },
@@ -93,8 +95,6 @@
"types": "./dist/commonjs/client/index.d.ts", "types": "./dist/commonjs/client/index.d.ts",
"module": "./dist/esm/client/index.js", "module": "./dist/esm/client/index.js",
"dependencies": { "dependencies": {
"@polar-sh/checkout": "0.1.10",
"@polar-sh/sdk": "0.32.11",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"convex-helpers": "^0.1.63", "convex-helpers": "^0.1.63",
"remeda": "^2.20.2", "remeda": "^2.20.2",

View File

@@ -1,5 +1,10 @@
import "./polyfill"; import "./polyfill";
import { Polar as PolarSdk } from "@polar-sh/sdk"; import { PolarCore } from "@polar-sh/sdk/core.js";
import { customersCreate } from "@polar-sh/sdk/funcs/customersCreate.js";
import { checkoutsCreate } from "@polar-sh/sdk/funcs/checkoutsCreate.js";
import { customerSessionsCreate } from "@polar-sh/sdk/funcs/customerSessionsCreate.js";
import { subscriptionsUpdate } from "@polar-sh/sdk/funcs/subscriptionsUpdate.js";
import type { Checkout } from "@polar-sh/sdk/models/components/checkout.js"; import type { Checkout } from "@polar-sh/sdk/models/components/checkout.js";
import type { WebhookProductCreatedPayload } from "@polar-sh/sdk/models/components/webhookproductcreatedpayload.js"; import type { WebhookProductCreatedPayload } from "@polar-sh/sdk/models/components/webhookproductcreatedpayload.js";
import type { WebhookProductUpdatedPayload } from "@polar-sh/sdk/models/components/webhookproductupdatedpayload.js"; import type { WebhookProductUpdatedPayload } from "@polar-sh/sdk/models/components/webhookproductupdatedpayload.js";
@@ -48,7 +53,7 @@ export class Polar<
DataModel extends GenericDataModel = GenericDataModel, DataModel extends GenericDataModel = GenericDataModel,
Products extends Record<string, string> = Record<string, string>, Products extends Record<string, string> = Record<string, string>,
> { > {
public sdk: PolarSdk; public polar: PolarCore;
public products: Products; public products: Products;
private organizationToken: string; private organizationToken: string;
private webhookSecret: string; private webhookSecret: string;
@@ -77,7 +82,7 @@ export class Polar<
(process.env["POLAR_SERVER"] as "sandbox" | "production") ?? (process.env["POLAR_SERVER"] as "sandbox" | "production") ??
"sandbox"; "sandbox";
this.sdk = new PolarSdk({ this.polar = new PolarCore({
accessToken: this.organizationToken, accessToken: this.organizationToken,
server: this.server, server: this.server,
}); });
@@ -116,20 +121,23 @@ export class Polar<
const customerId = const customerId =
dbCustomer?.id || dbCustomer?.id ||
( (
await this.sdk.customers.create({ await customersCreate(this.polar, {
email, email,
metadata: { metadata: {
userId, userId,
}, },
}) })
).id; ).value?.id;
if (!customerId) {
throw new Error("Customer not created");
}
if (!dbCustomer) { if (!dbCustomer) {
await ctx.runMutation(this.component.lib.insertCustomer, { await ctx.runMutation(this.component.lib.insertCustomer, {
id: customerId, id: customerId,
userId, userId,
}); });
} }
return this.sdk.checkouts.create({ const checkout = await checkoutsCreate(this.polar, {
allowDiscountCodes: true, allowDiscountCodes: true,
customerId, customerId,
embedOrigin: origin, embedOrigin: origin,
@@ -138,6 +146,10 @@ export class Polar<
? { products: productIds } ? { products: productIds }
: { products: productIds }), : { products: productIds }),
}); });
if (!checkout.value) {
throw new Error("Checkout not created");
}
return checkout.value;
} }
async createCustomerPortalSession( async createCustomerPortalSession(
ctx: GenericActionCtx<DataModel>, ctx: GenericActionCtx<DataModel>,
@@ -152,11 +164,14 @@ export class Polar<
throw new Error("Customer not found"); throw new Error("Customer not found");
} }
const session = await this.sdk.customerSessions.create({ const session = await customerSessionsCreate(this.polar, {
customerId: customer.id, customerId: customer.id,
}); });
if (!session.value) {
throw new Error("Customer session not created");
}
return { url: session.customerPortalUrl }; return { url: session.value.customerPortalUrl };
} }
listProducts( listProducts(
ctx: RunQueryCtx, ctx: RunQueryCtx,
@@ -209,12 +224,16 @@ export class Polar<
if (subscription.productId === productId) { if (subscription.productId === productId) {
throw new Error("Subscription already on this product"); throw new Error("Subscription already on this product");
} }
await this.sdk.subscriptions.update({ const updatedSubscription = await subscriptionsUpdate(this.polar, {
id: subscription.id, id: subscription.id,
subscriptionUpdate: { subscriptionUpdate: {
productId, productId,
}, },
}); });
if (!updatedSubscription.value) {
throw new Error("Subscription not updated");
}
return updatedSubscription.value;
} }
async cancelSubscription( async cancelSubscription(
ctx: RunActionCtx, ctx: RunActionCtx,
@@ -228,13 +247,17 @@ export class Polar<
if (subscription.status !== "active") { if (subscription.status !== "active") {
throw new Error("Subscription is not active"); throw new Error("Subscription is not active");
} }
await this.sdk.subscriptions.update({ const updatedSubscription = await subscriptionsUpdate(this.polar, {
id: subscription.id, id: subscription.id,
subscriptionUpdate: { subscriptionUpdate: {
cancelAtPeriodEnd: revokeImmediately ? undefined : true, cancelAtPeriodEnd: revokeImmediately ? undefined : true,
revoke: revokeImmediately ? true : undefined, revoke: revokeImmediately ? true : undefined,
}, },
}); });
if (!updatedSubscription.value) {
throw new Error("Subscription not updated");
}
return updatedSubscription.value;
} }
api() { api() {
return { return {

View File

@@ -16,6 +16,7 @@ import type {
FilterApi, FilterApi,
FunctionReference, FunctionReference,
} from "convex/server"; } from "convex/server";
/** /**
* A utility for referencing Convex functions in your app's API. * A utility for referencing Convex functions in your app's API.
* *

View File

@@ -1,4 +1,6 @@
import { Polar as PolarSdk } from "@polar-sh/sdk"; import { PolarCore } from "@polar-sh/sdk/core";
import { productsList } from "@polar-sh/sdk/funcs/productsList.js";
import { v } from "convex/values"; import { v } from "convex/values";
import { action, mutation, query } from "./_generated/server"; import { action, mutation, query } from "./_generated/server";
import schema from "./schema"; import schema from "./schema";
@@ -278,22 +280,25 @@ export const syncProducts = action({
server: v.union(v.literal("sandbox"), v.literal("production")), server: v.union(v.literal("sandbox"), v.literal("production")),
}, },
handler: async (ctx, args) => { handler: async (ctx, args) => {
const sdk = new PolarSdk({ const polar = new PolarCore({
accessToken: args.polarAccessToken, accessToken: args.polarAccessToken,
server: args.server, server: args.server,
}); });
let page = 1; let page = 1;
let maxPage; let maxPage;
do { do {
const products = await sdk.products.list({ const products = await productsList(polar, {
page, page,
limit: 100, limit: 100,
}); });
if (!products.value) {
throw new Error("Failed to get products");
}
page = page + 1; page = page + 1;
maxPage = products.result.pagination.maxPage; maxPage = products.value.result.pagination.maxPage;
await ctx.runMutation(api.lib.updateProducts, { await ctx.runMutation(api.lib.updateProducts, {
polarAccessToken: args.polarAccessToken, polarAccessToken: args.polarAccessToken,
products: products.result.items.map(convertToDatabaseProduct), products: products.value.result.items.map(convertToDatabaseProduct),
}); });
} while (maxPage >= page); } while (maxPage >= page);
}, },

View File

@@ -12,8 +12,8 @@ import type {
import { GenericId } from "convex/values"; import { GenericId } from "convex/values";
import type { api } from "./_generated/api"; import type { api } from "./_generated/api";
import type { Doc } from "./_generated/dataModel"; import type { Doc } from "./_generated/dataModel";
import { Subscription } from "@polar-sh/sdk/models/components/subscription.js"; import type { Subscription } from "@polar-sh/sdk/models/components/subscription.js";
import { Product } from "@polar-sh/sdk/models/components/product.js"; import type { Product } from "@polar-sh/sdk/models/components/product.js";
export const omitSystemFields = < export const omitSystemFields = <
T extends { _id: string; _creationTime: number } | null | undefined, T extends { _id: string; _creationTime: number } | null | undefined,