@trendyvideo/billing 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/billing-coverage.test.d.ts +2 -0
- package/dist/__tests__/billing-coverage.test.d.ts.map +1 -0
- package/dist/__tests__/billing-coverage.test.js +170 -0
- package/dist/__tests__/billing-coverage.test.js.map +1 -0
- package/dist/__tests__/plan-limits.test.d.ts +2 -0
- package/dist/__tests__/plan-limits.test.d.ts.map +1 -0
- package/dist/__tests__/plan-limits.test.js +139 -0
- package/dist/__tests__/plan-limits.test.js.map +1 -0
- package/dist/__tests__/pricing.test.d.ts +2 -0
- package/dist/__tests__/pricing.test.d.ts.map +1 -0
- package/dist/__tests__/pricing.test.js +146 -0
- package/dist/__tests__/pricing.test.js.map +1 -0
- package/dist/__tests__/usage-tracker.test.d.ts +2 -0
- package/dist/__tests__/usage-tracker.test.d.ts.map +1 -0
- package/dist/__tests__/usage-tracker.test.js +210 -0
- package/dist/__tests__/usage-tracker.test.js.map +1 -0
- package/dist/alerts.d.ts +19 -0
- package/dist/alerts.d.ts.map +1 -0
- package/dist/alerts.js +90 -0
- package/dist/alerts.js.map +1 -0
- package/dist/guards/project-guard.d.ts +6 -0
- package/dist/guards/project-guard.d.ts.map +1 -0
- package/dist/guards/project-guard.js +56 -0
- package/dist/guards/project-guard.js.map +1 -0
- package/dist/guards/resolution-guard.d.ts +6 -0
- package/dist/guards/resolution-guard.d.ts.map +1 -0
- package/dist/guards/resolution-guard.js +40 -0
- package/dist/guards/resolution-guard.js.map +1 -0
- package/dist/guards/seat-guard.d.ts +8 -0
- package/dist/guards/seat-guard.d.ts.map +1 -0
- package/dist/guards/seat-guard.js +55 -0
- package/dist/guards/seat-guard.js.map +1 -0
- package/dist/guards/social-guard.d.ts +6 -0
- package/dist/guards/social-guard.d.ts.map +1 -0
- package/dist/guards/social-guard.js +52 -0
- package/dist/guards/social-guard.js.map +1 -0
- package/dist/guards/storage-guard.d.ts +14 -0
- package/dist/guards/storage-guard.d.ts.map +1 -0
- package/dist/guards/storage-guard.js +85 -0
- package/dist/guards/storage-guard.js.map +1 -0
- package/dist/guards/stream-guard.d.ts +7 -0
- package/dist/guards/stream-guard.d.ts.map +1 -0
- package/dist/guards/stream-guard.js +73 -0
- package/dist/guards/stream-guard.js.map +1 -0
- package/dist/guards/watch-guard.d.ts +13 -0
- package/dist/guards/watch-guard.d.ts.map +1 -0
- package/dist/guards/watch-guard.js +71 -0
- package/dist/guards/watch-guard.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/plan-limits.d.ts +25 -0
- package/dist/plan-limits.d.ts.map +1 -0
- package/dist/plan-limits.js +138 -0
- package/dist/plan-limits.js.map +1 -0
- package/dist/pricing.d.ts +32 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +126 -0
- package/dist/pricing.js.map +1 -0
- package/dist/types.d.ts +188 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/usage-checker.d.ts +26 -0
- package/dist/usage-checker.d.ts.map +1 -0
- package/dist/usage-checker.js +295 -0
- package/dist/usage-checker.js.map +1 -0
- package/dist/usage-tracker.d.ts +25 -0
- package/dist/usage-tracker.d.ts.map +1 -0
- package/dist/usage-tracker.js +181 -0
- package/dist/usage-tracker.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-guard.d.ts","sourceRoot":"","sources":["../../src/guards/storage-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAU7E;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CA+E7B"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkStorageQuota = checkStorageQuota;
|
|
4
|
+
const plan_limits_1 = require("../plan-limits");
|
|
5
|
+
const defaultLogger = {
|
|
6
|
+
debug: () => { },
|
|
7
|
+
info: (msg, meta) => console.log(`[billing] ${msg}`, meta ?? ''),
|
|
8
|
+
warn: (msg, meta) => console.warn(`[billing] ${msg}`, meta ?? ''),
|
|
9
|
+
error: (msg, meta) => console.error(`[billing] ${msg}`, meta ?? ''),
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Check whether an organization has enough storage quota remaining to
|
|
13
|
+
* accept a file of the given size.
|
|
14
|
+
*
|
|
15
|
+
* Storage usage is calculated via the `get_org_storage_usage_bytes` RPC
|
|
16
|
+
* (server-side SUM in Postgres). Falls back to client-side summation of
|
|
17
|
+
* `organization_files.metadata->file_size` if the RPC is not available.
|
|
18
|
+
*
|
|
19
|
+
* On error, fails OPEN (allows upload) since storage quota is a
|
|
20
|
+
* cost-protection measure, not a security boundary.
|
|
21
|
+
*/
|
|
22
|
+
async function checkStorageQuota(supabase, organizationId, fileSizeBytes, logger = defaultLogger) {
|
|
23
|
+
// 1. Fetch subscription + plan name
|
|
24
|
+
const { data: subscription } = await supabase
|
|
25
|
+
.from('organization_subscriptions')
|
|
26
|
+
.select(`
|
|
27
|
+
plan_id,
|
|
28
|
+
status,
|
|
29
|
+
subscription_plans (
|
|
30
|
+
id,
|
|
31
|
+
name
|
|
32
|
+
)
|
|
33
|
+
`)
|
|
34
|
+
.eq('organization_id', organizationId)
|
|
35
|
+
.single();
|
|
36
|
+
const plan = subscription?.subscription_plans;
|
|
37
|
+
const planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
|
|
38
|
+
const limits = (0, plan_limits_1.getPlanLimits)(planName);
|
|
39
|
+
const quotaBytes = limits.storageBytes;
|
|
40
|
+
const displayName = plan?.name ?? 'Free';
|
|
41
|
+
// 2. Calculate current storage usage
|
|
42
|
+
let currentUsageBytes = 0;
|
|
43
|
+
const { data: rpcResult, error: rpcError } = await supabase.rpc('get_org_storage_usage_bytes', { org_id: organizationId });
|
|
44
|
+
if (!rpcError && rpcResult !== null && rpcResult !== undefined) {
|
|
45
|
+
currentUsageBytes = Number(rpcResult) || 0;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Fallback: sum metadata->file_size client-side
|
|
49
|
+
if (rpcError) {
|
|
50
|
+
logger.warn('RPC get_org_storage_usage_bytes not available, falling back to client-side sum', {
|
|
51
|
+
error: rpcError.message,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const { data: files, error: filesError } = await supabase
|
|
55
|
+
.from('organization_files')
|
|
56
|
+
.select('metadata')
|
|
57
|
+
.eq('organization_id', organizationId);
|
|
58
|
+
if (filesError) {
|
|
59
|
+
logger.error('Failed to query organization_files', { error: filesError.message });
|
|
60
|
+
// Fail open
|
|
61
|
+
return { allowed: true, currentUsageBytes: 0, quotaBytes, planName: displayName };
|
|
62
|
+
}
|
|
63
|
+
for (const file of files ?? []) {
|
|
64
|
+
const meta = file.metadata;
|
|
65
|
+
if (meta && typeof meta.file_size === 'number') {
|
|
66
|
+
currentUsageBytes += meta.file_size;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 3. Check if adding the new file would exceed quota
|
|
71
|
+
const projectedUsage = currentUsageBytes + fileSizeBytes;
|
|
72
|
+
if (projectedUsage > quotaBytes) {
|
|
73
|
+
return {
|
|
74
|
+
allowed: false,
|
|
75
|
+
currentUsageBytes,
|
|
76
|
+
quotaBytes,
|
|
77
|
+
planName: displayName,
|
|
78
|
+
reason: `Storage limit exceeded. Your ${displayName} plan includes ${(0, plan_limits_1.formatBytes)(quotaBytes)} of storage. ` +
|
|
79
|
+
`You are currently using ${(0, plan_limits_1.formatBytes)(currentUsageBytes)} and this file (${(0, plan_limits_1.formatBytes)(fileSizeBytes)}) ` +
|
|
80
|
+
`would put you over the limit. Upgrade your plan at /billing to get more storage.`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { allowed: true, currentUsageBytes, quotaBytes, planName: displayName };
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=storage-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-guard.js","sourceRoot":"","sources":["../../src/guards/storage-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SupabaseClient, StreamLimitResult, BillingLogger } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Check whether an organization is allowed to start a new stream based on
|
|
4
|
+
* their plan's concurrent stream limit.
|
|
5
|
+
*/
|
|
6
|
+
export declare function checkStreamLimit(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<StreamLimitResult>;
|
|
7
|
+
//# sourceMappingURL=stream-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-guard.d.ts","sourceRoot":"","sources":["../../src/guards/stream-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAqC5E;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,iBAAiB,CAAC,CA4C5B"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkStreamLimit = checkStreamLimit;
|
|
4
|
+
const plan_limits_1 = require("../plan-limits");
|
|
5
|
+
const defaultLogger = {
|
|
6
|
+
debug: () => { },
|
|
7
|
+
info: (msg, meta) => console.log(`[billing] ${msg}`, meta ?? ''),
|
|
8
|
+
warn: (msg, meta) => console.warn(`[billing] ${msg}`, meta ?? ''),
|
|
9
|
+
error: (msg, meta) => console.error(`[billing] ${msg}`, meta ?? ''),
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Count streams that are currently "active" for an organization.
|
|
13
|
+
* Active statuses: starting, active, online, processing.
|
|
14
|
+
*/
|
|
15
|
+
async function countActiveStreams(supabase, organizationId, logger) {
|
|
16
|
+
const { count, error } = await supabase
|
|
17
|
+
.from('organization_files')
|
|
18
|
+
.select('id', { count: 'exact', head: true })
|
|
19
|
+
.eq('organization_id', organizationId)
|
|
20
|
+
.eq('file_type', 'stream')
|
|
21
|
+
.or('metadata->>status.eq.starting,metadata->>status.eq.active,metadata->>status.eq.online,metadata->>status.eq.processing');
|
|
22
|
+
if (error) {
|
|
23
|
+
logger.error('Error counting active streams', { error: error.message });
|
|
24
|
+
// Fail open
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
return count ?? 0;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check whether an organization is allowed to start a new stream based on
|
|
31
|
+
* their plan's concurrent stream limit.
|
|
32
|
+
*/
|
|
33
|
+
async function checkStreamLimit(supabase, organizationId, logger = defaultLogger) {
|
|
34
|
+
// 1. Fetch subscription + plan name
|
|
35
|
+
const { data: subscription, error: subError } = await supabase
|
|
36
|
+
.from('organization_subscriptions')
|
|
37
|
+
.select(`
|
|
38
|
+
plan_id,
|
|
39
|
+
status,
|
|
40
|
+
subscription_plans (
|
|
41
|
+
id,
|
|
42
|
+
name
|
|
43
|
+
)
|
|
44
|
+
`)
|
|
45
|
+
.eq('organization_id', organizationId)
|
|
46
|
+
.single();
|
|
47
|
+
let planName;
|
|
48
|
+
let displayName;
|
|
49
|
+
if (subError || !subscription) {
|
|
50
|
+
planName = 'free';
|
|
51
|
+
displayName = 'Free';
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const plan = subscription.subscription_plans;
|
|
55
|
+
planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
|
|
56
|
+
displayName = plan?.name ?? 'Free';
|
|
57
|
+
}
|
|
58
|
+
const limits = (0, plan_limits_1.getPlanLimits)(planName);
|
|
59
|
+
const maxStreams = limits.streams;
|
|
60
|
+
// 2. Count active streams
|
|
61
|
+
const activeCount = await countActiveStreams(supabase, organizationId, logger);
|
|
62
|
+
const allowed = activeCount < maxStreams;
|
|
63
|
+
return {
|
|
64
|
+
allowed,
|
|
65
|
+
activeStreams: activeCount,
|
|
66
|
+
maxStreams,
|
|
67
|
+
reason: allowed
|
|
68
|
+
? undefined
|
|
69
|
+
: `Your ${displayName} plan allows ${maxStreams} concurrent stream${maxStreams === 1 ? '' : 's'}. ` +
|
|
70
|
+
`You have ${activeCount} active. Please stop an existing stream or upgrade your plan.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=stream-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream-guard.js","sourceRoot":"","sources":["../../src/guards/stream-guard.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SupabaseClient, BillingLogger } from '../types';
|
|
2
|
+
export interface WatchLimitResult {
|
|
3
|
+
allowed: boolean;
|
|
4
|
+
currentWatches: number;
|
|
5
|
+
maxWatches: number;
|
|
6
|
+
reason?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Check whether an organization is allowed to add a new streamer watch
|
|
10
|
+
* based on their plan's maxWatches limit.
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkWatchLimit(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<WatchLimitResult>;
|
|
13
|
+
//# sourceMappingURL=watch-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-guard.d.ts","sourceRoot":"","sources":["../../src/guards/watch-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAgCD;;;GAGG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CA4C3B"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkWatchLimit = checkWatchLimit;
|
|
4
|
+
const plan_limits_1 = require("../plan-limits");
|
|
5
|
+
const defaultLogger = {
|
|
6
|
+
debug: () => { },
|
|
7
|
+
info: (msg, meta) => console.log(`[billing] ${msg}`, meta ?? ''),
|
|
8
|
+
warn: (msg, meta) => console.warn(`[billing] ${msg}`, meta ?? ''),
|
|
9
|
+
error: (msg, meta) => console.error(`[billing] ${msg}`, meta ?? ''),
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Count active streamer watches for an organization.
|
|
13
|
+
*/
|
|
14
|
+
async function countActiveWatches(supabase, organizationId, logger) {
|
|
15
|
+
const { count, error } = await supabase
|
|
16
|
+
.from('streamer_watches')
|
|
17
|
+
.select('id', { count: 'exact', head: true })
|
|
18
|
+
.eq('organization_id', organizationId)
|
|
19
|
+
.eq('is_active', true);
|
|
20
|
+
if (error) {
|
|
21
|
+
logger.error('Error counting active watches', { error: error.message });
|
|
22
|
+
// Fail open
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
return count ?? 0;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check whether an organization is allowed to add a new streamer watch
|
|
29
|
+
* based on their plan's maxWatches limit.
|
|
30
|
+
*/
|
|
31
|
+
async function checkWatchLimit(supabase, organizationId, logger = defaultLogger) {
|
|
32
|
+
// 1. Fetch subscription + plan name
|
|
33
|
+
const { data: subscription, error: subError } = await supabase
|
|
34
|
+
.from('organization_subscriptions')
|
|
35
|
+
.select(`
|
|
36
|
+
plan_id,
|
|
37
|
+
status,
|
|
38
|
+
subscription_plans (
|
|
39
|
+
id,
|
|
40
|
+
name
|
|
41
|
+
)
|
|
42
|
+
`)
|
|
43
|
+
.eq('organization_id', organizationId)
|
|
44
|
+
.single();
|
|
45
|
+
let planName;
|
|
46
|
+
let displayName;
|
|
47
|
+
if (subError || !subscription) {
|
|
48
|
+
planName = 'free';
|
|
49
|
+
displayName = 'Free';
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const plan = subscription.subscription_plans;
|
|
53
|
+
planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
|
|
54
|
+
displayName = plan?.name ?? 'Free';
|
|
55
|
+
}
|
|
56
|
+
const limits = (0, plan_limits_1.getPlanLimits)(planName);
|
|
57
|
+
const maxWatches = limits.maxWatches;
|
|
58
|
+
// 2. Count active watches
|
|
59
|
+
const currentCount = await countActiveWatches(supabase, organizationId, logger);
|
|
60
|
+
const allowed = currentCount < maxWatches;
|
|
61
|
+
return {
|
|
62
|
+
allowed,
|
|
63
|
+
currentWatches: currentCount,
|
|
64
|
+
maxWatches,
|
|
65
|
+
reason: allowed
|
|
66
|
+
? undefined
|
|
67
|
+
: `Your ${displayName} plan allows ${maxWatches} streamer watch${maxWatches === 1 ? '' : 'es'}. ` +
|
|
68
|
+
`You have ${currentCount} active. Please remove an existing watch or upgrade your plan.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=watch-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch-guard.js","sourceRoot":"","sources":["../../src/guards/watch-guard.ts"],"names":[],"mappings":";;AA4CA,0CAgDC;AA3FD,gDAAkE;AASlE,MAAM,aAAa,GAAkB;IACnC,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;IACf,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,GAAG,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC;IAChE,IAAI,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,GAAG,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC;IACjE,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,GAAG,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC;CACpE,CAAC;AAEF;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAC/B,QAAwB,EACxB,cAAsB,EACtB,MAAqB;IAErB,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACpC,IAAI,CAAC,kBAAkB,CAAC;SACxB,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC5C,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAEzB,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,KAAK,CAAC,+BAA+B,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACxE,YAAY;QACZ,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,KAAK,IAAI,CAAC,CAAC;AACpB,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,eAAe,CACnC,QAAwB,EACxB,cAAsB,EACtB,SAAwB,aAAa;IAErC,oCAAoC;IACpC,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,QAAQ;SAC3D,IAAI,CAAC,4BAA4B,CAAC;SAClC,MAAM,CAAC;;;;;;;KAOP,CAAC;SACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,MAAM,EAAE,CAAC;IAEZ,IAAI,QAAgB,CAAC;IACrB,IAAI,WAAmB,CAAC;IAExB,IAAI,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QAC9B,QAAQ,GAAG,MAAM,CAAC;QAClB,WAAW,GAAG,MAAM,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,YAAY,CAAC,kBAA6D,CAAC;QACxF,QAAQ,GAAG,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzC,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAG,IAAA,2BAAa,EAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;IAErC,0BAA0B;IAC1B,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;IAEhF,MAAM,OAAO,GAAG,YAAY,GAAG,UAAU,CAAC;IAE1C,OAAO;QACL,OAAO;QACP,cAAc,EAAE,YAAY;QAC5B,UAAU;QACV,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,QAAQ,WAAW,gBAAgB,UAAU,kBAAkB,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI;gBAC/F,YAAY,YAAY,gEAAgE;KAC7F,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type { ModelPricing, PlanName, PlanLimits, UsageEvent, UsageRecord, UsageCheckResult, RenderLimitResult, StorageQuotaResult, StreamLimitResult, ProjectLimitResult, SocialAccountLimitResult, ResolutionLimitResult, SeatLimitResult, UsageAlertResult, BillingLogger, } from './types';
|
|
2
|
+
export type { WatchLimitResult } from './guards/watch-guard';
|
|
3
|
+
export { MODEL_PRICING, DEFAULT_PRICING, getPricing, calculateCostMicrodollars, MICRODOLLARS_PER_CREDIT, microdollarsToCredits, creditsToMicrodollars, } from './pricing';
|
|
4
|
+
export { PLAN_LIMITS, getPlanLimits, normalizePlanName, formatBytes, isResolutionAllowed, formatCredits, } from './plan-limits';
|
|
5
|
+
export { recordUsage, extractVertexUsageMetadata, } from './usage-tracker';
|
|
6
|
+
export type { VertexUsageMetadata } from './usage-tracker';
|
|
7
|
+
export { checkUsage, checkRenderLimit, incrementRenders, } from './usage-checker';
|
|
8
|
+
export { checkStorageQuota } from './guards/storage-guard';
|
|
9
|
+
export { checkStreamLimit } from './guards/stream-guard';
|
|
10
|
+
export { checkProjectLimit } from './guards/project-guard';
|
|
11
|
+
export { checkSocialAccountLimit } from './guards/social-guard';
|
|
12
|
+
export { checkResolutionLimit } from './guards/resolution-guard';
|
|
13
|
+
export { checkTeamSeatLimit } from './guards/seat-guard';
|
|
14
|
+
export { checkWatchLimit } from './guards/watch-guard';
|
|
15
|
+
export { shouldSendUsageAlert, USAGE_ALERT_THRESHOLD } from './alerts';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,aAAa,GACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,WAAW,EACX,0BAA0B,GAC3B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.USAGE_ALERT_THRESHOLD = exports.shouldSendUsageAlert = exports.checkWatchLimit = exports.checkTeamSeatLimit = exports.checkResolutionLimit = exports.checkSocialAccountLimit = exports.checkProjectLimit = exports.checkStreamLimit = exports.checkStorageQuota = exports.incrementRenders = exports.checkRenderLimit = exports.checkUsage = exports.extractVertexUsageMetadata = exports.recordUsage = exports.formatCredits = exports.isResolutionAllowed = exports.formatBytes = exports.normalizePlanName = exports.getPlanLimits = exports.PLAN_LIMITS = exports.creditsToMicrodollars = exports.microdollarsToCredits = exports.MICRODOLLARS_PER_CREDIT = exports.calculateCostMicrodollars = exports.getPricing = exports.DEFAULT_PRICING = exports.MODEL_PRICING = void 0;
|
|
4
|
+
// Pricing
|
|
5
|
+
var pricing_1 = require("./pricing");
|
|
6
|
+
Object.defineProperty(exports, "MODEL_PRICING", { enumerable: true, get: function () { return pricing_1.MODEL_PRICING; } });
|
|
7
|
+
Object.defineProperty(exports, "DEFAULT_PRICING", { enumerable: true, get: function () { return pricing_1.DEFAULT_PRICING; } });
|
|
8
|
+
Object.defineProperty(exports, "getPricing", { enumerable: true, get: function () { return pricing_1.getPricing; } });
|
|
9
|
+
Object.defineProperty(exports, "calculateCostMicrodollars", { enumerable: true, get: function () { return pricing_1.calculateCostMicrodollars; } });
|
|
10
|
+
Object.defineProperty(exports, "MICRODOLLARS_PER_CREDIT", { enumerable: true, get: function () { return pricing_1.MICRODOLLARS_PER_CREDIT; } });
|
|
11
|
+
Object.defineProperty(exports, "microdollarsToCredits", { enumerable: true, get: function () { return pricing_1.microdollarsToCredits; } });
|
|
12
|
+
Object.defineProperty(exports, "creditsToMicrodollars", { enumerable: true, get: function () { return pricing_1.creditsToMicrodollars; } });
|
|
13
|
+
// Plan limits
|
|
14
|
+
var plan_limits_1 = require("./plan-limits");
|
|
15
|
+
Object.defineProperty(exports, "PLAN_LIMITS", { enumerable: true, get: function () { return plan_limits_1.PLAN_LIMITS; } });
|
|
16
|
+
Object.defineProperty(exports, "getPlanLimits", { enumerable: true, get: function () { return plan_limits_1.getPlanLimits; } });
|
|
17
|
+
Object.defineProperty(exports, "normalizePlanName", { enumerable: true, get: function () { return plan_limits_1.normalizePlanName; } });
|
|
18
|
+
Object.defineProperty(exports, "formatBytes", { enumerable: true, get: function () { return plan_limits_1.formatBytes; } });
|
|
19
|
+
Object.defineProperty(exports, "isResolutionAllowed", { enumerable: true, get: function () { return plan_limits_1.isResolutionAllowed; } });
|
|
20
|
+
Object.defineProperty(exports, "formatCredits", { enumerable: true, get: function () { return plan_limits_1.formatCredits; } });
|
|
21
|
+
// Usage tracking
|
|
22
|
+
var usage_tracker_1 = require("./usage-tracker");
|
|
23
|
+
Object.defineProperty(exports, "recordUsage", { enumerable: true, get: function () { return usage_tracker_1.recordUsage; } });
|
|
24
|
+
Object.defineProperty(exports, "extractVertexUsageMetadata", { enumerable: true, get: function () { return usage_tracker_1.extractVertexUsageMetadata; } });
|
|
25
|
+
// Usage checking
|
|
26
|
+
var usage_checker_1 = require("./usage-checker");
|
|
27
|
+
Object.defineProperty(exports, "checkUsage", { enumerable: true, get: function () { return usage_checker_1.checkUsage; } });
|
|
28
|
+
Object.defineProperty(exports, "checkRenderLimit", { enumerable: true, get: function () { return usage_checker_1.checkRenderLimit; } });
|
|
29
|
+
Object.defineProperty(exports, "incrementRenders", { enumerable: true, get: function () { return usage_checker_1.incrementRenders; } });
|
|
30
|
+
// Guards
|
|
31
|
+
var storage_guard_1 = require("./guards/storage-guard");
|
|
32
|
+
Object.defineProperty(exports, "checkStorageQuota", { enumerable: true, get: function () { return storage_guard_1.checkStorageQuota; } });
|
|
33
|
+
var stream_guard_1 = require("./guards/stream-guard");
|
|
34
|
+
Object.defineProperty(exports, "checkStreamLimit", { enumerable: true, get: function () { return stream_guard_1.checkStreamLimit; } });
|
|
35
|
+
var project_guard_1 = require("./guards/project-guard");
|
|
36
|
+
Object.defineProperty(exports, "checkProjectLimit", { enumerable: true, get: function () { return project_guard_1.checkProjectLimit; } });
|
|
37
|
+
var social_guard_1 = require("./guards/social-guard");
|
|
38
|
+
Object.defineProperty(exports, "checkSocialAccountLimit", { enumerable: true, get: function () { return social_guard_1.checkSocialAccountLimit; } });
|
|
39
|
+
var resolution_guard_1 = require("./guards/resolution-guard");
|
|
40
|
+
Object.defineProperty(exports, "checkResolutionLimit", { enumerable: true, get: function () { return resolution_guard_1.checkResolutionLimit; } });
|
|
41
|
+
var seat_guard_1 = require("./guards/seat-guard");
|
|
42
|
+
Object.defineProperty(exports, "checkTeamSeatLimit", { enumerable: true, get: function () { return seat_guard_1.checkTeamSeatLimit; } });
|
|
43
|
+
var watch_guard_1 = require("./guards/watch-guard");
|
|
44
|
+
Object.defineProperty(exports, "checkWatchLimit", { enumerable: true, get: function () { return watch_guard_1.checkWatchLimit; } });
|
|
45
|
+
// Alerts
|
|
46
|
+
var alerts_1 = require("./alerts");
|
|
47
|
+
Object.defineProperty(exports, "shouldSendUsageAlert", { enumerable: true, get: function () { return alerts_1.shouldSendUsageAlert; } });
|
|
48
|
+
Object.defineProperty(exports, "USAGE_ALERT_THRESHOLD", { enumerable: true, get: function () { return alerts_1.USAGE_ALERT_THRESHOLD; } });
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAoBA,UAAU;AACV,qCAQmB;AAPjB,wGAAA,aAAa,OAAA;AACb,0GAAA,eAAe,OAAA;AACf,qGAAA,UAAU,OAAA;AACV,oHAAA,yBAAyB,OAAA;AACzB,kHAAA,uBAAuB,OAAA;AACvB,gHAAA,qBAAqB,OAAA;AACrB,gHAAA,qBAAqB,OAAA;AAGvB,cAAc;AACd,6CAOuB;AANrB,0GAAA,WAAW,OAAA;AACX,4GAAA,aAAa,OAAA;AACb,gHAAA,iBAAiB,OAAA;AACjB,0GAAA,WAAW,OAAA;AACX,kHAAA,mBAAmB,OAAA;AACnB,4GAAA,aAAa,OAAA;AAGf,iBAAiB;AACjB,iDAGyB;AAFvB,4GAAA,WAAW,OAAA;AACX,2HAAA,0BAA0B,OAAA;AAI5B,iBAAiB;AACjB,iDAIyB;AAHvB,2GAAA,UAAU,OAAA;AACV,iHAAA,gBAAgB,OAAA;AAChB,iHAAA,gBAAgB,OAAA;AAGlB,SAAS;AACT,wDAA2D;AAAlD,kHAAA,iBAAiB,OAAA;AAC1B,sDAAyD;AAAhD,gHAAA,gBAAgB,OAAA;AACzB,wDAA2D;AAAlD,kHAAA,iBAAiB,OAAA;AAC1B,sDAAgE;AAAvD,uHAAA,uBAAuB,OAAA;AAChC,8DAAiE;AAAxD,wHAAA,oBAAoB,OAAA;AAC7B,kDAAyD;AAAhD,gHAAA,kBAAkB,OAAA;AAC3B,oDAAuD;AAA9C,8GAAA,eAAe,OAAA;AAExB,SAAS;AACT,mCAAuE;AAA9D,8GAAA,oBAAoB,OAAA;AAAE,+GAAA,qBAAqB,OAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PlanName, PlanLimits } from './types';
|
|
2
|
+
export declare const PLAN_LIMITS: Record<PlanName, PlanLimits>;
|
|
3
|
+
/**
|
|
4
|
+
* Get plan limits by name, falling back to Free tier for unknown plans.
|
|
5
|
+
* Plan name is normalized to lowercase.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getPlanLimits(planName: string | null | undefined): PlanLimits;
|
|
8
|
+
/**
|
|
9
|
+
* Normalize a plan name from the database to a valid PlanName.
|
|
10
|
+
* Maps legacy names (starter → creator, business → agency) as well.
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizePlanName(name: string | null | undefined): PlanName;
|
|
13
|
+
/**
|
|
14
|
+
* Format a byte count into a human-readable string (e.g. "1.5 GB").
|
|
15
|
+
*/
|
|
16
|
+
export declare function formatBytes(bytes: number): string;
|
|
17
|
+
/**
|
|
18
|
+
* Check if a requested resolution is within the plan's maximum.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isResolutionAllowed(requested: string, maxAllowed: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Format a credit count into a human-readable string (e.g. "1.5K", "20K", "50").
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatCredits(credits: number): string;
|
|
25
|
+
//# sourceMappingURL=plan-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-limits.d.ts","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAS/C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,CA6DpD,CAAC;AAEF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,UAAU,CAG7E;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,QAAQ,CAO3E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAaD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAIlF;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAMrD"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PLAN_LIMITS = void 0;
|
|
4
|
+
exports.getPlanLimits = getPlanLimits;
|
|
5
|
+
exports.normalizePlanName = normalizePlanName;
|
|
6
|
+
exports.formatBytes = formatBytes;
|
|
7
|
+
exports.isResolutionAllowed = isResolutionAllowed;
|
|
8
|
+
exports.formatCredits = formatCredits;
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Plan Limits — Single Source of Truth
|
|
11
|
+
// ============================================================================
|
|
12
|
+
const GB = 1024 * 1024 * 1024;
|
|
13
|
+
const TB = 1024 * GB;
|
|
14
|
+
exports.PLAN_LIMITS = {
|
|
15
|
+
free: {
|
|
16
|
+
storageBytes: 5 * GB, // 5 GB
|
|
17
|
+
rendersPerMonth: 3,
|
|
18
|
+
streams: 1,
|
|
19
|
+
projects: 1,
|
|
20
|
+
socialAccounts: 1,
|
|
21
|
+
maxResolution: '720p',
|
|
22
|
+
seats: 1,
|
|
23
|
+
seatPrice: 0,
|
|
24
|
+
creditsPerSeat: 0,
|
|
25
|
+
watermark: true,
|
|
26
|
+
maxWatches: 0,
|
|
27
|
+
includedCredits: 250,
|
|
28
|
+
overageCapCredits: 0,
|
|
29
|
+
},
|
|
30
|
+
creator: {
|
|
31
|
+
storageBytes: 10 * GB, // 10 GB
|
|
32
|
+
rendersPerMonth: 10,
|
|
33
|
+
streams: 3,
|
|
34
|
+
projects: 5,
|
|
35
|
+
socialAccounts: 3,
|
|
36
|
+
maxResolution: '1080p',
|
|
37
|
+
seats: 1,
|
|
38
|
+
seatPrice: 0,
|
|
39
|
+
creditsPerSeat: 0,
|
|
40
|
+
watermark: false,
|
|
41
|
+
maxWatches: 3,
|
|
42
|
+
includedCredits: 1_500,
|
|
43
|
+
overageCapCredits: 10_000,
|
|
44
|
+
},
|
|
45
|
+
pro: {
|
|
46
|
+
storageBytes: 100 * GB, // 100 GB
|
|
47
|
+
rendersPerMonth: 30,
|
|
48
|
+
streams: 10,
|
|
49
|
+
projects: 25,
|
|
50
|
+
socialAccounts: 10,
|
|
51
|
+
maxResolution: '4k',
|
|
52
|
+
seats: 3,
|
|
53
|
+
seatPrice: 0,
|
|
54
|
+
creditsPerSeat: 0,
|
|
55
|
+
watermark: false,
|
|
56
|
+
maxWatches: 10,
|
|
57
|
+
includedCredits: 5_000,
|
|
58
|
+
overageCapCredits: 30_000,
|
|
59
|
+
},
|
|
60
|
+
agency: {
|
|
61
|
+
storageBytes: 1 * TB, // 1 TB
|
|
62
|
+
rendersPerMonth: 100,
|
|
63
|
+
streams: 50,
|
|
64
|
+
projects: Infinity,
|
|
65
|
+
socialAccounts: 50,
|
|
66
|
+
maxResolution: '4k',
|
|
67
|
+
seats: 5, // 5 base seats included
|
|
68
|
+
seatPrice: 4_900, // $49/seat in cents
|
|
69
|
+
creditsPerSeat: 4_000, // 4,000 credits per additional seat
|
|
70
|
+
watermark: false,
|
|
71
|
+
maxWatches: 50,
|
|
72
|
+
includedCredits: 20_000,
|
|
73
|
+
overageCapCredits: 100_000,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Get plan limits by name, falling back to Free tier for unknown plans.
|
|
78
|
+
* Plan name is normalized to lowercase.
|
|
79
|
+
*/
|
|
80
|
+
function getPlanLimits(planName) {
|
|
81
|
+
const key = (planName ?? 'free').toLowerCase().trim();
|
|
82
|
+
return exports.PLAN_LIMITS[key] ?? exports.PLAN_LIMITS.free;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Normalize a plan name from the database to a valid PlanName.
|
|
86
|
+
* Maps legacy names (starter → creator, business → agency) as well.
|
|
87
|
+
*/
|
|
88
|
+
function normalizePlanName(name) {
|
|
89
|
+
const raw = (name ?? 'free').toLowerCase().trim();
|
|
90
|
+
// Map legacy plan names
|
|
91
|
+
if (raw === 'starter')
|
|
92
|
+
return 'creator';
|
|
93
|
+
if (raw === 'business')
|
|
94
|
+
return 'agency';
|
|
95
|
+
if (raw in exports.PLAN_LIMITS)
|
|
96
|
+
return raw;
|
|
97
|
+
return 'free';
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Format a byte count into a human-readable string (e.g. "1.5 GB").
|
|
101
|
+
*/
|
|
102
|
+
function formatBytes(bytes) {
|
|
103
|
+
if (bytes === 0)
|
|
104
|
+
return '0 B';
|
|
105
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
106
|
+
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
107
|
+
const value = bytes / Math.pow(1024, i);
|
|
108
|
+
return `${value.toFixed(value < 10 ? 1 : 0)} ${units[i]}`;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Resolution ordering for comparison.
|
|
112
|
+
*/
|
|
113
|
+
const RESOLUTION_ORDER = {
|
|
114
|
+
'480p': 0,
|
|
115
|
+
'720p': 1,
|
|
116
|
+
'1080p': 2,
|
|
117
|
+
'4k': 3,
|
|
118
|
+
'8k': 4,
|
|
119
|
+
};
|
|
120
|
+
/**
|
|
121
|
+
* Check if a requested resolution is within the plan's maximum.
|
|
122
|
+
*/
|
|
123
|
+
function isResolutionAllowed(requested, maxAllowed) {
|
|
124
|
+
const reqOrder = RESOLUTION_ORDER[requested.toLowerCase()] ?? 0;
|
|
125
|
+
const maxOrder = RESOLUTION_ORDER[maxAllowed.toLowerCase()] ?? 0;
|
|
126
|
+
return reqOrder <= maxOrder;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Format a credit count into a human-readable string (e.g. "1.5K", "20K", "50").
|
|
130
|
+
*/
|
|
131
|
+
function formatCredits(credits) {
|
|
132
|
+
if (credits >= 1_000) {
|
|
133
|
+
const k = credits / 1_000;
|
|
134
|
+
return k % 1 === 0 ? `${k}K` : `${k.toFixed(1)}K`;
|
|
135
|
+
}
|
|
136
|
+
return credits.toLocaleString();
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=plan-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan-limits.js","sourceRoot":"","sources":["../src/plan-limits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ModelPricing } from './types';
|
|
2
|
+
export declare const MODEL_PRICING: Record<string, ModelPricing>;
|
|
3
|
+
/**
|
|
4
|
+
* Default pricing used when the model is not found in MODEL_PRICING.
|
|
5
|
+
* Falls back to Claude Sonnet pricing as a safe default.
|
|
6
|
+
*/
|
|
7
|
+
export declare const DEFAULT_PRICING: ModelPricing;
|
|
8
|
+
/**
|
|
9
|
+
* Look up pricing for a model. Tries exact match first, then prefix match
|
|
10
|
+
* (stripping date suffixes like `-20250514`), then falls back to DEFAULT_PRICING.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getPricing(model: string): ModelPricing;
|
|
13
|
+
/**
|
|
14
|
+
* Markup applied to all API costs to ensure profit margin.
|
|
15
|
+
* 1.5x means we charge 50% more than raw cost → ~33% margin.
|
|
16
|
+
* Applied to both token-based costs AND flat-rate overrides.
|
|
17
|
+
*/
|
|
18
|
+
export declare const COST_MARKUP = 1.5;
|
|
19
|
+
/**
|
|
20
|
+
* Calculate cost in microdollars (1 microdollar = $0.000001).
|
|
21
|
+
*
|
|
22
|
+
* Since pricing is per-million tokens:
|
|
23
|
+
* cost_usd = (tokens / 1_000_000) * rate_per_million
|
|
24
|
+
* cost_microdollars = cost_usd * 1_000_000 = tokens * rate_per_million
|
|
25
|
+
*
|
|
26
|
+
* All costs include COST_MARKUP (1.5x) for profit margin.
|
|
27
|
+
*/
|
|
28
|
+
export declare function calculateCostMicrodollars(model: string, inputTokens: number, outputTokens: number, cacheReadTokens?: number, cacheCreationTokens?: number, costMicrodollarsOverride?: number): number;
|
|
29
|
+
export declare const MICRODOLLARS_PER_CREDIT = 10000;
|
|
30
|
+
export declare function microdollarsToCredits(microdollars: number): number;
|
|
31
|
+
export declare function creditsToMicrodollars(credits: number): number;
|
|
32
|
+
//# sourceMappingURL=pricing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../src/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAOvC,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAkDtD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,YAK7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAgBtD;AAED;;;;GAIG;AACH,eAAO,MAAM,WAAW,MAAM,CAAC;AAE/B;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,mBAAmB,SAAI,EACvB,wBAAwB,CAAC,EAAE,MAAM,GAChC,MAAM,CAaR;AAMD,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAE7D"}
|