@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,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const usage_tracker_1 = require("../usage-tracker");
5
+ // ---------------------------------------------------------------------------
6
+ // Helpers
7
+ // ---------------------------------------------------------------------------
8
+ const silentLogger = {
9
+ debug: vitest_1.vi.fn(),
10
+ info: vitest_1.vi.fn(),
11
+ warn: vitest_1.vi.fn(),
12
+ error: vitest_1.vi.fn(),
13
+ };
14
+ function makeEvent(overrides = {}) {
15
+ return {
16
+ organizationId: 'org-123',
17
+ service: 'agent',
18
+ model: 'claude-sonnet-4-6',
19
+ inputTokens: 1000,
20
+ outputTokens: 500,
21
+ ...overrides,
22
+ };
23
+ }
24
+ function makeSupabaseMock(options = {}) {
25
+ const insertFn = vitest_1.vi.fn().mockResolvedValue({
26
+ error: options.insertError ? { message: options.insertError } : null,
27
+ });
28
+ const singleFn = vitest_1.vi.fn().mockResolvedValue({
29
+ data: options.subData ?? {
30
+ current_period_start: '2026-03-01',
31
+ current_period_end: '2026-04-01',
32
+ },
33
+ error: options.subError ? { message: options.subError } : null,
34
+ });
35
+ const limitFn = vitest_1.vi.fn().mockReturnValue({ single: singleFn });
36
+ const orderFn = vitest_1.vi.fn().mockReturnValue({ limit: limitFn });
37
+ const eqFn = vitest_1.vi.fn().mockReturnValue({ order: orderFn });
38
+ const selectFn = vitest_1.vi.fn().mockReturnValue({ eq: eqFn });
39
+ const fromFn = vitest_1.vi.fn().mockImplementation((table) => {
40
+ if (table === 'usage_events')
41
+ return { insert: insertFn };
42
+ if (table === 'organization_subscriptions')
43
+ return { select: selectFn };
44
+ if (table === 'usage_records') {
45
+ return {
46
+ upsert: vitest_1.vi.fn().mockResolvedValue({
47
+ error: options.upsertError ? { message: options.upsertError } : null,
48
+ }),
49
+ };
50
+ }
51
+ return {};
52
+ });
53
+ const rpcFn = vitest_1.vi.fn().mockImplementation((fnName) => {
54
+ if (fnName === 'record_usage_atomic') {
55
+ if (options.rpcAtomicError) {
56
+ return Promise.resolve({
57
+ data: null,
58
+ error: { message: options.rpcAtomicError },
59
+ });
60
+ }
61
+ return Promise.resolve({
62
+ data: 'evt-uuid-1234',
63
+ error: null,
64
+ });
65
+ }
66
+ if (fnName === 'upsert_usage_record') {
67
+ return Promise.resolve({
68
+ error: options.rpcError ? { message: options.rpcError } : null,
69
+ });
70
+ }
71
+ return Promise.resolve({ data: null, error: null });
72
+ });
73
+ return { from: fromFn, rpc: rpcFn };
74
+ }
75
+ // ---------------------------------------------------------------------------
76
+ // Tests
77
+ // ---------------------------------------------------------------------------
78
+ (0, vitest_1.describe)('recordUsage', () => {
79
+ (0, vitest_1.beforeEach)(() => {
80
+ vitest_1.vi.restoreAllMocks();
81
+ });
82
+ // =========================================================================
83
+ // Happy path: atomic RPC succeeds
84
+ // =========================================================================
85
+ (0, vitest_1.it)('calls record_usage_atomic RPC and does NOT use from() on success', async () => {
86
+ const supabase = makeSupabaseMock();
87
+ await (0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger);
88
+ // Should call the atomic RPC
89
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.objectContaining({
90
+ p_organization_id: 'org-123',
91
+ p_service: 'agent',
92
+ p_model: 'claude-sonnet-4-6',
93
+ p_input_tokens: 1000,
94
+ p_output_tokens: 500,
95
+ }));
96
+ // Should NOT fall back to from() calls
97
+ (0, vitest_1.expect)(supabase.from).not.toHaveBeenCalled();
98
+ });
99
+ (0, vitest_1.it)('passes creditsConsumed to the atomic RPC', async () => {
100
+ const supabase = makeSupabaseMock();
101
+ await (0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger);
102
+ // claude-sonnet-4-6: 1000*3 + 500*15 = 10500 microdollars
103
+ // 10500 / 10000 = 1.05 credits
104
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.objectContaining({
105
+ p_cost_microdollars: 10500,
106
+ p_credits_consumed: 1.05,
107
+ }));
108
+ });
109
+ (0, vitest_1.it)('passes conversationId to atomic RPC when provided', async () => {
110
+ const supabase = makeSupabaseMock();
111
+ const event = makeEvent({ conversationId: 'conv-456' });
112
+ await (0, usage_tracker_1.recordUsage)(supabase, event, silentLogger);
113
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.objectContaining({
114
+ p_conversation_id: 'conv-456',
115
+ }));
116
+ });
117
+ (0, vitest_1.it)('passes metadata to atomic RPC when provided', async () => {
118
+ const supabase = makeSupabaseMock();
119
+ const event = makeEvent({ metadata: { stream_id: 's-789' } });
120
+ await (0, usage_tracker_1.recordUsage)(supabase, event, silentLogger);
121
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.objectContaining({
122
+ p_metadata: { stream_id: 's-789' },
123
+ }));
124
+ });
125
+ (0, vitest_1.it)('uses costMicrodollarsOverride when provided', async () => {
126
+ const supabase = makeSupabaseMock();
127
+ const event = makeEvent({
128
+ model: 'chirp-tts',
129
+ inputTokens: 0,
130
+ outputTokens: 0,
131
+ costMicrodollarsOverride: 30000,
132
+ });
133
+ await (0, usage_tracker_1.recordUsage)(supabase, event, silentLogger);
134
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.objectContaining({
135
+ p_cost_microdollars: 30000,
136
+ }));
137
+ });
138
+ (0, vitest_1.it)('handles missing orgId without crashing', async () => {
139
+ const supabase = makeSupabaseMock();
140
+ const event = makeEvent({ organizationId: '' });
141
+ await (0, vitest_1.expect)((0, usage_tracker_1.recordUsage)(supabase, event, silentLogger)).resolves.toBeUndefined();
142
+ });
143
+ // =========================================================================
144
+ // Fallback path: atomic RPC fails → insert + upsert
145
+ // =========================================================================
146
+ (0, vitest_1.it)('falls back to insert + upsert_usage_record when atomic RPC fails', async () => {
147
+ const supabase = makeSupabaseMock({ rpcAtomicError: 'function not found' });
148
+ await (0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger);
149
+ // Should have tried atomic RPC first
150
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.any(Object));
151
+ // Should warn about falling back
152
+ (0, vitest_1.expect)(silentLogger.warn).toHaveBeenCalledWith('record_usage_atomic RPC failed, falling back to two-step', vitest_1.expect.any(Object));
153
+ // Should insert into usage_events
154
+ (0, vitest_1.expect)(supabase.from).toHaveBeenCalledWith('usage_events');
155
+ // Should look up subscription
156
+ (0, vitest_1.expect)(supabase.from).toHaveBeenCalledWith('organization_subscriptions');
157
+ // Should call upsert_usage_record RPC
158
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('upsert_usage_record', vitest_1.expect.objectContaining({
159
+ p_organization_id: 'org-123',
160
+ p_input_tokens: 1000,
161
+ p_output_tokens: 500,
162
+ }));
163
+ });
164
+ (0, vitest_1.it)('skips usage_records upsert when no subscription found (fallback path)', async () => {
165
+ const supabase = makeSupabaseMock({
166
+ rpcAtomicError: 'function not found',
167
+ subData: null,
168
+ subError: 'no rows',
169
+ });
170
+ await (0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger);
171
+ // Atomic RPC was called (and failed)
172
+ (0, vitest_1.expect)(supabase.rpc).toHaveBeenCalledWith('record_usage_atomic', vitest_1.expect.any(Object));
173
+ // upsert_usage_record should NOT be called when no subscription
174
+ (0, vitest_1.expect)(supabase.rpc).not.toHaveBeenCalledWith('upsert_usage_record', vitest_1.expect.any(Object));
175
+ // Warning should be logged
176
+ (0, vitest_1.expect)(silentLogger.warn).toHaveBeenCalledWith('No subscription found, skipping usage_records upsert', vitest_1.expect.any(Object));
177
+ });
178
+ // =========================================================================
179
+ // Error resilience
180
+ // =========================================================================
181
+ (0, vitest_1.it)('never throws even if supabase insert fails (fallback path)', async () => {
182
+ const supabase = makeSupabaseMock({
183
+ rpcAtomicError: 'function not found',
184
+ insertError: 'DB connection failed',
185
+ });
186
+ // Should not throw
187
+ await (0, vitest_1.expect)((0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger)).resolves.toBeUndefined();
188
+ // Error should be logged
189
+ (0, vitest_1.expect)(silentLogger.error).toHaveBeenCalled();
190
+ });
191
+ (0, vitest_1.it)('never throws even if both fallback RPCs fail', async () => {
192
+ const supabase = makeSupabaseMock({
193
+ rpcAtomicError: 'function not found',
194
+ rpcError: 'RPC not found',
195
+ upsertError: 'upsert failed',
196
+ });
197
+ await (0, vitest_1.expect)((0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger)).resolves.toBeUndefined();
198
+ });
199
+ (0, vitest_1.it)('never throws if supabase throws an unexpected error', async () => {
200
+ const supabase = {
201
+ rpc: vitest_1.vi.fn().mockImplementation(() => {
202
+ throw new Error('Unexpected crash');
203
+ }),
204
+ from: vitest_1.vi.fn(),
205
+ };
206
+ await (0, vitest_1.expect)((0, usage_tracker_1.recordUsage)(supabase, makeEvent(), silentLogger)).resolves.toBeUndefined();
207
+ (0, vitest_1.expect)(silentLogger.error).toHaveBeenCalledWith('Unexpected error recording usage', vitest_1.expect.objectContaining({ error: 'Unexpected crash' }));
208
+ });
209
+ });
210
+ //# sourceMappingURL=usage-tracker.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-tracker.test.js","sourceRoot":"","sources":["../../src/__tests__/usage-tracker.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { SupabaseClient, UsageAlertResult, BillingLogger } from './types';
2
+ /**
3
+ * Threshold at which a usage alert should be sent (80%).
4
+ */
5
+ export declare const USAGE_ALERT_THRESHOLD = 80;
6
+ /**
7
+ * Check if an organization has crossed the usage alert threshold and
8
+ * whether an alert has already been sent this billing period.
9
+ *
10
+ * Returns `{ shouldSend, percentUsed, alreadySent }`.
11
+ * Email sending is NOT handled here — that stays in the frontend/service layer.
12
+ *
13
+ * If `shouldSend` is true, this function also marks `usage_alert_sent = true`
14
+ * on the usage record to ensure idempotency.
15
+ *
16
+ * Never throws — all errors are caught and logged.
17
+ */
18
+ export declare function shouldSendUsageAlert(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<UsageAlertResult>;
19
+ //# sourceMappingURL=alerts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.d.ts","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAW1E;;GAEG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC;;;;;;;;;;;GAWG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,gBAAgB,CAAC,CAwE3B"}
package/dist/alerts.js ADDED
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.USAGE_ALERT_THRESHOLD = void 0;
4
+ exports.shouldSendUsageAlert = shouldSendUsageAlert;
5
+ const pricing_1 = require("./pricing");
6
+ const plan_limits_1 = require("./plan-limits");
7
+ const defaultLogger = {
8
+ debug: () => { },
9
+ info: (msg, meta) => console.log(`[billing] ${msg}`, meta ?? ''),
10
+ warn: (msg, meta) => console.warn(`[billing] ${msg}`, meta ?? ''),
11
+ error: (msg, meta) => console.error(`[billing] ${msg}`, meta ?? ''),
12
+ };
13
+ /**
14
+ * Threshold at which a usage alert should be sent (80%).
15
+ */
16
+ exports.USAGE_ALERT_THRESHOLD = 80;
17
+ /**
18
+ * Check if an organization has crossed the usage alert threshold and
19
+ * whether an alert has already been sent this billing period.
20
+ *
21
+ * Returns `{ shouldSend, percentUsed, alreadySent }`.
22
+ * Email sending is NOT handled here — that stays in the frontend/service layer.
23
+ *
24
+ * If `shouldSend` is true, this function also marks `usage_alert_sent = true`
25
+ * on the usage record to ensure idempotency.
26
+ *
27
+ * Never throws — all errors are caught and logged.
28
+ */
29
+ async function shouldSendUsageAlert(supabase, organizationId, logger = defaultLogger) {
30
+ try {
31
+ // 1. Get subscription + plan
32
+ const { data: sub } = await supabase
33
+ .from('organization_subscriptions')
34
+ .select(`
35
+ plan_id,
36
+ current_period_start,
37
+ current_period_end,
38
+ subscription_plans (
39
+ name
40
+ )
41
+ `)
42
+ .eq('organization_id', organizationId)
43
+ .single();
44
+ if (!sub) {
45
+ return { shouldSend: false, percentUsed: 0, alreadySent: false };
46
+ }
47
+ const plan = sub.subscription_plans;
48
+ const planLimits = (0, plan_limits_1.getPlanLimits)((0, plan_limits_1.normalizePlanName)(plan?.name));
49
+ if (planLimits.includedCredits <= 0) {
50
+ return { shouldSend: false, percentUsed: 0, alreadySent: false };
51
+ }
52
+ // 2. Get current usage
53
+ const { data: usage } = await supabase
54
+ .from('usage_records')
55
+ .select('usage_alert_sent')
56
+ .eq('organization_id', organizationId)
57
+ .eq('period_start', sub.current_period_start)
58
+ .maybeSingle();
59
+ if (!usage) {
60
+ return { shouldSend: false, percentUsed: 0, alreadySent: false };
61
+ }
62
+ const alreadySent = !!usage.usage_alert_sent;
63
+ // Credit-based threshold check (sole mechanism)
64
+ const costMicrodollars = usage.cost_microdollars ?? 0;
65
+ const creditsUsed = (0, pricing_1.microdollarsToCredits)(costMicrodollars);
66
+ const creditPercentUsed = planLimits.includedCredits > 0
67
+ ? (creditsUsed / planLimits.includedCredits) * 100
68
+ : 0;
69
+ // Trigger alert only when credit threshold is crossed
70
+ const thresholdCrossed = creditPercentUsed >= exports.USAGE_ALERT_THRESHOLD;
71
+ if (!thresholdCrossed || alreadySent) {
72
+ return { shouldSend: false, percentUsed: creditPercentUsed, alreadySent };
73
+ }
74
+ // 3. Mark alert as sent (idempotency)
75
+ await supabase
76
+ .from('usage_records')
77
+ .update({ usage_alert_sent: true })
78
+ .eq('organization_id', organizationId)
79
+ .eq('period_start', sub.current_period_start);
80
+ return { shouldSend: true, percentUsed: creditPercentUsed, alreadySent: false };
81
+ }
82
+ catch (err) {
83
+ logger.error('Failed to check usage alert', {
84
+ orgId: organizationId,
85
+ error: err instanceof Error ? err.message : String(err),
86
+ });
87
+ return { shouldSend: false, percentUsed: 0, alreadySent: false };
88
+ }
89
+ }
90
+ //# sourceMappingURL=alerts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alerts.js","sourceRoot":"","sources":["../src/alerts.ts"],"names":[],"mappings":";;;AA4BA,oDA4EC;AAvGD,uCAAkD;AAClD,+CAAiE;AAEjE,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;AACU,QAAA,qBAAqB,GAAG,EAAE,CAAC;AAExC;;;;;;;;;;;GAWG;AACI,KAAK,UAAU,oBAAoB,CACxC,QAAwB,EACxB,cAAsB,EACtB,SAAwB,aAAa;IAErC,IAAI,CAAC;QACH,6BAA6B;QAC7B,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,QAAQ;aACjC,IAAI,CAAC,4BAA4B,CAAC;aAClC,MAAM,CAAC;;;;;;;OAOP,CAAC;aACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;aACrC,MAAM,EAAE,CAAC;QAEZ,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QACnE,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,kBAET,CAAC;QAET,MAAM,UAAU,GAAG,IAAA,2BAAa,EAAC,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QAChE,IAAI,UAAU,CAAC,eAAe,IAAI,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QACnE,CAAC;QAED,uBAAuB;QACvB,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;aACnC,IAAI,CAAC,eAAe,CAAC;aACrB,MAAM,CAAC,kBAAkB,CAAC;aAC1B,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;aACrC,EAAE,CAAC,cAAc,EAAE,GAAG,CAAC,oBAAoB,CAAC;aAC5C,WAAW,EAAE,CAAC;QAEjB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;QACnE,CAAC;QAED,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;QAE7C,gDAAgD;QAChD,MAAM,gBAAgB,GAAY,KAAiC,CAAC,iBAA2B,IAAI,CAAC,CAAC;QACrG,MAAM,WAAW,GAAG,IAAA,+BAAqB,EAAC,gBAAgB,CAAC,CAAC;QAC5D,MAAM,iBAAiB,GAAG,UAAU,CAAC,eAAe,GAAG,CAAC;YACtD,CAAC,CAAC,CAAC,WAAW,GAAG,UAAU,CAAC,eAAe,CAAC,GAAG,GAAG;YAClD,CAAC,CAAC,CAAC,CAAC;QAEN,sDAAsD;QACtD,MAAM,gBAAgB,GAAG,iBAAiB,IAAI,6BAAqB,CAAC;QAEpE,IAAI,CAAC,gBAAgB,IAAI,WAAW,EAAE,CAAC;YACrC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC;QAC5E,CAAC;QAED,sCAAsC;QACtC,MAAM,QAAQ;aACX,IAAI,CAAC,eAAe,CAAC;aACrB,MAAM,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE,CAAC;aAClC,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;aACrC,EAAE,CAAC,cAAc,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC;QAEhD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,iBAAiB,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAClF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,6BAA6B,EAAE;YAC1C,KAAK,EAAE,cAAc;YACrB,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IACnE,CAAC;AACH,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { SupabaseClient, ProjectLimitResult, BillingLogger } from '../types';
2
+ /**
3
+ * Check whether an organization is allowed to create a new project.
4
+ */
5
+ export declare function checkProjectLimit(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<ProjectLimitResult>;
6
+ //# sourceMappingURL=project-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-guard.d.ts","sourceRoot":"","sources":["../../src/guards/project-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAU7E;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CA+C7B"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkProjectLimit = checkProjectLimit;
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 is allowed to create a new project.
13
+ */
14
+ async function checkProjectLimit(supabase, organizationId, logger = defaultLogger) {
15
+ // 1. Fetch subscription + plan
16
+ const { data: subscription } = await supabase
17
+ .from('organization_subscriptions')
18
+ .select(`
19
+ plan_id,
20
+ subscription_plans ( id, name )
21
+ `)
22
+ .eq('organization_id', organizationId)
23
+ .single();
24
+ const plan = subscription?.subscription_plans;
25
+ const planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
26
+ const displayName = plan?.name ?? 'Free';
27
+ const limits = (0, plan_limits_1.getPlanLimits)(planName);
28
+ const maxProjects = limits.projects;
29
+ // Infinity means unlimited
30
+ if (!isFinite(maxProjects)) {
31
+ return { allowed: true, currentProjects: 0, maxProjects };
32
+ }
33
+ // 2. Count current projects (non-archived)
34
+ const { count, error } = await supabase
35
+ .from('projects')
36
+ .select('id', { count: 'exact', head: true })
37
+ .eq('organization_id', organizationId)
38
+ .neq('status', 'archived');
39
+ if (error) {
40
+ logger.error('Failed to count projects', { error: error.message });
41
+ // Fail open
42
+ return { allowed: true, currentProjects: 0, maxProjects };
43
+ }
44
+ const currentProjects = count ?? 0;
45
+ const allowed = currentProjects < maxProjects;
46
+ return {
47
+ allowed,
48
+ currentProjects,
49
+ maxProjects,
50
+ reason: allowed
51
+ ? undefined
52
+ : `Your ${displayName} plan allows ${maxProjects} project${maxProjects === 1 ? '' : 's'}. ` +
53
+ `You have ${currentProjects}. Archive existing projects or upgrade your plan.`,
54
+ };
55
+ }
56
+ //# sourceMappingURL=project-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-guard.js","sourceRoot":"","sources":["../../src/guards/project-guard.ts"],"names":[],"mappings":";;AAaA,8CAmDC;AA/DD,gDAAkE;AAElE,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;AACI,KAAK,UAAU,iBAAiB,CACrC,QAAwB,EACxB,cAAsB,EACtB,SAAwB,aAAa;IAErC,+BAA+B;IAC/B,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,4BAA4B,CAAC;SAClC,MAAM,CAAC;;;KAGP,CAAC;SACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,MAAM,EAAE,CAAC;IAEZ,MAAM,IAAI,GAAG,YAAY,EAAE,kBAAoE,CAAC;IAChG,MAAM,QAAQ,GAAG,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,IAAA,2BAAa,EAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEpC,2BAA2B;IAC3B,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5D,CAAC;IAED,2CAA2C;IAC3C,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACpC,IAAI,CAAC,UAAU,CAAC;SAChB,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC5C,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,GAAG,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAE7B,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,KAAK,CAAC,0BAA0B,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACnE,YAAY;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,GAAG,WAAW,CAAC;IAE9C,OAAO;QACL,OAAO;QACP,eAAe;QACf,WAAW;QACX,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,QAAQ,WAAW,gBAAgB,WAAW,WAAW,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;gBACzF,YAAY,eAAe,mDAAmD;KACnF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { SupabaseClient, ResolutionLimitResult, BillingLogger } from '../types';
2
+ /**
3
+ * Check whether an organization's plan allows a given export resolution.
4
+ */
5
+ export declare function checkResolutionLimit(supabase: SupabaseClient, organizationId: string, requestedResolution: string, logger?: BillingLogger): Promise<ResolutionLimitResult>;
6
+ //# sourceMappingURL=resolution-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolution-guard.d.ts","sourceRoot":"","sources":["../../src/guards/resolution-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAUhF;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,mBAAmB,EAAE,MAAM,EAC3B,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,qBAAqB,CAAC,CA4BhC"}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkResolutionLimit = checkResolutionLimit;
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's plan allows a given export resolution.
13
+ */
14
+ async function checkResolutionLimit(supabase, organizationId, requestedResolution, logger = defaultLogger) {
15
+ // 1. Fetch subscription + plan
16
+ const { data: subscription } = await supabase
17
+ .from('organization_subscriptions')
18
+ .select(`
19
+ plan_id,
20
+ subscription_plans ( id, name )
21
+ `)
22
+ .eq('organization_id', organizationId)
23
+ .single();
24
+ const plan = subscription?.subscription_plans;
25
+ const planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
26
+ const displayName = plan?.name ?? 'Free';
27
+ const limits = (0, plan_limits_1.getPlanLimits)(planName);
28
+ const maxResolution = limits.maxResolution;
29
+ const allowed = (0, plan_limits_1.isResolutionAllowed)(requestedResolution, maxResolution);
30
+ return {
31
+ allowed,
32
+ requestedResolution,
33
+ maxResolution,
34
+ reason: allowed
35
+ ? undefined
36
+ : `Your ${displayName} plan supports up to ${maxResolution} resolution. ` +
37
+ `Upgrade your plan to export at ${requestedResolution}.`,
38
+ };
39
+ }
40
+ //# sourceMappingURL=resolution-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolution-guard.js","sourceRoot":"","sources":["../../src/guards/resolution-guard.ts"],"names":[],"mappings":";;AAaA,oDAiCC;AA7CD,gDAAuF;AAEvF,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;AACI,KAAK,UAAU,oBAAoB,CACxC,QAAwB,EACxB,cAAsB,EACtB,mBAA2B,EAC3B,SAAwB,aAAa;IAErC,+BAA+B;IAC/B,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,4BAA4B,CAAC;SAClC,MAAM,CAAC;;;KAGP,CAAC;SACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,MAAM,EAAE,CAAC;IAEZ,MAAM,IAAI,GAAG,YAAY,EAAE,kBAAoE,CAAC;IAChG,MAAM,QAAQ,GAAG,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,IAAA,2BAAa,EAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC;IAE3C,MAAM,OAAO,GAAG,IAAA,iCAAmB,EAAC,mBAAmB,EAAE,aAAa,CAAC,CAAC;IAExE,OAAO;QACL,OAAO;QACP,mBAAmB;QACnB,aAAa;QACb,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,QAAQ,WAAW,wBAAwB,aAAa,eAAe;gBACvE,kCAAkC,mBAAmB,GAAG;KAC7D,CAAC;AACJ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { SupabaseClient, SeatLimitResult, BillingLogger } from '../types';
2
+ /**
3
+ * Check whether an organization is allowed to add a new team member.
4
+ *
5
+ * Total allowed seats = plan base seats + additional_seats purchased.
6
+ */
7
+ export declare function checkTeamSeatLimit(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<SeatLimitResult>;
8
+ //# sourceMappingURL=seat-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-guard.d.ts","sourceRoot":"","sources":["../../src/guards/seat-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAU1E;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,eAAe,CAAC,CA2C1B"}
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkTeamSeatLimit = checkTeamSeatLimit;
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 is allowed to add a new team member.
13
+ *
14
+ * Total allowed seats = plan base seats + additional_seats purchased.
15
+ */
16
+ async function checkTeamSeatLimit(supabase, organizationId, logger = defaultLogger) {
17
+ // 1. Fetch subscription + plan + additional_seats
18
+ const { data: subscription } = await supabase
19
+ .from('organization_subscriptions')
20
+ .select(`
21
+ plan_id,
22
+ additional_seats,
23
+ subscription_plans ( id, name )
24
+ `)
25
+ .eq('organization_id', organizationId)
26
+ .single();
27
+ const plan = subscription?.subscription_plans;
28
+ const planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
29
+ const displayName = plan?.name ?? 'Free';
30
+ const limits = (0, plan_limits_1.getPlanLimits)(planName);
31
+ const additionalSeats = subscription?.additional_seats ?? 0;
32
+ const maxSeats = limits.seats + additionalSeats;
33
+ // 2. Count current members
34
+ const { count, error } = await supabase
35
+ .from('organization_members')
36
+ .select('id', { count: 'exact', head: true })
37
+ .eq('organization_id', organizationId);
38
+ if (error) {
39
+ logger.error('Failed to count organization members', { error: error.message });
40
+ // Fail open
41
+ return { allowed: true, currentSeats: 0, maxSeats };
42
+ }
43
+ const currentSeats = count ?? 0;
44
+ const allowed = currentSeats < maxSeats;
45
+ return {
46
+ allowed,
47
+ currentSeats,
48
+ maxSeats,
49
+ reason: allowed
50
+ ? undefined
51
+ : `Your ${displayName} plan allows ${maxSeats} team seat${maxSeats === 1 ? '' : 's'} (${limits.seats} included + ${additionalSeats} purchased). ` +
52
+ `You have ${currentSeats} members. Purchase additional seats or remove a member.`,
53
+ };
54
+ }
55
+ //# sourceMappingURL=seat-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seat-guard.js","sourceRoot":"","sources":["../../src/guards/seat-guard.ts"],"names":[],"mappings":";;AAeA,gDA+CC;AA7DD,gDAAkE;AAElE,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;;;;GAIG;AACI,KAAK,UAAU,kBAAkB,CACtC,QAAwB,EACxB,cAAsB,EACtB,SAAwB,aAAa;IAErC,kDAAkD;IAClD,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,4BAA4B,CAAC;SAClC,MAAM,CAAC;;;;KAIP,CAAC;SACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,MAAM,EAAE,CAAC;IAEZ,MAAM,IAAI,GAAG,YAAY,EAAE,kBAAoE,CAAC;IAChG,MAAM,QAAQ,GAAG,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,IAAA,2BAAa,EAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,eAAe,GAAY,YAAoB,EAAE,gBAAgB,IAAI,CAAC,CAAC;IAC7E,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,GAAG,eAAe,CAAC;IAEhD,2BAA2B;IAC3B,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACpC,IAAI,CAAC,sBAAsB,CAAC;SAC5B,MAAM,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;SAC5C,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;IAEzC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,KAAK,CAAC,sCAAsC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/E,YAAY;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC;IACtD,CAAC;IAED,MAAM,YAAY,GAAG,KAAK,IAAI,CAAC,CAAC;IAChC,MAAM,OAAO,GAAG,YAAY,GAAG,QAAQ,CAAC;IAExC,OAAO;QACL,OAAO;QACP,YAAY;QACZ,QAAQ;QACR,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,QAAQ,WAAW,gBAAgB,QAAQ,aAAa,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,KAAK,eAAe,eAAe,eAAe;gBAC/I,YAAY,YAAY,yDAAyD;KACtF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { SupabaseClient, SocialAccountLimitResult, BillingLogger } from '../types';
2
+ /**
3
+ * Check whether an organization is allowed to connect a new social media account.
4
+ */
5
+ export declare function checkSocialAccountLimit(supabase: SupabaseClient, organizationId: string, logger?: BillingLogger): Promise<SocialAccountLimitResult>;
6
+ //# sourceMappingURL=social-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social-guard.d.ts","sourceRoot":"","sources":["../../src/guards/social-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,wBAAwB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAUnF;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,wBAAwB,CAAC,CA0CnC"}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.checkSocialAccountLimit = checkSocialAccountLimit;
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 is allowed to connect a new social media account.
13
+ */
14
+ async function checkSocialAccountLimit(supabase, organizationId, logger = defaultLogger) {
15
+ // 1. Fetch subscription + plan
16
+ const { data: subscription } = await supabase
17
+ .from('organization_subscriptions')
18
+ .select(`
19
+ plan_id,
20
+ subscription_plans ( id, name )
21
+ `)
22
+ .eq('organization_id', organizationId)
23
+ .single();
24
+ const plan = subscription?.subscription_plans;
25
+ const planName = (0, plan_limits_1.normalizePlanName)(plan?.name);
26
+ const displayName = plan?.name ?? 'Free';
27
+ const limits = (0, plan_limits_1.getPlanLimits)(planName);
28
+ const maxAccounts = limits.socialAccounts;
29
+ // 2. Count current connected accounts
30
+ const { count, error } = await supabase
31
+ .from('social_media_accounts')
32
+ .select('id', { count: 'exact', head: true })
33
+ .eq('organization_id', organizationId)
34
+ .eq('is_active', true);
35
+ if (error) {
36
+ logger.error('Failed to count social accounts', { error: error.message });
37
+ // Fail open
38
+ return { allowed: true, currentAccounts: 0, maxAccounts };
39
+ }
40
+ const currentAccounts = count ?? 0;
41
+ const allowed = currentAccounts < maxAccounts;
42
+ return {
43
+ allowed,
44
+ currentAccounts,
45
+ maxAccounts,
46
+ reason: allowed
47
+ ? undefined
48
+ : `Your ${displayName} plan allows ${maxAccounts} social account${maxAccounts === 1 ? '' : 's'}. ` +
49
+ `You have ${currentAccounts} connected. Disconnect an account or upgrade your plan.`,
50
+ };
51
+ }
52
+ //# sourceMappingURL=social-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"social-guard.js","sourceRoot":"","sources":["../../src/guards/social-guard.ts"],"names":[],"mappings":";;AAaA,0DA8CC;AA1DD,gDAAkE;AAElE,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;AACI,KAAK,UAAU,uBAAuB,CAC3C,QAAwB,EACxB,cAAsB,EACtB,SAAwB,aAAa;IAErC,+BAA+B;IAC/B,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,4BAA4B,CAAC;SAClC,MAAM,CAAC;;;KAGP,CAAC;SACD,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SACrC,MAAM,EAAE,CAAC;IAEZ,MAAM,IAAI,GAAG,YAAY,EAAE,kBAAoE,CAAC;IAChG,MAAM,QAAQ,GAAG,IAAA,+BAAiB,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,IAAI,EAAE,IAAI,IAAI,MAAM,CAAC;IACzC,MAAM,MAAM,GAAG,IAAA,2BAAa,EAAC,QAAQ,CAAC,CAAC;IACvC,MAAM,WAAW,GAAG,MAAM,CAAC,cAAc,CAAC;IAE1C,sCAAsC;IACtC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACpC,IAAI,CAAC,uBAAuB,CAAC;SAC7B,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,iCAAiC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1E,YAAY;QACZ,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,GAAG,WAAW,CAAC;IAE9C,OAAO;QACL,OAAO;QACP,eAAe;QACf,WAAW;QACX,MAAM,EAAE,OAAO;YACb,CAAC,CAAC,SAAS;YACX,CAAC,CAAC,QAAQ,WAAW,gBAAgB,WAAW,kBAAkB,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;gBAChG,YAAY,eAAe,yDAAyD;KACzF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { SupabaseClient, StorageQuotaResult, BillingLogger } from '../types';
2
+ /**
3
+ * Check whether an organization has enough storage quota remaining to
4
+ * accept a file of the given size.
5
+ *
6
+ * Storage usage is calculated via the `get_org_storage_usage_bytes` RPC
7
+ * (server-side SUM in Postgres). Falls back to client-side summation of
8
+ * `organization_files.metadata->file_size` if the RPC is not available.
9
+ *
10
+ * On error, fails OPEN (allows upload) since storage quota is a
11
+ * cost-protection measure, not a security boundary.
12
+ */
13
+ export declare function checkStorageQuota(supabase: SupabaseClient, organizationId: string, fileSizeBytes: number, logger?: BillingLogger): Promise<StorageQuotaResult>;
14
+ //# sourceMappingURL=storage-guard.d.ts.map