Initial import of zappier-edge from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 12:58:25 -04:00
commit b8d239e759
120 changed files with 19867 additions and 0 deletions

173
src/pricing.ts Normal file
View file

@ -0,0 +1,173 @@
export type PriceRule =
| { kind: 'free' }
| { kind: 'fixed'; fixedCents: number }
| { kind: 'variable'; baseCents: number; perKbCents: number; perMbCents: number };
export interface TierConfig {
id: string;
name: string;
multiplier: number;
monthlyCreditCents: number;
defaultRule?: PriceRule;
}
export interface RateCard {
endpoints: Record<string, PriceRule>;
}
export interface TierCatalog {
find(id: string): TierConfig | undefined;
list(): TierConfig[];
}
export class ConfigTierCatalog implements TierCatalog {
constructor(private tiers: TierConfig[]) {}
find(id: string): TierConfig | undefined {
return this.tiers.find((t) => t.id === id);
}
list(): TierConfig[] {
return [...this.tiers];
}
}
export interface CallUsage {
metadataBytes: number;
attachmentBytes: number;
}
export interface Quote {
endpointId: string;
listCents: number;
totalCents: number;
breakdown: { baseCents: number; metadataCents: number; attachmentCents: number };
}
export interface PricingContext {
rateCard: RateCard;
tiers: TierCatalog;
}
export interface PricingStore {
getRateCard(): RateCard;
getTiers(): TierConfig[];
upsertEndpoint(endpointId: string, rule: PriceRule): void;
deleteEndpoint(endpointId: string): void;
upsertTier(tier: TierConfig): void;
deleteTier(tierId: string): void;
}
export const DEFAULT_RATE_CARD: RateCard = {
endpoints: {
status: { kind: 'free' },
'storage-list': { kind: 'free' },
transform: { kind: 'fixed', fixedCents: 4 },
add: { kind: 'free' },
timestamp: { kind: 'fixed', fixedCents: 4 },
'hash-lookup': { kind: 'free' },
receipt: { kind: 'free' },
storage: { kind: 'variable', baseCents: 10, perKbCents: 1, perMbCents: 50 },
},
};
export const DEFAULT_TIERS: TierConfig[] = [
{ id: 'free', name: 'Free', multiplier: 1, monthlyCreditCents: 100 },
{
id: 'pro',
name: 'Pro',
multiplier: 0.5,
monthlyCreditCents: 1000,
defaultRule: { kind: 'fixed', fixedCents: 8 },
},
{
id: 'business',
name: 'Business',
multiplier: 0.25,
monthlyCreditCents: 10000,
defaultRule: { kind: 'fixed', fixedCents: 8 },
},
];
export const DEFAULT_PRICING: PricingContext = {
rateCard: DEFAULT_RATE_CARD,
tiers: new ConfigTierCatalog(DEFAULT_TIERS),
};
export class InMemoryPricingStore implements PricingStore {
private endpoints: Record<string, PriceRule>;
private tiers: TierConfig[];
constructor(rateCard: RateCard = DEFAULT_RATE_CARD, tiers: TierConfig[] = DEFAULT_TIERS) {
this.endpoints = { ...rateCard.endpoints };
this.tiers = [...tiers];
}
getRateCard(): RateCard {
return { endpoints: { ...this.endpoints } };
}
getTiers(): TierConfig[] {
return [...this.tiers];
}
upsertEndpoint(endpointId: string, rule: PriceRule): void {
this.endpoints[endpointId] = rule;
}
deleteEndpoint(endpointId: string): void {
delete this.endpoints[endpointId];
}
upsertTier(tier: TierConfig): void {
const i = this.tiers.findIndex((t) => t.id === tier.id);
if (i >= 0) this.tiers[i] = tier;
else this.tiers.push(tier);
}
deleteTier(tierId: string): void {
this.tiers = this.tiers.filter((t) => t.id !== tierId);
}
}
export function quoteCall(
pricing: PricingContext,
tierId: string,
endpointId: string,
usage: CallUsage,
multiplierOverride?: number,
): Quote {
const tier = pricing.tiers.find(tierId);
if (!tier) throw new Error(`Unknown tier: ${tierId}`);
const rule = pricing.rateCard.endpoints[endpointId] ?? tier.defaultRule;
if (!rule) throw new Error(`No price rule for ${tierId}/${endpointId}`);
if (rule.kind === 'free') {
return {
endpointId,
listCents: 0,
totalCents: 0,
breakdown: { baseCents: 0, metadataCents: 0, attachmentCents: 0 },
};
}
let breakdown: Quote['breakdown'];
if (rule.kind === 'fixed') {
breakdown = { baseCents: rule.fixedCents, metadataCents: 0, attachmentCents: 0 };
} else {
breakdown = {
baseCents: rule.baseCents,
metadataCents: Math.ceil(usage.metadataBytes / 1024) * rule.perKbCents,
attachmentCents:
Math.ceil(usage.attachmentBytes / (1024 * 1024)) * rule.perMbCents,
};
}
const listCents = breakdown.baseCents + breakdown.metadataCents + breakdown.attachmentCents;
const multiplier = multiplierOverride ?? tier.multiplier;
return {
endpointId,
listCents,
totalCents: Math.round(listCents * multiplier),
breakdown,
};
}