@drawbridge/drawbridge-utils 0.0.65 → 0.0.66

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/index.cjs CHANGED
@@ -285,6 +285,67 @@ var constants_default = {
285
285
  }
286
286
  };
287
287
 
288
+ // lib/usage.js
289
+ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
290
+ if (!usageId || !$inc) return;
291
+ const safeInc = Object.fromEntries(
292
+ Object.entries($inc).filter(([, value]) => {
293
+ const number = Number(value);
294
+ return Number.isFinite(number) && number !== 0;
295
+ })
296
+ );
297
+ if (Object.keys(safeInc).length === 0) return;
298
+ return controller.update({
299
+ collection: "usage",
300
+ data: { $inc: safeInc },
301
+ options: {
302
+ bypassDocumentValidation: true,
303
+ ...session && { session }
304
+ },
305
+ query: { id: usageId }
306
+ });
307
+ };
308
+ var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
309
+ var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
310
+ if (!controller || !organization || !usageId || !type || !key) return;
311
+ const amount = Number(units);
312
+ if (!Number.isFinite(amount) || amount === 0) return;
313
+ let inserted = false;
314
+ try {
315
+ const { lastErrorObject } = await controller.update({
316
+ collection: "action",
317
+ data: {
318
+ $setOnInsert: {
319
+ organization,
320
+ usage: usageId,
321
+ type,
322
+ units: amount,
323
+ key,
324
+ meta,
325
+ ...traceId && { traceId }
326
+ }
327
+ },
328
+ options: {
329
+ bypassDocumentValidation: true,
330
+ includeResultMetadata: true,
331
+ upsert: true,
332
+ ...session && { session }
333
+ },
334
+ query: {
335
+ key,
336
+ usage: usageId
337
+ }
338
+ });
339
+ inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
340
+ } catch (error) {
341
+ if (isDuplicateKey(error) && !session) return;
342
+ throw error;
343
+ }
344
+ if (inserted) {
345
+ await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
346
+ }
347
+ };
348
+
288
349
  // index.js
289
350
  var nanoid = (0, import_nanoid.customAlphabet)("0123456789abcdefghijklmnopqrstuvwxyz", 8);
290
351
  var constants = constants_default;
@@ -373,65 +434,6 @@ var getPlanFeature = (plan, key) => {
373
434
  message: granted ? feature : error
374
435
  };
375
436
  };
376
- var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
377
- if (!usageId || !$inc) return;
378
- const safeInc = Object.fromEntries(
379
- Object.entries($inc).filter(([, value]) => {
380
- const number = Number(value);
381
- return Number.isFinite(number) && number !== 0;
382
- })
383
- );
384
- if (Object.keys(safeInc).length === 0) return;
385
- return controller.update({
386
- collection: "usage",
387
- data: { $inc: safeInc },
388
- options: {
389
- bypassDocumentValidation: true,
390
- ...session && { session }
391
- },
392
- query: { id: usageId }
393
- });
394
- };
395
- var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
396
- var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
397
- if (!controller || !organization || !usageId || !type || !key) return;
398
- const amount = Number(units);
399
- if (!Number.isFinite(amount) || amount === 0) return;
400
- let inserted = false;
401
- try {
402
- const { lastErrorObject } = await controller.update({
403
- collection: "action",
404
- data: {
405
- $setOnInsert: {
406
- organization,
407
- usage: usageId,
408
- type,
409
- units: amount,
410
- key,
411
- meta,
412
- ...traceId && { traceId }
413
- }
414
- },
415
- options: {
416
- bypassDocumentValidation: true,
417
- includeResultMetadata: true,
418
- upsert: true,
419
- ...session && { session }
420
- },
421
- query: {
422
- key,
423
- usage: usageId
424
- }
425
- });
426
- inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
427
- } catch (error) {
428
- if (isDuplicateKey(error) && !session) return;
429
- throw error;
430
- }
431
- if (inserted) {
432
- await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
433
- }
434
- };
435
437
  var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
436
438
  var reducers = {
437
439
  fields: (data2 = [], callback = () => ({})) => data2.reduce(
package/dist/index.d.cts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { data, code } from 'currency-codes';
2
2
  import { customAlphabet } from 'nanoid';
3
3
  import { colorFormatted, colorAccessible } from './color.cjs';
4
+ export { incrementUsageTotals, isDuplicateKey, recordAction } from './usage.cjs';
4
5
  import 'tinycolor2';
5
6
 
6
7
  const font = {
@@ -341,127 +342,6 @@ const getPlanFeature = ( plan, key ) => {
341
342
 
342
343
  };
343
344
 
344
- // Centralized $inc helper for the per-org usage doc. Replaces inline
345
- // $inc-with-bypass-validation patterns in drawbridge-api + drawbridge-sync.
346
- //
347
- // MongoDB's $inc with 0 / NaN / undefined / null writes the value into the
348
- // field (zeroing or NaN-ing it) instead of no-op'ing — so we filter the
349
- // $inc object to keep only finite, non-zero values and callers don't have
350
- // to remember the guard.
351
- //
352
- // `session` is forwarded when present so callers inside a transaction get
353
- // atomic writes; standalone callers omit it.
354
- const incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
355
-
356
- if( ! usageId || ! $inc ) return;
357
-
358
- const safeInc = Object.fromEntries(
359
- Object.entries( $inc ).filter( ([ , value ]) => {
360
-
361
- const number = Number( value );
362
-
363
- return Number.isFinite( number ) && number !== 0;
364
-
365
- })
366
- );
367
-
368
- if( Object.keys( safeInc ).length === 0 ) return;
369
-
370
- return controller.update({
371
- collection : 'usage',
372
- data : { $inc : safeInc },
373
- options : {
374
- bypassDocumentValidation : true,
375
- ...( session && { session })
376
- },
377
- query : { id : usageId }
378
- });
379
-
380
- };
381
-
382
- // True for a MongoDB duplicate-key error (unique-index violation).
383
- const isDuplicateKey = ( error ) =>
384
- error?.code === 11000
385
- || error?.code === '11000'
386
- || /E11000|duplicate key/i.test( error?.message || '' );
387
-
388
- // Record a billable action as a row in the append-only `action` ledger — the
389
- // source of truth for usage billing (summed per period at changeover).
390
- //
391
- // Idempotent via an UPSERT on the unique { usage, key }: a duplicate event (the
392
- // segment realtime path AND the sweep firing for the same membership, or a retry)
393
- // matches the existing row and no-ops — no duplicate-key error — so this is safe
394
- // to call INSIDE a caller's transaction (a thrown E11000 would abort the whole
395
- // transaction). The display cache `usage.totals.actions` is bumped ONLY when the
396
- // upsert actually inserts a new row, so a duplicate never double-counts.
397
- //
398
- // Pass `session` to make the ledger row + cache bump atomic with the caller's own
399
- // writes (e.g. the membership writes in a segment-sweep chunk, or the submission
400
- // docs) — all-or-nothing. Without a session it runs standalone.
401
- // key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
402
- // units billable units (default 1; N for batch, e.g. accepted entries)
403
- // meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
404
- const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
405
-
406
- if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
407
-
408
- const amount = Number( units );
409
-
410
- if( ! Number.isFinite( amount ) || amount === 0 ) return;
411
-
412
- let inserted = false;
413
-
414
- try {
415
-
416
- const { lastErrorObject } = await controller.update({
417
- collection : 'action',
418
- data : {
419
- $setOnInsert : {
420
- organization,
421
- usage : usageId,
422
- type,
423
- units : amount,
424
- key,
425
- meta,
426
- ...( traceId && { traceId })
427
- }
428
- },
429
- options : {
430
- bypassDocumentValidation : true,
431
- includeResultMetadata : true,
432
- upsert : true,
433
- ...( session && { session })
434
- },
435
- query : {
436
- key,
437
- usage : usageId
438
- }
439
- });
440
-
441
- inserted = Boolean( lastErrorObject?.upserted );
442
-
443
- } catch ( error ) {
444
-
445
- // A concurrent upsert of the same { usage, key } can still race to E11000.
446
- // Standalone: the other writer recorded it — skip. Inside a transaction:
447
- // the txn is already aborted, so re-throw to let the caller retry the whole
448
- // unit (which then sees the row and no-ops).
449
- if( isDuplicateKey( error ) && ! session ) return;
450
-
451
- throw error;
452
-
453
- }
454
-
455
- // Only a genuinely new row bumps the display cache (a matched duplicate must
456
- // not double-count). Joined to the caller's transaction when a session is given.
457
- if( inserted ){
458
-
459
- await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount }, session });
460
-
461
- }
462
-
463
- };
464
-
465
345
  const percentage = ( value1, value2, decimals = 1 ) => ( ( ( ( value1 || 0 ) / ( value2 || 0 ) ) * 100 ) || 0 ).toFixed( decimals );
466
346
 
467
347
  const reducers = {
@@ -560,4 +440,4 @@ const currencies = data.map( ( item ) => ({
560
440
 
561
441
  const currency = ( val ) => code( val );
562
442
 
563
- export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isDuplicateKey, isInfinite, megabyte, nanoid, percentage, recordAction, reducers, regex, shareUrls, urlRoot };
443
+ export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, infinite, isInfinite, megabyte, nanoid, percentage, reducers, regex, shareUrls, urlRoot };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { data, code } from 'currency-codes';
2
2
  import { customAlphabet } from 'nanoid';
3
3
  import { colorFormatted, colorAccessible } from './color.js';
4
+ export { incrementUsageTotals, isDuplicateKey, recordAction } from './usage.js';
4
5
  import 'tinycolor2';
5
6
 
6
7
  const font = {
@@ -341,127 +342,6 @@ const getPlanFeature = ( plan, key ) => {
341
342
 
342
343
  };
343
344
 
344
- // Centralized $inc helper for the per-org usage doc. Replaces inline
345
- // $inc-with-bypass-validation patterns in drawbridge-api + drawbridge-sync.
346
- //
347
- // MongoDB's $inc with 0 / NaN / undefined / null writes the value into the
348
- // field (zeroing or NaN-ing it) instead of no-op'ing — so we filter the
349
- // $inc object to keep only finite, non-zero values and callers don't have
350
- // to remember the guard.
351
- //
352
- // `session` is forwarded when present so callers inside a transaction get
353
- // atomic writes; standalone callers omit it.
354
- const incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
355
-
356
- if( ! usageId || ! $inc ) return;
357
-
358
- const safeInc = Object.fromEntries(
359
- Object.entries( $inc ).filter( ([ , value ]) => {
360
-
361
- const number = Number( value );
362
-
363
- return Number.isFinite( number ) && number !== 0;
364
-
365
- })
366
- );
367
-
368
- if( Object.keys( safeInc ).length === 0 ) return;
369
-
370
- return controller.update({
371
- collection : 'usage',
372
- data : { $inc : safeInc },
373
- options : {
374
- bypassDocumentValidation : true,
375
- ...( session && { session })
376
- },
377
- query : { id : usageId }
378
- });
379
-
380
- };
381
-
382
- // True for a MongoDB duplicate-key error (unique-index violation).
383
- const isDuplicateKey = ( error ) =>
384
- error?.code === 11000
385
- || error?.code === '11000'
386
- || /E11000|duplicate key/i.test( error?.message || '' );
387
-
388
- // Record a billable action as a row in the append-only `action` ledger — the
389
- // source of truth for usage billing (summed per period at changeover).
390
- //
391
- // Idempotent via an UPSERT on the unique { usage, key }: a duplicate event (the
392
- // segment realtime path AND the sweep firing for the same membership, or a retry)
393
- // matches the existing row and no-ops — no duplicate-key error — so this is safe
394
- // to call INSIDE a caller's transaction (a thrown E11000 would abort the whole
395
- // transaction). The display cache `usage.totals.actions` is bumped ONLY when the
396
- // upsert actually inserts a new row, so a duplicate never double-counts.
397
- //
398
- // Pass `session` to make the ledger row + cache bump atomic with the caller's own
399
- // writes (e.g. the membership writes in a segment-sweep chunk, or the submission
400
- // docs) — all-or-nothing. Without a session it runs standalone.
401
- // key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
402
- // units billable units (default 1; N for batch, e.g. accepted entries)
403
- // meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
404
- const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
405
-
406
- if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
407
-
408
- const amount = Number( units );
409
-
410
- if( ! Number.isFinite( amount ) || amount === 0 ) return;
411
-
412
- let inserted = false;
413
-
414
- try {
415
-
416
- const { lastErrorObject } = await controller.update({
417
- collection : 'action',
418
- data : {
419
- $setOnInsert : {
420
- organization,
421
- usage : usageId,
422
- type,
423
- units : amount,
424
- key,
425
- meta,
426
- ...( traceId && { traceId })
427
- }
428
- },
429
- options : {
430
- bypassDocumentValidation : true,
431
- includeResultMetadata : true,
432
- upsert : true,
433
- ...( session && { session })
434
- },
435
- query : {
436
- key,
437
- usage : usageId
438
- }
439
- });
440
-
441
- inserted = Boolean( lastErrorObject?.upserted );
442
-
443
- } catch ( error ) {
444
-
445
- // A concurrent upsert of the same { usage, key } can still race to E11000.
446
- // Standalone: the other writer recorded it — skip. Inside a transaction:
447
- // the txn is already aborted, so re-throw to let the caller retry the whole
448
- // unit (which then sees the row and no-ops).
449
- if( isDuplicateKey( error ) && ! session ) return;
450
-
451
- throw error;
452
-
453
- }
454
-
455
- // Only a genuinely new row bumps the display cache (a matched duplicate must
456
- // not double-count). Joined to the caller's transaction when a session is given.
457
- if( inserted ){
458
-
459
- await incrementUsageTotals({ controller, usageId, $inc : { 'totals.actions' : amount }, session });
460
-
461
- }
462
-
463
- };
464
-
465
345
  const percentage = ( value1, value2, decimals = 1 ) => ( ( ( ( value1 || 0 ) / ( value2 || 0 ) ) * 100 ) || 0 ).toFixed( decimals );
466
346
 
467
347
  const reducers = {
@@ -560,4 +440,4 @@ const currencies = data.map( ( item ) => ({
560
440
 
561
441
  const currency = ( val ) => code( val );
562
442
 
563
- export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, incrementUsageTotals, infinite, isDuplicateKey, isInfinite, megabyte, nanoid, percentage, recordAction, reducers, regex, shareUrls, urlRoot };
443
+ export { bytesToGB, bytesToMB, capitalize, constants, currencies, currency, expiredPaymentMethod, formatCurrency, formatDateString, formatNumber, getPlanFeature, gigabyte, infinite, isInfinite, megabyte, nanoid, percentage, reducers, regex, shareUrls, urlRoot };
package/dist/index.js CHANGED
@@ -229,6 +229,67 @@ var constants_default = {
229
229
  }
230
230
  };
231
231
 
232
+ // lib/usage.js
233
+ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
234
+ if (!usageId || !$inc) return;
235
+ const safeInc = Object.fromEntries(
236
+ Object.entries($inc).filter(([, value]) => {
237
+ const number = Number(value);
238
+ return Number.isFinite(number) && number !== 0;
239
+ })
240
+ );
241
+ if (Object.keys(safeInc).length === 0) return;
242
+ return controller.update({
243
+ collection: "usage",
244
+ data: { $inc: safeInc },
245
+ options: {
246
+ bypassDocumentValidation: true,
247
+ ...session && { session }
248
+ },
249
+ query: { id: usageId }
250
+ });
251
+ };
252
+ var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
253
+ var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
254
+ if (!controller || !organization || !usageId || !type || !key) return;
255
+ const amount = Number(units);
256
+ if (!Number.isFinite(amount) || amount === 0) return;
257
+ let inserted = false;
258
+ try {
259
+ const { lastErrorObject } = await controller.update({
260
+ collection: "action",
261
+ data: {
262
+ $setOnInsert: {
263
+ organization,
264
+ usage: usageId,
265
+ type,
266
+ units: amount,
267
+ key,
268
+ meta,
269
+ ...traceId && { traceId }
270
+ }
271
+ },
272
+ options: {
273
+ bypassDocumentValidation: true,
274
+ includeResultMetadata: true,
275
+ upsert: true,
276
+ ...session && { session }
277
+ },
278
+ query: {
279
+ key,
280
+ usage: usageId
281
+ }
282
+ });
283
+ inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
284
+ } catch (error) {
285
+ if (isDuplicateKey(error) && !session) return;
286
+ throw error;
287
+ }
288
+ if (inserted) {
289
+ await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
290
+ }
291
+ };
292
+
232
293
  // index.js
233
294
  var nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 8);
234
295
  var constants = constants_default;
@@ -317,65 +378,6 @@ var getPlanFeature = (plan, key) => {
317
378
  message: granted ? feature : error
318
379
  };
319
380
  };
320
- var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
321
- if (!usageId || !$inc) return;
322
- const safeInc = Object.fromEntries(
323
- Object.entries($inc).filter(([, value]) => {
324
- const number = Number(value);
325
- return Number.isFinite(number) && number !== 0;
326
- })
327
- );
328
- if (Object.keys(safeInc).length === 0) return;
329
- return controller.update({
330
- collection: "usage",
331
- data: { $inc: safeInc },
332
- options: {
333
- bypassDocumentValidation: true,
334
- ...session && { session }
335
- },
336
- query: { id: usageId }
337
- });
338
- };
339
- var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
340
- var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
341
- if (!controller || !organization || !usageId || !type || !key) return;
342
- const amount = Number(units);
343
- if (!Number.isFinite(amount) || amount === 0) return;
344
- let inserted = false;
345
- try {
346
- const { lastErrorObject } = await controller.update({
347
- collection: "action",
348
- data: {
349
- $setOnInsert: {
350
- organization,
351
- usage: usageId,
352
- type,
353
- units: amount,
354
- key,
355
- meta,
356
- ...traceId && { traceId }
357
- }
358
- },
359
- options: {
360
- bypassDocumentValidation: true,
361
- includeResultMetadata: true,
362
- upsert: true,
363
- ...session && { session }
364
- },
365
- query: {
366
- key,
367
- usage: usageId
368
- }
369
- });
370
- inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
371
- } catch (error) {
372
- if (isDuplicateKey(error) && !session) return;
373
- throw error;
374
- }
375
- if (inserted) {
376
- await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
377
- }
378
- };
379
381
  var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
380
382
  var reducers = {
381
383
  fields: (data2 = [], callback = () => ({})) => data2.reduce(
package/dist/plans.d.cts CHANGED
@@ -4,6 +4,7 @@ import 'currency-codes';
4
4
  import 'nanoid';
5
5
  import './color.cjs';
6
6
  import 'tinycolor2';
7
+ import './usage.cjs';
7
8
 
8
9
  const featuresFor = ( array = [] ) => Object.values({
9
10
  ...connection,
package/dist/plans.d.ts CHANGED
@@ -4,6 +4,7 @@ import 'currency-codes';
4
4
  import 'nanoid';
5
5
  import './color.js';
6
6
  import 'tinycolor2';
7
+ import './usage.js';
7
8
 
8
9
  const featuresFor = ( array = [] ) => Object.values({
9
10
  ...connection,
package/dist/usage.cjs ADDED
@@ -0,0 +1,91 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // lib/usage.js
20
+ var usage_exports = {};
21
+ __export(usage_exports, {
22
+ incrementUsageTotals: () => incrementUsageTotals,
23
+ isDuplicateKey: () => isDuplicateKey,
24
+ recordAction: () => recordAction
25
+ });
26
+ module.exports = __toCommonJS(usage_exports);
27
+ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
28
+ if (!usageId || !$inc) return;
29
+ const safeInc = Object.fromEntries(
30
+ Object.entries($inc).filter(([, value]) => {
31
+ const number = Number(value);
32
+ return Number.isFinite(number) && number !== 0;
33
+ })
34
+ );
35
+ if (Object.keys(safeInc).length === 0) return;
36
+ return controller.update({
37
+ collection: "usage",
38
+ data: { $inc: safeInc },
39
+ options: {
40
+ bypassDocumentValidation: true,
41
+ ...session && { session }
42
+ },
43
+ query: { id: usageId }
44
+ });
45
+ };
46
+ var isDuplicateKey = (error) => (error == null ? void 0 : error.code) === 11e3 || (error == null ? void 0 : error.code) === "11000" || /E11000|duplicate key/i.test((error == null ? void 0 : error.message) || "");
47
+ var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
48
+ if (!controller || !organization || !usageId || !type || !key) return;
49
+ const amount = Number(units);
50
+ if (!Number.isFinite(amount) || amount === 0) return;
51
+ let inserted = false;
52
+ try {
53
+ const { lastErrorObject } = await controller.update({
54
+ collection: "action",
55
+ data: {
56
+ $setOnInsert: {
57
+ organization,
58
+ usage: usageId,
59
+ type,
60
+ units: amount,
61
+ key,
62
+ meta,
63
+ ...traceId && { traceId }
64
+ }
65
+ },
66
+ options: {
67
+ bypassDocumentValidation: true,
68
+ includeResultMetadata: true,
69
+ upsert: true,
70
+ ...session && { session }
71
+ },
72
+ query: {
73
+ key,
74
+ usage: usageId
75
+ }
76
+ });
77
+ inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
78
+ } catch (error) {
79
+ if (isDuplicateKey(error) && !session) return;
80
+ throw error;
81
+ }
82
+ if (inserted) {
83
+ await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
84
+ }
85
+ };
86
+ // Annotate the CommonJS export names for ESM import in node:
87
+ 0 && (module.exports = {
88
+ incrementUsageTotals,
89
+ isDuplicateKey,
90
+ recordAction
91
+ });