@naturalcycles/abba 2.8.0 → 2.9.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.
package/dist/abba.js CHANGED
@@ -8,7 +8,7 @@ import { LRUMemoCache } from '@naturalcycles/nodejs-lib/lruMemoCache';
8
8
  import { bucketDao } from './dao/bucket.dao.js';
9
9
  import { experimentDao } from './dao/experiment.dao.js';
10
10
  import { userAssignmentDao } from './dao/userAssignment.dao.js';
11
- import { AssignmentStatus } from './types.js';
11
+ import { AbbaErrorCode, AssignmentStatus } from './types.js';
12
12
  import { canGenerateNewAssignments, generateUserAssignmentData, getUserExclusionSet, validateTotalBucketRatio, } from './util.js';
13
13
  /**
14
14
  * 10 minutes
@@ -150,7 +150,7 @@ export class Abba {
150
150
  const experiment = await this.experimentDao.requireById(experimentId);
151
151
  const hasBeenInactiveFor15Mins = experiment.status === AssignmentStatus.Inactive &&
152
152
  localTime(experiment.updated).isOlderThan(15, 'minute');
153
- _assert(hasBeenInactiveFor15Mins, 'Experiment must be inactive for at least 15 minutes before deletion');
153
+ _assert(hasBeenInactiveFor15Mins, 'Experiment must be inactive for at least 15 minutes before deletion', { code: AbbaErrorCode.ExperimentDeletionTooSoon });
154
154
  await this.userAssignmentDao.deleteByExperimentId(experimentId);
155
155
  await this.bucketDao.deleteByExperimentId(experimentId);
156
156
  await this.experimentDao.deleteById(experimentId);
@@ -167,7 +167,9 @@ export class Abba {
167
167
  */
168
168
  async getUserAssignment(experimentKey, userId, existingOnly, segmentationData) {
169
169
  const experiment = await this.experimentDao.getByKey(experimentKey);
170
- _assert(experiment, `Experiment does not exist: ${experimentKey}`);
170
+ _assert(experiment, `Experiment does not exist: ${experimentKey}`, {
171
+ code: AbbaErrorCode.ExperimentNotFound,
172
+ });
171
173
  // Inactive experiments should never return an assignment
172
174
  if (experiment.status === AssignmentStatus.Inactive) {
173
175
  return null;
@@ -193,7 +195,9 @@ export class Abba {
193
195
  if (!canGenerateNewAssignments(experiment, exclusionSet)) {
194
196
  return null;
195
197
  }
196
- _assert(segmentationData, 'Segmentation data required when creating a new assignment');
198
+ _assert(segmentationData, 'Segmentation data required when creating a new assignment', {
199
+ code: AbbaErrorCode.SegmentationDataRequired,
200
+ });
197
201
  const experimentWithBuckets = { ...experiment, buckets };
198
202
  const assignment = generateUserAssignmentData(experimentWithBuckets, userId, segmentationData);
199
203
  if (!assignment) {
package/dist/types.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import type { CommonDB } from '@naturalcycles/db-lib';
2
2
  import type { AnyObject, BaseDBEntity, IsoDate } from '@naturalcycles/js-lib/types';
3
+ export declare const AbbaErrorCode: {
4
+ readonly ExperimentNotFound: 'abba/experimentNotFound';
5
+ readonly ExperimentDeletionTooSoon: 'abba/experimentDeletionTooSoon';
6
+ readonly SegmentationDataRequired: 'abba/segmentationDataRequired';
7
+ readonly InvalidBucketRatio: 'abba/invalidBucketRatio';
8
+ readonly BucketDeterminationFailed: 'abba/bucketDeterminationFailed';
9
+ };
3
10
  export interface AbbaConfig {
4
11
  db: CommonDB;
5
12
  }
package/dist/types.js CHANGED
@@ -1,3 +1,10 @@
1
+ export const AbbaErrorCode = {
2
+ ExperimentNotFound: 'abba/experimentNotFound',
3
+ ExperimentDeletionTooSoon: 'abba/experimentDeletionTooSoon',
4
+ SegmentationDataRequired: 'abba/segmentationDataRequired',
5
+ InvalidBucketRatio: 'abba/invalidBucketRatio',
6
+ BucketDeterminationFailed: 'abba/bucketDeterminationFailed',
7
+ };
1
8
  export var AssignmentStatus;
2
9
  (function (AssignmentStatus) {
3
10
  /**
package/dist/util.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { localDate } from '@naturalcycles/js-lib/datetime';
2
+ import { _assert } from '@naturalcycles/js-lib/error/assert.js';
2
3
  import { satisfies } from 'semver';
3
- import { AssignmentStatus, SegmentationRuleOperator } from './types.js';
4
+ import { AbbaErrorCode, AssignmentStatus, SegmentationRuleOperator } from './types.js';
4
5
  /**
5
6
  * Generate a new assignment for a given user.
6
7
  * Doesn't save it.
@@ -53,9 +54,9 @@ export function determineBucket(buckets) {
53
54
  return b;
54
55
  }
55
56
  });
56
- if (!bucket) {
57
- throw new Error('Could not detetermine bucket from ratios');
58
- }
57
+ _assert(bucket, 'Could not determine bucket from ratios', {
58
+ code: AbbaErrorCode.BucketDeterminationFailed,
59
+ });
59
60
  return bucket;
60
61
  }
61
62
  /**
@@ -63,9 +64,7 @@ export function determineBucket(buckets) {
63
64
  */
64
65
  export function validateTotalBucketRatio(buckets) {
65
66
  const bucketSum = buckets.reduce((sum, current) => sum + current.ratio, 0);
66
- if (bucketSum !== 100) {
67
- throw new Error('Total bucket ratio must be 100 before you can activate an experiment');
68
- }
67
+ _assert(bucketSum === 100, 'Total bucket ratio must be 100 before you can activate an experiment', { code: AbbaErrorCode.InvalidBucketRatio });
69
68
  }
70
69
  /**
71
70
  * Validate a users segmentation data against multiple rules. Returns false if any fail
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/abba",
3
3
  "type": "module",
4
- "version": "2.8.0",
4
+ "version": "2.9.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/db-lib": "^10",
7
7
  "@naturalcycles/js-lib": "^15",
@@ -12,7 +12,7 @@
12
12
  "devDependencies": {
13
13
  "@types/semver": "^7",
14
14
  "@typescript/native-preview": "7.0.0-dev.20260401.1",
15
- "@naturalcycles/dev-lib": "20.42.0"
15
+ "@naturalcycles/dev-lib": "18.4.2"
16
16
  },
17
17
  "exports": {
18
18
  ".": "./dist/index.js"
package/src/abba.ts CHANGED
@@ -23,7 +23,7 @@ import type {
23
23
  UserAssignment,
24
24
  UserExperiment,
25
25
  } from './types.js'
26
- import { AssignmentStatus } from './types.js'
26
+ import { AbbaErrorCode, AssignmentStatus } from './types.js'
27
27
  import {
28
28
  canGenerateNewAssignments,
29
29
  generateUserAssignmentData,
@@ -233,6 +233,7 @@ export class Abba {
233
233
  _assert(
234
234
  hasBeenInactiveFor15Mins,
235
235
  'Experiment must be inactive for at least 15 minutes before deletion',
236
+ { code: AbbaErrorCode.ExperimentDeletionTooSoon },
236
237
  )
237
238
 
238
239
  await this.userAssignmentDao.deleteByExperimentId(experimentId)
@@ -257,7 +258,9 @@ export class Abba {
257
258
  segmentationData?: SegmentationData,
258
259
  ): Promise<DecoratedUserAssignment | null> {
259
260
  const experiment = await this.experimentDao.getByKey(experimentKey)
260
- _assert(experiment, `Experiment does not exist: ${experimentKey}`)
261
+ _assert(experiment, `Experiment does not exist: ${experimentKey}`, {
262
+ code: AbbaErrorCode.ExperimentNotFound,
263
+ })
261
264
 
262
265
  // Inactive experiments should never return an assignment
263
266
  if (experiment.status === AssignmentStatus.Inactive) {
@@ -291,7 +294,9 @@ export class Abba {
291
294
  return null
292
295
  }
293
296
 
294
- _assert(segmentationData, 'Segmentation data required when creating a new assignment')
297
+ _assert(segmentationData, 'Segmentation data required when creating a new assignment', {
298
+ code: AbbaErrorCode.SegmentationDataRequired,
299
+ })
295
300
 
296
301
  const experimentWithBuckets = { ...experiment, buckets }
297
302
  const assignment = generateUserAssignmentData(experimentWithBuckets, userId, segmentationData)
package/src/types.ts CHANGED
@@ -1,6 +1,14 @@
1
1
  import type { CommonDB } from '@naturalcycles/db-lib'
2
2
  import type { AnyObject, BaseDBEntity, IsoDate } from '@naturalcycles/js-lib/types'
3
3
 
4
+ export const AbbaErrorCode = {
5
+ ExperimentNotFound: 'abba/experimentNotFound',
6
+ ExperimentDeletionTooSoon: 'abba/experimentDeletionTooSoon',
7
+ SegmentationDataRequired: 'abba/segmentationDataRequired',
8
+ InvalidBucketRatio: 'abba/invalidBucketRatio',
9
+ BucketDeterminationFailed: 'abba/bucketDeterminationFailed',
10
+ } as const
11
+
4
12
  export interface AbbaConfig {
5
13
  db: CommonDB
6
14
  }
package/src/util.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { localDate } from '@naturalcycles/js-lib/datetime'
2
+ import { _assert } from '@naturalcycles/js-lib/error/assert.js'
2
3
  import type { Unsaved } from '@naturalcycles/js-lib/types'
3
4
  import { satisfies } from 'semver'
4
5
  import type {
@@ -13,7 +14,7 @@ import type {
13
14
  UserAssignment,
14
15
  UserExperiment,
15
16
  } from './types.js'
16
- import { AssignmentStatus, SegmentationRuleOperator } from './types.js'
17
+ import { AbbaErrorCode, AssignmentStatus, SegmentationRuleOperator } from './types.js'
17
18
 
18
19
  /**
19
20
  * Generate a new assignment for a given user.
@@ -78,9 +79,9 @@ export function determineBucket(buckets: Bucket[]): Bucket {
78
79
  }
79
80
  })
80
81
 
81
- if (!bucket) {
82
- throw new Error('Could not detetermine bucket from ratios')
83
- }
82
+ _assert(bucket, 'Could not determine bucket from ratios', {
83
+ code: AbbaErrorCode.BucketDeterminationFailed,
84
+ })
84
85
 
85
86
  return bucket
86
87
  }
@@ -90,9 +91,11 @@ export function determineBucket(buckets: Bucket[]): Bucket {
90
91
  */
91
92
  export function validateTotalBucketRatio(buckets: (Unsaved<Bucket> | BucketInput)[]): void {
92
93
  const bucketSum = buckets.reduce((sum, current) => sum + current.ratio, 0)
93
- if (bucketSum !== 100) {
94
- throw new Error('Total bucket ratio must be 100 before you can activate an experiment')
95
- }
94
+ _assert(
95
+ bucketSum === 100,
96
+ 'Total bucket ratio must be 100 before you can activate an experiment',
97
+ { code: AbbaErrorCode.InvalidBucketRatio },
98
+ )
96
99
  }
97
100
 
98
101
  /**