@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.
Files changed (73) hide show
  1. package/dist/__tests__/billing-coverage.test.d.ts +2 -0
  2. package/dist/__tests__/billing-coverage.test.d.ts.map +1 -0
  3. package/dist/__tests__/billing-coverage.test.js +170 -0
  4. package/dist/__tests__/billing-coverage.test.js.map +1 -0
  5. package/dist/__tests__/plan-limits.test.d.ts +2 -0
  6. package/dist/__tests__/plan-limits.test.d.ts.map +1 -0
  7. package/dist/__tests__/plan-limits.test.js +139 -0
  8. package/dist/__tests__/plan-limits.test.js.map +1 -0
  9. package/dist/__tests__/pricing.test.d.ts +2 -0
  10. package/dist/__tests__/pricing.test.d.ts.map +1 -0
  11. package/dist/__tests__/pricing.test.js +146 -0
  12. package/dist/__tests__/pricing.test.js.map +1 -0
  13. package/dist/__tests__/usage-tracker.test.d.ts +2 -0
  14. package/dist/__tests__/usage-tracker.test.d.ts.map +1 -0
  15. package/dist/__tests__/usage-tracker.test.js +210 -0
  16. package/dist/__tests__/usage-tracker.test.js.map +1 -0
  17. package/dist/alerts.d.ts +19 -0
  18. package/dist/alerts.d.ts.map +1 -0
  19. package/dist/alerts.js +90 -0
  20. package/dist/alerts.js.map +1 -0
  21. package/dist/guards/project-guard.d.ts +6 -0
  22. package/dist/guards/project-guard.d.ts.map +1 -0
  23. package/dist/guards/project-guard.js +56 -0
  24. package/dist/guards/project-guard.js.map +1 -0
  25. package/dist/guards/resolution-guard.d.ts +6 -0
  26. package/dist/guards/resolution-guard.d.ts.map +1 -0
  27. package/dist/guards/resolution-guard.js +40 -0
  28. package/dist/guards/resolution-guard.js.map +1 -0
  29. package/dist/guards/seat-guard.d.ts +8 -0
  30. package/dist/guards/seat-guard.d.ts.map +1 -0
  31. package/dist/guards/seat-guard.js +55 -0
  32. package/dist/guards/seat-guard.js.map +1 -0
  33. package/dist/guards/social-guard.d.ts +6 -0
  34. package/dist/guards/social-guard.d.ts.map +1 -0
  35. package/dist/guards/social-guard.js +52 -0
  36. package/dist/guards/social-guard.js.map +1 -0
  37. package/dist/guards/storage-guard.d.ts +14 -0
  38. package/dist/guards/storage-guard.d.ts.map +1 -0
  39. package/dist/guards/storage-guard.js +85 -0
  40. package/dist/guards/storage-guard.js.map +1 -0
  41. package/dist/guards/stream-guard.d.ts +7 -0
  42. package/dist/guards/stream-guard.d.ts.map +1 -0
  43. package/dist/guards/stream-guard.js +73 -0
  44. package/dist/guards/stream-guard.js.map +1 -0
  45. package/dist/guards/watch-guard.d.ts +13 -0
  46. package/dist/guards/watch-guard.d.ts.map +1 -0
  47. package/dist/guards/watch-guard.js +71 -0
  48. package/dist/guards/watch-guard.js.map +1 -0
  49. package/dist/index.d.ts +16 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +49 -0
  52. package/dist/index.js.map +1 -0
  53. package/dist/plan-limits.d.ts +25 -0
  54. package/dist/plan-limits.d.ts.map +1 -0
  55. package/dist/plan-limits.js +138 -0
  56. package/dist/plan-limits.js.map +1 -0
  57. package/dist/pricing.d.ts +32 -0
  58. package/dist/pricing.d.ts.map +1 -0
  59. package/dist/pricing.js +126 -0
  60. package/dist/pricing.js.map +1 -0
  61. package/dist/types.d.ts +188 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +3 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/usage-checker.d.ts +26 -0
  66. package/dist/usage-checker.d.ts.map +1 -0
  67. package/dist/usage-checker.js +295 -0
  68. package/dist/usage-checker.js.map +1 -0
  69. package/dist/usage-tracker.d.ts +25 -0
  70. package/dist/usage-tracker.d.ts.map +1 -0
  71. package/dist/usage-tracker.js +181 -0
  72. package/dist/usage-tracker.js.map +1 -0
  73. 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"}
@@ -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"}