I am trying to boil down a pretty complicated problem into its essence so I can get some help on how to model or architect it. Here it goes.
Say we are compiling functions in this order:
function test() {
sum(mul(2, 3), mul(3, 4))
}
function sum(a, b) {
return a + b
}
function mul(a, b) {
return a * b
}
We end up with an AST something like this:
{
type: 'Program',
blocks: [
{
type: 'Function',
name: 'test',
args: [],
body: [
{
type: 'Call',
function: 'sum',
args: [
{
type: 'Call',
function: 'mul',
...
},
...
]
}
]
},
{
type: 'Function',
name: 'mul',
args: ...,
body: ...
},
{
type: 'Function',
name: 'sum',
args: ...,
body: ...
}
]
}
Now we start compiling this AST into more easily manipulated objects, with direct pointers to functions and such. The final result might look like this:
{
type: 'Program',
blocks: [
{
type: 'Function',
name: 'test',
args: [],
body: [
{
type: 'Call',
pointer: 2,
args: [
{
type: 'Call',
pointer: 1,
...
},
...
]
}
]
},
{
type: 'Function',
name: 'mul',
args: ...,
body: ...
},
{
type: 'Function',
name: 'sum',
args: ...,
body: ...
}
]
}
The main difference is that the "final" version has a pointer to the index where the function is defined. This is a very rough sketch. The reality would be there could be multiple passes required to resolve some context sensitivity, and so you end up with multiple partial/intermediate data structures in the transition from the AST to the final compiled object.
How do you make types to deal with this situation? The ideal is that there is an "initial" and a "final" type. The reality is that on our first pass, we have a "placeholder type" for the function calls, which we can't resolve until we have completed our first pass. So on the first pass, we have:
function: String
On the second pass we change it to:
pointer: Int
How do you reconcile this? How do you architect the algorithm so as to allow for these "placeholder" types for the final data structure?
I have tried searching the web for these sorts of topics but haven't found anything:
partial types
intermediate types
placeholder types
virtual types
temporary types
transitional types
how to have temporary placeholders in data structures
etc.
Create a hashmap.
In a first pass write name/index pairs to the hashmap without modifying the AST itself. For the example that would result in this hashmap (represented in JSON format):
{
"mul": 1,
"sum": 2
}
In a second pass you can use the hashmap to replace references to the keys of this hashmap with a pointer property that gets the corresponding value.
I would suggest not trying to understand how to store intermediate data types, but understanding how to store "references" or "holes". Go look up how a typical serialization/deserialization algorithm works (especially one that can deal with something like repeated substructure or circular references): http://www.dietmar-kuehl.de/mirror/c++-faq/serialization.html
It may give you helpful ideas.
Related
After having implemented dataloader in the respective resolvers to solve the N+1 problem, I also need to be able to solve the N+N problem.
I need a decently efficient data loading mechanism to get a relation like this:
{
persons (active: true) {
id,
given_name,
projects (active: true) {
id,
title,
}
}
}
I've created a naive implementation for this, returning
{
persons: [
{
id: 1,
given_name: 'Mike'
projects: [
{
id: 1,
title: 'API'
},
{
id: 2,
title: 'Frontend'
}
]
}
{
id: 2,
given_name: 'Eddie'
projects: [
{
id: 2,
title: 'Frontend'
},
{
id: 3,
title: 'Testing'
}
]
}
]
}
In SQL the underlying structure would be represented by a many many to many relationship.
Is there a similiar tool like dataloader for solving this or can this maybe even be solved with dataloader itself?
The expectation with GraphQL is that the trip to the database is generally the fastest thing you can do, so you just add a resolver to Person.projects that makes a call to the database. You can still use dataLoaders for that.
const resolvers = {
Query: {
persons(parent, args, context) {
// 1st call to database
return someUsersService.list()
},
},
Person: {
projects(parent, args, context) {
// this should be a dataLoader behind the scenes.
// Makes second call to database
return projectsService.loadByUserId(parent.id)
}
}
}
Just remember that now your dataLoader is expecting to return an Array of objects in each slot instead of a single object.
I'm currently in the process of transforming a REST API into GraphQL, but I've hit a bit of a snag in one of the endpoints.
Currently, this endpoint returns an object who's keys can be an unlimited set of strings, and whos values all match a certain shape.
So, as a rudimentary example, I have this situation...
// response
{
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 },
}
Again, the keys are not known at runtime and can be an unlimited set of strings.
In TypeScript, for example, this sort of situation is handled by creating an interface using an indexable field signature, like so...
interface Data {
id: string;
count: number;
}
interface Response {
[key: string]: Data;
}
So, my question is: Is this sort of thing possible with graphql? How would I go about creating a type/schema for this?
Thanks in advance!
I think that one solution can be usage of JSON.stringify() method
exampleQuery: {
type: GraphQLString,
resolve: (root, args, context) => {
let obj = {
foo: { id: 'foo', count: 3 },
bar: { id: 'bar', count: 6 },
baz: { id: 'baz', count: 1 }
};
return JSON.stringify(obj);
}
}
Then, after retrieving the result of GraphQL query you could use JSON.parse(result) (in case the part performing the query is also written in JavaScript - otherwise you would have to use equivalent method of other language to parse the incoming JSON response).
Disadvantage of such a solution is that you do not have the possibility to choose what fields of obj you want to retrieve from the query, but, as you said, the returning object can have unlimited set of strings that probably are not known on the front end of the application, so there is no need to choose it's keys, am I right?
I'm pretty new to GraphQL and within my root query I have two fields that are very similar aside from their "type" property, that I would like to combine.
allPosts returns an array of post objects, while post returns a single post.
Each field is using the same schema, and the loaders/resources are being determined within those respective fields based on the argument passed in.
const RootQuery = new graphql.GraphQLObjectType({
name: 'Query',
description: 'Root Query',
fields: {
allPosts: {
type: new graphql.GraphQLList(postType),
args: {
categoryName: {
type: graphql.GraphQLString
}
},
resolve: (root, args) => resolver(args)
},
post: {
type: postType,
args: {
slug: {
type: graphql.GraphQLString
}
},
resolve: (root, args) => resolver(args)
},
});
Is it possible to combine these two fields into one and have the type determined by the argument passed in or another variable?
No, you can't!
Once you define a field as GraphQLList, you always get an array. There is no chance that you suddenly get an object instead of array of.
Same apply to other case when you define field as GraphQLObjectType (or any other scalar type) and you want get an array as result.
Those two fields have really different purposes.
Anyway, you can always add a limit logic to your allPosts field and limit the result to one. But, nevertheless you get always array with only one post
When defining a query in a schema, how do I refer to a value of an GraphQLEnumType declared previously, to use it as the default value of an argument?
Let's say I've defined following ObservationPeriod GraphQLEnumType:
observationPeriodEnum = new GraphQLEnumType {
name: "ObservationPeriod"
description: "One of the performance metrics observation periods"
values:
Daily:
value: '1D'
description: "Daily"
[…]
}
and use it as the type of query argument period:
queryRootType = new GraphQLObjectType {
name: "QueryRoot"
description: "Query entry points to the DWH."
fields:
performance:
type: performanceType
description: "Given a portfolio EID, an observation period (defaults to YTD)
and as-of date, as well as the source performance engine,
return the matching performance metrics."
args:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily ← how to achieve this?
[…]
}
Currently I'm using the actual '1D' string value as the default value; this works:
period:
type: observationPeriodEnum
defaultValue: '1D'
But is there a way I could use the Daily symbolic name instead? I couldn't find a way to use the names within the schema itself. Is there something I overlooked?
I'm asking, because I was expecting an enum type to behave as a set of constants also, and to be able to use them like this in the schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Naïve workaround:
##
# Given a GraphQLEnumType instance, this macro function injects the names
# of its enum values as keys the instance itself and returns the modified
# GraphQLEnumType instance.
#
modifiedWithNameKeys = (enumType) ->
for ev in enumType.getValues()
unless enumType[ ev.name]?
enumType[ ev.name] = ev.value
else
console.warn "SCHEMA> Enum name #{ev.name} conflicts with key of same
name on GraphQLEnumType object; it won't be injected for value lookup"
enumType
observationPeriodEnum = modifiedWithNameKeys new GraphQLEnumType {
name: "description: "Daily""
values:
[…]
which allows to use it as desired in schema definition:
period:
type: observationPeriodEnum
defaultValue: observationPeriodEnum.Daily
Of course, this modifier fullfils its promise, only as long as the enum names do not interfere with GraphQLEnumType existing method and variable names (which are currently: name, description, _values, _enumConfig, _valueLookup, _nameLookup, getValues, serialize, parseValue, _getValueLookup, _getNameLookup and toString — see definition of GraphQLEnumType class around line 687 in https://github.com/graphql/graphql-js/blob/master/src/type/definition.js#L687)
I just ran into this. My enum:
const contributorArgs = Object.assign(
{},
connectionArgs, {
sort: {
type: new GraphQLEnumType({
name: 'ContributorSort',
values: {
top: { value: 0 },
},
})
},
}
);
In my queries, I was doing:
... on Topic {
id
contributors(first: 10, sort: 'top') {
...
}
}
Turns out you just don't quote the value (which after thinking about it makes sense; it's a value in the enum type, not an actual value:
... on Topic {
id
contributors(first: 10, sort: top) {
...
}
}
It's possible to declare enum values as default inputs via the schema definition language, but it looks like you are only using the JS library APIs. You might be able to get to a solution by taking a look at the ASTs for the working example and comparing that with the AST from what your JS code is producing.
Sorry not a solution, but hope that helps!
I found a pull request adding a method .getValue() to enum types, which returns name and value. In your case this call:
observationPeriodEnum.getValue('Daily');
would return:
{
name: 'Daily',
value: '1D'
}
I am looking for an example where JSON constructed from the server side is used to represent objects that are then translated into customized widgets in dojo. The JSON would have to be very specific in its structure, so it would not be a very general solution. Could someone point me to an example of this. It would essentially be the reverse of this
http://docs.dojocampus.org/dojo/formToJson
First of all let me point out that JSON produced by dojo.formToJson() is not enough to recreate the original widgets:
{"field1": "value1", "field2": "value2"}
field1 can be literally anything: a checkbox, a radio button, a select, a text area, a text box, or anything else. You have to be more specific what widgets to use to represent fields. And I am not even touching the whole UI presentation layer: placement, styling, and so on.
But it is possible to a certain degree.
If we want to use Dojo widgets (Dijits), we can leverage the fact that they all are created uniformly:
var myDijit = new dijit.form.DijitName(props, node);
In this line:
dijit.form.DijitName is a dijit's class.
props is a dijit-specific properties.
node is an anchor node where to place this dijit. It is optional, and you don't need to specify it, but at some point you have to insert your dijit manually.
So let's encode this information as a JSON string taking this dijit snippet as an example:
var myDijit = new dijit.form.DropDownSelect({
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
}, "myNode");
The corresponding JSON can be something like that:
{
type: "DropDownSelect",
props: {
options: [
{ label: 'foo', value: 'foo', selected: true },
{ label: 'bar', value: 'bar' }
]
},
node: "myNode"
}
And the code to parse it:
function createDijit(json){
if(!json.type){
throw new Error("type is missing!");
}
var cls = dojo.getObject(json.type, false, dijit.form);
if(!cls){
// we couldn't find the type in dijit.form
// dojox widget? custom widget? let's try the global scope
cls = dojo.getObject(json.type, false);
}
if(!cls){
throw new Error("cannot find your widget type!");
}
var myDijit = new cls(json.props, json.node);
return myDijit;
}
That's it. This snippet correctly handles the dot notation in types, and it is smart enough to check the global scope too, so you can use JSON like that for your custom dijits:
{
type: "my.form.Box",
props: {
label: "The answer is:",
value: 42
},
node: "answer"
}
You can treat DOM elements the same way by wrapping dojo.create() function, which unifies the creation of DOM elements:
var myWidget = dojo.create("input", {
type: "text",
value: "42"
}, "myNode", "replace");
Obviously you can specify any placement option, or no placement at all.
Now let's repeat the familiar procedure and create our JSON sample:
{
tag: "input",
props: {
type: "text",
value: 42
},
node: "myNode",
pos: "replace"
}
And the code to parse it is straightforward:
function createNode(json){
if(!json.tag){
throw new Error("tag is missing!");
}
var myNode = dojo.create(json.tag, json.props, json.node, json.pos);
return myNode;
}
You can even categorize JSON items dynamically:
function create(json){
if("tag" in json){
// this is a node definition
return createNode(json);
}
// otherwise it is a dijit definition
return createDijit(json);
}
You can represent your form as an array of JSON snippets we defined earlier and go over it creating your widgets:
function createForm(array){
dojo.forEach(array, create);
}
All functions are trivial and essentially one-liners — just how I like it ;-)
I hope it'll give you something to build on your own custom solution.