@naturalcycles/abba 2.0.2 → 2.1.1
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 +8 -6
- package/dist/abba.js +23 -23
- package/dist/dao/bucket.dao.d.ts +1 -0
- package/dist/dao/bucket.dao.js +3 -0
- package/dist/dao/experiment.dao.d.ts +7 -3
- package/dist/dao/experiment.dao.js +6 -0
- package/dist/dao/userAssignment.dao.d.ts +1 -0
- package/dist/dao/userAssignment.dao.js +3 -0
- package/dist/migrations/init.sql +1 -0
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/src/abba.ts +35 -28
- package/src/dao/bucket.dao.ts +4 -0
- package/src/dao/experiment.dao.ts +18 -6
- package/src/dao/userAssignment.dao.ts +4 -0
- package/src/migrations/init.sql +1 -0
- package/src/types.ts +4 -0
package/dist/abba.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Unsaved } from '@naturalcycles/js-lib';
|
|
2
|
+
import { type GetAllExperimentsOpts } from './dao/experiment.dao.js';
|
|
2
3
|
import type { AbbaConfig, Bucket, DecoratedUserAssignment, Experiment, ExperimentAssignmentStatistics, ExperimentWithBuckets, SegmentationData, UserExperiment } from './types.js';
|
|
3
4
|
export declare class Abba {
|
|
4
5
|
cfg: AbbaConfig;
|
|
@@ -10,16 +11,16 @@ export declare class Abba {
|
|
|
10
11
|
* Returns all experiments.
|
|
11
12
|
* Cached (see CACHE_TTL)
|
|
12
13
|
*/
|
|
13
|
-
getAllExperimentsWithBuckets(): Promise<ExperimentWithBuckets[]>;
|
|
14
|
-
getAllExperimentsWithUserAssignments(userId: string): Promise<UserExperiment[]>;
|
|
14
|
+
getAllExperimentsWithBuckets(opts?: GetAllExperimentsOpts): Promise<ExperimentWithBuckets[]>;
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Returns all experiments.
|
|
17
17
|
*/
|
|
18
|
-
|
|
18
|
+
getAllExperimentsWithBucketsNoCache(opts?: GetAllExperimentsOpts): Promise<ExperimentWithBuckets[]>;
|
|
19
|
+
getUserExperiments(userId: string): Promise<UserExperiment[]>;
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
+
* Updates all user assignments with a given userId with the provided userId.
|
|
21
22
|
*/
|
|
22
|
-
|
|
23
|
+
updateUserId(oldId: string, newId: string): Promise<void>;
|
|
23
24
|
/**
|
|
24
25
|
* Creates a new experiment.
|
|
25
26
|
* Cold method.
|
|
@@ -34,6 +35,7 @@ export declare class Abba {
|
|
|
34
35
|
* Ensures that mutual exclusions are maintained
|
|
35
36
|
*/
|
|
36
37
|
private updateExclusions;
|
|
38
|
+
softDeleteExperiment(experimentId: string): Promise<void>;
|
|
37
39
|
/**
|
|
38
40
|
* Delete an experiment. Removes all user assignments and buckets.
|
|
39
41
|
* Requires the experiment to have been inactive for at least 15 minutes in order to
|
package/dist/abba.js
CHANGED
|
@@ -21,11 +21,22 @@ export class Abba {
|
|
|
21
21
|
* Returns all experiments.
|
|
22
22
|
* Cached (see CACHE_TTL)
|
|
23
23
|
*/
|
|
24
|
-
async getAllExperimentsWithBuckets() {
|
|
25
|
-
return await this.getAllExperimentsWithBucketsNoCache();
|
|
24
|
+
async getAllExperimentsWithBuckets(opts) {
|
|
25
|
+
return await this.getAllExperimentsWithBucketsNoCache(opts);
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Returns all experiments.
|
|
29
|
+
*/
|
|
30
|
+
async getAllExperimentsWithBucketsNoCache(opts) {
|
|
31
|
+
const experiments = await this.experimentDao.getAllExperiments(opts);
|
|
32
|
+
const buckets = await this.bucketDao.getAll();
|
|
33
|
+
return experiments.map(experiment => ({
|
|
34
|
+
...experiment,
|
|
35
|
+
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
async getUserExperiments(userId) {
|
|
39
|
+
const experiments = await this.getAllExperimentsWithBuckets({ includeDeleted: false });
|
|
29
40
|
const experimentIds = experiments.map(e => e.id);
|
|
30
41
|
const userAssignments = await this.userAssignmentDao.getUserAssigmentsByExperimentIds(userId, experimentIds);
|
|
31
42
|
return experiments.map(experiment => {
|
|
@@ -53,17 +64,6 @@ export class Abba {
|
|
|
53
64
|
const query = this.userAssignmentDao.query().filterEq('userId', oldId);
|
|
54
65
|
await this.userAssignmentDao.patchByQuery(query, { userId: newId });
|
|
55
66
|
}
|
|
56
|
-
/**
|
|
57
|
-
* Returns all experiments.
|
|
58
|
-
*/
|
|
59
|
-
async getAllExperimentsWithBucketsNoCache() {
|
|
60
|
-
const experiments = await this.experimentDao.getAll();
|
|
61
|
-
const buckets = await this.bucketDao.getAll();
|
|
62
|
-
return experiments.map(experiment => ({
|
|
63
|
-
...experiment,
|
|
64
|
-
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
65
|
-
}));
|
|
66
|
-
}
|
|
67
67
|
/**
|
|
68
68
|
* Creates a new experiment.
|
|
69
69
|
* Cold method.
|
|
@@ -120,6 +120,10 @@ export class Abba {
|
|
|
120
120
|
});
|
|
121
121
|
await this.experimentDao.saveBatch(requiresUpdating, { saveMethod: 'update' });
|
|
122
122
|
}
|
|
123
|
+
async softDeleteExperiment(experimentId) {
|
|
124
|
+
await this.experimentDao.patchById(experimentId, { deleted: true, exclusions: [] }, { saveMethod: 'update' });
|
|
125
|
+
await this.updateExclusions(experimentId, []);
|
|
126
|
+
}
|
|
123
127
|
/**
|
|
124
128
|
* Delete an experiment. Removes all user assignments and buckets.
|
|
125
129
|
* Requires the experiment to have been inactive for at least 15 minutes in order to
|
|
@@ -131,12 +135,8 @@ export class Abba {
|
|
|
131
135
|
const hasBeenInactiveFor15Mins = experiment.status === AssignmentStatus.Inactive &&
|
|
132
136
|
localTime(experiment.updated).isOlderThan(15, 'minute');
|
|
133
137
|
_assert(hasBeenInactiveFor15Mins, 'Experiment must be inactive for at least 15 minutes before deletion');
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
.filterEq('experimentId', experimentId);
|
|
137
|
-
await this.userAssignmentDao.deleteByQuery(userAssignmentDeleteQuery, { chunkSize: 50000 });
|
|
138
|
-
const bucketDeleteQuery = this.bucketDao.query().filterEq('experimentId', experimentId);
|
|
139
|
-
await this.bucketDao.deleteByQuery(bucketDeleteQuery);
|
|
138
|
+
await this.userAssignmentDao.deleteByExperimentId(experimentId);
|
|
139
|
+
await this.bucketDao.deleteByExperimentId(experimentId);
|
|
140
140
|
await this.experimentDao.deleteById(experimentId);
|
|
141
141
|
await this.updateExclusions(experimentId, []);
|
|
142
142
|
}
|
|
@@ -172,7 +172,7 @@ export class Abba {
|
|
|
172
172
|
if (existingOnly || experiment.status === AssignmentStatus.Paused) {
|
|
173
173
|
return null;
|
|
174
174
|
}
|
|
175
|
-
const experiments = await this.
|
|
175
|
+
const experiments = await this.getUserExperiments(userId);
|
|
176
176
|
const exclusionSet = getUserExclusionSet(experiments);
|
|
177
177
|
if (!canGenerateNewAssignments(experiment, exclusionSet)) {
|
|
178
178
|
return null;
|
|
@@ -219,7 +219,7 @@ export class Abba {
|
|
|
219
219
|
* Hot method.
|
|
220
220
|
*/
|
|
221
221
|
async generateUserAssignments(userId, segmentationData, existingOnly = false) {
|
|
222
|
-
const experiments = await this.
|
|
222
|
+
const experiments = await this.getUserExperiments(userId);
|
|
223
223
|
const exclusionSet = getUserExclusionSet(experiments);
|
|
224
224
|
// Shuffling means that randomisation occurs in the mutual exclusion
|
|
225
225
|
// as experiments are looped through sequentially, this removes the risk of the same experiment always being assigned first in the list of mutually exclusive experiments
|
package/dist/dao/bucket.dao.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ type BucketDBM = BaseBucket & {
|
|
|
6
6
|
};
|
|
7
7
|
export declare class BucketDao extends CommonDao<Bucket, BucketDBM> {
|
|
8
8
|
getByExperimentId(experimentId: string): Promise<Bucket[]>;
|
|
9
|
+
deleteByExperimentId(experimentId: string): Promise<void>;
|
|
9
10
|
}
|
|
10
11
|
export declare function bucketDao(db: CommonDB): BucketDao;
|
|
11
12
|
export {};
|
package/dist/dao/bucket.dao.js
CHANGED
|
@@ -3,6 +3,9 @@ export class BucketDao extends CommonDao {
|
|
|
3
3
|
async getByExperimentId(experimentId) {
|
|
4
4
|
return await this.query().filterEq('experimentId', experimentId).runQuery();
|
|
5
5
|
}
|
|
6
|
+
async deleteByExperimentId(experimentId) {
|
|
7
|
+
await this.query().filterEq('experimentId', experimentId).deleteByQuery();
|
|
8
|
+
}
|
|
6
9
|
}
|
|
7
10
|
export function bucketDao(db) {
|
|
8
11
|
return new BucketDao({
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { CommonDB } from '@naturalcycles/db-lib';
|
|
2
2
|
import { CommonDao } from '@naturalcycles/db-lib';
|
|
3
3
|
import type { BaseExperiment, Experiment } from '../types.js';
|
|
4
|
+
export declare class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {
|
|
5
|
+
getAllExperiments(opt?: GetAllExperimentsOpts): Promise<Experiment[]>;
|
|
6
|
+
getByKey(key: string): Promise<Experiment | null>;
|
|
7
|
+
}
|
|
8
|
+
export declare function experimentDao(db: CommonDB): ExperimentDao;
|
|
4
9
|
type ExperimentDBM = BaseExperiment & {
|
|
5
10
|
rules: string | null;
|
|
6
11
|
exclusions: string | null;
|
|
7
12
|
data: string | null;
|
|
8
13
|
};
|
|
9
|
-
export
|
|
10
|
-
|
|
14
|
+
export interface GetAllExperimentsOpts {
|
|
15
|
+
includeDeleted?: boolean;
|
|
11
16
|
}
|
|
12
|
-
export declare function experimentDao(db: CommonDB): ExperimentDao;
|
|
13
17
|
export {};
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { CommonDao } from '@naturalcycles/db-lib';
|
|
2
2
|
import { localDate } from '@naturalcycles/js-lib';
|
|
3
3
|
export class ExperimentDao extends CommonDao {
|
|
4
|
+
async getAllExperiments(opt) {
|
|
5
|
+
if (!opt?.includeDeleted) {
|
|
6
|
+
return await this.getAll();
|
|
7
|
+
}
|
|
8
|
+
return await this.query().filterEq('deleted', false).runQuery();
|
|
9
|
+
}
|
|
4
10
|
async getByKey(key) {
|
|
5
11
|
return await this.getOneBy('key', key);
|
|
6
12
|
}
|
|
@@ -4,6 +4,7 @@ import type { UserAssignment } from '../types.js';
|
|
|
4
4
|
export declare class UserAssignmentDao extends CommonDao<UserAssignment> {
|
|
5
5
|
getUserAssignmentByExperimentId(userId: string, experimentId: string): Promise<UserAssignment | null>;
|
|
6
6
|
getUserAssigmentsByExperimentIds(userId: string, experimentIds: string[]): Promise<UserAssignment[]>;
|
|
7
|
+
deleteByExperimentId(experimentId: string): Promise<void>;
|
|
7
8
|
getCountByExperimentId(experimentId: string): Promise<number>;
|
|
8
9
|
getCountByBucketId(bucketId: string): Promise<number>;
|
|
9
10
|
}
|
|
@@ -9,6 +9,9 @@ export class UserAssignmentDao extends CommonDao {
|
|
|
9
9
|
const query = this.query().filterEq('userId', userId).filterIn('experimentId', experimentIds);
|
|
10
10
|
return await this.runQuery(query);
|
|
11
11
|
}
|
|
12
|
+
async deleteByExperimentId(experimentId) {
|
|
13
|
+
await this.query().filterEq('experimentId', experimentId).deleteByQuery();
|
|
14
|
+
}
|
|
12
15
|
async getCountByExperimentId(experimentId) {
|
|
13
16
|
return await this.query().filterEq('experimentId', experimentId).runQueryCount();
|
|
14
17
|
}
|
package/dist/migrations/init.sql
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -29,6 +29,10 @@ export type BaseExperiment = BaseDBEntity & {
|
|
|
29
29
|
* Date range end for the experiment assignments
|
|
30
30
|
*/
|
|
31
31
|
endDateExcl: IsoDate;
|
|
32
|
+
/**
|
|
33
|
+
* Whether the experiment is flagged as deleted. This acts as a soft delete only.
|
|
34
|
+
*/
|
|
35
|
+
deleted: boolean;
|
|
32
36
|
};
|
|
33
37
|
export type Experiment = BaseExperiment & {
|
|
34
38
|
rules: SegmentationRule[];
|
package/package.json
CHANGED
package/src/abba.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Unsaved } from '@naturalcycles/js-lib'
|
|
|
2
2
|
import { _assert, _Memo, _shuffle, localTime, pMap } from '@naturalcycles/js-lib'
|
|
3
3
|
import { LRUMemoCache } from '@naturalcycles/nodejs-lib'
|
|
4
4
|
import { bucketDao } from './dao/bucket.dao.js'
|
|
5
|
-
import { experimentDao } from './dao/experiment.dao.js'
|
|
5
|
+
import { experimentDao, type GetAllExperimentsOpts } from './dao/experiment.dao.js'
|
|
6
6
|
import { userAssignmentDao } from './dao/userAssignment.dao.js'
|
|
7
7
|
import type {
|
|
8
8
|
AbbaConfig,
|
|
@@ -41,12 +41,29 @@ export class Abba {
|
|
|
41
41
|
* Cached (see CACHE_TTL)
|
|
42
42
|
*/
|
|
43
43
|
@_Memo({ cacheFactory: () => new LRUMemoCache({ ttl: CACHE_TTL, max: 1 }) })
|
|
44
|
-
async getAllExperimentsWithBuckets(
|
|
45
|
-
|
|
44
|
+
async getAllExperimentsWithBuckets(
|
|
45
|
+
opts?: GetAllExperimentsOpts,
|
|
46
|
+
): Promise<ExperimentWithBuckets[]> {
|
|
47
|
+
return await this.getAllExperimentsWithBucketsNoCache(opts)
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Returns all experiments.
|
|
52
|
+
*/
|
|
53
|
+
async getAllExperimentsWithBucketsNoCache(
|
|
54
|
+
opts?: GetAllExperimentsOpts,
|
|
55
|
+
): Promise<ExperimentWithBuckets[]> {
|
|
56
|
+
const experiments = await this.experimentDao.getAllExperiments(opts)
|
|
57
|
+
const buckets = await this.bucketDao.getAll()
|
|
58
|
+
|
|
59
|
+
return experiments.map(experiment => ({
|
|
60
|
+
...experiment,
|
|
61
|
+
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
62
|
+
}))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getUserExperiments(userId: string): Promise<UserExperiment[]> {
|
|
66
|
+
const experiments = await this.getAllExperimentsWithBuckets({ includeDeleted: false })
|
|
50
67
|
|
|
51
68
|
const experimentIds = experiments.map(e => e.id)
|
|
52
69
|
const userAssignments = await this.userAssignmentDao.getUserAssigmentsByExperimentIds(
|
|
@@ -84,19 +101,6 @@ export class Abba {
|
|
|
84
101
|
await this.userAssignmentDao.patchByQuery(query, { userId: newId })
|
|
85
102
|
}
|
|
86
103
|
|
|
87
|
-
/**
|
|
88
|
-
* Returns all experiments.
|
|
89
|
-
*/
|
|
90
|
-
async getAllExperimentsWithBucketsNoCache(): Promise<ExperimentWithBuckets[]> {
|
|
91
|
-
const experiments = await this.experimentDao.getAll()
|
|
92
|
-
const buckets = await this.bucketDao.getAll()
|
|
93
|
-
|
|
94
|
-
return experiments.map(experiment => ({
|
|
95
|
-
...experiment,
|
|
96
|
-
buckets: buckets.filter(bucket => bucket.experimentId === experiment.id),
|
|
97
|
-
}))
|
|
98
|
-
}
|
|
99
|
-
|
|
100
104
|
/**
|
|
101
105
|
* Creates a new experiment.
|
|
102
106
|
* Cold method.
|
|
@@ -180,6 +184,15 @@ export class Abba {
|
|
|
180
184
|
await this.experimentDao.saveBatch(requiresUpdating, { saveMethod: 'update' })
|
|
181
185
|
}
|
|
182
186
|
|
|
187
|
+
async softDeleteExperiment(experimentId: string): Promise<void> {
|
|
188
|
+
await this.experimentDao.patchById(
|
|
189
|
+
experimentId,
|
|
190
|
+
{ deleted: true, exclusions: [] },
|
|
191
|
+
{ saveMethod: 'update' },
|
|
192
|
+
)
|
|
193
|
+
await this.updateExclusions(experimentId, [])
|
|
194
|
+
}
|
|
195
|
+
|
|
183
196
|
/**
|
|
184
197
|
* Delete an experiment. Removes all user assignments and buckets.
|
|
185
198
|
* Requires the experiment to have been inactive for at least 15 minutes in order to
|
|
@@ -197,14 +210,8 @@ export class Abba {
|
|
|
197
210
|
'Experiment must be inactive for at least 15 minutes before deletion',
|
|
198
211
|
)
|
|
199
212
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
.filterEq('experimentId', experimentId)
|
|
203
|
-
await this.userAssignmentDao.deleteByQuery(userAssignmentDeleteQuery, { chunkSize: 50000 })
|
|
204
|
-
|
|
205
|
-
const bucketDeleteQuery = this.bucketDao.query().filterEq('experimentId', experimentId)
|
|
206
|
-
await this.bucketDao.deleteByQuery(bucketDeleteQuery)
|
|
207
|
-
|
|
213
|
+
await this.userAssignmentDao.deleteByExperimentId(experimentId)
|
|
214
|
+
await this.bucketDao.deleteByExperimentId(experimentId)
|
|
208
215
|
await this.experimentDao.deleteById(experimentId)
|
|
209
216
|
await this.updateExclusions(experimentId, [])
|
|
210
217
|
}
|
|
@@ -253,7 +260,7 @@ export class Abba {
|
|
|
253
260
|
return null
|
|
254
261
|
}
|
|
255
262
|
|
|
256
|
-
const experiments = await this.
|
|
263
|
+
const experiments = await this.getUserExperiments(userId)
|
|
257
264
|
const exclusionSet = getUserExclusionSet(experiments)
|
|
258
265
|
if (!canGenerateNewAssignments(experiment, exclusionSet)) {
|
|
259
266
|
return null
|
|
@@ -311,7 +318,7 @@ export class Abba {
|
|
|
311
318
|
segmentationData: SegmentationData,
|
|
312
319
|
existingOnly = false,
|
|
313
320
|
): Promise<DecoratedUserAssignment[]> {
|
|
314
|
-
const experiments = await this.
|
|
321
|
+
const experiments = await this.getUserExperiments(userId)
|
|
315
322
|
const exclusionSet = getUserExclusionSet(experiments)
|
|
316
323
|
|
|
317
324
|
// Shuffling means that randomisation occurs in the mutual exclusion
|
package/src/dao/bucket.dao.ts
CHANGED
|
@@ -10,6 +10,10 @@ export class BucketDao extends CommonDao<Bucket, BucketDBM> {
|
|
|
10
10
|
async getByExperimentId(experimentId: string): Promise<Bucket[]> {
|
|
11
11
|
return await this.query().filterEq('experimentId', experimentId).runQuery()
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
async deleteByExperimentId(experimentId: string): Promise<void> {
|
|
15
|
+
await this.query().filterEq('experimentId', experimentId).deleteByQuery()
|
|
16
|
+
}
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
export function bucketDao(db: CommonDB): BucketDao {
|
|
@@ -4,13 +4,15 @@ import type { IsoDate } from '@naturalcycles/js-lib'
|
|
|
4
4
|
import { localDate } from '@naturalcycles/js-lib'
|
|
5
5
|
import type { BaseExperiment, Experiment } from '../types.js'
|
|
6
6
|
|
|
7
|
-
type ExperimentDBM = BaseExperiment & {
|
|
8
|
-
rules: string | null
|
|
9
|
-
exclusions: string | null
|
|
10
|
-
data: string | null
|
|
11
|
-
}
|
|
12
|
-
|
|
13
7
|
export class ExperimentDao extends CommonDao<Experiment, ExperimentDBM> {
|
|
8
|
+
async getAllExperiments(opt?: GetAllExperimentsOpts): Promise<Experiment[]> {
|
|
9
|
+
if (!opt?.includeDeleted) {
|
|
10
|
+
return await this.getAll()
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return await this.query().filterEq('deleted', false).runQuery()
|
|
14
|
+
}
|
|
15
|
+
|
|
14
16
|
async getByKey(key: string): Promise<Experiment | null> {
|
|
15
17
|
return await this.getOneBy('key', key)
|
|
16
18
|
}
|
|
@@ -58,3 +60,13 @@ function parseMySQLDate(date: string): IsoDate {
|
|
|
58
60
|
if (date instanceof Date) return localDate(date).toISODate()
|
|
59
61
|
return date as IsoDate
|
|
60
62
|
}
|
|
63
|
+
|
|
64
|
+
type ExperimentDBM = BaseExperiment & {
|
|
65
|
+
rules: string | null
|
|
66
|
+
exclusions: string | null
|
|
67
|
+
data: string | null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface GetAllExperimentsOpts {
|
|
71
|
+
includeDeleted?: boolean
|
|
72
|
+
}
|
|
@@ -20,6 +20,10 @@ export class UserAssignmentDao extends CommonDao<UserAssignment> {
|
|
|
20
20
|
return await this.runQuery(query)
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
async deleteByExperimentId(experimentId: string): Promise<void> {
|
|
24
|
+
await this.query().filterEq('experimentId', experimentId).deleteByQuery()
|
|
25
|
+
}
|
|
26
|
+
|
|
23
27
|
async getCountByExperimentId(experimentId: string): Promise<number> {
|
|
24
28
|
return await this.query().filterEq('experimentId', experimentId).runQueryCount()
|
|
25
29
|
}
|
package/src/migrations/init.sql
CHANGED
package/src/types.ts
CHANGED
|
@@ -31,6 +31,10 @@ export type BaseExperiment = BaseDBEntity & {
|
|
|
31
31
|
* Date range end for the experiment assignments
|
|
32
32
|
*/
|
|
33
33
|
endDateExcl: IsoDate
|
|
34
|
+
/**
|
|
35
|
+
* Whether the experiment is flagged as deleted. This acts as a soft delete only.
|
|
36
|
+
*/
|
|
37
|
+
deleted: boolean
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
export type Experiment = BaseExperiment & {
|