Skip to content

Commit 0076d38

Browse files
authored
Stabilize queries generated object typedefs (#1298)
Different instances can produce different field orders for Object codecs, so we should have an fully ordered sorting algorithm to stabilize the order. This should help codegen to be deterministic, or at least get us a little closer. The sorting is now grouped in a logic manner like this: - Required single properties - Optional single properties - Required multi properties - Optional multi properties - Required single links - Optional single links - Required multi links - Optional multi links Within those groups, fields are sorted by name.
1 parent 9cd5df4 commit 0076d38

File tree

1 file changed

+60
-12
lines changed

1 file changed

+60
-12
lines changed

packages/gel/src/reflection/analyzeQuery.ts

Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,39 @@ const genDef = <Codec extends CodecLike>(
127127
[codecType as AbstractClass<CodecLike>, generator as CodecGenerator] as const;
128128
export { genDef as defineCodecGeneratorTuple };
129129

130+
type FieldDef = {
131+
name: string;
132+
cardinality: Cardinality;
133+
codec: ICodec;
134+
};
135+
136+
const getSortPriority = (field: FieldDef) => {
137+
if (!(field.codec instanceof ObjectCodec)) {
138+
switch (field.cardinality) {
139+
case Cardinality.One:
140+
return 0;
141+
case Cardinality.AtLeastOne:
142+
return 1;
143+
case Cardinality.AtMostOne:
144+
return 2;
145+
case Cardinality.Many:
146+
return 3;
147+
}
148+
} else {
149+
switch (field.cardinality) {
150+
case Cardinality.One:
151+
return 4;
152+
case Cardinality.AtLeastOne:
153+
return 5;
154+
case Cardinality.AtMostOne:
155+
return 6;
156+
case Cardinality.Many:
157+
return 7;
158+
}
159+
}
160+
return 8;
161+
};
162+
130163
export const defaultCodecGenerators: CodecGeneratorMap = new Map([
131164
genDef(NullCodec, () => "null"),
132165
genDef(EnumCodec, (codec) => {
@@ -140,12 +173,27 @@ export const defaultCodecGenerators: CodecGeneratorMap = new Map([
140173
}),
141174
genDef(ObjectCodec, (codec, ctx) => {
142175
const subCodecs = codec.getSubcodecs();
143-
const fields = codec.getFields().map((field, i) => ({
176+
const originalFields = codec.getFields();
177+
178+
const fieldsWithCodecs = originalFields.map((field, i) => ({
144179
name: field.name,
145-
codec: subCodecs[i],
146180
cardinality: util.parseCardinality(field.cardinality),
181+
codec: subCodecs[i],
147182
}));
148-
return generateTsObject(fields, ctx);
183+
184+
const sortedFieldsWithCodecs = fieldsWithCodecs.sort((a, b) => {
185+
const aPriority = getSortPriority(a);
186+
const bPriority = getSortPriority(b);
187+
188+
if (aPriority !== bPriority) {
189+
return aPriority - bPriority;
190+
}
191+
192+
// Within the same group, sort by name
193+
return a.name.localeCompare(b.name);
194+
});
195+
196+
return generateTsObject(sortedFieldsWithCodecs, ctx);
149197
}),
150198
genDef(NamedTupleCodec, (codec, ctx) => {
151199
const subCodecs = codec.getSubcodecs();
@@ -185,18 +233,18 @@ export const defaultCodecGenerators: CodecGeneratorMap = new Map([
185233
]);
186234

187235
export const generateTsObject = (
188-
fields: Parameters<typeof generateTsObjectField>[0][],
236+
fields: FieldDef[],
189237
ctx: CodecGeneratorContext,
190238
) => {
191239
const properties = fields.map((field) => generateTsObjectField(field, ctx));
192240
return `{\n${properties.join("\n")}\n${ctx.indent}}`;
193241
};
194242

195243
export const generateTsObjectField = (
196-
field: { name: string; cardinality: Cardinality; codec: ICodec },
244+
field: FieldDef,
197245
ctx: CodecGeneratorContext,
198246
) => {
199-
const codec = unwrapSetCodec(field.codec, field.cardinality);
247+
const codec = unwrapSetCodec(field);
200248

201249
const name = JSON.stringify(field.name);
202250
const value = ctx.applyCardinality(
@@ -210,15 +258,15 @@ export const generateTsObjectField = (
210258
return `${ctx.indent} ${isReadonly}${name}${questionMark}: ${value};`;
211259
};
212260

213-
function unwrapSetCodec(codec: ICodec, cardinality: Cardinality) {
214-
if (!(codec instanceof SetCodec)) {
215-
return codec;
261+
function unwrapSetCodec(field: FieldDef) {
262+
if (!(field.codec instanceof SetCodec)) {
263+
return field.codec;
216264
}
217265
if (
218-
cardinality === Cardinality.Many ||
219-
cardinality === Cardinality.AtLeastOne
266+
field.cardinality === Cardinality.Many ||
267+
field.cardinality === Cardinality.AtLeastOne
220268
) {
221-
return codec.getSubcodecs()[0];
269+
return field.codec.getSubcodecs()[0];
222270
}
223271
throw new Error("Sub-codec is SetCodec, but upper cardinality is one");
224272
}

0 commit comments

Comments
 (0)