@naturalcycles/abba 1.23.1 → 1.25.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.d.ts CHANGED
@@ -48,7 +48,7 @@ export declare class Abba {
48
48
  * @param existingOnly Do not generate any new assignments for this experiment
49
49
  * @param segmentationData Required if existingOnly is false
50
50
  */
51
- getUserAssignment(experimentKey: string, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<GeneratedUserAssignment | null>;
51
+ getUserAssignment(experimentKey: string, userId: string, existingOnly: boolean, segmentationData?: SegmentationData): Promise<GeneratedUserAssignment>;
52
52
  /**
53
53
  * Get all existing user assignments.
54
54
  * Hot method.
package/dist/abba.js CHANGED
@@ -129,38 +129,56 @@ class Abba {
129
129
  (0, js_lib_1._assert)(experiment, `Experiment does not exist: ${experimentKey}`);
130
130
  // Inactive experiments should never return an assignment
131
131
  if (experiment.status === types_1.AssignmentStatus.Inactive)
132
- return null;
132
+ return {
133
+ experiment,
134
+ assignment: null,
135
+ };
133
136
  const buckets = await this.bucketDao.getBy('experimentId', experiment.id);
134
137
  const existingAssignments = await this.userAssignmentDao.getBy('userId', userId);
135
138
  const existing = existingAssignments.find(a => a.experimentId === experiment.id);
136
139
  if (existing) {
137
140
  const bucket = buckets.find(b => b.id === existing.bucketId);
138
141
  return {
139
- ...existing,
140
- experimentKey: experiment.key,
141
- bucketKey: bucket?.key || null,
142
- bucketData: bucket?.data || null,
142
+ experiment,
143
+ assignment: {
144
+ ...existing,
145
+ experimentKey: experiment.key,
146
+ bucketKey: bucket?.key || null,
147
+ bucketData: bucket?.data || null,
148
+ },
143
149
  };
144
150
  }
145
151
  // No existing assignment, but we don't want to generate a new one
146
152
  if (existingOnly || experiment.status === types_1.AssignmentStatus.Paused)
147
- return null;
153
+ return {
154
+ experiment,
155
+ assignment: null,
156
+ };
148
157
  const experiments = await this.getAllExperiments();
149
158
  const exclusionSet = (0, util_1.getUserExclusionSet)(experiments, existingAssignments);
150
159
  if (!(0, util_1.canGenerateNewAssignments)(experiment, exclusionSet))
151
- return null;
160
+ return {
161
+ experiment,
162
+ assignment: null,
163
+ };
152
164
  (0, js_lib_1._assert)(segmentationData, 'Segmentation data required when creating a new assignment');
153
165
  const experimentWithBuckets = { ...experiment, buckets };
154
166
  const assignment = (0, util_1.generateUserAssignmentData)(experimentWithBuckets, userId, segmentationData);
155
167
  if (!assignment)
156
- return null;
168
+ return {
169
+ experiment,
170
+ assignment: null,
171
+ };
157
172
  const newAssignment = await this.userAssignmentDao.save(assignment);
158
173
  const bucket = buckets.find(b => b.id === newAssignment.bucketId);
159
174
  return {
160
- ...newAssignment,
161
- experimentKey: experiment.key,
162
- bucketKey: bucket?.key || null,
163
- bucketData: bucket?.data || null,
175
+ experiment,
176
+ assignment: {
177
+ ...newAssignment,
178
+ experimentKey: experiment.key,
179
+ bucketKey: bucket?.key || null,
180
+ bucketData: bucket?.data || null,
181
+ },
164
182
  };
165
183
  }
166
184
  /**
@@ -175,10 +193,13 @@ class Abba {
175
193
  const experiment = await this.experimentDao.requireById(assignment.experimentId);
176
194
  const bucket = await this.bucketDao.getById(assignment.bucketId);
177
195
  return {
178
- ...assignment,
179
- experimentKey: experiment.key,
180
- bucketKey: bucket?.key || null,
181
- bucketData: bucket?.data || null,
196
+ experiment,
197
+ assignment: {
198
+ ...assignment,
199
+ experimentKey: experiment.key,
200
+ bucketKey: bucket?.key || null,
201
+ bucketData: bucket?.data || null,
202
+ },
182
203
  };
183
204
  });
184
205
  }
@@ -202,10 +223,13 @@ class Abba {
202
223
  if (existing) {
203
224
  const bucket = experiment.buckets.find(b => b.id === existing.bucketId);
204
225
  assignments.push({
205
- ...existing,
206
- experimentKey: experiment.key,
207
- bucketKey: bucket?.key || null,
208
- bucketData: bucket?.data || null,
226
+ experiment,
227
+ assignment: {
228
+ ...existing,
229
+ experimentKey: experiment.key,
230
+ bucketKey: bucket?.key || null,
231
+ bucketData: bucket?.data || null,
232
+ },
209
233
  });
210
234
  }
211
235
  else if (!existingOnly && (0, util_1.canGenerateNewAssignments)(experiment, exclusionSet)) {
@@ -215,10 +239,13 @@ class Abba {
215
239
  newAssignments.push(created);
216
240
  const bucket = experiment.buckets.find(b => b.id === created.bucketId);
217
241
  assignments.push({
218
- ...created,
219
- experimentKey: experiment.key,
220
- bucketKey: bucket?.key || null,
221
- bucketData: bucket?.data || null,
242
+ experiment,
243
+ assignment: {
244
+ ...created,
245
+ experimentKey: experiment.key,
246
+ bucketKey: bucket?.key || null,
247
+ bucketData: bucket?.data || null,
248
+ },
222
249
  });
223
250
  // Prevent future exclusion clashes
224
251
  experiment.exclusions.forEach(experimentId => exclusionSet.add(experimentId));
@@ -4,6 +4,7 @@ import { BaseExperiment, Experiment } from '../types';
4
4
  type ExperimentDBM = Saved<BaseExperiment> & {
5
5
  rules: string | null;
6
6
  exclusions: string | null;
7
+ data: string | null;
7
8
  };
8
9
  export declare class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {
9
10
  }
@@ -18,6 +18,7 @@ const experimentDao = (db) => new ExperimentDao({
18
18
  exclusions: bm.exclusions.length
19
19
  ? JSON.stringify(bm.exclusions.map(exclusion => exclusion.toString()))
20
20
  : null,
21
+ data: bm.data ? JSON.stringify(bm.data) : null,
21
22
  }),
22
23
  beforeDBMToBM: dbm => ({
23
24
  ...dbm,
@@ -29,6 +30,7 @@ const experimentDao = (db) => new ExperimentDao({
29
30
  exclusions: (dbm.exclusions &&
30
31
  JSON.parse(dbm.exclusions).map((exclusion) => exclusion.toString())) ||
31
32
  [],
33
+ data: dbm.data ? JSON.parse(dbm.data) : null,
32
34
  }),
33
35
  },
34
36
  });
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `Experiment` (
24
24
  `updated` INT NOT NULL,
25
25
  `rules` JSON NULL,
26
26
  `exclusions` JSON NULL,
27
+ `data` JSON NULL,
27
28
 
28
29
  PRIMARY KEY (`id`),
29
30
  UNIQUE INDEX `key_unique` (`key`)
package/dist/types.d.ts CHANGED
@@ -33,6 +33,7 @@ export type BaseExperiment = BaseDBEntity & {
33
33
  export type Experiment = BaseExperiment & {
34
34
  rules: SegmentationRule[];
35
35
  exclusions: string[];
36
+ data: AnyObject | null;
36
37
  };
37
38
  export type ExperimentWithBuckets = Saved<Experiment> & {
38
39
  buckets: Saved<Bucket>[];
@@ -50,11 +51,14 @@ export type UserAssignment = BaseDBEntity & {
50
51
  experimentId: string;
51
52
  bucketId: string | null;
52
53
  };
53
- export type GeneratedUserAssignment = Saved<UserAssignment> & {
54
- experimentKey: string;
55
- bucketKey: string | null;
56
- bucketData: AnyObject | null;
57
- };
54
+ export interface GeneratedUserAssignment {
55
+ assignment: (Saved<UserAssignment> & {
56
+ experimentKey: string;
57
+ bucketKey: string | null;
58
+ bucketData: AnyObject | null;
59
+ }) | null;
60
+ experiment: Saved<Experiment>;
61
+ }
58
62
  export type SegmentationData = AnyObject;
59
63
  export declare enum AssignmentStatus {
60
64
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/abba",
3
- "version": "1.23.1",
3
+ "version": "1.25.0",
4
4
  "scripts": {
5
5
  "prepare": "husky"
6
6
  },
package/src/abba.ts CHANGED
@@ -178,12 +178,16 @@ export class Abba {
178
178
  userId: string,
179
179
  existingOnly: boolean,
180
180
  segmentationData?: SegmentationData,
181
- ): Promise<GeneratedUserAssignment | null> {
181
+ ): Promise<GeneratedUserAssignment> {
182
182
  const experiment = await this.experimentDao.getOneBy('key', experimentKey)
183
183
  _assert(experiment, `Experiment does not exist: ${experimentKey}`)
184
184
 
185
185
  // Inactive experiments should never return an assignment
186
- if (experiment.status === AssignmentStatus.Inactive) return null
186
+ if (experiment.status === AssignmentStatus.Inactive)
187
+ return {
188
+ experiment,
189
+ assignment: null,
190
+ }
187
191
 
188
192
  const buckets = await this.bucketDao.getBy('experimentId', experiment.id)
189
193
  const existingAssignments = await this.userAssignmentDao.getBy('userId', userId)
@@ -191,35 +195,53 @@ export class Abba {
191
195
  if (existing) {
192
196
  const bucket = buckets.find(b => b.id === existing.bucketId)
193
197
  return {
194
- ...existing,
195
- experimentKey: experiment.key,
196
- bucketKey: bucket?.key || null,
197
- bucketData: bucket?.data || null,
198
+ experiment,
199
+ assignment: {
200
+ ...existing,
201
+ experimentKey: experiment.key,
202
+ bucketKey: bucket?.key || null,
203
+ bucketData: bucket?.data || null,
204
+ },
198
205
  }
199
206
  }
200
207
 
201
208
  // No existing assignment, but we don't want to generate a new one
202
- if (existingOnly || experiment.status === AssignmentStatus.Paused) return null
209
+ if (existingOnly || experiment.status === AssignmentStatus.Paused)
210
+ return {
211
+ experiment,
212
+ assignment: null,
213
+ }
203
214
 
204
215
  const experiments = await this.getAllExperiments()
205
216
  const exclusionSet = getUserExclusionSet(experiments, existingAssignments)
206
- if (!canGenerateNewAssignments(experiment, exclusionSet)) return null
217
+ if (!canGenerateNewAssignments(experiment, exclusionSet))
218
+ return {
219
+ experiment,
220
+ assignment: null,
221
+ }
207
222
 
208
223
  _assert(segmentationData, 'Segmentation data required when creating a new assignment')
209
224
 
210
225
  const experimentWithBuckets = { ...experiment, buckets }
211
226
  const assignment = generateUserAssignmentData(experimentWithBuckets, userId, segmentationData)
212
- if (!assignment) return null
227
+ if (!assignment)
228
+ return {
229
+ experiment,
230
+ assignment: null,
231
+ }
213
232
 
214
233
  const newAssignment = await this.userAssignmentDao.save(assignment)
215
234
 
216
235
  const bucket = buckets.find(b => b.id === newAssignment.bucketId)
217
236
 
218
237
  return {
219
- ...newAssignment,
220
- experimentKey: experiment.key,
221
- bucketKey: bucket?.key || null,
222
- bucketData: bucket?.data || null,
238
+ experiment,
239
+ assignment: {
240
+ ...newAssignment,
241
+ experimentKey: experiment.key,
242
+ bucketKey: bucket?.key || null,
243
+ bucketData: bucket?.data || null,
244
+ },
223
245
  }
224
246
  }
225
247
 
@@ -235,10 +257,13 @@ export class Abba {
235
257
  const experiment = await this.experimentDao.requireById(assignment.experimentId)
236
258
  const bucket = await this.bucketDao.getById(assignment.bucketId)
237
259
  return {
238
- ...assignment,
239
- experimentKey: experiment.key,
240
- bucketKey: bucket?.key || null,
241
- bucketData: bucket?.data || null,
260
+ experiment,
261
+ assignment: {
262
+ ...assignment,
263
+ experimentKey: experiment.key,
264
+ bucketKey: bucket?.key || null,
265
+ bucketData: bucket?.data || null,
266
+ },
242
267
  }
243
268
  })
244
269
  }
@@ -273,10 +298,13 @@ export class Abba {
273
298
  if (existing) {
274
299
  const bucket = experiment.buckets.find(b => b.id === existing.bucketId)
275
300
  assignments.push({
276
- ...existing,
277
- experimentKey: experiment.key,
278
- bucketKey: bucket?.key || null,
279
- bucketData: bucket?.data || null,
301
+ experiment,
302
+ assignment: {
303
+ ...existing,
304
+ experimentKey: experiment.key,
305
+ bucketKey: bucket?.key || null,
306
+ bucketData: bucket?.data || null,
307
+ },
280
308
  })
281
309
  } else if (!existingOnly && canGenerateNewAssignments(experiment, exclusionSet)) {
282
310
  const assignment = generateUserAssignmentData(experiment, userId, segmentationData)
@@ -285,10 +313,13 @@ export class Abba {
285
313
  newAssignments.push(created)
286
314
  const bucket = experiment.buckets.find(b => b.id === created.bucketId)
287
315
  assignments.push({
288
- ...created,
289
- experimentKey: experiment.key,
290
- bucketKey: bucket?.key || null,
291
- bucketData: bucket?.data || null,
316
+ experiment,
317
+ assignment: {
318
+ ...created,
319
+ experimentKey: experiment.key,
320
+ bucketKey: bucket?.key || null,
321
+ bucketData: bucket?.data || null,
322
+ },
292
323
  })
293
324
  // Prevent future exclusion clashes
294
325
  experiment.exclusions.forEach(experimentId => exclusionSet.add(experimentId))
@@ -5,6 +5,7 @@ import { BaseExperiment, Experiment } from '../types'
5
5
  type ExperimentDBM = Saved<BaseExperiment> & {
6
6
  rules: string | null
7
7
  exclusions: string | null
8
+ data: string | null
8
9
  }
9
10
 
10
11
  export class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {}
@@ -22,6 +23,7 @@ export const experimentDao = (db: CommonDB): ExperimentDao =>
22
23
  exclusions: bm.exclusions.length
23
24
  ? JSON.stringify(bm.exclusions.map(exclusion => exclusion.toString()))
24
25
  : null,
26
+ data: bm.data ? JSON.stringify(bm.data) : null,
25
27
  }),
26
28
  beforeDBMToBM: dbm => ({
27
29
  ...dbm,
@@ -34,6 +36,7 @@ export const experimentDao = (db: CommonDB): ExperimentDao =>
34
36
  (dbm.exclusions &&
35
37
  JSON.parse(dbm.exclusions).map((exclusion: string | number) => exclusion.toString())) ||
36
38
  [],
39
+ data: dbm.data ? JSON.parse(dbm.data) : null,
37
40
  }),
38
41
  },
39
42
  })
@@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS `Experiment` (
24
24
  `updated` INT NOT NULL,
25
25
  `rules` JSON NULL,
26
26
  `exclusions` JSON NULL,
27
+ `data` JSON NULL,
27
28
 
28
29
  PRIMARY KEY (`id`),
29
30
  UNIQUE INDEX `key_unique` (`key`)
package/src/types.ts CHANGED
@@ -36,6 +36,7 @@ export type BaseExperiment = BaseDBEntity & {
36
36
  export type Experiment = BaseExperiment & {
37
37
  rules: SegmentationRule[]
38
38
  exclusions: string[]
39
+ data: AnyObject | null
39
40
  }
40
41
 
41
42
  export type ExperimentWithBuckets = Saved<Experiment> & {
@@ -58,10 +59,15 @@ export type UserAssignment = BaseDBEntity & {
58
59
  bucketId: string | null
59
60
  }
60
61
 
61
- export type GeneratedUserAssignment = Saved<UserAssignment> & {
62
- experimentKey: string
63
- bucketKey: string | null
64
- bucketData: AnyObject | null
62
+ export interface GeneratedUserAssignment {
63
+ assignment:
64
+ | (Saved<UserAssignment> & {
65
+ experimentKey: string
66
+ bucketKey: string | null
67
+ bucketData: AnyObject | null
68
+ })
69
+ | null
70
+ experiment: Saved<Experiment>
65
71
  }
66
72
 
67
73
  export type SegmentationData = AnyObject