-
Notifications
You must be signed in to change notification settings - Fork 71
Expand file tree
/
Copy pathprecompute.ts
More file actions
194 lines (176 loc) · 6.57 KB
/
precompute.ts
File metadata and controls
194 lines (176 loc) · 6.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import type { JsonValue } from '..';
import * as s from '../lib/serialization';
import { cartesianIterator, combineFlags } from '../shared';
import type { Flag } from './types';
type FlagsArray = readonly Flag<any, any>[];
type ValuesArray = readonly any[];
/**
* Resolves a list of flags
* @param flags - list of flags
* @returns - an array of evaluated flag values with one entry per flag
*/
export async function evaluate<T extends FlagsArray>(
flags: T,
): Promise<{ [K in keyof T]: Awaited<ReturnType<T[K]>> }> {
return Promise.all(flags.map((flag) => flag())) as Promise<{
[K in keyof T]: Awaited<ReturnType<T[K]>>;
}>;
}
/**
* Evaluate a list of feature flags and generate a signed string representing their values.
*
* This convenience function call combines `evaluate` and `serialize`.
*
* @param flags - list of flags
* @returns - a string representing evaluated flags
*/
export async function precompute<T extends FlagsArray>(
flags: T,
): Promise<string> {
const values = await evaluate(flags);
return serialize(flags, values);
}
/**
* Combines flag declarations with values.
* @param flags - flag declarations
* @param values - flag values
* @returns - A record where the keys are flag keys and the values are flag values.
*/
export function combine(flags: FlagsArray, values: ValuesArray) {
return combineFlags(flags, values);
}
/**
* Takes a list of feature flag declarations and their values and turns them into a short, signed string.
*
* The returned string is signed to avoid enumeration attacks.
*
* When a feature flag's `options` contains the value the flag resolved to, then the encoding will store it's index only, leading to better compression. Boolean values and null are compressed even when the options are not declared on the flag.
*
* @param flags - A list of feature flags
* @param values - A list of the values of the flags declared in ´flags`
* @param secret - The secret to use for signing the result
* @returns - A short string representing the values.
*/
export async function serialize(
flags: FlagsArray,
values: ValuesArray,
secret: string | undefined = process.env.FLAGS_SECRET,
) {
if (!secret) {
throw new Error('flags: Can not serialize due to missing secret');
}
return s.serialize(combine(flags, values), flags, secret);
}
/**
* Decodes all flags given the list of flags used to encode. Returns an object consisting of each flag's key and its resolved value.
* @param flags - Flags used when `code` was generated by `precompute` or `serialize`.
* @param code - The code returned from `serialize`
* @param secret - The secret to use for signing the result
* @returns - An object consisting of each flag's key and its resolved value.
*/
export async function deserialize(
flags: FlagsArray,
code: string,
secret: string | undefined = process.env.FLAGS_SECRET,
) {
if (!secret) {
throw new Error('flags: Can not serialize due to missing secret');
}
return s.deserialize(code, flags, secret);
}
/**
* Decodes the value of one or multiple flags given the list of flags used to encode and the code.
*
* @param flag - Flag or list of flags to decode
* @param precomputeFlags - Flags used when `code` was generated by `serialize`
* @param code - The code returned from `serialize`
* @param secret - The secret to use for verifying the signature
*/
export async function getPrecomputed<T extends JsonValue>(
flag: Flag<T, any>,
precomputeFlags: FlagsArray,
code: string,
secret?: string,
): Promise<T>;
/**
* Decodes the value of one or multiple flags given the list of flags used to encode and the code.
*
* @param flag - Flag or list of flags to decode
* @param precomputeFlags - Flags used when `code` was generated by `serialize`
* @param code - The code returned from `serialize`
* @param secret - The secret to use for verifying the signature
*/
export async function getPrecomputed<
T extends JsonValue,
K extends readonly Flag<T, any>[],
>(
flags: readonly [...K],
precomputeFlags: FlagsArray,
code: string,
secret?: string,
): Promise<{ [P in keyof K]: K[P] extends Flag<infer U, any> ? U : never }>;
/**
* Decodes the value of one or multiple flags given the list of flags used to encode and the code.
*
* @param flag - Flag or list of flags to decode
* @param precomputeFlags - Flags used when `code` was generated by `serialize`
* @param code - The code returned from `serialize`
* @param secret - The secret to use for verifying the signature
*/
export async function getPrecomputed<T extends JsonValue>(
flagOrFlags: Flag<T, any> | readonly Flag<T, any>[],
precomputeFlags: FlagsArray,
code: string,
secret: string | undefined = process.env.FLAGS_SECRET,
): Promise<any> {
if (!secret) {
throw new Error(
'flags: getPrecomputed was called without a secret. Please set FLAGS_SECRET environment variable.',
);
}
const flagSet = await deserialize(precomputeFlags, code, secret);
if (Array.isArray(flagOrFlags)) {
// Handle case when an array of flags is passed
return flagOrFlags.map((flag) => flagSet[flag.key]);
} else {
// Handle case when a single flag is passed
return flagSet[(flagOrFlags as Flag<T, any>).key];
}
}
/**
* Generates all permutations given a list of feature flags based on the options declared on each flag.
* @param flags - The list of feature flags
* @param filter - An optional filter function which gets called with each permutation.
* @param secret - The secret sign the generated permutation with
* @returns An array of strings representing each permutation
*/
export async function generatePermutations(
flags: FlagsArray,
filter: ((permutation: Record<string, JsonValue>) => boolean) | null = null,
secret: string = process.env.FLAGS_SECRET!,
): Promise<string[]> {
if (!secret) {
throw new Error(
'flags: generatePermutations was called without a secret. Please set FLAGS_SECRET environment variable.',
);
}
const options = flags.map((flag) => {
// infer boolean permutations if you don't declare any options.
//
// to explicitly opt out you need to use "filter"
if (!flag.options) return [false, true];
return flag.options.map((option) => option.value);
});
const list: Record<string, JsonValue>[] = [];
for (const permutation of cartesianIterator(options)) {
const permObject = permutation.reduce<Record<string, JsonValue>>(
(acc, value, index) => {
acc[flags[index]!.key] = value;
return acc;
},
{},
);
if (!filter || filter(permObject)) list.push(permObject);
}
return Promise.all(list.map((values) => s.serialize(values, flags, secret)));
}