@voxgig/sdkgen 1.5.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cmp/AgentGuideContent.js +5 -0
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +13 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.d.ts +3 -1
- package/dist/helpers/canonType.js +44 -1
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/packageMeta.js +7 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +4 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/project/.sdk/model/feature/paging.aontu +7 -0
- package/project/.sdk/model/target/py-data.aontu +64 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
- package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
- package/project/.sdk/tm/c/core/sdk.h +7 -0
- package/project/.sdk/tm/c/feature/paging.c +141 -2
- package/project/.sdk/tm/c/utility/graphql.c +178 -0
- package/project/.sdk/tm/c/utility/make_options.c +1 -1
- package/project/.sdk/tm/c/utility/make_response.c +6 -0
- package/project/.sdk/tm/c/utility/make_spec.c +23 -3
- package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
- package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
- package/project/.sdk/tm/cpp/core/types.hpp +83 -0
- package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
- package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
- package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
- package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
- package/project/.sdk/tm/dart/lib/Point.dart +7 -0
- package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
- package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
- package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
- package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
- package/project/.sdk/tm/go/utility/graphql.go +168 -0
- package/project/.sdk/tm/go/utility/make_options.go +1 -1
- package/project/.sdk/tm/go/utility/make_response.go +6 -0
- package/project/.sdk/tm/go/utility/make_spec.go +19 -2
- package/project/.sdk/tm/go/utility/register.go +2 -0
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
- package/project/.sdk/tm/java/core/SdkClient.java +93 -0
- package/project/.sdk/tm/java/core/Utility.java +4 -0
- package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
- package/project/.sdk/tm/java/utility/Graphql.java +191 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
- package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
- package/project/.sdk/tm/java/utility/Register.java +2 -0
- package/project/.sdk/tm/js/src/Point.js +5 -0
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
- package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
- package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
- package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
- package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
- package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
- package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
- package/project/.sdk/tm/lua/utility/register.lua +3 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
- package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
- package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
- package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
- package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
- package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
- package/project/.sdk/tm/perl/utility/register.pm +1 -0
- package/project/.sdk/tm/php/core/UtilityType.php +2 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
- package/project/.sdk/tm/php/utility/Graphql.php +176 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
- package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
- package/project/.sdk/tm/php/utility/Register.php +3 -0
- package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
- package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
- package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
- package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
- package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
- package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
- package/project/.sdk/tm/py/test/feature_harness.py +6 -6
- package/project/.sdk/tm/py/test/runner.py +1 -1
- package/project/.sdk/tm/py/test/struct_runner.py +1 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
- package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
- package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
- package/project/.sdk/tm/py-data/LICENSE +22 -0
- package/project/.sdk/tm/py-data/Makefile +57 -0
- package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
- package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
- package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
- package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
- package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/test/__init__.py +0 -0
- package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
- package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
- package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
- package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
- package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
- package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
- package/project/.sdk/tm/rb/utility/register.rb +3 -0
- package/project/.sdk/tm/rust/feature/paging.rs +124 -2
- package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
- package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
- package/project/.sdk/tm/rust/utility/mod.rs +1 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
- package/project/.sdk/tm/scala/core/Utility.scala +4 -0
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
- package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
- package/project/.sdk/tm/scala/utility/Make.scala +7 -1
- package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
- package/project/.sdk/tm/scala/utility/Register.scala +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
- package/project/.sdk/tm/ts/src/Point.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
- package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +198 -3
- package/project/.sdk/tm/zig/feature/paging.zig +128 -1
- package/src/cmp/AgentGuideContent.ts +5 -0
- package/src/cmp/Feature.ts +13 -0
- package/src/helpers/canonType.ts +46 -0
- package/src/helpers/packageMeta.ts +7 -0
- package/src/sdkgen.ts +6 -1
- package/project/.sdk/tm/py/utility/register.py +0 -68
- /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
- /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
- /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
|
@@ -3,6 +3,8 @@ package feature
|
|
|
3
3
|
import (
|
|
4
4
|
"regexp"
|
|
5
5
|
|
|
6
|
+
vs "github.com/voxgig/struct"
|
|
7
|
+
|
|
6
8
|
"GOMODULE/core"
|
|
7
9
|
)
|
|
8
10
|
|
|
@@ -64,6 +66,14 @@ func (f *PagingFeature) PreRequest(ctx *core.Context) {
|
|
|
64
66
|
paging = ctx.Ctrl.Paging
|
|
65
67
|
}
|
|
66
68
|
|
|
69
|
+
// GraphQL paginates through operation VARIABLES, not the query string.
|
|
70
|
+
// This hook runs after makeSpec, so spec.Body already holds the
|
|
71
|
+
// { query, variables } envelope, and before makeFetchDef serialises it.
|
|
72
|
+
if kind, _ := vs.GetProp(ctx.Point, "kind").(string); kind == "graphql" {
|
|
73
|
+
f.graphqlPreRequest(ctx, paging)
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
67
77
|
if cursor, has := paging["cursor"]; has && cursor != nil {
|
|
68
78
|
spec.Query[cursorParam] = cursor
|
|
69
79
|
} else if spec.Query[pageParam] == nil {
|
|
@@ -79,6 +89,43 @@ func (f *PagingFeature) PreRequest(ctx *core.Context) {
|
|
|
79
89
|
}
|
|
80
90
|
}
|
|
81
91
|
|
|
92
|
+
// Relay pagination: the cursor is the `after` variable (or whatever the
|
|
93
|
+
// model named it), and the page size is `first`.
|
|
94
|
+
func (f *PagingFeature) graphqlPreRequest(ctx *core.Context, paging map[string]any) {
|
|
95
|
+
body, ok := ctx.Spec.Body.(map[string]any)
|
|
96
|
+
if !ok {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
vars, ok := body["variables"].(map[string]any)
|
|
101
|
+
if !ok {
|
|
102
|
+
vars = map[string]any{}
|
|
103
|
+
body["variables"] = vars
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
afterVar := foptStr(f.options, "afterVar", "after")
|
|
107
|
+
firstVar := foptStr(f.options, "firstVar", "first")
|
|
108
|
+
|
|
109
|
+
// Only bind variables the operation actually declares, or the server
|
|
110
|
+
// rejects the document.
|
|
111
|
+
declared := map[string]bool{}
|
|
112
|
+
if varlist, ok := vs.GetPath([]any{"graphql", "vars"}, ctx.Point).([]any); ok {
|
|
113
|
+
for _, v := range varlist {
|
|
114
|
+
if name, ok := vs.GetProp(v, "name").(string); ok {
|
|
115
|
+
declared[name] = true
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if cursor, has := paging["cursor"]; has && cursor != nil && declared[afterVar] {
|
|
121
|
+
vars[afterVar] = cursor
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if f.options["limit"] != nil && vars[firstVar] == nil && declared[firstVar] {
|
|
125
|
+
vars[firstVar] = foptInt(f.options, "limit", 0)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
82
129
|
func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
83
130
|
if !f.Active || !f.isList(ctx) {
|
|
84
131
|
return
|
|
@@ -107,6 +154,39 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
107
154
|
}
|
|
108
155
|
}
|
|
109
156
|
|
|
157
|
+
// Set when the response states hasMore outright, rather than leaving it
|
|
158
|
+
// to be inferred from the presence of a cursor.
|
|
159
|
+
explicitMore := false
|
|
160
|
+
|
|
161
|
+
// Relay connections carry the cursor in pageInfo, at the path the model
|
|
162
|
+
// recorded for this op.
|
|
163
|
+
page := vs.GetPath([]any{"graphql", "page"}, ctx.Point)
|
|
164
|
+
if pm, ok := page.(map[string]any); ok {
|
|
165
|
+
if _, isMap := body.(map[string]any); isMap {
|
|
166
|
+
// `connpath` locates the connection object inside the response
|
|
167
|
+
// envelope (data.<field>); the cursor/more paths are relative
|
|
168
|
+
// to it.
|
|
169
|
+
conn := body
|
|
170
|
+
if cp, ok := pm["connpath"].(string); ok && cp != "" {
|
|
171
|
+
if sub := vs.GetPath(cp, body); sub != nil {
|
|
172
|
+
conn = sub
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if cp, ok := pm["cursor"].(string); ok && cp != "" {
|
|
177
|
+
if cursor := vs.GetPath(cp, conn); cursor != nil {
|
|
178
|
+
paging["cursor"] = cursor
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if mp, ok := pm["more"].(string); ok && mp != "" {
|
|
182
|
+
if more, ok := vs.GetPath(mp, conn).(bool); ok {
|
|
183
|
+
paging["hasMore"] = more
|
|
184
|
+
explicitMore = true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
110
190
|
// Body-level cursors.
|
|
111
191
|
if bm, ok := body.(map[string]any); ok {
|
|
112
192
|
if bm["next"] != nil && paging["next"] == nil {
|
|
@@ -120,10 +200,16 @@ func (f *PagingFeature) PreResult(ctx *core.Context) {
|
|
|
120
200
|
}
|
|
121
201
|
if hasMore, ok := bm["hasMore"].(bool); ok {
|
|
122
202
|
paging["hasMore"] = hasMore
|
|
203
|
+
explicitMore = true
|
|
123
204
|
}
|
|
124
205
|
}
|
|
125
206
|
|
|
126
|
-
|
|
207
|
+
// Cursor presence only INFERS another page. When the server stated the
|
|
208
|
+
// answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
|
|
209
|
+
// that wins: a final page normally carries both an end cursor and
|
|
210
|
+
// hasNextPage false, and inferring from the cursor there would send the
|
|
211
|
+
// caller back for a page that does not exist, forever.
|
|
212
|
+
if !explicitMore && paging["hasMore"] != true &&
|
|
127
213
|
(paging["next"] != nil || paging["cursor"] != nil || paging["nextPage"] != nil) {
|
|
128
214
|
paging["hasMore"] = true
|
|
129
215
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
package utility
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strconv"
|
|
5
|
+
"strings"
|
|
6
|
+
|
|
7
|
+
vs "github.com/voxgig/struct"
|
|
8
|
+
|
|
9
|
+
"GOMODULE/core"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
// GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
|
|
13
|
+
// produces uses this file unchanged. The API-specific part — which
|
|
14
|
+
// operations exist and what each one's document is — is model data,
|
|
15
|
+
// computed once by apidef and emitted into Config.
|
|
16
|
+
//
|
|
17
|
+
// Two jobs:
|
|
18
|
+
//
|
|
19
|
+
// GraphqlBody — build { query, variables } for a point, binding the
|
|
20
|
+
// op's arguments to the document's declared variables.
|
|
21
|
+
//
|
|
22
|
+
// GraphqlErrors — lift a GraphQL failure into an SDK error. GraphQL
|
|
23
|
+
// reports failures as a top-level `errors` array under
|
|
24
|
+
// HTTP 200, so the status-driven path in resultBasic
|
|
25
|
+
// never sees them.
|
|
26
|
+
|
|
27
|
+
// Content type every GraphQL-over-HTTP request uses.
|
|
28
|
+
const GraphqlContentType = "application/json"
|
|
29
|
+
|
|
30
|
+
// Map a GraphQL error to the same error codes the HTTP path produces, so a
|
|
31
|
+
// caller handles auth or rate limiting identically on both transports.
|
|
32
|
+
// Servers put the machine-readable code in `extensions.code`; Linear-style
|
|
33
|
+
// APIs use `extensions.type`.
|
|
34
|
+
func graphqlErrorCodeUtil(gqlerr any) string {
|
|
35
|
+
ext, _ := vs.GetProp(gqlerr, "extensions").(map[string]any)
|
|
36
|
+
|
|
37
|
+
raw, _ := vs.GetProp(ext, "code").(string)
|
|
38
|
+
if raw == "" {
|
|
39
|
+
raw, _ = vs.GetProp(ext, "type").(string)
|
|
40
|
+
}
|
|
41
|
+
raw = strings.ToUpper(raw)
|
|
42
|
+
|
|
43
|
+
if strings.Contains(raw, "AUTH") || strings.Contains(raw, "FORBIDDEN") ||
|
|
44
|
+
strings.Contains(raw, "UNAUTHENTICATED") {
|
|
45
|
+
return "request_auth"
|
|
46
|
+
}
|
|
47
|
+
if strings.Contains(raw, "RATELIMIT") || strings.Contains(raw, "RATE_LIMIT") ||
|
|
48
|
+
strings.Contains(raw, "TOO_MANY") {
|
|
49
|
+
return "request_ratelimit"
|
|
50
|
+
}
|
|
51
|
+
if strings.Contains(raw, "BAD_USER_INPUT") || strings.Contains(raw, "VALIDATION") ||
|
|
52
|
+
strings.Contains(raw, "INVALID") {
|
|
53
|
+
return "request_invalid"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return "request_graphql"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build the request body for a GraphQL point.
|
|
60
|
+
//
|
|
61
|
+
// Variables come from the op's own arguments: a named variable binds to the
|
|
62
|
+
// like-named argument (`from`), and the input-object variable (empty `from`)
|
|
63
|
+
// takes the request data as a whole — which is what makes a generated
|
|
64
|
+
// create/update call look exactly like its REST equivalent.
|
|
65
|
+
func graphqlBodyUtil(ctx *core.Context) any {
|
|
66
|
+
gql, _ := vs.GetProp(ctx.Point, "graphql").(map[string]any)
|
|
67
|
+
if gql == nil {
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// reqmatch/reqdata hold the caller's arguments for THIS call; data/match
|
|
72
|
+
// hold the entity's current state. Which pair depends on whether the op
|
|
73
|
+
// takes match or data input. A named variable falls back to the current
|
|
74
|
+
// state, so updating a loaded entity with just {title} still binds the
|
|
75
|
+
// stored id the mutation requires.
|
|
76
|
+
reqsrc, datasrc := ctx.Reqmatch, ctx.Match
|
|
77
|
+
if ctx.Op != nil && ctx.Op.Input == "data" {
|
|
78
|
+
reqsrc, datasrc = ctx.Reqdata, ctx.Data
|
|
79
|
+
}
|
|
80
|
+
if reqsrc == nil {
|
|
81
|
+
reqsrc = map[string]any{}
|
|
82
|
+
}
|
|
83
|
+
if datasrc == nil {
|
|
84
|
+
datasrc = map[string]any{}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
variables := map[string]any{}
|
|
88
|
+
|
|
89
|
+
varlist, _ := vs.GetProp(gql, "vars").([]any)
|
|
90
|
+
for _, v := range varlist {
|
|
91
|
+
spec, _ := v.(map[string]any)
|
|
92
|
+
if spec == nil {
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
name, _ := vs.GetProp(spec, "name").(string)
|
|
97
|
+
from, _ := vs.GetProp(spec, "from").(string)
|
|
98
|
+
|
|
99
|
+
if from == "" {
|
|
100
|
+
// The input object IS the request body. Strip the action
|
|
101
|
+
// selector, which is an SDK-side point discriminator, not an
|
|
102
|
+
// API field.
|
|
103
|
+
body := map[string]any{}
|
|
104
|
+
for k, val := range reqsrc {
|
|
105
|
+
if k != "$action" {
|
|
106
|
+
body[k] = val
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
variables[name] = body
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Only send variables the caller actually supplied: sending an
|
|
114
|
+
// explicit null would clear a field on many APIs.
|
|
115
|
+
val := vs.GetProp(reqsrc, from)
|
|
116
|
+
if val == nil {
|
|
117
|
+
val = vs.GetProp(datasrc, from)
|
|
118
|
+
}
|
|
119
|
+
if val != nil {
|
|
120
|
+
variables[name] = val
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
doc, _ := vs.GetProp(gql, "doc").(string)
|
|
125
|
+
|
|
126
|
+
return map[string]any{
|
|
127
|
+
"query": doc,
|
|
128
|
+
"variables": variables,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Inspect a decoded GraphQL response body and record a failure when the
|
|
133
|
+
// server reported one. Returns true when an error was recorded.
|
|
134
|
+
//
|
|
135
|
+
// Partial data (`data` alongside `errors`) is treated as failure: the REST
|
|
136
|
+
// surface has no partial-success concept, and silently returning half an
|
|
137
|
+
// object would be worse than failing. The raw envelope stays available on
|
|
138
|
+
// the result for callers that need it.
|
|
139
|
+
func graphqlErrorsUtil(ctx *core.Context) bool {
|
|
140
|
+
if ctx.Result == nil || ctx.Point == nil {
|
|
141
|
+
return false
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
kind, _ := vs.GetProp(ctx.Point, "kind").(string)
|
|
145
|
+
if kind != "graphql" {
|
|
146
|
+
return false
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
errlist, _ := vs.GetProp(ctx.Result.Body, "errors").([]any)
|
|
150
|
+
if len(errlist) == 0 {
|
|
151
|
+
return false
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
first := errlist[0]
|
|
155
|
+
msg, _ := vs.GetProp(first, "message").(string)
|
|
156
|
+
if msg == "" {
|
|
157
|
+
msg = "graphql error"
|
|
158
|
+
}
|
|
159
|
+
if len(errlist) > 1 {
|
|
160
|
+
msg = msg + " (+" + strconv.Itoa(len(errlist)-1) + " more)"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
ctx.Result.Err = ctx.MakeError(graphqlErrorCodeUtil(first), "graphql: "+msg)
|
|
164
|
+
ctx.Result.Ok = false
|
|
165
|
+
|
|
166
|
+
return true
|
|
167
|
+
}
|
|
168
|
+
|
|
@@ -86,7 +86,7 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
|
|
|
86
86
|
},
|
|
87
87
|
"allow": map[string]any{
|
|
88
88
|
"method": "GET,PUT,POST,PATCH,DELETE,OPTIONS",
|
|
89
|
-
"op": "create,update,load,list,remove,command,direct",
|
|
89
|
+
"op": "create,update,load,list,remove,command,direct,graphql",
|
|
90
90
|
},
|
|
91
91
|
"entity": map[string]any{
|
|
92
92
|
"`$CHILD`": map[string]any{
|
|
@@ -32,6 +32,12 @@ func makeResponseUtil(ctx *core.Context) (*core.Response, error) {
|
|
|
32
32
|
utility.ResultBasic(ctx)
|
|
33
33
|
utility.ResultHeaders(ctx)
|
|
34
34
|
utility.ResultBody(ctx)
|
|
35
|
+
|
|
36
|
+
// GraphQL reports failures as a top-level `errors` array under HTTP
|
|
37
|
+
// 200, so ResultBasic's status check never sees them. Lift them here,
|
|
38
|
+
// before the response transform tries to unwrap data that is not there.
|
|
39
|
+
utility.GraphqlErrors(ctx)
|
|
40
|
+
|
|
35
41
|
utility.TransformResponse(ctx)
|
|
36
42
|
|
|
37
43
|
if result.Err == nil {
|
|
@@ -51,8 +51,25 @@ func makeSpecUtil(ctx *core.Context) (*core.Spec, error) {
|
|
|
51
51
|
ctx.Spec.Params = utility.PrepareParams(ctx)
|
|
52
52
|
ctx.Spec.Query = utility.PrepareQuery(ctx)
|
|
53
53
|
ctx.Spec.Headers = utility.PrepareHeaders(ctx)
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
|
|
55
|
+
kind, _ := vs.GetProp(point, "kind").(string)
|
|
56
|
+
|
|
57
|
+
if kind == "graphql" {
|
|
58
|
+
// GraphQL addresses one endpoint: no path parts, no query string,
|
|
59
|
+
// and the body carries the operation. PrepareBody is skipped
|
|
60
|
+
// deliberately — it only emits a body for data-input ops, whereas
|
|
61
|
+
// every GraphQL op posts one, including load/list/remove.
|
|
62
|
+
ctx.Spec.Body = utility.GraphqlBody(ctx)
|
|
63
|
+
ctx.Spec.Path = ""
|
|
64
|
+
// PrepareQuery already copied the op's match arguments into the
|
|
65
|
+
// query string. Those same values are bound as operation
|
|
66
|
+
// variables, so leaving them would send /graphql?id=i1.
|
|
67
|
+
ctx.Spec.Query = map[string]any{}
|
|
68
|
+
ctx.Spec.Headers["content-type"] = GraphqlContentType
|
|
69
|
+
} else {
|
|
70
|
+
ctx.Spec.Body = utility.PrepareBody(ctx)
|
|
71
|
+
ctx.Spec.Path = utility.PreparePath(ctx)
|
|
72
|
+
}
|
|
56
73
|
|
|
57
74
|
if ctx.Ctrl.Explain != nil {
|
|
58
75
|
ctx.Ctrl.Explain["spec"] = ctx.Spec
|
|
@@ -31,6 +31,8 @@ func registerAll(u *core.Utility) {
|
|
|
31
31
|
u.PrepareParams = prepareParamsUtil
|
|
32
32
|
u.PreparePath = preparePathUtil
|
|
33
33
|
u.PrepareQuery = prepareQueryUtil
|
|
34
|
+
u.GraphqlBody = graphqlBodyUtil
|
|
35
|
+
u.GraphqlErrors = graphqlErrorsUtil
|
|
34
36
|
u.ResultBasic = resultBasicUtil
|
|
35
37
|
u.ResultBody = resultBodyUtil
|
|
36
38
|
u.ResultHeaders = resultHeadersUtil
|
|
@@ -672,8 +672,14 @@ pagingFeature = do
|
|
|
672
672
|
opts <- readIORef options
|
|
673
673
|
pageParam <- optStr opts "pageParam" "page"; limitParam <- optStr opts "limitParam" "limit"; cursorParam <- optStr opts "cursorParam" "cursor"
|
|
674
674
|
ctrl <- readIORef (cCtrl ctx); pgv <- getp ctrl "paging"; let paging = case pgv of { VMap _ -> pgv; _ -> VNoval }
|
|
675
|
+
-- GraphQL paginates through operation VARIABLES, not the
|
|
676
|
+
-- query string. This hook runs after makeSpec, so spec.body
|
|
677
|
+
-- already holds the { query, variables } envelope, and before
|
|
678
|
+
-- makeFetchDef serialises it.
|
|
679
|
+
point <- readIORef (cPoint ctx)
|
|
680
|
+
kind <- getStrD point "kind" ""
|
|
675
681
|
cur <- case paging of VMap _ -> getp paging "cursor"; _ -> pure VNoval
|
|
676
|
-
case cur of
|
|
682
|
+
if kind == "graphql" then graphqlPreRequest opts specV point paging else case cur of
|
|
677
683
|
VNoval -> do
|
|
678
684
|
pv <- getp q pageParam
|
|
679
685
|
case pv of
|
|
@@ -688,8 +694,9 @@ pagingFeature = do
|
|
|
688
694
|
VNoval -> do sp <- getp opts "startPage"; setp q pageParam (case sp of VNum n -> VNum n; _ -> VNum 1)
|
|
689
695
|
_ -> pure ()
|
|
690
696
|
c -> setp q cursorParam c
|
|
691
|
-
|
|
692
|
-
|
|
697
|
+
when (kind /= "graphql") $ do
|
|
698
|
+
lim <- getp opts "limit"
|
|
699
|
+
case lim of VNoval -> pure (); VNull -> pure (); _ -> do lv <- getp q limitParam; case lv of VNoval -> setp q limitParam lim; _ -> pure ()
|
|
693
700
|
_ -> pure ()
|
|
694
701
|
"PreResult" -> do
|
|
695
702
|
rv <- readIORef (cResult ctx)
|
|
@@ -704,16 +711,49 @@ pagingFeature = do
|
|
|
704
711
|
paging <- jo [("page", xpage), ("totalCount", xtot), ("nextPage", xnext), ("next", VNoval), ("cursor", VNoval), ("hasMore", VBool False)]
|
|
705
712
|
lnk <- headerCI headersM "link"
|
|
706
713
|
case lnk of VNoval -> pure (); VNull -> pure (); _ -> case extractNext (vstring lnk) of Just nx -> setp paging "next" (VStr nx); Nothing -> pure ()
|
|
707
|
-
|
|
714
|
+
-- Relay connections carry the cursor in pageInfo, at the
|
|
715
|
+
-- path the model recorded for this op. `explicitMore` is set
|
|
716
|
+
-- when the server states hasMore outright, rather than
|
|
717
|
+
-- leaving it to be inferred from the presence of a cursor.
|
|
718
|
+
point <- readIORef (cPoint ctx)
|
|
719
|
+
gqlpage <- getpathS point "graphql.page"
|
|
720
|
+
relayMore <- case (gqlpage, body) of
|
|
721
|
+
(VMap _, VMap _) -> do
|
|
722
|
+
-- `connpath` locates the connection object inside the
|
|
723
|
+
-- response envelope (data.<field>); the cursor/more paths
|
|
724
|
+
-- are relative to it.
|
|
725
|
+
connpath <- getStrD gqlpage "connpath" ""
|
|
726
|
+
conn <- if null connpath then pure body else do
|
|
727
|
+
sub <- getpathS body connpath
|
|
728
|
+
pure (if isNullish sub then body else sub)
|
|
729
|
+
cursorpath <- getStrD gqlpage "cursor" ""
|
|
730
|
+
when (not (null cursorpath)) $ do
|
|
731
|
+
c <- getpathS conn cursorpath
|
|
732
|
+
when (not (isNullish c)) (setp paging "cursor" c)
|
|
733
|
+
morepath <- getStrD gqlpage "more" ""
|
|
734
|
+
if null morepath then pure False else do
|
|
735
|
+
mv <- getpathS conn morepath
|
|
736
|
+
case mv of
|
|
737
|
+
VBool mb -> do setp paging "hasMore" (VBool mb); pure True
|
|
738
|
+
_ -> pure False
|
|
739
|
+
_ -> pure False
|
|
740
|
+
bodyMore <- case body of
|
|
708
741
|
VMap _ -> do
|
|
709
742
|
bn <- getp body "next"; case bn of VNoval -> pure (); VNull -> pure (); _ -> do cn <- getp paging "next"; when (isNullish cn) (setp paging "next" bn)
|
|
710
743
|
bc <- getp body "cursor"; case bc of VNoval -> pure (); VNull -> pure (); _ -> setp paging "cursor" bc
|
|
711
744
|
bnc <- getp body "nextCursor"; case bnc of VNoval -> pure (); VNull -> pure (); _ -> setp paging "cursor" bnc
|
|
712
|
-
bhm <- getp body "hasMore"; case bhm of VBool bb -> setp paging "hasMore" (VBool bb); _ -> pure
|
|
713
|
-
_ -> pure
|
|
745
|
+
bhm <- getp body "hasMore"; case bhm of { VBool bb -> do { setp paging "hasMore" (VBool bb); pure True }; _ -> pure False }
|
|
746
|
+
_ -> pure False
|
|
747
|
+
-- Cursor presence only INFERS another page. When the server
|
|
748
|
+
-- stated the answer outright — relay's `hasNextPage: false`,
|
|
749
|
+
-- or a body `hasMore` — that wins: a final page normally
|
|
750
|
+
-- carries both an end cursor and hasNextPage false, and
|
|
751
|
+
-- inferring from the cursor there would send the caller back
|
|
752
|
+
-- for a page that does not exist, forever.
|
|
753
|
+
let explicitMore = relayMore || bodyMore
|
|
714
754
|
hmV <- getp paging "hasMore"; nx <- getp paging "next"; cu2 <- getp paging "cursor"; np <- getp paging "nextPage"
|
|
715
755
|
let hm = isTrueV hmV || not (isNullish nx) || not (isNullish cu2) || not (isNullish np)
|
|
716
|
-
setp paging "hasMore" (VBool hm)
|
|
756
|
+
when (not explicitMore) (setp paging "hasMore" (VBool hm))
|
|
717
757
|
setp rv "paging" paging
|
|
718
758
|
cl <- cc ctx; lastM <- jo [("last", paging)]; trackSet cl "paging" lastM
|
|
719
759
|
_ -> pure ()
|
|
@@ -721,6 +761,37 @@ pagingFeature = do
|
|
|
721
761
|
initFn _ opts = do om <- toOptsMap opts; writeIORef options om; a <- optActive opts; writeIORef active a
|
|
722
762
|
pure Feature { fName = "paging", fVersion = "0.0.1", fActive = active, fOptions = fopts, fInit = initFn, fHook = hookFn }
|
|
723
763
|
|
|
764
|
+
-- Relay pagination: the cursor is the `after` variable (or whatever the model
|
|
765
|
+
-- named it), and the page size is `first`.
|
|
766
|
+
graphqlPreRequest :: Value -> Value -> Value -> Value -> IO ()
|
|
767
|
+
graphqlPreRequest opts specV point paging = do
|
|
768
|
+
body <- getp specV "body"
|
|
769
|
+
case body of
|
|
770
|
+
VMap _ -> do
|
|
771
|
+
vv <- getp body "variables"
|
|
772
|
+
variables <- case vv of
|
|
773
|
+
VMap _ -> pure vv
|
|
774
|
+
_ -> do m <- emptyMap; setp body "variables" m; pure m
|
|
775
|
+
|
|
776
|
+
afterVar <- optStr opts "afterVar" "after"
|
|
777
|
+
firstVar <- optStr opts "firstVar" "first"
|
|
778
|
+
|
|
779
|
+
-- Only bind variables the operation actually declares, or the server
|
|
780
|
+
-- rejects the document.
|
|
781
|
+
vl <- getpathS point "graphql.vars"
|
|
782
|
+
varlist <- case vl of VList _ -> listItems vl; _ -> pure []
|
|
783
|
+
declared <- mapM (\v -> getStrD v "name" "") varlist
|
|
784
|
+
|
|
785
|
+
cur <- case paging of VMap _ -> getp paging "cursor"; _ -> pure VNoval
|
|
786
|
+
when (not (isNullish cur) && afterVar `elem` declared) $
|
|
787
|
+
setp variables afterVar cur
|
|
788
|
+
|
|
789
|
+
lim <- getp opts "limit"
|
|
790
|
+
existing <- getp variables firstVar
|
|
791
|
+
when (not (isNullish lim) && isNullish existing && firstVar `elem` declared) $
|
|
792
|
+
setp variables firstVar lim
|
|
793
|
+
_ -> pure ()
|
|
794
|
+
|
|
724
795
|
elemIndex :: Eq a => a -> [a] -> Maybe Int
|
|
725
796
|
elemIndex x = go 0 where go _ [] = Nothing; go i (y : ys) = if x == y then Just i else go (i + 1) ys
|
|
726
797
|
|
|
@@ -1074,8 +1145,76 @@ prepare client fetchargs = do
|
|
|
1074
1145
|
(fd, merr2) <- makeFetchDefUtil ctx
|
|
1075
1146
|
case merr2 of Just e -> throwIO (SdkException e); Nothing -> pure fd
|
|
1076
1147
|
|
|
1148
|
+
-- Is this raw-access op permitted by the SDK's allow.op option?
|
|
1149
|
+
opAllowed :: Client -> String -> IO Bool
|
|
1150
|
+
opAllowed client op = do
|
|
1151
|
+
opts <- readIORef (clOptions client)
|
|
1152
|
+
allow <- getpathS opts "allow.op"
|
|
1153
|
+
pure (case allow of VStr s -> substrContains s op; _ -> False)
|
|
1154
|
+
|
|
1155
|
+
opDenied :: Client -> String -> IO Value
|
|
1156
|
+
opDenied client op = do
|
|
1157
|
+
opts <- readIORef (clOptions client)
|
|
1158
|
+
allow <- getpathS opts "allow.op"
|
|
1159
|
+
let a = case allow of VStr s -> s; _ -> ""
|
|
1160
|
+
jo [ ("ok", VBool False)
|
|
1161
|
+
, ("err", VStr ("ProjectNameSDK: " ++ op ++ ": operation not allowed by"
|
|
1162
|
+
++ " SDK option allow.op value: \"" ++ a ++ "\"")) ]
|
|
1163
|
+
|
|
1164
|
+
-- Raw endpoint access is operator-controllable, like every entity op.
|
|
1165
|
+
-- Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
|
|
1166
|
+
-- either one reaches the same endpoint.
|
|
1077
1167
|
direct :: Client -> Value -> IO Value
|
|
1078
1168
|
direct client fetchargs = do
|
|
1169
|
+
allowed <- opAllowed client "direct"
|
|
1170
|
+
if not allowed then opDenied client "direct" else rawRequest client fetchargs
|
|
1171
|
+
|
|
1172
|
+
-- Raw GraphQL access: the pressure valve that makes the generated surface's
|
|
1173
|
+
-- deliberate omissions (per-call selection sets, typed filter builders,
|
|
1174
|
+
-- batching, subscriptions) livable — the whole schema stays reachable.
|
|
1175
|
+
--
|
|
1176
|
+
-- Thin wrapper over the same prepare/fetch path direct uses, with the one
|
|
1177
|
+
-- thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
|
|
1178
|
+
-- a top-level `errors` array, so status alone would report a failed query as
|
|
1179
|
+
-- ok.
|
|
1180
|
+
--
|
|
1181
|
+
-- NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
|
|
1182
|
+
-- or paging features apply.
|
|
1183
|
+
graphql :: Client -> String -> Value -> Value -> IO Value
|
|
1184
|
+
graphql client query variables ctrl = do
|
|
1185
|
+
allowed <- opAllowed client "graphql"
|
|
1186
|
+
if not allowed then opDenied client "graphql" else do
|
|
1187
|
+
vars <- case variables of VMap _ -> pure variables; _ -> emptyMap
|
|
1188
|
+
ctl <- case ctrl of VMap _ -> pure ctrl; _ -> emptyMap
|
|
1189
|
+
headers <- jo [("content-type", VStr "application/json")]
|
|
1190
|
+
body <- jo [("query", VStr query), ("variables", vars)]
|
|
1191
|
+
fa <- jo [ ("method", VStr "POST"), ("headers", headers)
|
|
1192
|
+
, ("body", body), ("ctrl", ctl) ]
|
|
1193
|
+
res <- rawRequest client fa
|
|
1194
|
+
|
|
1195
|
+
-- Errors are read BEFORE any status check: a GraphQL parse or validation
|
|
1196
|
+
-- failure comes back as HTTP 400 carrying the standard { errors: [...] }
|
|
1197
|
+
-- body, and the raw path represents a non-2xx as ok:False with no err —
|
|
1198
|
+
-- so returning early on status would discard the server's own
|
|
1199
|
+
-- diagnostics, which are the only useful part of that response.
|
|
1200
|
+
ev <- getpathS res "data.errors"
|
|
1201
|
+
errors <- case ev of VList _ -> listItems ev; _ -> pure []
|
|
1202
|
+
case errors of
|
|
1203
|
+
[] -> pure res
|
|
1204
|
+
(firsterr : _) -> do
|
|
1205
|
+
m0 <- getStrD firsterr "message" ""
|
|
1206
|
+
let msg = if null m0 then "graphql error" else m0
|
|
1207
|
+
setp res "ok" (VBool False)
|
|
1208
|
+
setp res "err" (VStr ("ProjectNameSDK: graphql: " ++ msg))
|
|
1209
|
+
setp res "graphql" ev
|
|
1210
|
+
pure res
|
|
1211
|
+
|
|
1212
|
+
-- Ungated request path shared by direct and graphql, each of which checks its
|
|
1213
|
+
-- own allow.op token first. Separate, rather than a flag on fetchargs: a
|
|
1214
|
+
-- caller-supplied marker would let anyone opt straight back out of the gate
|
|
1215
|
+
-- by passing it.
|
|
1216
|
+
rawRequest :: Client -> Value -> IO Value
|
|
1217
|
+
rawRequest client fetchargs = do
|
|
1079
1218
|
let u = clUtility client
|
|
1080
1219
|
fa <- case fetchargs of VNoval -> emptyMap; _ -> pure fetchargs
|
|
1081
1220
|
res <- try (prepare client fa) :: IO (Either SdkException Value)
|