prismake / typegql Goto Github PK
View Code? Open in Web Editor NEWCreate GraphQL schema with TypeScript classes.
Home Page: https://prismake.github.io/typegql/
License: MIT License
Create GraphQL schema with TypeScript classes.
Home Page: https://prismake.github.io/typegql/
License: MIT License
typegql works well with typegql, would be good to prepare some working example
In my experience, resolvers can get pretty large. Because of this, I am afraid defining all resolvers in the schema class will become unwieldy. What is the best approach to moving a resolver to a separate file, then using it in the schema class?
If there is an agreement on such a process, I would be happy to amend the documentation.
Currently I have found one approach that seems promising:
import doSomething from "..."
@Mutation()
public doSomething(
@Inject(doSomething) value: boolean,
): boolean {
return value
}
I havent been able to narrow it down yet, but using graphql-tools": "^3.0.2"
and merging schemas breaks queries.
Running the merge schema example code should replicate this issue. Need to actually pull the source to confirm.
Hi there, think the library fits a real good niche. The typeorm fit in particular is real nice
I've hit a wall however when trying to accepts arguments to a query field. The schema just doesn't seem to include the name
argument for the query field.
I have experimented at length with adding @Arg()
etc also
Here's a reproduction repo - https://github.com/elmpp/typegql-params
This screenshot shows the graphiql request failing - https://github.com/elmpp/typegql-params/blob/master/ScreenShot.png
I've been using typegql with objection.js for a little while now and I have couple of getters in my codebase. Would it be possible to support them? Currently typegql is throwing when I try to put @Field
on a getter.
Hey,
do you have any recommendations to enable a Pagination as a result field?
I ran into some issues adding a return field to the Query type.
@Query({type: [Listing]})
async getAllListings(latitudeFrom: number, latitudeTo: number, longitudeFrom: number, longitudeTo: number, page: number, limit: number): Promise<{result: Listing[], total_count: number}> {
const [results, count] = await Listing.findAndCount({
where: {
latitude: Between(latitudeFrom, latitudeTo),
longitude: Between(longitudeFrom, longitudeTo)
},
cache: false,
take: limit,
skip: (page - 1) * limit
});
return {
result: results,
total_count: count
};
}
This code is resulting in the error message
"Expected Iterable, but did not find one for field Query.getAllListings."
The problem is maybe on the way to understand what i can pass as a type to the Query element. Is there any kind of documentation how to resolve this issue?
Greetings
Michael
Is there a best practice for generating typescript interfaces for a client? Apollo codegen uses either grapql files or typescript files with a gql tag.
Thanks!
the quote from:
https://19majkel94.github.io/type-graphql/docs/types-and-fields.html
Why function syntax, not simple { type: Rate } config object? Because this way we solve problems with circular dependencies (e.g. Post <--> User), so it was adopted as a convention. You can use the shorthand syntax @Field(() => Rate) if you want to safe some keystrokes but it might be less readable for others.
we tried with @Ivacko to import Before
and After
hooks, but we weren't able to run the code.
We tried both:
import { Before } from 'typegql/types/domains'
import { Before } from 'typegql'
in the first case:
Error: Cannot find module 'typegql/types/domains'
at Function.Module._resolveFilename (module.js:536:15)
at Function.wrappedResolveFilename [as _resolveFilename] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/newrelic/lib/shimmer.js:326:39)
at Function.Module._load (module.js:466:25)
at Function.wrappedLoad [as _load] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/newrelic/lib/shimmer.js:332:38)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/home/capaj/git_projects/looop/project-alpha/back-end/src/gql/commonSchema.ts:22:19)
at Module._compile (module.js:635:30)
at Module.m._compile (/tmp/ts-node-dev-hook-68135323187724.js:44:25)
at Module._extensions..js (module.js:646:10)
at require.extensions.(anonymous function) (/tmp/ts-node-dev-hook-68135323187724.js:46:14)
at Object.nodeDevHook [as .ts] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/ts-node-dev/lib/hook.js:61:7)
at Module.load (module.js:554:32)
at tryModuleLoad (module.js:497:12)
at Function.Module._load (module.js:489:3)
at Function.wrappedLoad [as _load] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/newrelic/lib/shimmer.js:332:38)
in the second:
TypeError: typegql_2.Before is not a function
at Object.<anonymous> (/home/capaj/git_projects/looop/project-alpha/back-end/src/gql/commonSchema.ts:55:15)
at Module._compile (module.js:635:30)
at Module.m._compile (/tmp/ts-node-dev-hook-68135323187724.js:44:25)
at Module._extensions..js (module.js:646:10)
at require.extensions.(anonymous function) (/tmp/ts-node-dev-hook-68135323187724.js:46:14)
at Object.nodeDevHook [as .ts] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/ts-node-dev/lib/hook.js:61:7)
at Module.load (module.js:554:32)
at tryModuleLoad (module.js:497:12)
at Function.Module._load (module.js:489:3)
at Function.wrappedLoad [as _load] (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/newrelic/lib/shimmer.js:332:38)
at Module.require (module.js:579:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (/home/capaj/git_projects/looop/project-alpha/back-end/src/gql/studentSchema.ts:13:24)
at Module._compile (module.js:635:30)
at Module.m._compile (/tmp/ts-node-dev-hook-68135323187724.js:44:25)
at Module._extensions..js (module.js:646:10)
How are you supposed to import it?
Note that we have
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
similar to what type-graphql has. It would be nice to be able to reuse input classes on frontend.
it is currently possible to register a Field
onto a class at runtime, by invoking manually like this:
const decoratorFunction = Field({
type,
name: relationName,
isNullable
})
decoratorFunction(prototype, fieldName)
however this is not possible with Arg
because for dynamically created method, we don't have any metadata. The compileFieldArgs
doesn't register any arguments, because calling
var inferedRawArgs = Reflect.getMetadata('design:paramtypes', target.prototype, fieldName);
obviously returns undefined and the whole compileFieldArgs
function just returns without doing anything more.
It would be great If we could have some kind of
{registerDynamicArgument} from 'typegql'
registerDynamicArgument(myClass, "myMethod", "myArgumentName", {
isNullable: true,
type: String
})
I have a field like this:
@Field({ type: UserEventModel })
async inviteUser(
@Context ctx: UserSessionModel,
userId: number,
attending: boolean,
sendInviteEmail: boolean = true
): Promise<UserEventModel> {
... some implementeation
}
when I run this, I get:
Error: Expected undefined to be a GraphQL nullable type.
at invariant (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/graphql/jsutils/invariant.js:19:11)
at assertNullableType (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/graphql/type/definition.js:327:51)
at new GraphQLNonNull (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/graphql/type/definition.js:294:19)
at enhanceType (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:635:21)
at /home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:662:25
at Array.forEach (<anonymous>)
at convertArgsArrayToArgsMap (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:653:14)
at compileFieldArgs (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:687:12)
at compileFieldConfig$$1 (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:760:16)
at /home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:787:39
at Array.forEach (<anonymous>)
at getAllFields (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:782:25)
at /home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:795:39
at Array.forEach (<anonymous>)
at compileAllFields$$1 (/home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:794:23)
at /home/capaj/git_projects/looop/project-alpha/back-end/node_modules/@capaj/typegql/lib/index.js:829:16
when I put a log into
function convertArgsArrayToArgsMap(target, fieldName, argsTypes, registeredArgs) {
the last 4 argument name's are:
argName: userId
originalType: Float
argName: attending
originalType: Boolean
argName: sendInviteEmail
originalType: Boolean
argName: [
originalType: undefined
of course when I remove the default argument value, it all works.
I am trying to prototype an application using typeorm and typegql to evaluate the feasibility of building a query efficient graphql api using this stack.
Given the following schema (Issue & User) :
@ObjectType()
@Entity()
export class Issue {
@Field({type: () => User})
@ManyToOne(
type => User,
user => user.createdIssues
)
@Index()
creator: Promise<User>;
// ... other fields
}
@ObjectType()
@Entity()
export class User {
@OneToMany(
type => Issue,
issue => issue.creator
)
createdIssues: Promise<Issue[]>;
// ... other fields
}
I would like that when a user makes a query like:
{
issues {
id
}
}
I perform a select only on the issues table.
And when a user makes a query like:
{
issues {
id,
creator {
name,
id
}
}
}
I perform a select query on the join of two tables.
What is the best approach for doing such kind of forward query resolution of find parameters based on user query ?
So far after digging around the source and some experimentation I have arrived at what I need using the @Inject
decorator:
@Schema()
export class SuperSchema {
@Query({
type: [Issue]
})
async issues(
@Inject((source, args, context, info) => {
const fieldNodes = info.fieldNodes;
if (!fieldNodes) return false;
return fieldNodes.some((fn) => {
if (!fn.selectionSet) return false;
return fn.selectionSet.selections.some((s: any) => s && s.name && s.name.value === "creator");
})
})
shouldIncludeCreator: boolean
) {
const repo = getRepository(Issue);
const relations = [];
if (shouldIncludeCreator) {
relations.push("creator");
}
const issues = await repo.find({relations});
return issues;
}
}
But I find this to be overly verbose for what I see a common use case in my application.
This can perhaps be simplified by extracting some utility functions but it will still be very repetitive when I have a dozen plus models, all exposed through the graphql api.
So I want to know if there is a better solution for this that I am missing out on ?
My familiarity with both typeorm and graphql is very limited (a few days of casual exploration).
I try
@Mutation({type: Number, description: ''})
public async addSkillsSwToProfile(skillsSwInputDto: [SkillsSwInputDto]): Promise<number> {
return 1;
}
and getting
Error: @type SavageWorlds.addSkillsSwToProfile(skillsSwInputDto <-------): Could not infer type of argument. Make sure to use native GraphQLInputType, native scalar like 'String' or class decorated with @InputObjectType
graphqlmastery.com writes use GraphQLList
I try
@OneToMany(type => Family, family => family.childhood)
@Field({type: () => Family})
family: Family[];
Error: @ObjectType Family.childhood: Validation of type failed. Resolved type for @field must be GraphQLOutputType.
I use typegql with objection.js model, on this model I have a Field like this:
@ObjectType
class WorkspaceModel extends TimestampedModel {
static tableName = 'workspaces'
@Field() readonly id!: number
@Field() user_id: number
@Field() name: string
@Field()
is_open: boolean
}
This is the WorkspaceModel object I am returning in my Query
:
{
"id": 3,
"is_open": false,
"name": "Workspace from test",
"openEnrolmentCount": 0,
"role": "OWNER",
"topicsCount": 0,
}
this is what I get from graphql:
{
"id": 3,
"is_open": null,
"name": "Workspace from test",
"openEnrolmentCount": 0,
"role": "OWNER",
"topicsCount": 0,
}
So I ran a test in my boilerplate an indeed using this schema:
@ObjectType({ description: 'Simple product object type' })
class Product extends Model {
@Field() is_open: boolean
}
@SchemaRoot()
class SuperSchema {
@Query({ type: Product, isNullable: false })
prod(): Product {
const product = new Product()
product.is_open = false
return product
}
}
when the field is true
it is returned correctly.
What's the difference between this library and type-graphql?
Is there a reason other than personal preference why Arg
decorator is not nullable by default same as Field
?
when I specify a field decorator such as this:
@ObjectType({ description: 'Simple product object type' })
class Product {
@Field()
async wow(): Promise<number> {
return 10
}
}
I get this error:
TypeError: undefined is not a promise
at Promise (<anonymous>)
at resolveType (/home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:227:24)
at inferTypeOrThrow (/home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:389:12)
at resolveRegisteredOrInferedType (/home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:607:12)
at compileFieldConfig$$1 (/home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:625:24)
at /home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:658:39
at Array.forEach (<anonymous>)
at getAllFields (/home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:653:25)
at /home/capaj/git_projects/tests/type-gql/node_modules/typegql/lib/index.js:666:39
at Array.forEach (<anonymous>)
Which is fine-you need to specify the type explicitly using {type: Number}
, but would it be possible to maybe at least print out a name of the field and the name of the class?
It's quite hard to find where the error is coming from if I have just added many fields
this is written often and is quite verbose to write. Compared to just @Field()
it's much lengthier.
If there was an alias such as @FieldNN
or maybe @NonNullField
it would definitely save me some reading/writing.
From the implementation perspective it's trivial and I think the saved characters are worth having such alias.
function inferTypeByTarget
just accesses a variable in order to infer a type. This is not compatible with getters, because it is invoking them just by accesing it.
Also when getters are invoked at runtime they are invoked on the prototype rather than from source
. This causes problems when getters try to access some instance field. Fix incoming.
Does it support custom scalars?
typescript supports mixins like this:
https://www.typescriptlang.org/docs/handbook/mixins.html
But this approach doesn't work with typegql decorators to enhance a class which should be a GQL object type.
It would be nice if ObjectType()
could take an array of mixins to apply before compiling the type.
Hi,
first, I will thank you for this awesome package. ๐
Currently we are start a new project and would like to implement this package (and give you feedback, if you like).
I think I found the first bug ๐.
My code:
import { Schema, Query, ObjectType, Field, Mutation, compileSchema } from 'typegql';
@ObjectType({ description: 'Simple product object type' })
class Product {
@Field()
isExpensive() {
return this.price > 50;
}
@Field() name: string;
@Field() price: number;
}
@Schema()
class SuperSchema {
@Query()
hello(): Product {
return new Product();
}
}
export const compiledSchema = compileSchema(SuperSchema);
Errostack:
return _super !== null && _super.apply(this, arguments) || this;
^
Error: @ObjectType Product.isExpensive: Could not infer return type and no type is forced. In case of circular dependencies make sure to force types of instead of infering them.
source-map-support.js:439
at FieldError.BaseError [as constructor] (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:280:42)
at new FieldError (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:710:24)
at inferTypeOrThrow (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:421:15)
at resolveRegisteredOrInferedType (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:641:12)
at compileFieldConfig$$1 (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:659:24)
at /home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:692:39
at Array.forEach (<anonymous>)
at getAllFields (/home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:687:25)
at /home/rene/Projects/jennyjs/node_modules/typegql/lib/index.js:700:39
at Array.forEach (<anonymous>)
Is this a Bug, or what I'm doing wrong?
Thanks
As well as I know, I can only get the context in @Before
decorator. How can I do something like this?
@Query({ type: [Provider] })
async providers({ context }): Promise<Provider[]> {
It might be useful to specify things that belong to the authenticated user (in the database query, where clause) or get user itself, like me{}
.
example error I get on the graphiql:
{
"errors": [
{
"message": "Cannot use GraphQLSchema \"[object Object]\" from another module or realm.\n\nEnsure that there is only one instance of \"graphql\" in the node_modules\ndirectory. If different versions of \"graphql\" are the dependencies of other\nrelied on modules, use \"resolutions\" to ensure only one version is installed.\n\nhttps://yarnpkg.com/en/docs/selective-version-resolutions\n\nDuplicate \"graphql\" modules cannot be used at the same time since different\nversions may have different capabilities and behavior. The data from one\nversion used in the function from another could produce confusing and\nspurious results."
}
]
}
Field
is nullable by default, whereas InputField
is not.
This is inconsistent and also it doesn't make much sense- at least in the API I am building. We have quite a few GQL calls like this:
mutation {
user(id: ${id}) {
patch(input: {organisation_role: "role"}) {
organisation_role
}
}
}
now if I wanted to edit another field on my user, for example email
:
mutation {
user(id: ${id}) {
patch(input: {email: "[email protected]"}) {
email
}
}
}
so what I end up doing is marking all of my input fields on User
entity as isNullable
anyway. I've got the alias I've made, but it would make it easier if typegql had input fields as nullable by default.
What do you think @pie6k ?
How to inject dependencies using Containers like typedi?
Is context good place to get things like di?
I've noticed a couple situations that return null rather than the expected value...
It seems to me that any falsy value is being resolved as null
.
currently I have this in my app:
@ObjectType()
export class OrganisationFeatures extends ConstructorAssigner {
@Field() campaigns: boolean
@Field() custom_email_sender_address: boolean
@Field() OKTA_SAML: boolean
@Field() search_analytics: boolean
@Field() SCORM: boolean
@Field() custom_font: boolean
}
@InputObjectType()
export class OrganisationFeaturesInput extends ConstructorAssigner {
@InputField() campaigns: boolean
@InputField() custom_email_sender_address: boolean
@InputField() OKTA_SAML: boolean
@InputField() search_analytics: boolean
@InputField() SCORM: boolean
@InputField() custom_font: boolean
}
I'd love to be able to define both at once doing:
@DuplexObjectType() // using a special type I can register both at once
export class OrganisationFeatures extends ConstructorAssigner {
@Field() campaigns: boolean
@Field() custom_email_sender_address: boolean
@Field() OKTA_SAML: boolean
@Field() search_analytics: boolean
@Field() SCORM: boolean
@Field() custom_font: boolean
}
I've got:
@ObjectType()
class TimestampedModel extends Model {
@Field({ type: GraphQLDateTime })
created_at: Date
@Field({ type: GraphQLDateTime })
updated_at: Date
}
@ObjectType({ description: 'Simple product object type' })
class Product extends TimestampedModel {
@Field({ type: String })
name: string
@Field({ type: Number })
price: number
@After(() => {
console.log('arguments', arguments)
})
@Field()
get wow(): number {
return 10
}
@Field() is_open: boolean
}
@ObjectType()
class UberProduct extends Product {
@Field({ type: String })
name2: string
}
@ObjectType()
class ExtraUberProduct extends UberProduct {
@Field({ type: String })
name3: string
}
@SchemaRoot()
class SuperSchema {
@Query({ type: String })
hello(name: string): boolean {
return false
}
@Query({ type: ExtraUberProduct, isNullable: false })
prod(): ExtraUberProduct {
const product = new ExtraUberProduct()
product.name = 'name'
product.price = 5
product.is_open = false
return product
}
}
const schema = compileSchema({ roots: [SuperSchema] })
console.log('schema: ', printSchema(schema))
the output is:
schema: type ExtraUberProduct {
name2: String
name3: String
}
type Query {
hello(name: String!): String
prod: ExtraUberProduct!
}
I expect it to be:
schema: type ExtraUberProduct {
created_at: DateTime
updated_at: DateTime
name: String
price: Float
wow: Float
is_open: Boolean
name2: String
name3: String
}
type Query {
hello(name: String!): String
prod: ExtraUberProduct!
}
currently if you want to handle dates correctly for a field, you need to explicitly use a scalar, like this:
@Field({ type: GraphQLDateTime })
created_at: Date
since this is very common-it would be great to enable this behavior by default, so you can omit the explicit type:
@Field()
created_at: Date
Do you currently have any plans for adding support for GraphQLInterfaceType?
I found no way to make arrays / graphql lists work. Aren't they supported or did I just not get it?
Constructor function is not called
// Import statements
@Schema()
class RegistrationSchema {
constructor (private registrationService: RegistrationService) { }
@Query()
hello(me: string): string {
return 'hello world!';
}
@Mutation({ type: RegistrationType })
async registerUser(@Arg() newRegistrantData: RegistrationInputType): Promise<RegistrationType> {
const errors = await validate(newRegistrantData);
if (errors.length) {
throw SevenBoom.badRequest('Data sent to the sever failed validation', errors);
}
try {
await this.registrationService.registerUser(newRegistrantData);
return newRegistrantData;
} catch (error) {
throw SevenBoom.badImplementation('Internal Server Error', error);
}
}
}
const compiledSchema = compileSchema(RegistrationSchema);
export default compiledSchema;
Expected value of type "User" but got: [object Object]
@Query()
hello(): User {
return {
name: 'Dave'
};
}
Scratching my head on this. I think I've narrowed it down to the function expects an actual instance of the User object and not an anonymous object. Is there a better way to do this?
If an instance of the class does need to be returned do you need copy properties into an instance like below to make sure the types are correct?
Object.assign(new User(), userShapeFromDb)
As @guard decorator is in fact normal @before hook that simply throws error or not, I'd consider it not needed abstraction that might be useless and confusing for some people use-cases.
It would be also very easy to create custom 'Guard' with 'Before' hook without any limitations coming from abstraction layer.
Reference:
typegql/src/domains/hooks/index.ts
Line 35 in df69cd8
Currently if I return a class from @Mutation()
and @Query()
and I want to have a field like patch
(
) on the class, this field is exposed on mutations and on queries as well. I'd like to avoid this-hence the new decorator. This could be achieved with a decorator config like @Field({mutationOnly: true})
or @Field({query: false})
, but IMHO it's own decorator is more explicit way.
I was trying to merge 2 different GraphQLSchema created by compiling 2 different typegql schemas using the standard mergeSchemas function from the graphql-tools. After that, when I try to start the server and hit the /graphql endpoint, I get an error saying:
{"errors":[{"message":"GraphQL middleware options must contain a schema."}]}
Is this something that should work as I expect it would? I just took the schema in the "basic-express-server" example and the schema in the "nested-mutation-or-query" example and tried to merge them like so:
import { schema, schemaAdv } from './schemas';
const compiledSchema = mergeSchemas({schemas: [schema, schemaAdv]});
const app = express();
app.use(
'/graphql',
graphqlHTTP({
compiledSchema,
graphiql: true,
}),
);
app.listen(3000, () => {
console.log('Api ready on port 3000');
});
Even though the server starts up just fine, trying to navigate to the /graphql endpoint throws the above error.
If this is not the supported way to merge schemas, is there any other way? Any pointers?
Thanks,
Shani.
There seems to be an issue with the types namely services/error
and services/utils
.
I've include a project to duplicate the issue here andrew-w-ross/typegql-issue
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.