@drawbridge/drawbridge-utils 0.0.62 → 0.0.63
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 +26 -10
- package/dist/index.d.cts +48 -22
- package/dist/index.d.ts +48 -22
- package/dist/index.js +26 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -393,28 +393,44 @@ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
393
393
|
});
|
|
394
394
|
};
|
|
395
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 }) => {
|
|
396
|
+
var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
|
|
397
397
|
if (!controller || !organization || !usageId || !type || !key) return;
|
|
398
398
|
const amount = Number(units);
|
|
399
399
|
if (!Number.isFinite(amount) || amount === 0) return;
|
|
400
|
+
let inserted = false;
|
|
400
401
|
try {
|
|
401
|
-
await controller.
|
|
402
|
+
const { lastErrorObject } = await controller.update({
|
|
402
403
|
collection: "action",
|
|
403
404
|
data: {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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: {
|
|
408
422
|
key,
|
|
409
|
-
|
|
410
|
-
...traceId && { traceId }
|
|
423
|
+
usage: usageId
|
|
411
424
|
}
|
|
412
425
|
});
|
|
426
|
+
inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
|
|
413
427
|
} catch (error) {
|
|
414
|
-
if (isDuplicateKey(error)) return;
|
|
428
|
+
if (isDuplicateKey(error) && !session) return;
|
|
415
429
|
throw error;
|
|
416
430
|
}
|
|
417
|
-
|
|
431
|
+
if (inserted) {
|
|
432
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
433
|
+
}
|
|
418
434
|
};
|
|
419
435
|
var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
|
|
420
436
|
var reducers = {
|
package/dist/index.d.cts
CHANGED
|
@@ -386,18 +386,22 @@ const isDuplicateKey = ( error ) =>
|
|
|
386
386
|
|| /E11000|duplicate key/i.test( error?.message || '' );
|
|
387
387
|
|
|
388
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
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
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.
|
|
397
401
|
// key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
|
|
398
402
|
// units billable units (default 1; N for batch, e.g. accepted entries)
|
|
399
403
|
// meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
|
|
400
|
-
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
404
|
+
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
|
|
401
405
|
|
|
402
406
|
if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
|
|
403
407
|
|
|
@@ -405,34 +409,56 @@ const recordAction = async ({ controller, organization, usageId, type, units = 1
|
|
|
405
409
|
|
|
406
410
|
if( ! Number.isFinite( amount ) || amount === 0 ) return;
|
|
407
411
|
|
|
412
|
+
let inserted = false;
|
|
413
|
+
|
|
408
414
|
try {
|
|
409
415
|
|
|
410
|
-
await controller.
|
|
416
|
+
const { lastErrorObject } = await controller.update({
|
|
411
417
|
collection : 'action',
|
|
412
418
|
data : {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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 : {
|
|
417
436
|
key,
|
|
418
|
-
|
|
419
|
-
...( traceId && { traceId })
|
|
437
|
+
usage : usageId
|
|
420
438
|
}
|
|
421
439
|
});
|
|
422
440
|
|
|
441
|
+
inserted = Boolean( lastErrorObject?.upserted );
|
|
442
|
+
|
|
423
443
|
} catch ( error ) {
|
|
424
444
|
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
|
|
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;
|
|
428
450
|
|
|
429
451
|
throw error;
|
|
430
452
|
|
|
431
453
|
}
|
|
432
454
|
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
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
|
+
}
|
|
436
462
|
|
|
437
463
|
};
|
|
438
464
|
|
package/dist/index.d.ts
CHANGED
|
@@ -386,18 +386,22 @@ const isDuplicateKey = ( error ) =>
|
|
|
386
386
|
|| /E11000|duplicate key/i.test( error?.message || '' );
|
|
387
387
|
|
|
388
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
|
-
//
|
|
392
|
-
//
|
|
393
|
-
//
|
|
394
|
-
//
|
|
395
|
-
//
|
|
396
|
-
//
|
|
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.
|
|
397
401
|
// key deterministic per source event, e.g. 'segment.join:<contact>:<segment>'
|
|
398
402
|
// units billable units (default 1; N for batch, e.g. accepted entries)
|
|
399
403
|
// meta attribution ids { campaign, page, submission, segment, workflow, step, ... }
|
|
400
|
-
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId }) => {
|
|
404
|
+
const recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
|
|
401
405
|
|
|
402
406
|
if( ! controller || ! organization || ! usageId || ! type || ! key ) return;
|
|
403
407
|
|
|
@@ -405,34 +409,56 @@ const recordAction = async ({ controller, organization, usageId, type, units = 1
|
|
|
405
409
|
|
|
406
410
|
if( ! Number.isFinite( amount ) || amount === 0 ) return;
|
|
407
411
|
|
|
412
|
+
let inserted = false;
|
|
413
|
+
|
|
408
414
|
try {
|
|
409
415
|
|
|
410
|
-
await controller.
|
|
416
|
+
const { lastErrorObject } = await controller.update({
|
|
411
417
|
collection : 'action',
|
|
412
418
|
data : {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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 : {
|
|
417
436
|
key,
|
|
418
|
-
|
|
419
|
-
...( traceId && { traceId })
|
|
437
|
+
usage : usageId
|
|
420
438
|
}
|
|
421
439
|
});
|
|
422
440
|
|
|
441
|
+
inserted = Boolean( lastErrorObject?.upserted );
|
|
442
|
+
|
|
423
443
|
} catch ( error ) {
|
|
424
444
|
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
|
|
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;
|
|
428
450
|
|
|
429
451
|
throw error;
|
|
430
452
|
|
|
431
453
|
}
|
|
432
454
|
|
|
433
|
-
//
|
|
434
|
-
//
|
|
435
|
-
|
|
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
|
+
}
|
|
436
462
|
|
|
437
463
|
};
|
|
438
464
|
|
package/dist/index.js
CHANGED
|
@@ -337,28 +337,44 @@ var incrementUsageTotals = async ({ controller, usageId, $inc, session }) => {
|
|
|
337
337
|
});
|
|
338
338
|
};
|
|
339
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 }) => {
|
|
340
|
+
var recordAction = async ({ controller, organization, usageId, type, units = 1, key, meta = {}, traceId, session }) => {
|
|
341
341
|
if (!controller || !organization || !usageId || !type || !key) return;
|
|
342
342
|
const amount = Number(units);
|
|
343
343
|
if (!Number.isFinite(amount) || amount === 0) return;
|
|
344
|
+
let inserted = false;
|
|
344
345
|
try {
|
|
345
|
-
await controller.
|
|
346
|
+
const { lastErrorObject } = await controller.update({
|
|
346
347
|
collection: "action",
|
|
347
348
|
data: {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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: {
|
|
352
366
|
key,
|
|
353
|
-
|
|
354
|
-
...traceId && { traceId }
|
|
367
|
+
usage: usageId
|
|
355
368
|
}
|
|
356
369
|
});
|
|
370
|
+
inserted = Boolean(lastErrorObject == null ? void 0 : lastErrorObject.upserted);
|
|
357
371
|
} catch (error) {
|
|
358
|
-
if (isDuplicateKey(error)) return;
|
|
372
|
+
if (isDuplicateKey(error) && !session) return;
|
|
359
373
|
throw error;
|
|
360
374
|
}
|
|
361
|
-
|
|
375
|
+
if (inserted) {
|
|
376
|
+
await incrementUsageTotals({ controller, usageId, $inc: { "totals.actions": amount }, session });
|
|
377
|
+
}
|
|
362
378
|
};
|
|
363
379
|
var percentage = (value1, value2, decimals = 1) => ((value1 || 0) / (value2 || 0) * 100 || 0).toFixed(decimals);
|
|
364
380
|
var reducers = {
|
package/package.json
CHANGED