sling_gql_gen
A Dart CLI code generator: turns a GraphQL introspection JSON document into
typed Accessor classes for the sling_gql
runtime. Docs: https://tpucci.github.io/sling_gql/
Usage
Add it as a dev dependency of your Flutter app and run it against your schema's introspection JSON:
flutter pub add --dev sling_gql_gen
dart run sling_gql_gen \
--schema graphql/schema.json \
--out lib/generated/schema.dart
(Inside this repository, the example app runs the same command as
dart run ../packages/sling_gql_gen/bin/sling_gql_gen.dart …, or
melos run generate.)
This writes lib/generated/schema.dart and formats it with dart format
(a formatting failure is logged but does not fail the run). The generated
file starts with a header comment explaining the $-rename scheme
(type$, $typename, $eq, ...) in one place, so a reader never has to
go digging for why an identifier looks the way it does.
Flags:
--schema(one of--schema/--endpoint): path to the schema. A.jsonfile is read as introspection JSON, i.e. the standard{"__schema": {...}}result of the introspection query; any other extension as SDL (schema.graphql), e.g. the schema file a backend keeps in its repository. Both produce the same output.--endpoint(one of--schema/--endpoint): GraphQL URL to introspect instead;-H/--header(repeatable) adds HTTP headers to that request (-H "Authorization: Bearer …").--out(required): path of the Dart file to generate. Parent directories are created as needed.--runtime-import(optional): overrides thesling_gqlimport in the generated file. Defaults topackage:sling_gql/sling_gql.dart.--key-field(optional, defaultid): the field that makes an object type a normalized entity (see below).--scalar(optional, repeatable): custom scalar mapping,Name=DartType[:converterExpr].--scalar DateTime=DateTimeuses the built-inDateTime.parse/.toIso8601String()converter; aDartTypeother thanDateTimeneeds a converter, e.g.--scalar Money=Decimal:MoneyConvertercallsMoneyConverter.parse/MoneyConverter.serialize(a class with those two static methods you provide). Scalars without a--scalarflag keep the default in the table below.
What it generates
class Query extends Accessorfor the schema's query root type, withQuery(super.recorder, super.selection, super.path);andQuery.root(Recorder r) : super(r, r.root, const []);constructors.MutationandSubscriptionroots get the same shape, plusextension SlingMutations(client.mutate(...)) andextension SlingSubscriptions(client.subscribe(...)), and theslingSchemaconstant bundling the roots and--key-fieldforSlingClient(schema: slingSchema)(the client's cache then normalizes on the same key field; no hand-keptNormalization(keyField:)), andhash:— a 64-bit FNV-1a of the generated file itself, so it changes whenever the schema,--key-field, a--scalarmapping or the generator changes the output, and is the same on every run and platform; andfields:— every field of every object type with its signature ('(id: ID!) Launch','[String!]!'; the roots asROOT_QUERY,ROOT_MUTATIONandROOT_SUBSCRIPTION, interfaces and unions without fields), aconstmap at the end of the file. Persistence adapters (sling_gql_sqflite) migrate a store written under another hash alongfields, keeping what the new code can still read. Andenums:(each enum's values) andpossibleTypes:(each interface's and union's concrete types), whichsling_gql_test'sMockGraphQLServer(schema:)uses to fill the fields a test leaves out.- One
class <Name> extends Accessorper otherOBJECTtype (skipping the operation roots and introspection__*types), with a getter per argument-less field and a method (named optional /requiredparams) per field with arguments. Scalar and enum fields without arguments also get a setter for optimistic writes, except where a write could never be right: the key field (--key-field, defaultid) of a keyed type (writing it would corrupt the entity key), every field ofPageInfo, andtotalCount/pageInfoon connection-shaped types (those withpageInfoplusnodesoredges). - One
class <Name> extends AccessorperUNIONandINTERFACEtype: the interface's own fields as above, one getter per possible type (Launch? get asLaunch => on('Launch', Launch.new, keyed: true);, the inline fragment... on Launch,nullfor another type) andT? when<T>({T Function(Launch launch)? launch, …, T Function()? orElse})dispatching on__typenamethroughAccessor.whenType(every branch runs on a skeleton, so one request covers all of them). An interface declaring the key field is keyed like an object type (never alookup). A getter clashing with a field, or a parameter with a keyword, another parameter,orElseor its own type, gets a trailing$. enum <EnumName> { value('VALUE'), …, unknown('') }for eachENUMtype. Constants are the lowerCamelCase of the wire name (PARTIAL_FAILURE→partialFailure);graphqlNameis the wire value,fromGraphQL(String)maps a wire value back (returningunknownfor a value added to the schema after generation, so aswitchstays exhaustive), andtoGraphQL()is what arguments and input fields serialize with — it throws anArgumentErrorforunknown. Enum fields are read throughAccessor.enumValue/enumList(same miss/skeleton semantics as scalars) and their setters write the wire name. Constants that would clash with a Dart keyword or an enum member (unknown,values,index,name, …) get a trailing$:UNKNOWN→unknown$.class <InputName> { const <InputName>({...}); ...; toJson() {...} }for eachINPUT_OBJECTtype.extension SlingCacheAccess on CacheScope<Query>with one method per keyed type (anOBJECTwith a scalar--key-field), returning the cached entity ornullwithout ever fetching:Launch? launch(String id) => entity('Launch', id, Launch.new);— used asclient.cacheScope.launch('launch-181'). The method is the type name with a lowercase first letter; a clash with a Dart keyword, aCacheScopemember (query,list,entity,evict, …) or another type's method gets a trailing$(Entity→entity$). Omitted when no type is keyed.
All getters are nullable (T?, R?, List<R>?) regardless of the schema's
non-null markers, since cache data may be missing — see
packages/sling_gql/lib/src/accessor.dart.
Development
cd packages/sling_gql_gen
dart pub get
dart analyze
dart test
Versioning
From 1.0 sling_gql_gen follows semantic versioning and shares its major
version with sling_gql. Public: the CLI flags and the shape of the
generated code, and the library's generate, generatedCodeHash,
IntrospectionSchema (with its Gql* / TypeRef types), ScalarMapping
and introspectionQuery; src/ is not. Code generated by 1.y runs on
sling_gql 1.x for every x ≥ y; when a minor emits code that needs a newer
runtime, its changelog names the minimum sling_gql version. Deprecated APIs keep working for at least one minor release and are
removed only in the next major. Full policy:
Versioning & deprecation; coming from 0.x:
Upgrading to 1.0. Security issues:
SECURITY.md.
Libraries
- sling_gql_gen
- Code generator: turns a GraphQL schema (introspection JSON or SDL) into
sling_gql-compatible typedAccessorclasses.