Openstatus
www.openstatus.dev
1import type { WorkspacePlan } from "../workspaces/validation";
2import { allPlans } from "./config";
3import { type Addons, type Limits, limitsSchema } from "./schema";
4
5export function getLimit<T extends keyof Limits>(limits: Limits, limit: T) {
6 return limits[limit] || allPlans.free.limits[limit];
7}
8
9export function getLimits(plan: WorkspacePlan | null) {
10 return allPlans[plan || "free"].limits;
11}
12
13export function getPlanConfig(plan: WorkspacePlan | null) {
14 return allPlans[plan || "free"];
15}
16
17export function getCurrency({
18 continent,
19 country,
20}: {
21 continent: string;
22 country: string;
23}) {
24 if (country === "IN") {
25 return "INR";
26 }
27 if (continent === "EU") {
28 return "EUR";
29 }
30 return "USD";
31}
32
33type PriceObject = {
34 USD: number;
35 EUR: number;
36 INR: number;
37};
38
39type PriceConfig = {
40 value: number;
41 locale: string;
42 currency: string;
43};
44
45function getLocaleForCurrency(currency: string): string {
46 return currency === "EUR" ? "fr-FR" : "en-US";
47}
48
49function resolvePriceConfig(
50 price: PriceObject,
51 currency?: string,
52): PriceConfig {
53 const effectiveCurrency = currency && currency in price ? currency : "USD";
54 const value = price[effectiveCurrency as keyof PriceObject];
55 const locale = getLocaleForCurrency(effectiveCurrency);
56
57 return { value, locale, currency: effectiveCurrency };
58}
59
60export function getPriceConfig(plan: WorkspacePlan, currency?: string) {
61 const planConfig = allPlans[plan];
62 return resolvePriceConfig(planConfig.price, currency);
63}
64
65export function getAddonPriceConfig(
66 plan: WorkspacePlan,
67 addon: keyof Addons,
68 currency?: string,
69) {
70 const addonConfig = allPlans[plan].addons[addon];
71 if (!addonConfig) {
72 return null;
73 }
74 return resolvePriceConfig(addonConfig.price, currency);
75}
76
77export function getPlansForLimit(
78 currentPlan: WorkspacePlan,
79 limit: keyof Limits,
80): WorkspacePlan[] {
81 const currentLimitValue = allPlans[currentPlan].limits[limit];
82 const planOrder: WorkspacePlan[] = ["free", "starter", "team"];
83
84 // Get plans that come after the current plan
85 const availablePlans = planOrder.filter((plan) => {
86 const planIndex = planOrder.indexOf(plan);
87 const currentIndex = planOrder.indexOf(currentPlan);
88 return planIndex > currentIndex;
89 });
90
91 // Filter plans based on the limit feature value
92 return availablePlans.filter((plan) => {
93 const planLimitValue = allPlans[plan].limits[limit];
94
95 // For boolean limits, only show plans where the feature is enabled
96 if (typeof currentLimitValue === "boolean") {
97 return planLimitValue === true;
98 }
99
100 // For numeric limits, show plans with higher values
101 if (
102 typeof currentLimitValue === "number" &&
103 typeof planLimitValue === "number"
104 ) {
105 return planLimitValue > currentLimitValue;
106 }
107
108 // For array limits (e.g., periodicity, regions), show plans with more options
109 if (Array.isArray(currentLimitValue) && Array.isArray(planLimitValue)) {
110 return planLimitValue.length > currentLimitValue.length;
111 }
112
113 // For string limits (e.g., data-retention), check if it's "better"
114 // This is a simple heuristic - could be improved based on specific needs
115 if (
116 typeof currentLimitValue === "string" &&
117 typeof planLimitValue === "string"
118 ) {
119 return planLimitValue !== currentLimitValue;
120 }
121
122 // For "Unlimited" string literal in members
123 if (planLimitValue === "Unlimited") {
124 return true;
125 }
126
127 return false;
128 });
129}
130
131/**
132 * Update an addon value in limits
133 * @param limits - Current workspace limits
134 * @param addon - Addon key to update
135 * @param value - The value to set (boolean for toggle addons, number for quantity addons)
136 * @returns Updated limits object
137 */
138export function updateAddonInLimits(
139 limits: Limits,
140 addon: keyof Addons,
141 value: boolean | number,
142): Limits {
143 const currentValue = limits[addon];
144 const newLimits = { ...limits };
145
146 // Infer addon type from the limit field type and set the value
147 if (typeof currentValue === "boolean" && typeof value === "boolean") {
148 // Toggle addon: set boolean value
149 (newLimits[addon] as boolean) = value;
150 } else if (typeof currentValue === "number" && typeof value === "number") {
151 // Quantity addon: set numeric value (ensure it doesn't go below 0)
152 (newLimits[addon] as number) = Math.max(0, value);
153 }
154
155 return limitsSchema.parse(newLimits);
156}