Skip to content

Commit b397289

Browse files
authored
generate raw response types (#764)
- For each entrypoint, generate a raw response type - This is a TS type that corresponds to the type of data we would receive from the network - Next steps include: adding an additional general to the entrypoint/normalization AST (which will be this type), and add a `writeDataToStore(entrypoint, rawResponse)`-like API
1 parent 03da208 commit b397289

File tree

37 files changed

+759
-17
lines changed

37 files changed

+759
-17
lines changed

crates/artifact_content/src/entrypoint_artifact.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use crate::{
22
generate_artifacts::{
33
ENTRYPOINT_FILE_NAME, NORMALIZATION_AST, NORMALIZATION_AST_FILE_NAME, QUERY_TEXT,
4-
QUERY_TEXT_FILE_NAME, RESOLVER_OUTPUT_TYPE, RESOLVER_PARAM_TYPE, RESOLVER_READER,
5-
RefetchQueryArtifactImport,
4+
QUERY_TEXT_FILE_NAME, RAW_RESPONSE_TYPE, RESOLVER_OUTPUT_TYPE, RESOLVER_PARAM_TYPE,
5+
RESOLVER_READER, RefetchQueryArtifactImport,
66
},
77
imperatively_loaded_fields::get_paths_and_contents_for_imperatively_loaded_field,
88
normalization_ast_text::generate_normalization_ast_text,
99
operation_text::{OperationText, generate_operation_text},
1010
persisted_documents::PersistedDocuments,
11+
raw_response_type::generate_raw_response_type,
1112
};
1213
use common_lang_types::{
1314
ArtifactPathAndContent, ClientScalarSelectableName, ParentObjectEntityNameAndSelectableName,
@@ -286,6 +287,8 @@ pub(crate) fn generate_entrypoint_artifacts_with_client_field_traversal_result<
286287
&directive_set,
287288
);
288289

290+
let raw_response_type = generate_raw_response_type(db, merged_selection_map, 0);
291+
289292
let mut path_and_contents = Vec::with_capacity(refetch_paths_with_variables.len() + 3);
290293
path_and_contents.push(ArtifactPathAndContent {
291294
file_content: format!("export default '{query_text}';"),
@@ -313,6 +316,17 @@ pub(crate) fn generate_entrypoint_artifacts_with_client_field_traversal_result<
313316
}
314317
.some(),
315318
});
319+
path_and_contents.push(ArtifactPathAndContent {
320+
file_content: format!(
321+
"export type {}__{}__rawResponse = {raw_response_type}\n",
322+
type_name, field_name,
323+
),
324+
file_name: *RAW_RESPONSE_TYPE,
325+
type_and_field: Some(ParentObjectEntityNameAndSelectableName {
326+
parent_object_entity_name: type_name,
327+
selectable_name: field_name,
328+
}),
329+
});
316330
path_and_contents.push(ArtifactPathAndContent {
317331
file_content: entrypoint_file_content,
318332
file_name: *ENTRYPOINT_FILE_NAME,

crates/artifact_content/src/generate_artifacts.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ use isograph_lang_types::{
1717
};
1818
use isograph_schema::{
1919
ClientFieldVariant, ClientScalarSelectable, ClientSelectableId, FieldMapItem,
20-
FieldTraversalResult, ID_ENTITY_NAME, IsographDatabase, LINK_FIELD_NAME, NameAndArguments,
21-
NetworkProtocol, NormalizationKey, RefetchStrategy, ScalarSelectableId, SelectableTrait,
22-
ServerEntityName, ServerObjectSelectableVariant, UserWrittenClientTypeInfo, ValidatedSelection,
23-
ValidatedVariableDefinition, ValidationError, WrappedSelectionMapSelection,
20+
FieldTraversalResult, ID_ENTITY_NAME, IsographDatabase, LINK_FIELD_NAME, NODE_FIELD_NAME,
21+
NameAndArguments, NetworkProtocol, NormalizationKey, RefetchStrategy, ScalarSelectableId,
22+
SelectableTrait, ServerEntityName, ServerObjectSelectableVariant, UserWrittenClientTypeInfo,
23+
ValidatedSelection, ValidatedVariableDefinition, ValidationError, WrappedSelectionMapSelection,
2424
accessible_client_fields, client_object_selectable_named, client_scalar_selectable_named,
2525
client_selectable_map, client_selectable_named, description, fetchable_types,
2626
inline_fragment_reader_selection_set, output_type_annotation, selectable_named,
@@ -64,6 +64,7 @@ lazy_static! {
6464
pub static ref ISO_TS: ArtifactFilePrefix = "iso".intern().into();
6565
pub static ref NORMALIZATION_AST_FILE_NAME: ArtifactFileName =
6666
"normalization_ast.ts".intern().into();
67+
pub static ref RAW_RESPONSE_TYPE: ArtifactFileName = "raw_response_type.ts".intern().into();
6768
pub static ref NORMALIZATION_AST: ArtifactFilePrefix = "normalization_ast".intern().into();
6869
pub static ref QUERY_TEXT_FILE_NAME: ArtifactFileName = "query_text.ts".intern().into();
6970
pub static ref QUERY_TEXT: ArtifactFilePrefix = "query_text".intern().into();
@@ -360,16 +361,29 @@ fn get_artifact_path_and_content_impl<TNetworkProtocol: NetworkProtocol>(
360361
variable_definitions_iter.collect::<Vec<_>>(),
361362
),
362363
RefetchStrategy::UseRefetchField(_) => {
364+
let fetchable_types_map =
365+
fetchable_types(db).as_ref().expect(
366+
"Expected parsing to have succeeded. \
367+
This is indicative of a bug in Isograph.",
368+
);
369+
370+
let query_id = fetchable_types_map
371+
.iter()
372+
.find(|(_, root_operation_name)| {
373+
root_operation_name.0 == "query"
374+
})
375+
.expect("Expected query to be found")
376+
.0;
377+
363378
let wrapped_map = selection_map_wrapped(
364379
merged_selection_map.clone(),
365380
vec![
366381
WrappedSelectionMapSelection::InlineFragment(
367382
type_to_refine_to.name,
368383
),
369384
WrappedSelectionMapSelection::LinkedField {
370-
server_object_selectable_name: "node"
371-
.intern()
372-
.into(),
385+
parent_object_entity_name: *query_id,
386+
server_object_selectable_name: *NODE_FIELD_NAME,
373387
arguments: vec![id_arg.clone()],
374388
concrete_type: None,
375389
},

crates/artifact_content/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ mod iso_overload_file;
88
mod normalization_ast_text;
99
mod operation_text;
1010
mod persisted_documents;
11+
mod raw_response_type;
1112
mod reader_ast;
1213
mod refetch_reader_artifact;
1314
mod ts_config;
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
use intern::Lookup;
2+
use isograph_schema::{
3+
IsographDatabase, MergedSelectionMap, MergedServerSelection, NetworkProtocol,
4+
server_object_selectable_named, server_scalar_entity_javascript_name,
5+
server_scalar_selectable_named,
6+
};
7+
use std::collections::BTreeMap;
8+
9+
use crate::generate_artifacts::print_javascript_type_declaration;
10+
11+
pub fn generate_raw_response_type<TNetworkProtocol: NetworkProtocol>(
12+
db: &IsographDatabase<TNetworkProtocol>,
13+
selection_map: &MergedSelectionMap,
14+
indentation_level: u8,
15+
) -> String {
16+
let indent = &" ".repeat(indentation_level as usize).to_string();
17+
18+
let mut raw_response_type = String::new();
19+
raw_response_type.push_str(&format!("{}{{\n", indent));
20+
generate_raw_response_type_inner(
21+
db,
22+
&mut raw_response_type,
23+
selection_map,
24+
indentation_level + 1,
25+
);
26+
raw_response_type.push_str(&format!("{}}}\n", indent));
27+
raw_response_type
28+
}
29+
30+
pub fn generate_raw_response_type_inner<TNetworkProtocol: NetworkProtocol>(
31+
db: &IsographDatabase<TNetworkProtocol>,
32+
raw_response_type: &mut String,
33+
selection_map: &MergedSelectionMap,
34+
indentation_level: u8,
35+
) {
36+
let indent = &" ".repeat(indentation_level as usize).to_string();
37+
let mut raw_response_type_inner = String::new();
38+
let mut fragments = BTreeMap::new();
39+
40+
for item in selection_map.values() {
41+
match &item {
42+
MergedServerSelection::ScalarField(scalar_field) => {
43+
let normalization_alias = scalar_field.normalization_alias();
44+
let name = normalization_alias
45+
.as_deref()
46+
.unwrap_or(scalar_field.name.lookup());
47+
48+
let server_scalar_selectable = server_scalar_selectable_named(
49+
db,
50+
scalar_field.parent_object_entity_name,
51+
scalar_field.name.into(),
52+
)
53+
.as_ref()
54+
.expect(
55+
"Expected validation to have succeeded. \
56+
This is indicative of a bug in Isograph.",
57+
)
58+
.as_ref()
59+
.expect(
60+
"Expected selectable to exist. \
61+
This is indicative of a bug in Isograph.",
62+
);
63+
64+
let raw_type = server_scalar_selectable.target_scalar_entity.as_ref().map(
65+
&mut |scalar_entity_name| match server_scalar_selectable
66+
.javascript_type_override
67+
{
68+
Some(javascript_name) => javascript_name,
69+
None => server_scalar_entity_javascript_name(db, *scalar_entity_name)
70+
.as_ref()
71+
.expect(
72+
"Expected parsing to not have failed. \
73+
This is indicative of a bug in Isograph.",
74+
)
75+
.expect(
76+
"Expected entity to exist. \
77+
This is indicative of a bug in Isograph.",
78+
),
79+
},
80+
);
81+
// TODO: make sure undefined is treated as null when normalizing data
82+
// https://github.com/isographlabs/isograph/issues/776
83+
// let is_optional = matches!(
84+
// server_scalar_selectable.target_scalar_entity,
85+
// TypeAnnotation::Union(_)
86+
// );
87+
88+
raw_response_type_inner.push_str(&format!(
89+
"{indent}{name}: {},\n",
90+
// if is_optional { "?" } else { "" },
91+
print_javascript_type_declaration(&raw_type)
92+
));
93+
}
94+
MergedServerSelection::LinkedField(linked_field) => {
95+
let normalization_alias = linked_field.normalization_alias();
96+
let name = normalization_alias
97+
.as_deref()
98+
.unwrap_or(linked_field.name.lookup());
99+
100+
let server_object_selectable = server_object_selectable_named(
101+
db,
102+
linked_field.parent_object_entity_name,
103+
linked_field.name.into(),
104+
)
105+
.as_ref()
106+
.expect(
107+
"Expected validation to have succeeded. \
108+
This is indicative of a bug in Isograph.",
109+
)
110+
.as_ref()
111+
.expect(
112+
"Expected selectable to exist. \
113+
This is indicative of a bug in Isograph.",
114+
);
115+
116+
let raw_type =
117+
server_object_selectable
118+
.target_object_entity
119+
.as_ref()
120+
.map(&mut |_| {
121+
let mut raw_response_type_declaration = String::new();
122+
raw_response_type_declaration.push_str("{\n");
123+
generate_raw_response_type_inner(
124+
db,
125+
&mut raw_response_type_declaration,
126+
&linked_field.selection_map,
127+
indentation_level + 1,
128+
);
129+
raw_response_type_declaration.push_str(&format!("{indent}}}"));
130+
raw_response_type_declaration
131+
});
132+
// TODO: make sure undefined is treated as null when normalizing data
133+
// https://github.com/isographlabs/isograph/issues/776
134+
// let is_optional = matches!(
135+
// server_object_selectable.target_object_entity,
136+
// TypeAnnotation::Union(_)
137+
// );
138+
139+
raw_response_type_inner.push_str(&format!(
140+
"{indent}{name}: {},\n",
141+
// if is_optional { "?" } else { "" },
142+
print_javascript_type_declaration(&raw_type)
143+
));
144+
}
145+
MergedServerSelection::ClientPointer(_) => {}
146+
MergedServerSelection::InlineFragment(inline_fragment) => {
147+
let mut raw_response_type = String::new();
148+
let mut inline_fragment_selection_map = BTreeMap::new();
149+
150+
inline_fragment_selection_map.extend(selection_map.clone().into_iter().filter(
151+
|(_, value)| !matches!(value, MergedServerSelection::InlineFragment(_)),
152+
));
153+
154+
inline_fragment_selection_map
155+
.extend(inline_fragment.selection_map.clone().into_iter());
156+
157+
generate_raw_response_type_inner(
158+
db,
159+
&mut raw_response_type,
160+
&inline_fragment_selection_map,
161+
indentation_level,
162+
);
163+
164+
fragments.insert(inline_fragment.type_to_refine_to, raw_response_type);
165+
}
166+
}
167+
}
168+
169+
if fragments.is_empty() {
170+
raw_response_type.push_str(&raw_response_type_inner);
171+
} else {
172+
let indent = &" ".repeat((indentation_level - 1) as usize).to_string();
173+
174+
let mut iter = fragments.into_iter();
175+
if let Some((_, fragment)) = iter.next() {
176+
raw_response_type.push_str(&fragment);
177+
};
178+
179+
for (_, fragment) in iter {
180+
raw_response_type.push_str(&format!("{indent}}} | {{\n"));
181+
raw_response_type.push_str(&fragment);
182+
}
183+
}
184+
}

crates/isograph_schema/src/create_additional_fields/expose_field_directive.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ pub fn create_new_exposed_field<TNetworkProtocol: NetworkProtocol>(
119119
let client_field_scalar_selection_name = expose_as.unwrap_or(mutation_field.name.item.into());
120120
// TODO what is going on here. Should mutation_field have a checked way of converting to LinkedField?
121121
let top_level_schema_field_name = mutation_field.name.item.unchecked_conversion();
122+
let top_level_schema_field_parent_object_entity_name = mutation_field.parent_object_entity_name;
122123
let mutation_field_arguments = mutation_field.arguments.clone();
123124
let description = expose_field_to_insert
124125
.description
@@ -196,6 +197,8 @@ pub fn create_new_exposed_field<TNetworkProtocol: NetworkProtocol>(
196197
let x = match server_object_selectable.object_selectable_variant {
197198
ServerObjectSelectableVariant::LinkedField => {
198199
WrappedSelectionMapSelection::LinkedField {
200+
parent_object_entity_name: server_object_selectable
201+
.parent_object_entity_name,
199202
server_object_selectable_name: server_object_selectable.name.item,
200203
arguments: vec![],
201204
concrete_type: primary_field_concrete_type,
@@ -226,6 +229,7 @@ pub fn create_new_exposed_field<TNetworkProtocol: NetworkProtocol>(
226229
top_level_schema_field_name,
227230
&top_level_schema_field_arguments,
228231
top_level_schema_field_concrete_type,
232+
top_level_schema_field_parent_object_entity_name,
229233
));
230234

231235
let mutation_client_scalar_selectable = ClientScalarSelectable {

0 commit comments

Comments
 (0)