@@ -46,6 +46,31 @@ async function maybeNotifyTableRowLimit(
4646 }
4747}
4848
49+ /**
50+ * Fire-and-forget the table row-limit threshold email for an accepted insert,
51+ * gated so only near-limit writes pay the cost. Shared by every insert path
52+ * ({@link assertRowCapacity} and the transactional upsert/import branches that
53+ * check capacity with {@link wouldExceedRowLimit} instead). Pass the pre-insert
54+ * `currentRowCount` so a delete-then-insert jump re-arms correctly.
55+ */
56+ export function notifyTableRowUsage ( params : {
57+ workspaceId : string
58+ currentRowCount : number
59+ addedRows : number
60+ limit : number
61+ } ) : void {
62+ if ( params . limit <= 0 ) return
63+ const projected = params . currentRowCount + params . addedRows
64+ if ( ( projected / params . limit ) * 100 >= TABLE_ROW_NOTIFY_PERCENT ) {
65+ void maybeNotifyTableRowLimit (
66+ params . workspaceId ,
67+ params . currentRowCount ,
68+ projected ,
69+ params . limit
70+ )
71+ }
72+ }
73+
4974/**
5075 * Plan lookups hit billing + subscription tables (2-3 queries). Row-limit checks
5176 * run on every insert, so a short TTL keeps the hot path off the DB. Plan changes
@@ -179,12 +204,12 @@ export async function assertRowCapacity(params: {
179204 }
180205
181206 // Accepted write: warn (best-effort) once the table crosses the notify band.
182- if ( limit > 0 ) {
183- const projected = params . currentRowCount + params . addedRows
184- if ( ( projected / limit ) * 100 >= TABLE_ROW_NOTIFY_PERCENT ) {
185- void maybeNotifyTableRowLimit ( params . workspaceId , params . currentRowCount , projected , limit )
186- }
187- }
207+ notifyTableRowUsage ( {
208+ workspaceId : params . workspaceId ,
209+ currentRowCount : params . currentRowCount ,
210+ addedRows : params . addedRows ,
211+ limit ,
212+ } )
188213}
189214
190215/**
0 commit comments