@drawbridge/drawbridge-utils 0.0.80 → 0.0.82

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/ai.cjs CHANGED
@@ -101,9 +101,6 @@ var insertTransaction = async ({
101
101
  session,
102
102
  ...rest
103
103
  }) => {
104
- var _a;
105
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
106
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
107
104
  const trace = (0, import_drawbridge_telemetry.currentTraceId)();
108
105
  await db.create({
109
106
  authenticated: user,
@@ -115,7 +112,6 @@ var insertTransaction = async ({
115
112
  category,
116
113
  source,
117
114
  amount,
118
- ...balance && { balance },
119
115
  ...trace && { trace },
120
116
  ...rest,
121
117
  ...stripeInvoiceId && { stripeInvoiceId },
package/dist/ai.js CHANGED
@@ -67,9 +67,6 @@ var insertTransaction = async ({
67
67
  session,
68
68
  ...rest
69
69
  }) => {
70
- var _a;
71
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
72
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
73
70
  const trace = currentTraceId();
74
71
  await db.create({
75
72
  authenticated: user,
@@ -81,7 +78,6 @@ var insertTransaction = async ({
81
78
  category,
82
79
  source,
83
80
  amount,
84
- ...balance && { balance },
85
81
  ...trace && { trace },
86
82
  ...rest,
87
83
  ...stripeInvoiceId && { stripeInvoiceId },
package/dist/billing.cjs CHANGED
@@ -22,6 +22,7 @@ __export(billing_exports, {
22
22
  MARKUP: () => MARKUP,
23
23
  action: () => action,
24
24
  ai: () => ai,
25
+ balance: () => balance,
25
26
  grant: () => grant,
26
27
  scrape: () => scrape
27
28
  });
@@ -42,9 +43,6 @@ var insertTransaction = async ({
42
43
  session,
43
44
  ...rest
44
45
  }) => {
45
- var _a;
46
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
47
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
48
46
  const trace = (0, import_drawbridge_telemetry.currentTraceId)();
49
47
  await db.create({
50
48
  authenticated: user,
@@ -56,7 +54,6 @@ var insertTransaction = async ({
56
54
  category,
57
55
  source,
58
56
  amount,
59
- ...balance && { balance },
60
57
  ...trace && { trace },
61
58
  ...rest,
62
59
  ...stripeInvoiceId && { stripeInvoiceId },
@@ -341,6 +338,28 @@ var scrape = {
341
338
  }
342
339
  }
343
340
  };
341
+ var balance = async ({ db, user, type }) => {
342
+ const userId = user && user.id || user;
343
+ if (!db || !userId || !type) return 0;
344
+ const [checkpoint] = await db.aggregate({
345
+ collection: "balanceCheckpoint",
346
+ pipeline: [
347
+ { $match: { user: userId, type } },
348
+ { $sort: { _id: -1 } },
349
+ { $limit: 1 }
350
+ ]
351
+ }).catch(() => []);
352
+ const match = { user: userId, type };
353
+ if (checkpoint == null ? void 0 : checkpoint.throughId) match._id = { $gt: checkpoint.throughId };
354
+ const [summed] = await db.aggregate({
355
+ collection: "transaction",
356
+ pipeline: [
357
+ { $match: match },
358
+ { $group: { _id: null, total: { $sum: "$amount" } } }
359
+ ]
360
+ });
361
+ return ((checkpoint == null ? void 0 : checkpoint.value) || 0) + ((summed == null ? void 0 : summed.total) || 0);
362
+ };
344
363
  var grant = {
345
364
  // System-issued credit (signup welcome, migration catch-up). Pass `session`
346
365
  // to enroll the credit in the caller's transaction.
@@ -471,6 +490,7 @@ var action = {
471
490
  MARKUP,
472
491
  action,
473
492
  ai,
493
+ balance,
474
494
  grant,
475
495
  scrape
476
496
  });
@@ -318,10 +318,58 @@ const scrape = {
318
318
 
319
319
  };
320
320
 
321
+ // ─── Balance (derived from the ledger) ──────────────────────────────────────────
322
+
323
+ // The AI credit balance is DERIVED, never stored — so it cannot drift from the
324
+ // ledger. balance = the newest checkpoint's cumulative value + Σ amount of the
325
+ // rows after it (credits +, debits −); with no checkpoint it sums the whole
326
+ // ledger. Checkpoints (`balanceCheckpoint`) are immutable rollup rows written by
327
+ // drawbridge-sync's nightly job to bound the scan on long-lived accounts — this
328
+ // reader tolerates their absence and falls back to the full sum. The
329
+ // { user:1, type:1, _id:1, amount:1 } index on `transaction` keeps both the full
330
+ // and post-checkpoint sums index-only.
331
+ //
332
+ // `user` accepts an id string or a user object (uses `.id`). `db` is any
333
+ // @drawbridge/mongodb controller (uses `.aggregate`). Returns integer cents;
334
+ // MAY be negative when a generation over-spent past zero — callers clamp the
335
+ // DISPLAY to 0, while the credit gate treats `<= 0` as "no credits".
336
+ const balance = async ({ db, user, type }) => {
337
+
338
+ const userId = ( user && user.id ) || user;
339
+
340
+ if( ! db || ! userId || ! type ) return 0;
341
+
342
+ // Newest immutable checkpoint: its `value` is the cumulative balance through
343
+ // `throughId`, so we only sum rows inserted after it. Missing collection / no
344
+ // checkpoint yet → [] and we sum the whole ledger.
345
+ const [ checkpoint ] = await db.aggregate({
346
+ collection : 'balanceCheckpoint',
347
+ pipeline : [
348
+ { $match : { user : userId, type } },
349
+ { $sort : { _id : -1 } },
350
+ { $limit : 1 }
351
+ ]
352
+ }).catch( () => [] );
353
+
354
+ const match = { user : userId, type };
355
+ if( checkpoint?.throughId ) match._id = { $gt : checkpoint.throughId };
356
+
357
+ const [ summed ] = await db.aggregate({
358
+ collection : 'transaction',
359
+ pipeline : [
360
+ { $match : match },
361
+ { $group : { _id : null, total : { $sum : '$amount' } } }
362
+ ]
363
+ });
364
+
365
+ return ( checkpoint?.value || 0 ) + ( summed?.total || 0 );
366
+
367
+ };
368
+
321
369
  // ─── Grants (credit rail) ──────────────────────────────────────────────────────
322
370
 
323
- // Promotional credits to a user's AI balance. `source` captures who acted; the
324
- // stream listener on the transaction collection does the balance.ai $inc.
371
+ // Promotional credits to a user's AI balance append-only rows the derived
372
+ // `balance` sums. `source` captures who acted.
325
373
  const grant = {
326
374
 
327
375
  // System-issued credit (signup welcome, migration catch-up). Pass `session`
@@ -497,4 +545,4 @@ const action = {
497
545
 
498
546
  };
499
547
 
500
- export { MARKUP, action, ai, grant, scrape };
548
+ export { MARKUP, action, ai, balance, grant, scrape };
package/dist/billing.d.ts CHANGED
@@ -318,10 +318,58 @@ const scrape = {
318
318
 
319
319
  };
320
320
 
321
+ // ─── Balance (derived from the ledger) ──────────────────────────────────────────
322
+
323
+ // The AI credit balance is DERIVED, never stored — so it cannot drift from the
324
+ // ledger. balance = the newest checkpoint's cumulative value + Σ amount of the
325
+ // rows after it (credits +, debits −); with no checkpoint it sums the whole
326
+ // ledger. Checkpoints (`balanceCheckpoint`) are immutable rollup rows written by
327
+ // drawbridge-sync's nightly job to bound the scan on long-lived accounts — this
328
+ // reader tolerates their absence and falls back to the full sum. The
329
+ // { user:1, type:1, _id:1, amount:1 } index on `transaction` keeps both the full
330
+ // and post-checkpoint sums index-only.
331
+ //
332
+ // `user` accepts an id string or a user object (uses `.id`). `db` is any
333
+ // @drawbridge/mongodb controller (uses `.aggregate`). Returns integer cents;
334
+ // MAY be negative when a generation over-spent past zero — callers clamp the
335
+ // DISPLAY to 0, while the credit gate treats `<= 0` as "no credits".
336
+ const balance = async ({ db, user, type }) => {
337
+
338
+ const userId = ( user && user.id ) || user;
339
+
340
+ if( ! db || ! userId || ! type ) return 0;
341
+
342
+ // Newest immutable checkpoint: its `value` is the cumulative balance through
343
+ // `throughId`, so we only sum rows inserted after it. Missing collection / no
344
+ // checkpoint yet → [] and we sum the whole ledger.
345
+ const [ checkpoint ] = await db.aggregate({
346
+ collection : 'balanceCheckpoint',
347
+ pipeline : [
348
+ { $match : { user : userId, type } },
349
+ { $sort : { _id : -1 } },
350
+ { $limit : 1 }
351
+ ]
352
+ }).catch( () => [] );
353
+
354
+ const match = { user : userId, type };
355
+ if( checkpoint?.throughId ) match._id = { $gt : checkpoint.throughId };
356
+
357
+ const [ summed ] = await db.aggregate({
358
+ collection : 'transaction',
359
+ pipeline : [
360
+ { $match : match },
361
+ { $group : { _id : null, total : { $sum : '$amount' } } }
362
+ ]
363
+ });
364
+
365
+ return ( checkpoint?.value || 0 ) + ( summed?.total || 0 );
366
+
367
+ };
368
+
321
369
  // ─── Grants (credit rail) ──────────────────────────────────────────────────────
322
370
 
323
- // Promotional credits to a user's AI balance. `source` captures who acted; the
324
- // stream listener on the transaction collection does the balance.ai $inc.
371
+ // Promotional credits to a user's AI balance append-only rows the derived
372
+ // `balance` sums. `source` captures who acted.
325
373
  const grant = {
326
374
 
327
375
  // System-issued credit (signup welcome, migration catch-up). Pass `session`
@@ -497,4 +545,4 @@ const action = {
497
545
 
498
546
  };
499
547
 
500
- export { MARKUP, action, ai, grant, scrape };
548
+ export { MARKUP, action, ai, balance, grant, scrape };
package/dist/billing.js CHANGED
@@ -13,9 +13,6 @@ var insertTransaction = async ({
13
13
  session,
14
14
  ...rest
15
15
  }) => {
16
- var _a;
17
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
18
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
19
16
  const trace = currentTraceId();
20
17
  await db.create({
21
18
  authenticated: user,
@@ -27,7 +24,6 @@ var insertTransaction = async ({
27
24
  category,
28
25
  source,
29
26
  amount,
30
- ...balance && { balance },
31
27
  ...trace && { trace },
32
28
  ...rest,
33
29
  ...stripeInvoiceId && { stripeInvoiceId },
@@ -312,6 +308,28 @@ var scrape = {
312
308
  }
313
309
  }
314
310
  };
311
+ var balance = async ({ db, user, type }) => {
312
+ const userId = user && user.id || user;
313
+ if (!db || !userId || !type) return 0;
314
+ const [checkpoint] = await db.aggregate({
315
+ collection: "balanceCheckpoint",
316
+ pipeline: [
317
+ { $match: { user: userId, type } },
318
+ { $sort: { _id: -1 } },
319
+ { $limit: 1 }
320
+ ]
321
+ }).catch(() => []);
322
+ const match = { user: userId, type };
323
+ if (checkpoint == null ? void 0 : checkpoint.throughId) match._id = { $gt: checkpoint.throughId };
324
+ const [summed] = await db.aggregate({
325
+ collection: "transaction",
326
+ pipeline: [
327
+ { $match: match },
328
+ { $group: { _id: null, total: { $sum: "$amount" } } }
329
+ ]
330
+ });
331
+ return ((checkpoint == null ? void 0 : checkpoint.value) || 0) + ((summed == null ? void 0 : summed.total) || 0);
332
+ };
315
333
  var grant = {
316
334
  // System-issued credit (signup welcome, migration catch-up). Pass `session`
317
335
  // to enroll the credit in the caller's transaction.
@@ -441,6 +459,7 @@ export {
441
459
  MARKUP,
442
460
  action,
443
461
  ai,
462
+ balance,
444
463
  grant,
445
464
  scrape
446
465
  };
package/dist/plans.cjs CHANGED
@@ -342,7 +342,11 @@ var plans = {
342
342
  limits: all.limits({ actions: 15e3, members: 5, storage: gigabyte * 20 }),
343
343
  marketing: {
344
344
  description: "Expand your reach and grow your lead pipeline.",
345
- features: [],
345
+ features: [
346
+ "Analytics",
347
+ "Custom subdomain / URLs",
348
+ "Confirmation page ads"
349
+ ],
346
350
  limits: [
347
351
  ["Actions per month", "15,000"],
348
352
  ["Affiliates", "Unlimited"],
@@ -368,7 +372,11 @@ var plans = {
368
372
  limits: all.limits({ actions: 4e4, members: 10, storage: gigabyte * 50 }),
369
373
  marketing: {
370
374
  description: "Accelerate acquisition with more power and flexibility.",
371
- features: [],
375
+ features: [
376
+ "Analytics",
377
+ "Custom subdomain / URLs",
378
+ "Confirmation page ads"
379
+ ],
372
380
  limits: [
373
381
  ["Actions per month", "40,000"],
374
382
  ["Affiliates", "Unlimited"],
@@ -394,7 +402,11 @@ var plans = {
394
402
  limits: all.limits({ actions: 1e5, members: infinite, storage: gigabyte * 100 }),
395
403
  marketing: {
396
404
  description: "Built for brands focused on results.",
397
- features: [],
405
+ features: [
406
+ "Analytics",
407
+ "Custom subdomain / URLs",
408
+ "Confirmation page ads"
409
+ ],
398
410
  limits: [
399
411
  ["Actions per month", "100,000"],
400
412
  ["Affiliates", "Unlimited"],
package/dist/plans.d.cts CHANGED
@@ -114,7 +114,11 @@ const plans = {
114
114
  limits : all.limits({ actions : 15000, members : 5, storage : gigabyte * 20 }),
115
115
  marketing : {
116
116
  description : 'Expand your reach and grow your lead pipeline.',
117
- features : [],
117
+ features : [
118
+ 'Analytics',
119
+ 'Custom subdomain / URLs',
120
+ 'Confirmation page ads'
121
+ ],
118
122
  limits : [
119
123
  [ 'Actions per month', '15,000' ],
120
124
  [ 'Affiliates', 'Unlimited' ],
@@ -140,7 +144,11 @@ const plans = {
140
144
  limits : all.limits({ actions : 40000, members : 10, storage : gigabyte * 50 }),
141
145
  marketing : {
142
146
  description : 'Accelerate acquisition with more power and flexibility.',
143
- features : [],
147
+ features : [
148
+ 'Analytics',
149
+ 'Custom subdomain / URLs',
150
+ 'Confirmation page ads'
151
+ ],
144
152
  limits : [
145
153
  [ 'Actions per month', '40,000' ],
146
154
  [ 'Affiliates', 'Unlimited' ],
@@ -166,7 +174,11 @@ const plans = {
166
174
  limits : all.limits({ actions : 100000, members : infinite, storage : gigabyte * 100 }),
167
175
  marketing : {
168
176
  description : 'Built for brands focused on results.',
169
- features : [],
177
+ features : [
178
+ 'Analytics',
179
+ 'Custom subdomain / URLs',
180
+ 'Confirmation page ads'
181
+ ],
170
182
  limits : [
171
183
  [ 'Actions per month', '100,000' ],
172
184
  [ 'Affiliates', 'Unlimited' ],
package/dist/plans.d.ts CHANGED
@@ -114,7 +114,11 @@ const plans = {
114
114
  limits : all.limits({ actions : 15000, members : 5, storage : gigabyte * 20 }),
115
115
  marketing : {
116
116
  description : 'Expand your reach and grow your lead pipeline.',
117
- features : [],
117
+ features : [
118
+ 'Analytics',
119
+ 'Custom subdomain / URLs',
120
+ 'Confirmation page ads'
121
+ ],
118
122
  limits : [
119
123
  [ 'Actions per month', '15,000' ],
120
124
  [ 'Affiliates', 'Unlimited' ],
@@ -140,7 +144,11 @@ const plans = {
140
144
  limits : all.limits({ actions : 40000, members : 10, storage : gigabyte * 50 }),
141
145
  marketing : {
142
146
  description : 'Accelerate acquisition with more power and flexibility.',
143
- features : [],
147
+ features : [
148
+ 'Analytics',
149
+ 'Custom subdomain / URLs',
150
+ 'Confirmation page ads'
151
+ ],
144
152
  limits : [
145
153
  [ 'Actions per month', '40,000' ],
146
154
  [ 'Affiliates', 'Unlimited' ],
@@ -166,7 +174,11 @@ const plans = {
166
174
  limits : all.limits({ actions : 100000, members : infinite, storage : gigabyte * 100 }),
167
175
  marketing : {
168
176
  description : 'Built for brands focused on results.',
169
- features : [],
177
+ features : [
178
+ 'Analytics',
179
+ 'Custom subdomain / URLs',
180
+ 'Confirmation page ads'
181
+ ],
170
182
  limits : [
171
183
  [ 'Actions per month', '100,000' ],
172
184
  [ 'Affiliates', 'Unlimited' ],
package/dist/plans.js CHANGED
@@ -305,7 +305,11 @@ var plans = {
305
305
  limits: all.limits({ actions: 15e3, members: 5, storage: gigabyte * 20 }),
306
306
  marketing: {
307
307
  description: "Expand your reach and grow your lead pipeline.",
308
- features: [],
308
+ features: [
309
+ "Analytics",
310
+ "Custom subdomain / URLs",
311
+ "Confirmation page ads"
312
+ ],
309
313
  limits: [
310
314
  ["Actions per month", "15,000"],
311
315
  ["Affiliates", "Unlimited"],
@@ -331,7 +335,11 @@ var plans = {
331
335
  limits: all.limits({ actions: 4e4, members: 10, storage: gigabyte * 50 }),
332
336
  marketing: {
333
337
  description: "Accelerate acquisition with more power and flexibility.",
334
- features: [],
338
+ features: [
339
+ "Analytics",
340
+ "Custom subdomain / URLs",
341
+ "Confirmation page ads"
342
+ ],
335
343
  limits: [
336
344
  ["Actions per month", "40,000"],
337
345
  ["Affiliates", "Unlimited"],
@@ -357,7 +365,11 @@ var plans = {
357
365
  limits: all.limits({ actions: 1e5, members: infinite, storage: gigabyte * 100 }),
358
366
  marketing: {
359
367
  description: "Built for brands focused on results.",
360
- features: [],
368
+ features: [
369
+ "Analytics",
370
+ "Custom subdomain / URLs",
371
+ "Confirmation page ads"
372
+ ],
361
373
  limits: [
362
374
  ["Actions per month", "100,000"],
363
375
  ["Affiliates", "Unlimited"],
@@ -37,9 +37,6 @@ var insertTransaction = async ({
37
37
  session,
38
38
  ...rest
39
39
  }) => {
40
- var _a;
41
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
42
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
43
40
  const trace = (0, import_drawbridge_telemetry.currentTraceId)();
44
41
  await db.create({
45
42
  authenticated: user,
@@ -51,7 +48,6 @@ var insertTransaction = async ({
51
48
  category,
52
49
  source,
53
50
  amount,
54
- ...balance && { balance },
55
51
  ...trace && { trace },
56
52
  ...rest,
57
53
  ...stripeInvoiceId && { stripeInvoiceId },
@@ -2,12 +2,13 @@ import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
2
2
 
3
3
  // Ledger transaction insert helpers — no balance writes happen here.
4
4
  //
5
- // User-balance updates are the exclusive job of drawbridge-sync's
6
- // stream/transaction.js change-stream listener: every insert into the
7
- // `transaction` collection triggers an atomic $inc on user.balance.<type>
8
- // (guarded against going negative). This decouples the API surface from
9
- // the balance arithmetic and makes the `transaction` collection the single
10
- // source of truth for both audit history and balance derivation.
5
+ // The `transaction` collection is the SINGLE SOURCE OF TRUTH for balance:
6
+ // there is no stored balance field anywhere. The balance is DERIVED by summing
7
+ // a user's rows on read (see `balance` in billing.js) credits (+) and debits
8
+ // (−) so it can never drift from the ledger. credit()/debit() only append
9
+ // rows; drawbridge-sync's stream/transaction.js reacts to each insert to fire
10
+ // low/depleted notifications and emit `account.update` (it no longer maintains
11
+ // a balance).
11
12
  //
12
13
  // import { credit, debit } from '@drawbridge/drawbridge-utils/transactions';
13
14
  //
@@ -21,12 +22,9 @@ import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
21
22
  // `db` is the wrapper returned by @drawbridge/mongodb — credit/debit
22
23
  // duck-type against its `.create` interface so this module has no hard dep.
23
24
  //
24
- // `user` is the user the transaction is being recorded against. The helper
25
- // reads `user.balance.<type>` to stamp `balance: { before, after }` on the
26
- // row as an audit snapshot of what the actor saw at action time — useful
27
- // for support / dispute resolution. When `balance.<type>` isn't on the user
28
- // (fresh signup, webhook-initiated row), the snapshot is omitted, which is
29
- // the honest representation of "no prior view to record".
25
+ // `user` is the user the transaction is being recorded against (stored as
26
+ // `user.id`). No per-row balance snapshot is stamped the running balance at
27
+ // any row is recoverable by summing the ledger up to it.
30
28
 
31
29
  const insertTransaction = async ({
32
30
  db,
@@ -42,10 +40,10 @@ const insertTransaction = async ({
42
40
  ...rest
43
41
  }) => {
44
42
 
45
- const beforeRaw = user?.balance?.[ type ];
46
- const balance = typeof beforeRaw === 'number'
47
- ? { before : beforeRaw, after : beforeRaw + amount }
48
- : undefined;
43
+ // No balance snapshot is stamped: the balance is DERIVED from this ledger
44
+ // (billing.balance sums it on read), so the running balance at any row is
45
+ // recoverable by summing up to that row a stored before/after would be
46
+ // redundant and, now that user.balance is gone, has no cheap source anyway.
49
47
 
50
48
  // Pull the active Sentry trace ID so every credit/debit auto-correlates
51
49
  // to the originating HTTP request or BullMQ job — useful for grouping
@@ -65,7 +63,6 @@ const insertTransaction = async ({
65
63
  category,
66
64
  source,
67
65
  amount,
68
- ...( balance && { balance }),
69
66
  ...( trace && { trace }),
70
67
  ...rest,
71
68
  ...( stripeInvoiceId && { stripeInvoiceId }),
@@ -2,12 +2,13 @@ import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
2
2
 
3
3
  // Ledger transaction insert helpers — no balance writes happen here.
4
4
  //
5
- // User-balance updates are the exclusive job of drawbridge-sync's
6
- // stream/transaction.js change-stream listener: every insert into the
7
- // `transaction` collection triggers an atomic $inc on user.balance.<type>
8
- // (guarded against going negative). This decouples the API surface from
9
- // the balance arithmetic and makes the `transaction` collection the single
10
- // source of truth for both audit history and balance derivation.
5
+ // The `transaction` collection is the SINGLE SOURCE OF TRUTH for balance:
6
+ // there is no stored balance field anywhere. The balance is DERIVED by summing
7
+ // a user's rows on read (see `balance` in billing.js) credits (+) and debits
8
+ // (−) so it can never drift from the ledger. credit()/debit() only append
9
+ // rows; drawbridge-sync's stream/transaction.js reacts to each insert to fire
10
+ // low/depleted notifications and emit `account.update` (it no longer maintains
11
+ // a balance).
11
12
  //
12
13
  // import { credit, debit } from '@drawbridge/drawbridge-utils/transactions';
13
14
  //
@@ -21,12 +22,9 @@ import { currentTraceId } from '@drawbridge/drawbridge-telemetry';
21
22
  // `db` is the wrapper returned by @drawbridge/mongodb — credit/debit
22
23
  // duck-type against its `.create` interface so this module has no hard dep.
23
24
  //
24
- // `user` is the user the transaction is being recorded against. The helper
25
- // reads `user.balance.<type>` to stamp `balance: { before, after }` on the
26
- // row as an audit snapshot of what the actor saw at action time — useful
27
- // for support / dispute resolution. When `balance.<type>` isn't on the user
28
- // (fresh signup, webhook-initiated row), the snapshot is omitted, which is
29
- // the honest representation of "no prior view to record".
25
+ // `user` is the user the transaction is being recorded against (stored as
26
+ // `user.id`). No per-row balance snapshot is stamped the running balance at
27
+ // any row is recoverable by summing the ledger up to it.
30
28
 
31
29
  const insertTransaction = async ({
32
30
  db,
@@ -42,10 +40,10 @@ const insertTransaction = async ({
42
40
  ...rest
43
41
  }) => {
44
42
 
45
- const beforeRaw = user?.balance?.[ type ];
46
- const balance = typeof beforeRaw === 'number'
47
- ? { before : beforeRaw, after : beforeRaw + amount }
48
- : undefined;
43
+ // No balance snapshot is stamped: the balance is DERIVED from this ledger
44
+ // (billing.balance sums it on read), so the running balance at any row is
45
+ // recoverable by summing up to that row a stored before/after would be
46
+ // redundant and, now that user.balance is gone, has no cheap source anyway.
49
47
 
50
48
  // Pull the active Sentry trace ID so every credit/debit auto-correlates
51
49
  // to the originating HTTP request or BullMQ job — useful for grouping
@@ -65,7 +63,6 @@ const insertTransaction = async ({
65
63
  category,
66
64
  source,
67
65
  amount,
68
- ...( balance && { balance }),
69
66
  ...( trace && { trace }),
70
67
  ...rest,
71
68
  ...( stripeInvoiceId && { stripeInvoiceId }),
@@ -13,9 +13,6 @@ var insertTransaction = async ({
13
13
  session,
14
14
  ...rest
15
15
  }) => {
16
- var _a;
17
- const beforeRaw = (_a = user == null ? void 0 : user.balance) == null ? void 0 : _a[type];
18
- const balance = typeof beforeRaw === "number" ? { before: beforeRaw, after: beforeRaw + amount } : void 0;
19
16
  const trace = currentTraceId();
20
17
  await db.create({
21
18
  authenticated: user,
@@ -27,7 +24,6 @@ var insertTransaction = async ({
27
24
  category,
28
25
  source,
29
26
  amount,
30
- ...balance && { balance },
31
27
  ...trace && { trace },
32
28
  ...rest,
33
29
  ...stripeInvoiceId && { stripeInvoiceId },
package/package.json CHANGED
@@ -164,5 +164,5 @@
164
164
  "test": "node --test test/"
165
165
  },
166
166
  "types": "dist/index.d.ts",
167
- "version": "0.0.80"
167
+ "version": "0.0.82"
168
168
  }