#Erik Sipsma3294 Semi available this
1 messages · Page 1 of 1 (latest)
Awesome, yeah I just got back from lunch after the demo and started looking at gqlgen again (or replacing it, we'll see)
đ
I'll keep you updated.
Another thing in the meantime is that since yarn doesn't use any codegen at all right now you could check to see that it can still successfully call alpine+core, just as another verification
Oh yeah, nice
Need some light cleanup of the proxy thing, but basically I renamed remoteschema to extension and inside now there's an API proxy
so e.g.
schema, err := extension.Load(p.Context, r.gw, r.platform, obj)
if err != nil {
return nil, err
}
if err := r.router.Add(schema); err != nil {
return nil, err
}
and in engine
solveOpts := bkclient.SolveOpt{
Session: []session.Attachable{
secretsprovider.NewSecretProvider(secretStore),
extension.NewAPIProxy(router),
},
}
so everything about the "runtime protocol" is neatly tucked in the extension package
and the router is buildkit unaware
also there's a secret.Store -- moved the hashing logic in there, with an AddSecret so it's ready to add new secrets by API whenever we want
(we could probably change the cloak CLI to load secrets through the API, rather than passing them as EngineOpts)
Semi-related, and low conviction: if/when we get rid of gqlgen, maybe it'd be a good time to try and make creating an extension "semi pleasant" without codegen -- then add codegen on top?
Nice! How did it go?
Good! Got a ton of really helpful feedback. The whole GraphiQL part went smoothly too
(we could probably change the cloak CLI to load secrets through the API, rather than passing them as EngineOpts)
Yep, will make sense especially when we add a newcreateSecretor whatever we call it api tocore.
Actually it would in theory be possible to do the same thing with localdirs (i.e. they could be registered with the session provider after start), which would be nice at least for consistency and possibly for actual functionality (i.e. host client can decide after the engine starts which localdir to provide based on output from other actions).
The only problem is that buildkit's go client hides all that in private methods, so has to be done in roundabout way. Probably only makes sense to support it once we split up cloak client+server into separate processes (when that will all have to change anyways)
All the changes look great though. I'm sad that the way I'm currently trying to hack gqlgen is going to be extremely ugly (if it works at all) since all the code is starting to look good now, but we'll fix it up once we switch to a custom model
For this, agree, the current way it works without codegen is that you provide a map of resolver name->callback: https://github.com/dagger/cloak/blob/2c61557c38c5a9f1f4b820235bb838c0c68803c4/sdk/go/dagger/server.go#L18
It's ugly cause of the ArgsInput, which is formatted to appease the code generated by gqlgen, but we can clean it up once we move away, at which point it could be borderline pleasant to skip the codegen (as long as you are okay with dealing with interface{} and casting yourself)
Good to know for the demo!
Yep, that would be neat, we'd probably need to be able to distinguish "calls" so we'd restrict who can add local dirs (e.g. you don't want random extensions to grab /home)
for gqlgen it's fine, we just need to get us through the next couple of weeks until we have our own thing
Yep, we could make the API cleaner, can be incremental. Maybe when we redo the codegen, if we start without codegen, it'll force us to have a nicer SDK. It's also good to split the problems of SDK/codegen -- ideally we focus on having a clean SDK and we get some help with the codegen since it's not sustainable to own all of this
Yep, that would be neat, we'd probably need to be able to distinguish "calls" so we'd restrict who can add local dirs (e.g. you don't want random extensions to grab /home)
Yeah my thinking is that either this would be through a "privileged" call (i.e. the schema that makes it available is only presented to host clients) or maybe when another extension uses it, the "localdir" actually refers to their filesystem (i.e. in the container). The second use case is a bit odd but might be some use cases? Just thinking out loud
So today I'm a bit busy with meetings, but basically my plan for the next couple of days is:
- finish some minor cleanup
- get this branch to work & merge (need help with this)
- ideally, write ONE test. it's the trickiest since it needs a pattern+utils. But at least we have a starting point to add new ones
Yeah, TBD ... I'm still not sure of the best pattern going forward (e.g. "todoapp" is an extension vs "todoapp" is a program)
if it's a program the problem is easier since there's an "inside API" and "outside API"
Anyway, have to run, I'll check my messages
Sounds great, going to try to go into focus mode with this gqlgen stuff to get it over with, we'll see what comes out later tonight...
Actually found a decent enough path for now (I'll be able to sleep at night). Required lots of customization of the plugin+template but now this schema:
extend type Query {
alpine: Alpine!
}
type Alpine {
build(pkgs: [String!]): Filesystem!
blah: Foo!
}
type Foo {
bar(baz: String, abc: String!): String!
}
results in this generated code:
package main
import (
"context"
"encoding/json"
"github.com/dagger/cloak/sdk/go/dagger"
)
func (r *alpineResolver) Build(ctx context.Context, pkgs []string) (*dagger.Filesystem, error) {
panic("not implemented")
}
func (r *fooResolver) Bar(ctx context.Context, baz *string, abc string) (string, error) {
panic("not implemented")
}
type alpineResolver struct{}
type fooResolver struct{}
func main() {
dagger.Serve(context.Background(), map[string]func(context.Context, dagger.ArgsInput) (interface{}, error){
<more autogenerated code...>
Plenty of messy details left but seems good enough to get us by for now. Next up is to try genqclient...
(note partially to self: this still doesn't handle the case where multiple subresolvers have the same field name, need to provide path as input to runtime rather than just name of field)
Turns out genqlient seems to work fine so far, the only part requiring effort has been to re-add the ability to get operations from loadExtensions, but have that going now too.
Do you mind if I push to your branch? Want to push this once I finish up (only last big chunk is to try the ts client codegen w/ the new setup). I also found a couple small misc things to fix (need to re-add llb.UniqueLocalID to the marshalling of local ops, or else we'll have caching problems again)
Yep please go ahead
I just pushed some other changes -- cleaned up engine and a few other things
moved secrets loading into the CLI, engine is pretty dumb now
Don't think it'll conflict
I was in the middle of other changes but will continue tomorrow. I was trying to make engine "the way we'll want in the future" (e.g. pretend it'll only start an engine instance somewhere, nothing more), so the only thing it returns is a client to the now-running engine
even for cloak dev -- realized the only thing we need is an HTTP client to cloak engine, the CLI itself can proxy to it. There's this nifty httputil thing:
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: "fake.invalid",
})
proxy.Transport = client.Transport
return http.ListenAndServe(fmt.Sprintf(":%d", devServerPort), proxy)
So truly, the only contract we need is for engine.Start() to somehow figure out how to start an engine (currently using the built-in in go code, could be fork-exec from TS, could be in a container somewhere, we don't care) and return an http.Client. The rest can be handled by the caller, and this approach works in any language
(for tomorrow)
Pushed the codegen changes. The codegen is actually all working fine I think, but I'm running into what I am suspecting might be a bug with the way the new router sets up default resolvers.
Actions like alpine work, but right now todoapp outputs this everytime:
{
"todoapp": {
"deploy": {
"deployUrl": "{}",
"url": "{}"
}
}
}
I verified w/ println debugging in remoteSchema.resolve that netlify is being invoked and actually returning the right data to the resolver with the correct fields and urls filled out. It just somehow gets dropped when returned to the client. The queries being run by the generated client of todoapp also look correct, the low level client there makes valid looking queries and receives the same raw data where fields are set to "{}" (so it doesn't appear to be an unmarshalling issue).
My best guess is that this has something to do with the way resolvers are setup here: https://github.com/dagger/cloak/blob/6b031462c34204a81274dfd74305e5fb53b96dc7/extension/extension.go#L111-L111
That would explain why the raw output would should deployUrl and url as the value "{}", which otherwise seems odd.
Not at all sure about any of this course, most likely missing something obvious... But been stuck on this bug for a few hours now, so hopefully it becomes obvious what's wrong after sleeping on it.
Actually yeah, I just temporarily changed this line: https://github.com/dagger/cloak/blob/6b031462c34204a81274dfd74305e5fb53b96dc7/extension/extension.go#L111-L111
to be
return "", nil
and now the output of todoapp deploy is:
{
"todoapp": {
"deploy": {
"deployUrl": "",
"url": ""
}
}
}
So there must be something wrong there. Still haven't fully pieced it together
Actually that last message gave me an idea and I managed to fix it: https://github.com/dagger/cloak/commit/50d858ea2548b3a9ee0d5f999e95da47e7854637
I basically just explicitly wrote out what the implicit resolver does there.
This sort of makes sense now that I think about it. The thing that caused me extreme confusion (and actually still does), is that we have to use p.Info.FieldName to get the name of the field. If you use field.Name.Value then it will always be set to just a single field (i.e. it will always be logsUrl even for the resolver setup for url and deployUrl).
This must have something to do with the closure and use of pointers, but it actually still doesn't add up to me at the moment. Either way, that was just incredibly confusing for my println debugging, whatever the cause it seems like using p.Info.FieldName will suffice for the moment.
Oh that was a silly workaround I put in place, forgot to add a FIXME (the return {}). The problem I had was that query { alpine { build } } was not working because there was no alpine resolver so I just put {} temporarily, sorry you wasted time on this
Although I wasnât sure what the solution would be â catching up on your messages but looks like you found it
The confusing bit for me was that trivial resolvers are taken care of (e.g. deployUrl) and we shouldnât put any â however alpine actually does need a resolver
re: field.Name.Value â I think thatâs just the closure and the fact field comes from the for loop iterator
Doing field := field inside the for loop should take care of the it
Looking at the fix again â that makes sense, itâs basically a âtrivialâ resolver
What Iâm not sure about is, I believe, not putting any resolver at all should achieve the same result (graphql-go should be taking care of that with its own trivial resolver)
What I donât understand is what kind of condition we should use to figure out whether we should return {} (e.g. for âalpineâ), nothing (trivial resolver), or our own trivial resolver
It works now but I kinda want to understand this part since itâs pretty crucial
-
Is the root problem/solution that alpine should have provided a resolver for alpine, rather than me trying to fix it at the remote schema level? For instance this assumption breaks if this were alpine: String! rather than alpine: Alpine!. However itâs pretty tedious for the user to have to do that
-
If the above was the case, then we wouldnât need to put any resolver for non-delegated fields (i.e graphql-go would be taking care of it)
-
Side discovery: The solution you found is actually super interesting and gave me an idea: couldnât we use what youâve done instead of the âheuristics/directivesâ? Basically we put a resolver on every single field. If the parent contains the field, we use it (trivial). If not, we invoke the action. Which means the extension doesnât need to specify what has a resolver or not, they either return the data directly or theyâll be explicitly called for it. Maybe itâs cool, maybe itâs too magic
No worries at all, it's a tricky issue we had to figure out eventually.
Looking at it with fresh eyes now, I believe the issue here and the reason that we didn't encounter this in the previous iteration is that the logic of if len(field.Arguments) == 0 is checked against every field, including "leaf" fields that return scalars (like url and deployUrl).
That results in us setting an explicit resolver (return struct{}{}) for those fields, which means the trivial resolver will not kick in. In the previous iteration, we didn't hit this because we only set explicit resolvers for "top-level" fields right under Query (the hacky behavior we correctly moved away from).
I think what I wrote does work correctly+consistently, but one alternative would have been to use return struct{}{} only for fields that return objects and skip it whenever a scalar is being returned. That alternative is more complicated though, so probably shouldn't switch to it; just making a note.
If the parent contains the field, we use it (trivial). If not, we invoke the action.
I see the appeal but agree it might be too magical. For instance, if the user defines an explicit subresolver for a field, it's now conditional on the parent (or any ancestor really) whether that subresolver will be called.
From my limited experience looking at graphql examples, it seems like the more typical pattern is that the explicit subresolver will always be called but it will have access to the parent return value and can use that if it chooses. That is slightly more appealing just because it's more simple+consistent. Worth more thought though
Right... I didn't notice it last night but those vars are from for loops... đ
Looking at it with fresh eyes now, I believe the issue here and the reason that we didn't encounter this in the previous iteration is that the logic of if len(field.Arguments) == 0 is checked against every field, including "leaf" fields that return scalars (like url and deployUrl).
Right. I'm wondering about this:
- Is the root problem/solution that alpine should have provided a resolver for alpine, rather than me trying to fix it at the remote schema level? For instance this assumption breaks if this were alpine: String! rather than alpine: Alpine!. However itâs pretty tedious for the user to have to do that
I'm noticing some weirdness on the branch (can't get alpine loading properly), are you seeing this too? Might be some of my local stuff but I stashed and I think I'm seeing the same
No I'm successfully executing alpine now (and todoapp, etc.).
I was just adding support for handling what I mentioned before where multiple subresolvers have the same name, but I'll stash and try everything fresh from the resolver branch
huh, I think I might have a cache issue, I don't understand how. I'm seeing the old schema for alpine when I printf, but it's definitely the new one in the file
Yeah this works for me on the resolver branch, no local changes:
cd examples/alpine
cloak query <<'EOF'
{
alpine{
build(pkgs:["curl"]) {
exec(input: {args:["curl", "https://dagger.io"]}) {
stdout(lines: 1)
}
}
}
}
EOF
Where are you printf'ing the schema from that you see the old one?
sdl, err := fs.ReadFile(ctx, gw, schemaPath)
if err != nil {
return nil, err
}
fmt.Fprintf(os.Stderr, "SDL: %s\n", string(sdl))
SDL: type Query {
build(pkgs: [String!]): Filesystem!
}
$ head -3 ./examples/alpine/schema.graphql
extend type Query {
alpine: Alpine!
}
went straight to the source, extension.Load
Yeah that's weird, I just added that line and get in the output:
SDL: extend type Query {
alpine: Alpine!
}
type Alpine {
build(pkgs: [String!]): Filesystem!
}
so either I'm doing something very silly, or my cache has gone horribly wrong
Same problem after a git stash though
So it's potentially the latter
Yeah I just changed alpine's schema file locally and then re-ran the query, I see the change picked up in the Fprintf:
SDL: extend type Query {
alpine: Alpine!
}
type Alpine {
build(pkgs: [String!]): Filesystem!
foo: String!
}
so the cache is getting invalidated as expected for me
Yeah, re-created the buildkit container and now it works
That's mildly concerning, but at least it works...
Yeah not sure what else to do for now...
You had this commit right?
Technically could be related
yeah made sure to re-pull
https://github.com/dagger/cloak/commit/391ccfc367a83a356c7cff52c95642c2e85991dc
Ok so I've split "stitch" into Merge() and compile. Merge does all the merging of the schemas using our types, and finally compile takes an already merged schema and translates that into a graphql.Schema using graphql-go-tools
So basically now core is merged as one
so now we can use this to dump extension schemas from stubbing (before core was a bunch of individual schemas)
Oh nice! That should let me fix this todo: https://github.com/dagger/cloak/blob/1ecbdf6e8c2ebbeec581c2f626d7d20576cbdddc/cmd/cloak/config/config.go#L241-L241
Will also need the same thing for operations, but I'm guessing that'll be trivial for me to add too
I have the fix to provide paths to runtimes instead of just field names (the corner case mentioned before) working, was thankfully very straightforward actually. Just need to go re-run generation on all the packages and test, will push in a bit
At that point I feel like the router branch should be pretty much ready to merge? All the extensions are working again, no regressions in terms of dx. Is there anything else you think needs to happen first?
Yep, I saw that one, that's what this commit is about
and yeah it merges operations too, so we're good on that front as well
There's the point about resolver mapping / trivial resolvers, I'm still unsure about
Yeah so, just to double check, the approach you're imagining is that user extensions would have to define, e.g.
func (r *queryResolver) Alpine(ctx context) (*Alpine, error) {
return &Alpine{}, nil
}
Whereas today that can be skipped and only the explicit resolvers need to be defined. Then the cloak server just always calls the resolver, doesn't have to have special handling
I think that makes sense, obviously the codegen needs to handle that so users don't end up with more handwritten boilerplate, but I think it should be relatively straightforward?
The way I did it (return {}) is a hack. I don't know if we want that, specifically, but something along those lines to remove the hardcoded part
for instance, my hack won't work with this:
extend Query {
debian: Filesystem!
}
this would return a broken filesystem
seems safer to just not do any magic here, let the trivial resolvers do what they want, and we rely on the remote resolver logic to take care of this
Yep, makes sense. I guess there's a very small performance decrease in some cases but far too early to worry about that. Let me finish the current update to pass paths to the runtime, then I can give this a try quick. It might not be that much of a change to the gqlgen plugin
in terms of conditions though there's 2 options:
- every field with arguments AND every field under query AND every field with a specific directive (e.g.
exitCode: Int! @resolve) - using the pseudo-magic logic based on the trivial resolver you wrote (e.g. we invoke the action when the field is not present). So in this case "alpine" would trigger since there's none in Query, but deployUrl wouldn't because it's already returned by deploy
cool!
Yeah the other alternative would be a bit of magic ...
if it's under Query then make up a "root resolver" based on the field type
that works too I guess
core { extension(name: "core") { schema } }
for now -- loadExtension takes a name and we use this to index them
I think we might want for that name to come from the manifest itself instead, but idk. Works for now I guess
And I think now we can remove api/ altogether
Okay just finally found a way to convince gqlgen to do this (there's a non-obvious way to distinguish fields under query that are from the current schema and from other external ones that were imported), so now the alpine schema results in this being generated:
func (r *alpineResolver) Build(ctx context.Context, pkgs []string) (*dagger.Filesystem, error) {
panic("not implemented")
}
func (r *queryResolver) Alpine(ctx context.Context) (*Alpine, error) {
panic("not implemented")
}
type alpineResolver struct{}
type queryResolver struct{}
<more generated code...>
Just need to switch the default implementation from panic to return <the correct empty type>, nil and should be good for this
Awesome thanks! I rebased and everything looks good so far
Also side note: the runtime protocol is not passing parent objects to subresolvers yet. I'm okay with this very temporarily, not worth blocking the merge of router on it, but will be an important immediate follow up
was going to make an issue but apparently I thought of this several days ago too? https://github.com/dagger/cloak/issues/32#issuecomment-1206851355
is this still required?
last dependency on api
==> side not for later, but maybe we should stop generating stubs per extension. A single generated file for the entire API would work, I think?
api.Core.Image(...)
api.Alpine.Build(....)
On our end it would simplify things greatly -- no need to know schemas individually, no "grafting" schemas together, tools can use the regular introspection API to generate
On the DX side, everything could be in a single dagger.gen.go or whatever, no need to have a hierarchy
Part of the DX "sprint" I guess, just wanted to put it out there
Yeah we still need to include core schema, but we don't have to obtain it from that api package, can update to grab it the same way we do in config.go now
We need core schema so that user actions can e.g. return Filesystem!
it's still a bit of a hack, the more general solution is that all the types of all dependencies should be available
but can do that in a next iteration
this is when generating client stubs? yeah I mean whatever is easiest there. having different deps imported from different packages sort of helps with the namespacing problem, at least in go, but it doesn't totally solve so yeah we should think about it
yeah sounds good
I think for instance it would help also with all the types of all dependencies should be available
otherwise we'd have to cross reference packages, or duplicate types
whereas one-shot we avoid all of that
now I finally got the gqlgen plugin to replace panic(implement me) with returns of zero values of the return value by default, e.g.
func (r *queryResolver) Alpine(ctx context.Context) (*Alpine, error) {
return new(Alpine), nil
}
So now I can try getting rid of the commit i pushed last night and see what happens
yep that makes sense I think
might also help with chaining, if we go "there" (e.g. chain a yarn to a filesystem): api.Core.Git(...).Yarn(...)
makes a ton of sense in typescript (e.g. import { core, yarn } from "./dagger.gen.ts")
maybe in Go it's nicer to split them up in packages, but that should be a concern of the generator (as in, generator always works in "bulk" for the whole API, but could decide to split in folders)
so there's no dependency analysis, no grafting core, etc -- one schema, one generated output (either single file or multiple folders if it makes sense for a given language)
anyway, I think we're just missing this and then ready to merge
i was about to do it but then got stuck with func (plugin) InjectSourceEarly() which is also using coreschema
btw -- ok if I remove cmd/demo and cmd/test? They're broken now, vscode keeps complaining
Yep, time to say goodbye
sounds good, I just successfully invoked alpine with this whole if block deleted https://github.com/dagger/cloak/blob/94f81909c96b35af32fd468b3df528c62484d66b/extension/extension.go#L109
But I also just realized there's one more complication when you have a resolver that returns an object but you want the trivial resolver: in order to do that from the user action you need the parent object. So that actually will be an immediate requirement to finish this up
I'm gonna just try it really quick, the codegen might be annoying but may as well just get it over with
But don't need to block the merge on any of that
Yep yep, or it can wait
i mean, the merging can wait
or we can merge right away actually, since it works
wdyt?
no just merge it now, it works, I'll push this to main separately
done!
{
alpine {
build(pkgs: ["jq", "gcc"]) {
exec(input: {args: ["echo", "foo"]}) {
stdout
}
}
}
}
and this works
okay including the parent was pretty easy actually, just ran go todoapp with those changes successfully, so just gotta update a few ts actions and should be good to go there too now
Ok so I'm back into a branch already
Just wanted to double check something with you before merging, not sure if overkill
I wanted to add a simple test for core, ended up being heavier than I thought: https://github.com/dagger/cloak/commit/bb469e299addce135a6e1a0b07404c57b97b1378
basically there were import cycles (engine needs core, core needs engine for tests) so I put schemas under core/schema and added an init function where each each registers to core
slightly overkills things but I don't see another way around, except if the SDK was magically able to talk to cloak without needing engine
I'm not opposed to that, the alternative I can think of is to just put those tests in their own package like /integtest or /internal/integtest or whatever. I realize they are meant to just test core, but since they do involve the engine too then they end up being more like integ tests
which isn't a bad thing
but up to you, I don't have strong feelings on it. init is nice to avoid I guess but not a big deal at all
Yeah, I don't think we can do "non-integration" tests ... core schemas depend both on buildkit being bootstrapped, and also on the graphql-go stuff (e.g. can't call those functions directly)
I'm personally fine with that. All else being equal I prefer integ+e2e tests. Obviously in the real world all else is never equal and sometimes unit or smaller scale tests make more sense. But in this case I think it's fine at least for now
okay that change to pass parent objects and remove resolver heuristics looks good, everything still working, pushed it: https://github.com/dagger/cloak/commit/29fb5d7836bc0f874bc9c314dbcbf3d8071c2051
The heuristics in extension.go are gone now and each user extensions has every resolver explicitly defined. For go, the trivial resolver is the default implementation filled in by the generator (replacing panic(implement me)). For TS, since we don't have implementation stub generators yet, its some extra boilerplate, which is annoying but will be fixed when we start doing codegen for that too.
Actually on that note, that's one of the things I want tackle in the very near future: adding codegen for TS implementation stubs. I want to do it by modeling it as an extension; that way it can serve as a base for starting to convert all the rest of our codegen over to extensions + a common framework
awesome
yeah, we should probably look at the future of stubbing, like, we we want to find an existing library, do our own, etc
I think I'm good with init -- it's actually pretty nice that schema files are self contained (I kept forgetting adding them in the merge list)
added a testutil so we don't have to refactor every single test if we change the engine's API or use a different graphql client library
what's up with docstrings btw, is it ok if we add them or does it mess up with codegen?
Cool that sounds good to me
We can try adding them again now, the problem last time wasn't codegen, it was tools.MakeExecutableSchema
It would sometimes, but not always, return a schema with empty typemap
but no error
ah crap
I mean it's worth a try again
I was wondering at the time if the fact that we were assembling the string on the fly sometimes created actual problems where it wasn't parseable
we should get an error in that case of course, so it would still be a bug in tools, but less so i suppose
does main still work for you?
Error: failed to solve: input:2: Variable "$id" cannot be non-input type "String!".
getting this, not sure where it's coming from
That's what happens when tools.MakeExecutableSchema starts outputting empty typemaps
main worked for me on the commit on most recently pushed
I stashed and i get the same error
just tried running after pulling the two commits pushed most recently and am getting the same thing.
when I reset back to my most recent commit it works again (git reset --hard 29fb5d7836bc0f874bc9c314dbcbf3d8071c2051)
ah dang, we probably have a conflict that didn't quite conflict
I just did a rebase where I removed my commit and left the two most recent ones and I get the same error
Oh. I think I only tried the unit tests, and they still pass
so my branch was probably always broken, with functioning tests
These errors are the worst because there's zero indication from tools what the problem is. It just gives you 1 bit of information
In the past for me, it ended up being:
- the doc strings
- referencing a type in my stitched schema that didn't exist
- referencing a type in my stitched schema that didn't have a resolver associated with it
looking into it
not obvious where it's coming from, the change I made modified the structure of core, but i didn't change a single query/resolver
ah! got it
damn
silly problem
the init's are not being called because I'm not importing the pacakge. my tests worked because they run in the same package, so it triggers init
so core is empty
the thing is, if i do import them, then back to the cyclic dependency problem
ok so, got to split into an integrarion test package
Q: should I leave the schemas as is (e.g. in their own core/schema thing) or was it better before?
Yeah I was just looking again to see if anything else is possible but didn't find anything.
Q: should I leave the schemas as is (e.g. in their own core/schema thing) or was it better before?
I think it's fine how it is now; it's pretty arbitrary to me so no need to undo everything
fixed on main
I was curious so I just tried really quickly to see what happens if I did this too alpine's schema:
extend type Query {
alpine: Alpine!
}
type Alpine {
build(pkgs: [String!]): Filesystem!
}
extend type Filesystem {
foo(bar: String!): String!
}
Sad news: the gqlgen plugin doesn't generate methods for the filesystem extension.
Good news: the schema is stitched in perfectly and if I by-hand add in an implementation to alpine's generated code for Filesystem.foo, then I can successfully invoke it! e.g.
{
core {
image(ref:"busybox") {
foo(bar:"xyz")
}
}
}
works! It gets routed to the correct resolver from the alpine extension
Nice!