@naturalcycles/abba 1.15.0 → 1.15.2

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
@@ -62,7 +62,7 @@ class Abba {
62
62
  if (experiment.status === types_1.AssignmentStatus.Active) {
63
63
  (0, util_1.validateTotalBucketRatio)(buckets);
64
64
  }
65
- const updatedExperiment = await this.experimentDao.save(experiment);
65
+ const updatedExperiment = await this.experimentDao.save(experiment, { saveMethod: 'update' });
66
66
  const updatedBuckets = await this.bucketDao.saveBatch(buckets.map(b => ({ ...b, experimentId: experiment.id })));
67
67
  await this.updateExclusions(updatedExperiment.id, updatedExperiment.exclusions);
68
68
  return {
@@ -90,7 +90,7 @@ class Abba {
90
90
  requiresUpdating.push(experiment);
91
91
  }
92
92
  });
93
- await this.experimentDao.saveBatch(requiresUpdating);
93
+ await this.experimentDao.saveBatch(requiresUpdating, { saveMethod: 'update' });
94
94
  }
95
95
  /**
96
96
  * Delete an experiment. Removes all user assignments and buckets.
@@ -11,7 +11,6 @@ const experimentDao = (db) => new ExperimentDao({
11
11
  table: 'Experiment',
12
12
  createId: false,
13
13
  idType: 'number',
14
- assignGeneratedIds: true,
15
14
  hooks: {
16
15
  beforeBMToDBM: bm => ({
17
16
  ...bm,
package/dist/types.d.ts CHANGED
@@ -49,9 +49,17 @@ export declare enum AssignmentStatus {
49
49
  }
50
50
  export interface SegmentationRule {
51
51
  key: string;
52
- operator: '==' | '!=' | 'semver' | 'regex' | 'boolean';
52
+ operator: SegmentationRuleOperator;
53
53
  value: string | boolean | number;
54
54
  }
55
+ export declare enum SegmentationRuleOperator {
56
+ Equals = "==",
57
+ NotEquals = "!=",
58
+ Semver = "semver",
59
+ Regex = "regex",
60
+ Boolean = "boolean"
61
+ }
62
+ export type SegmentationRuleFn = (segmentationProp: string | boolean | number, ruleValue: SegmentationRule['value']) => boolean;
55
63
  export interface AssignmentStatistics {
56
64
  sampled: number;
57
65
  buckets: {
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AssignmentStatus = void 0;
3
+ exports.SegmentationRuleOperator = exports.AssignmentStatus = void 0;
4
4
  var AssignmentStatus;
5
5
  (function (AssignmentStatus) {
6
6
  /**
@@ -16,3 +16,12 @@ var AssignmentStatus;
16
16
  */
17
17
  AssignmentStatus[AssignmentStatus["Inactive"] = 3] = "Inactive";
18
18
  })(AssignmentStatus = exports.AssignmentStatus || (exports.AssignmentStatus = {}));
19
+ var SegmentationRuleOperator;
20
+ (function (SegmentationRuleOperator) {
21
+ SegmentationRuleOperator["Equals"] = "==";
22
+ SegmentationRuleOperator["NotEquals"] = "!=";
23
+ SegmentationRuleOperator["Semver"] = "semver";
24
+ SegmentationRuleOperator["Regex"] = "regex";
25
+ /* eslint-disable id-blacklist */
26
+ SegmentationRuleOperator["Boolean"] = "boolean";
27
+ })(SegmentationRuleOperator = exports.SegmentationRuleOperator || (exports.SegmentationRuleOperator = {}));
package/dist/util.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Saved } from '@naturalcycles/js-lib';
2
- import { Bucket, ExclusionSet, Experiment, ExperimentWithBuckets, SegmentationData, SegmentationRule, UserAssignment } from './types';
2
+ import { Bucket, ExclusionSet, Experiment, ExperimentWithBuckets, SegmentationData, SegmentationRule, SegmentationRuleFn, SegmentationRuleOperator, UserAssignment } from './types';
3
3
  /**
4
4
  * Generate a new assignment for a given user.
5
5
  * Doesn't save it.
@@ -30,9 +30,9 @@ export declare const validateTotalBucketRatio: (buckets: Bucket[]) => void;
30
30
  */
31
31
  export declare const validateSegmentationRules: (rules: SegmentationRule[], segmentationData: SegmentationData) => boolean;
32
32
  /**
33
- * Validate a users segmentation data against a single rule
33
+ * Map of segmentation rule validators
34
34
  */
35
- export declare const validateSegmentationRule: (rule: SegmentationRule, data: SegmentationData) => boolean;
35
+ export declare const segmentationRuleMap: Record<SegmentationRuleOperator, SegmentationRuleFn>;
36
36
  /**
37
37
  * Returns true if an experiment is able to generate new assignments based on status and start/end dates
38
38
  */
package/dist/util.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUserExclusionSet = exports.canGenerateNewAssignments = exports.validateSegmentationRule = exports.validateSegmentationRules = exports.validateTotalBucketRatio = exports.determineBucket = exports.determineAssignment = exports.rollDie = exports.generateUserAssignmentData = void 0;
3
+ exports.getUserExclusionSet = exports.canGenerateNewAssignments = exports.segmentationRuleMap = exports.validateSegmentationRules = exports.validateTotalBucketRatio = exports.determineBucket = exports.determineAssignment = exports.rollDie = exports.generateUserAssignmentData = void 0;
4
4
  const js_lib_1 = require("@naturalcycles/js-lib");
5
5
  const semver_1 = require("semver");
6
6
  const types_1 = require("./types");
@@ -81,35 +81,23 @@ exports.validateTotalBucketRatio = validateTotalBucketRatio;
81
81
  */
82
82
  const validateSegmentationRules = (rules, segmentationData) => {
83
83
  for (const rule of rules) {
84
- if (!(0, exports.validateSegmentationRule)(rule, segmentationData))
84
+ const { key, value, operator } = rule;
85
+ if (!exports.segmentationRuleMap[operator](segmentationData[key], value))
85
86
  return false;
86
87
  }
87
88
  return true;
88
89
  };
89
90
  exports.validateSegmentationRules = validateSegmentationRules;
90
91
  /**
91
- * Validate a users segmentation data against a single rule
92
+ * Map of segmentation rule validators
92
93
  */
93
- const validateSegmentationRule = (rule, data) => {
94
- const { key, value, operator } = rule;
95
- if (operator === '==') {
96
- return data[key] === value;
97
- }
98
- else if (operator === '!=') {
99
- return data[key] !== value;
100
- }
101
- else if (operator === 'semver') {
102
- return (0, semver_1.satisfies)(data[key]?.toString() || '', value.toString());
103
- }
104
- else if (operator === 'regex') {
105
- return new RegExp(value.toString()).test(data[key]?.toString() || '');
106
- }
107
- else if (operator === 'boolean') {
108
- return Boolean(value) === data[key];
109
- }
110
- return false;
94
+ exports.segmentationRuleMap = {
95
+ [types_1.SegmentationRuleOperator.Equals]: (key, ruleValue) => key === ruleValue,
96
+ [types_1.SegmentationRuleOperator.NotEquals]: (key, ruleValue) => key !== ruleValue,
97
+ [types_1.SegmentationRuleOperator.Semver]: (key, ruleValue) => (0, semver_1.satisfies)(key?.toString() || '', ruleValue.toString()),
98
+ [types_1.SegmentationRuleOperator.Regex]: (key, ruleValue) => new RegExp(`${ruleValue}`).test(`${key || ''}`),
99
+ [types_1.SegmentationRuleOperator.Boolean]: (key, ruleValue) => new RegExp(`${ruleValue}`).test(`${key || ''}`),
111
100
  };
112
- exports.validateSegmentationRule = validateSegmentationRule;
113
101
  /**
114
102
  * Returns true if an experiment is able to generate new assignments based on status and start/end dates
115
103
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/abba",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build": "build",
@@ -14,7 +14,7 @@
14
14
  },
15
15
  "devDependencies": {
16
16
  "@naturalcycles/dev-lib": "^13.15.0",
17
- "@types/node": "^18.11.18",
17
+ "@types/node": "^20.2.4",
18
18
  "@types/semver": "^7.3.9",
19
19
  "jest": "^29.3.1"
20
20
  },
package/src/abba.ts CHANGED
@@ -92,7 +92,7 @@ export class Abba {
92
92
  validateTotalBucketRatio(buckets)
93
93
  }
94
94
 
95
- const updatedExperiment = await this.experimentDao.save(experiment)
95
+ const updatedExperiment = await this.experimentDao.save(experiment, { saveMethod: 'update' })
96
96
  const updatedBuckets = await this.bucketDao.saveBatch(
97
97
  buckets.map(b => ({ ...b, experimentId: experiment.id })),
98
98
  )
@@ -132,7 +132,7 @@ export class Abba {
132
132
  }
133
133
  })
134
134
 
135
- await this.experimentDao.saveBatch(requiresUpdating)
135
+ await this.experimentDao.saveBatch(requiresUpdating, { saveMethod: 'update' })
136
136
  }
137
137
 
138
138
  /**
@@ -13,9 +13,8 @@ export const experimentDao = (db: CommonDB): ExperimentDao =>
13
13
  new ExperimentDao({
14
14
  db,
15
15
  table: 'Experiment',
16
- createId: false, // mysql auto_increment is used instead
16
+ createId: false, // Always provided on create
17
17
  idType: 'number',
18
- assignGeneratedIds: true,
19
18
  hooks: {
20
19
  beforeBMToDBM: bm => ({
21
20
  ...bm,
package/src/types.ts CHANGED
@@ -60,10 +60,24 @@ export enum AssignmentStatus {
60
60
 
61
61
  export interface SegmentationRule {
62
62
  key: string
63
- operator: '==' | '!=' | 'semver' | 'regex' | 'boolean'
63
+ operator: SegmentationRuleOperator
64
64
  value: string | boolean | number
65
65
  }
66
66
 
67
+ export enum SegmentationRuleOperator {
68
+ Equals = '==',
69
+ NotEquals = '!=',
70
+ Semver = 'semver',
71
+ Regex = 'regex',
72
+ /* eslint-disable id-blacklist */
73
+ Boolean = 'boolean',
74
+ }
75
+
76
+ export type SegmentationRuleFn = (
77
+ segmentationProp: string | boolean | number,
78
+ ruleValue: SegmentationRule['value'],
79
+ ) => boolean
80
+
67
81
  export interface AssignmentStatistics {
68
82
  sampled: number
69
83
  buckets: {
package/src/util.ts CHANGED
@@ -8,6 +8,8 @@ import {
8
8
  ExperimentWithBuckets,
9
9
  SegmentationData,
10
10
  SegmentationRule,
11
+ SegmentationRuleFn,
12
+ SegmentationRuleOperator,
11
13
  UserAssignment,
12
14
  } from './types'
13
15
 
@@ -99,31 +101,24 @@ export const validateSegmentationRules = (
99
101
  segmentationData: SegmentationData,
100
102
  ): boolean => {
101
103
  for (const rule of rules) {
102
- if (!validateSegmentationRule(rule, segmentationData)) return false
104
+ const { key, value, operator } = rule
105
+ if (!segmentationRuleMap[operator](segmentationData[key], value)) return false
103
106
  }
104
107
  return true
105
108
  }
106
109
 
107
110
  /**
108
- * Validate a users segmentation data against a single rule
111
+ * Map of segmentation rule validators
109
112
  */
110
- export const validateSegmentationRule = (
111
- rule: SegmentationRule,
112
- data: SegmentationData,
113
- ): boolean => {
114
- const { key, value, operator } = rule
115
- if (operator === '==') {
116
- return data[key] === value
117
- } else if (operator === '!=') {
118
- return data[key] !== value
119
- } else if (operator === 'semver') {
120
- return satisfies(data[key]?.toString() || '', value.toString())
121
- } else if (operator === 'regex') {
122
- return new RegExp(value.toString()).test(data[key]?.toString() || '')
123
- } else if (operator === 'boolean') {
124
- return Boolean(value) === data[key]
125
- }
126
- return false
113
+ export const segmentationRuleMap: Record<SegmentationRuleOperator, SegmentationRuleFn> = {
114
+ [SegmentationRuleOperator.Equals]: (key, ruleValue) => key === ruleValue,
115
+ [SegmentationRuleOperator.NotEquals]: (key, ruleValue) => key !== ruleValue,
116
+ [SegmentationRuleOperator.Semver]: (key, ruleValue) =>
117
+ satisfies(key?.toString() || '', ruleValue.toString()),
118
+ [SegmentationRuleOperator.Regex]: (key, ruleValue) =>
119
+ new RegExp(`${ruleValue}`).test(`${key || ''}`),
120
+ [SegmentationRuleOperator.Boolean]: (key, ruleValue) =>
121
+ new RegExp(`${ruleValue}`).test(`${key || ''}`),
127
122
  }
128
123
 
129
124
  /**