@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.
Files changed (270) hide show
  1. package/dist/cmp/AgentGuideContent.js +5 -0
  2. package/dist/cmp/AgentGuideContent.js.map +1 -1
  3. package/dist/cmp/Feature.js +13 -0
  4. package/dist/cmp/Feature.js.map +1 -1
  5. package/dist/helpers/canonType.d.ts +3 -1
  6. package/dist/helpers/canonType.js +44 -1
  7. package/dist/helpers/canonType.js.map +1 -1
  8. package/dist/helpers/packageMeta.js +7 -0
  9. package/dist/helpers/packageMeta.js.map +1 -1
  10. package/dist/sdkgen.d.ts +3 -3
  11. package/dist/sdkgen.js +4 -1
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +3 -3
  15. package/project/.sdk/model/feature/paging.aontu +7 -0
  16. package/project/.sdk/model/target/py-data.aontu +64 -0
  17. package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
  18. package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
  19. package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
  20. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
  21. package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
  22. package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
  23. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
  24. package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
  25. package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
  26. package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
  27. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
  28. package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
  29. package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
  30. package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
  31. package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
  32. package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
  33. package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
  34. package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
  35. package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
  36. package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
  37. package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
  38. package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
  39. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
  40. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
  41. package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
  42. package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
  43. package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
  44. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
  45. package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
  46. package/project/.sdk/tm/c/core/sdk.h +7 -0
  47. package/project/.sdk/tm/c/feature/paging.c +141 -2
  48. package/project/.sdk/tm/c/utility/graphql.c +178 -0
  49. package/project/.sdk/tm/c/utility/make_options.c +1 -1
  50. package/project/.sdk/tm/c/utility/make_response.c +6 -0
  51. package/project/.sdk/tm/c/utility/make_spec.c +23 -3
  52. package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
  53. package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
  54. package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
  55. package/project/.sdk/tm/cpp/core/types.hpp +83 -0
  56. package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
  57. package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
  58. package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
  59. package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
  60. package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
  61. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
  62. package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
  63. package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
  64. package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
  65. package/project/.sdk/tm/dart/lib/Point.dart +7 -0
  66. package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
  67. package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
  68. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
  69. package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
  70. package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
  71. package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
  72. package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
  73. package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
  74. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
  75. package/project/.sdk/tm/go/core/utility_type.go +2 -0
  76. package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
  77. package/project/.sdk/tm/go/utility/graphql.go +168 -0
  78. package/project/.sdk/tm/go/utility/make_options.go +1 -1
  79. package/project/.sdk/tm/go/utility/make_response.go +6 -0
  80. package/project/.sdk/tm/go/utility/make_spec.go +19 -2
  81. package/project/.sdk/tm/go/utility/register.go +2 -0
  82. package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
  83. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
  84. package/project/.sdk/tm/java/core/SdkClient.java +93 -0
  85. package/project/.sdk/tm/java/core/Utility.java +4 -0
  86. package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
  87. package/project/.sdk/tm/java/utility/Graphql.java +191 -0
  88. package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
  89. package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
  90. package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
  91. package/project/.sdk/tm/java/utility/Register.java +2 -0
  92. package/project/.sdk/tm/js/src/Point.js +5 -0
  93. package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
  94. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
  95. package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
  96. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
  97. package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
  98. package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
  99. package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
  100. package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
  101. package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
  102. package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
  103. package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
  104. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
  105. package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
  106. package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
  107. package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
  108. package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
  109. package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
  110. package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
  111. package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
  112. package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
  113. package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
  114. package/project/.sdk/tm/lua/utility/register.lua +3 -0
  115. package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
  116. package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
  117. package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
  118. package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
  119. package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
  120. package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
  121. package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
  122. package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
  123. package/project/.sdk/tm/perl/utility/register.pm +1 -0
  124. package/project/.sdk/tm/php/core/UtilityType.php +2 -0
  125. package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
  126. package/project/.sdk/tm/php/utility/Graphql.php +176 -0
  127. package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
  128. package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
  129. package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
  130. package/project/.sdk/tm/php/utility/Register.php +3 -0
  131. package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
  132. package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
  133. package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
  134. package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
  135. package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
  136. package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
  137. package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
  138. package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
  139. package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
  140. package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
  141. package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
  142. package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
  143. package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
  144. package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
  145. package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
  146. package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
  147. package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
  148. package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
  149. package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
  150. package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
  151. package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
  152. package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
  153. package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
  154. package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
  155. package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
  156. package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
  157. package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
  158. package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
  159. package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
  160. package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
  161. package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
  162. package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
  163. package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
  164. package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
  165. package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
  166. package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
  167. package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
  168. package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
  169. package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
  170. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
  171. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
  172. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
  173. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
  174. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
  175. package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
  176. package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
  177. package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
  178. package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
  179. package/project/.sdk/tm/py/test/feature_harness.py +6 -6
  180. package/project/.sdk/tm/py/test/runner.py +1 -1
  181. package/project/.sdk/tm/py/test/struct_runner.py +1 -1
  182. package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
  183. package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
  184. package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
  185. package/project/.sdk/tm/py-data/LICENSE +22 -0
  186. package/project/.sdk/tm/py-data/Makefile +57 -0
  187. package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
  188. package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
  189. package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
  190. package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
  191. package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
  192. package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
  193. package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
  194. package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
  195. package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
  196. package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
  197. package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
  198. package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
  199. package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
  200. package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
  201. package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
  202. package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
  203. package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
  204. package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
  205. package/project/.sdk/tm/py-data/test/__init__.py +0 -0
  206. package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
  207. package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
  208. package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
  209. package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
  210. package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
  211. package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
  212. package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
  213. package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
  214. package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
  215. package/project/.sdk/tm/rb/utility/register.rb +3 -0
  216. package/project/.sdk/tm/rust/feature/paging.rs +124 -2
  217. package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
  218. package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
  219. package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
  220. package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
  221. package/project/.sdk/tm/rust/utility/mod.rs +1 -0
  222. package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
  223. package/project/.sdk/tm/scala/core/Utility.scala +4 -0
  224. package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
  225. package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
  226. package/project/.sdk/tm/scala/utility/Make.scala +7 -1
  227. package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
  228. package/project/.sdk/tm/scala/utility/Register.scala +2 -0
  229. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
  230. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
  231. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
  232. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
  233. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
  234. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
  235. package/project/.sdk/tm/ts/src/Point.ts +7 -0
  236. package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
  237. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
  238. package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
  239. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
  240. package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
  241. package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
  242. package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
  243. package/project/.sdk/tm/zig/core/utility.zig +198 -3
  244. package/project/.sdk/tm/zig/feature/paging.zig +128 -1
  245. package/src/cmp/AgentGuideContent.ts +5 -0
  246. package/src/cmp/Feature.ts +13 -0
  247. package/src/helpers/canonType.ts +46 -0
  248. package/src/helpers/packageMeta.ts +7 -0
  249. package/src/sdkgen.ts +6 -1
  250. package/project/.sdk/tm/py/utility/register.py +0 -68
  251. /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
  252. /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
  253. /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
  254. /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
  255. /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
  256. /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
  257. /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
  258. /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
  259. /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
  260. /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
  261. /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
  262. /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
  263. /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
  264. /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
  265. /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
  266. /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
  267. /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
  268. /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
  269. /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
  270. /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
@@ -54,8 +54,23 @@ final class MakeSpec {
54
54
  ctx.spec.params = utility.prepareParams.apply(ctx);
55
55
  ctx.spec.query = utility.prepareQuery.apply(ctx);
56
56
  ctx.spec.headers = utility.prepareHeaders.apply(ctx);
57
- ctx.spec.body = utility.prepareBody.apply(ctx);
58
- ctx.spec.path = utility.preparePath.apply(ctx);
57
+
58
+ if ("graphql".equals(Struct.getprop(point, "kind", null))) {
59
+ // GraphQL addresses one endpoint: no path parts, no query string, and
60
+ // the body carries the operation. prepareBody is skipped deliberately
61
+ // — it only emits a body for data-input ops, whereas every GraphQL op
62
+ // posts one, including load/list/remove.
63
+ ctx.spec.body = utility.graphqlBody.apply(ctx);
64
+ ctx.spec.path = "";
65
+ // prepareQuery already copied the op's match arguments into the query
66
+ // string. Those same values are bound as operation variables, so
67
+ // leaving them would send /graphql?id=i1.
68
+ ctx.spec.query = new LinkedHashMap<>();
69
+ ctx.spec.headers.put("content-type", Graphql.GRAPHQL_CONTENT_TYPE);
70
+ } else {
71
+ ctx.spec.body = utility.prepareBody.apply(ctx);
72
+ ctx.spec.path = utility.preparePath.apply(ctx);
73
+ }
59
74
 
60
75
  if (ctx.ctrl.explain != null) {
61
76
  ctx.ctrl.explain.put("spec", ctx.spec);
@@ -32,6 +32,8 @@ public final class Register {
32
32
  u.prepareParams = PrepareParams::prepareParams;
33
33
  u.preparePath = PreparePath::preparePath;
34
34
  u.prepareQuery = PrepareQuery::prepareQuery;
35
+ u.graphqlBody = Graphql::graphqlBody;
36
+ u.graphqlErrors = Graphql::graphqlErrors;
35
37
  u.resultBasic = ResultBasic::resultBasic;
36
38
  u.resultBody = ResultBody::resultBody;
37
39
  u.resultHeaders = ResultHeaders::resultHeaders;
@@ -5,6 +5,11 @@ const { getprop } = require('./utility/StructUtility')
5
5
  class Point {
6
6
  constructor(altmap) {
7
7
  this.args = getprop(altmap, 'args', { params: [] })
8
+ // Transport this point speaks: 'http' (default) or 'graphql'. GraphQL
9
+ // points carry their operation document in `graphql` and address the
10
+ // single endpoint, so method is always POST and parts is empty.
11
+ this.kind = getprop(altmap, 'kind', 'http')
12
+ this.graphql = getprop(altmap, 'graphql')
8
13
  this.rename = getprop(altmap, 'rename', { params: {} })
9
14
  this.method = getprop(altmap, 'method', '')
10
15
  this.orig = getprop(altmap, 'orig', '')
@@ -46,6 +46,14 @@ class PagingFeature extends BaseFeature {
46
46
  // A per-call cursor/page from ctrl takes priority (used by auto-iteration).
47
47
  const paging = (ctx.ctrl && ctx.ctrl.paging) || {}
48
48
 
49
+ // GraphQL paginates through operation VARIABLES, not the query string.
50
+ // This hook runs after makeSpec, so spec.body already holds the
51
+ // { query, variables } envelope, and before makeFetchDef serialises it.
52
+ const point = ctx.point
53
+ if ('graphql' === (null == point ? undefined : point.kind)) {
54
+ return this._graphqlPreRequest(ctx, paging)
55
+ }
56
+
49
57
  if (null != paging.cursor) {
50
58
  spec.query[cursorParam] = paging.cursor
51
59
  }
@@ -71,6 +79,10 @@ class PagingFeature extends BaseFeature {
71
79
  const headers = result.headers || {}
72
80
  const body = result.body
73
81
 
82
+ // Set when the response states hasMore outright, rather than leaving it
83
+ // to be inferred from the presence of a cursor.
84
+ let explicitMore = false
85
+
74
86
  const paging = {
75
87
  page: this._num(this._header(headers, 'x-page')),
76
88
  totalCount: this._num(this._header(headers, 'x-total-count')),
@@ -89,16 +101,49 @@ class PagingFeature extends BaseFeature {
89
101
  }
90
102
  }
91
103
 
104
+ // Relay connections carry the cursor in pageInfo, at the path the model
105
+ // recorded for this op.
106
+ const point = ctx.point
107
+ const page = null == point ? undefined : (point.graphql || {}).page
108
+
109
+ if (null != page && body && 'object' === typeof body) {
110
+ const struct = ctx.utility.struct
111
+
112
+ // `connpath` locates the connection object inside the response
113
+ // envelope (data.<field>); the cursor/more paths are relative to it.
114
+ const conn = null == page.connpath || '' === page.connpath ? body :
115
+ (struct.getpath(body, page.connpath) || body)
116
+
117
+ const cursor = struct.getpath(conn, page.cursor)
118
+ const more = struct.getpath(conn, page.more)
119
+
120
+ if (null != cursor) { paging.cursor = cursor }
121
+ if ('boolean' === typeof more) {
122
+ paging.hasMore = more
123
+ explicitMore = true
124
+ }
125
+ }
126
+
92
127
  // Body-level cursors.
93
128
  if (body && 'object' === typeof body) {
94
129
  if (null != body.next) { paging.next = paging.next || body.next }
95
130
  if (null != body.cursor) { paging.cursor = body.cursor }
96
131
  if (null != body.nextCursor) { paging.cursor = body.nextCursor }
97
- if ('boolean' === typeof body.hasMore) { paging.hasMore = body.hasMore }
132
+ if ('boolean' === typeof body.hasMore) {
133
+ paging.hasMore = body.hasMore
134
+ explicitMore = true
135
+ }
98
136
  }
99
137
 
100
- paging.hasMore = paging.hasMore ||
101
- null != paging.next || null != paging.cursor || null != paging.nextPage
138
+ // Cursor presence only INFERS another page. When the server stated the
139
+ // answer outright relay's `hasNextPage: false`, or a body `hasMore` —
140
+ // that wins: a final page normally carries both an end cursor and
141
+ // hasNextPage false, and inferring from the cursor there would send the
142
+ // caller back for a page that does not exist, forever.
143
+ if (!explicitMore) {
144
+ paging.hasMore = paging.hasMore ||
145
+ null != paging.next || null != paging.cursor || null != paging.nextPage
146
+ }
102
147
 
103
148
  result.paging = paging
104
149
 
@@ -107,6 +152,40 @@ class PagingFeature extends BaseFeature {
107
152
  }
108
153
 
109
154
 
155
+
156
+ // Relay pagination: the cursor is the `after` variable (or whatever the
157
+ // model recorded), and the page size the `first` variable.
158
+ _graphqlPreRequest(ctx, paging) {
159
+ const spec = ctx.spec
160
+ const point = ctx.point
161
+
162
+ const body = spec.body
163
+ if (null == body || 'object' !== typeof body) {
164
+ return
165
+ }
166
+
167
+ const vars = (body.variables = body.variables || {})
168
+
169
+ const afterVar = this._options.afterVar || 'after'
170
+ const firstVar = this._options.firstVar || 'first'
171
+
172
+ // Only bind variables the operation actually declares, or the server
173
+ // rejects the document.
174
+ const declared = {}
175
+ for (const v of ((point.graphql && point.graphql.vars) || [])) {
176
+ declared[v.name] = true
177
+ }
178
+
179
+ if (null != paging.cursor && declared[afterVar]) {
180
+ vars[afterVar] = paging.cursor
181
+ }
182
+
183
+ if (null != this._options.limit && null == vars[firstVar] &&
184
+ declared[firstVar]) {
185
+ vars[firstVar] = this._options.limit
186
+ }
187
+ }
188
+
110
189
  _isList(ctx) {
111
190
  const ops = this._options.ops || ['list']
112
191
  return ops.indexOf(ctx.op && ctx.op.name) >= 0
@@ -61,8 +61,20 @@ class TestFeature extends BaseFeature {
61
61
  if (null == data || 'string' !== typeof restf) {
62
62
  return data
63
63
  }
64
- const m = restf.match(/^`body\.([^`.]+)`$/)
65
- return null == m ? data : { [m[1]]: data }
64
+ // Rebuild whatever nesting the op's response transform unwraps, so
65
+ // the mock agrees with the model. Multi-segment on purpose: GraphQL
66
+ // ops unwrap `body.data.<field>` (and `body.data.<field>.<entity>`
67
+ // for mutation payloads), not just a single envelope property.
68
+ const m = restf.match(/^`body\.(.+)`$/)
69
+ if (null == m) {
70
+ return data
71
+ }
72
+ let out = data
73
+ const segs = m[1].split('.')
74
+ for (let i = segs.length - 1; 0 <= i; i--) {
75
+ out = { [segs[i]]: out }
76
+ }
77
+ return out
66
78
  }
67
79
 
68
80
  function respond(status, data, res) {
@@ -0,0 +1,148 @@
1
+ // GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
2
+ // produces uses this file unchanged. The API-specific part — which
3
+ // operations exist and what each one's document is — is model data,
4
+ // computed once by apidef and emitted into Config.
5
+ //
6
+ // Two jobs:
7
+ //
8
+ // graphqlBody — build { query, variables } for a point, binding the
9
+ // op's arguments to the document's declared variables.
10
+ //
11
+ // graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL
12
+ // reports failures as a top-level `errors` array under
13
+ // HTTP 200, so the status-driven path in resultBasic
14
+ // never sees them.
15
+
16
+
17
+ // Content type every GraphQL-over-HTTP request uses.
18
+ const GRAPHQL_CONTENT_TYPE = 'application/json'
19
+
20
+
21
+ // Map a GraphQL error to the same error codes the HTTP path produces, so a
22
+ // caller handles auth or rate limiting identically on both transports.
23
+ // Servers put the machine-readable code in `extensions.code`; Linear-style
24
+ // APIs use `extensions.type`.
25
+ function graphqlErrorCode(gqlerr) {
26
+ const ext = (null == gqlerr ? undefined : gqlerr.extensions) || {}
27
+ const raw = String(ext.code || ext.type || '').toUpperCase()
28
+
29
+ if (raw.includes('AUTH') || raw.includes('FORBIDDEN') ||
30
+ raw.includes('UNAUTHENTICATED')) {
31
+ return 'request_auth'
32
+ }
33
+ if (raw.includes('RATELIMIT') || raw.includes('RATE_LIMIT') ||
34
+ raw.includes('TOO_MANY')) {
35
+ return 'request_ratelimit'
36
+ }
37
+ if (raw.includes('BAD_USER_INPUT') || raw.includes('VALIDATION') ||
38
+ raw.includes('INVALID')) {
39
+ return 'request_invalid'
40
+ }
41
+
42
+ return 'request_graphql'
43
+ }
44
+
45
+
46
+ // Build the request body for a GraphQL point.
47
+ //
48
+ // Variables come from the op's own arguments: a named variable binds to the
49
+ // like-named argument (`from`), and the input-object variable (empty `from`)
50
+ // takes the request data as a whole — which is what makes a generated
51
+ // create/update call look exactly like its REST equivalent.
52
+ function graphqlBody(ctx) {
53
+ const utility = ctx.utility
54
+ const struct = utility.struct
55
+ const getprop = struct.getprop
56
+
57
+ const point = ctx.point
58
+ const gql = null == point ? undefined : point.graphql
59
+
60
+ if (null == gql) {
61
+ return undefined
62
+ }
63
+
64
+ const op = ctx.op
65
+ const input = null == op ? 'match' : op.input
66
+
67
+ // reqmatch/reqdata hold the caller's arguments for this operation.
68
+ const reqsrc = getprop(ctx, 'req' + input) || {}
69
+ const datasrc = getprop(ctx, input) || {}
70
+
71
+ const variables = {}
72
+
73
+ for (const spec of (gql.vars || [])) {
74
+ if ('' === spec.from || null == spec.from) {
75
+ // The input object IS the request body. Strip the action selector,
76
+ // which is an SDK-side point discriminator, not an API field.
77
+ const body = {}
78
+ for (const key of Object.keys(reqsrc)) {
79
+ if ('$action' !== key) {
80
+ body[key] = reqsrc[key]
81
+ }
82
+ }
83
+ variables[spec.name] = body
84
+ }
85
+ else {
86
+ const val = undefined !== getprop(reqsrc, spec.from) ?
87
+ getprop(reqsrc, spec.from) : getprop(datasrc, spec.from)
88
+
89
+ // Only send variables the caller actually supplied: sending an
90
+ // explicit null would clear a field on many APIs.
91
+ if (undefined !== val && null !== val) {
92
+ variables[spec.name] = val
93
+ }
94
+ }
95
+ }
96
+
97
+ return {
98
+ query: gql.doc,
99
+ variables,
100
+ }
101
+ }
102
+
103
+
104
+ // Inspect a decoded GraphQL response body and record a failure when the
105
+ // server reported one. Returns true when an error was recorded.
106
+ //
107
+ // Partial data (`data` alongside `errors`) is treated as failure: the REST
108
+ // surface has no partial-success concept, and silently returning half an
109
+ // object would be worse than failing. The raw envelope stays available on
110
+ // the result for callers that need it.
111
+ function graphqlErrors(ctx) {
112
+ const result = ctx.result
113
+ const point = ctx.point
114
+
115
+ if (null == result || null == point || 'graphql' !== point.kind) {
116
+ return false
117
+ }
118
+
119
+ const body = result.body
120
+ const errors = null == body ? undefined : body.errors
121
+
122
+ if (null == errors || !Array.isArray(errors) || 0 === errors.length) {
123
+ return false
124
+ }
125
+
126
+ const first = errors[0]
127
+ const msg = (null == first ? undefined : first.message) || 'graphql error'
128
+ const code = graphqlErrorCode(first)
129
+
130
+ const err = ctx.error(code,
131
+ 'graphql: ' + msg + (1 < errors.length ?
132
+ ' (+' + (errors.length - 1) + ' more)' : ''))
133
+
134
+ // Keep the full error list reachable for diagnostics.
135
+ err.graphql = errors
136
+ result.err = err
137
+ result.ok = false
138
+
139
+ return true
140
+ }
141
+
142
+
143
+ module.exports = {
144
+ graphqlBody,
145
+ graphqlErrors,
146
+ graphqlErrorCode,
147
+ GRAPHQL_CONTENT_TYPE,
148
+ }
@@ -52,7 +52,7 @@ function makeOptions(ctx) {
52
52
  },
53
53
  allow: {
54
54
  method: 'GET,PUT,POST,PATCH,DELETE,OPTIONS',
55
- op: 'create,update,load,list,remove,command,direct'
55
+ op: 'create,update,load,list,remove,command,direct,graphql'
56
56
  },
57
57
  entity: {
58
58
  '`$CHILD`': {
@@ -10,6 +10,7 @@ async function makeResponse(ctx) {
10
10
  const resultHeaders = utility.resultHeaders
11
11
  const resultBody = utility.resultBody
12
12
  const transformResponse = utility.transformResponse
13
+ const graphqlErrors = utility.graphqlErrors
13
14
 
14
15
  const spec = ctx.spec
15
16
  const result = ctx.result
@@ -33,6 +34,12 @@ async function makeResponse(ctx) {
33
34
  resultBasic(ctx)
34
35
  resultHeaders(ctx)
35
36
  await resultBody(ctx)
37
+
38
+ // GraphQL reports failures as a top-level `errors` array under HTTP 200,
39
+ // so resultBasic's status check never sees them. Lift them here, before
40
+ // the response transform tries to unwrap data that is not there.
41
+ graphqlErrors(ctx)
42
+
36
43
  transformResponse(ctx)
37
44
 
38
45
  if (null == result.err) {
@@ -37,8 +37,25 @@ function makeSpec(ctx) {
37
37
  ctx.spec.params = prepareParams(ctx)
38
38
  ctx.spec.query = prepareQuery(ctx)
39
39
  ctx.spec.headers = prepareHeaders(ctx)
40
- ctx.spec.body = prepareBody(ctx)
41
- ctx.spec.path = preparePath(ctx)
40
+
41
+ if ('graphql' === point.kind) {
42
+ // GraphQL addresses one endpoint: no path parts, no query string, and
43
+ // the body carries the operation. prepareBody is skipped deliberately —
44
+ // it only emits a body for data-input ops (create/update), whereas every
45
+ // GraphQL op posts one, including load/list/remove.
46
+ ctx.spec.body = utility.graphqlBody(ctx)
47
+ ctx.spec.path = ''
48
+ // prepareQuery already copied the op's match arguments into the query
49
+ // string. Those same values are bound as operation variables, so leaving
50
+ // them would send /graphql?id=i1 — duplicating the argument, leaking it
51
+ // into the URL, and failing servers that reject unknown query params.
52
+ ctx.spec.query = {}
53
+ ctx.spec.headers['content-type'] = utility.GRAPHQL_CONTENT_TYPE
54
+ }
55
+ else {
56
+ ctx.spec.body = prepareBody(ctx)
57
+ ctx.spec.path = preparePath(ctx)
58
+ }
42
59
 
43
60
  if (ctx.ctrl.explain) {
44
61
  ctx.ctrl.explain.spec = ctx.spec
@@ -15,6 +15,7 @@ const { makeResponse } = require('./MakeResponseUtility')
15
15
  const { makeResult } = require('./MakeResultUtility')
16
16
  const { makePoint } = require('./MakePointUtility')
17
17
  const { makeSpec } = require('./MakeSpecUtility')
18
+ const { graphqlBody, graphqlErrors, GRAPHQL_CONTENT_TYPE } = require('./GraphqlUtility')
18
19
  const { makeUrl } = require('./MakeUrlUtility')
19
20
  const { param } = require('./ParamUtility')
20
21
  const { prepareAuth } = require('./PrepareAuthUtility')
@@ -50,6 +51,9 @@ class Utility {
50
51
  makeResult = makeResult
51
52
  makePoint = makePoint
52
53
  makeSpec = makeSpec
54
+ graphqlBody = graphqlBody
55
+ graphqlErrors = graphqlErrors
56
+ GRAPHQL_CONTENT_TYPE = GRAPHQL_CONTENT_TYPE
53
57
  makeUrl = makeUrl
54
58
  param = param
55
59
  prepareAuth = prepareAuth
@@ -159,7 +159,91 @@ abstract class SdkClient(sdkopts: MutableMap<String, Any?>?) {
159
159
  return utility.makeFetchDef(ctx)
160
160
  }
161
161
 
162
+ // Raw endpoint access is operator-controllable, like every entity op.
163
+ // Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
164
+ // either one reaches the same endpoint.
162
165
  fun direct(fetchargsIn: MutableMap<String, Any?>?): MutableMap<String, Any?> {
166
+ if (!opAllowed("direct")) {
167
+ return opDenied("direct")
168
+ }
169
+
170
+ return rawRequest(fetchargsIn)
171
+ }
172
+
173
+ // Is this raw-access op permitted by the SDK's allow.op option?
174
+ private fun opAllowed(op: String): Boolean {
175
+ val allow = Struct.getpath(this.options, listOf("allow", "op"))
176
+ return allow is String && allow.contains(op)
177
+ }
178
+
179
+ private fun opDenied(op: String): MutableMap<String, Any?> {
180
+ val allow = Struct.getpath(this.options, listOf("allow", "op")) as? String
181
+ val out = linkedMapOf<String, Any?>()
182
+ out["ok"] = false
183
+ out["err"] = SdkError(op + "_allow",
184
+ "ProjectNameSDK: " + op + ": operation not allowed by" +
185
+ " SDK option allow.op value: \"" + (allow ?: "") + "\"", null)
186
+ return out
187
+ }
188
+
189
+ // Raw GraphQL access: the pressure valve that makes the generated
190
+ // surface's deliberate omissions (per-call selection sets, typed filter
191
+ // builders, batching, subscriptions) livable — the whole schema stays
192
+ // reachable.
193
+ //
194
+ // Thin wrapper over the same prepare/fetch path direct uses, with the one
195
+ // thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200
196
+ // as a top-level `errors` array, so status alone would report a failed
197
+ // query as ok.
198
+ //
199
+ // NOTE: like direct, this bypasses the feature pipeline — no retry,
200
+ // ratelimit or paging features apply.
201
+ @JvmOverloads
202
+ fun graphql(
203
+ query: String,
204
+ variables: MutableMap<String, Any?>? = null,
205
+ ctrl: MutableMap<String, Any?>? = null,
206
+ ): MutableMap<String, Any?> {
207
+ if (!opAllowed("graphql")) {
208
+ return opDenied("graphql")
209
+ }
210
+
211
+ val res = rawRequest(linkedMapOf<String, Any?>(
212
+ "method" to "POST",
213
+ "headers" to linkedMapOf<String, Any?>(
214
+ "content-type" to "application/json"),
215
+ "body" to linkedMapOf<String, Any?>(
216
+ "query" to query,
217
+ "variables" to (variables ?: linkedMapOf<String, Any?>())),
218
+ "ctrl" to (ctrl ?: linkedMapOf<String, Any?>()),
219
+ ))
220
+
221
+ // Errors are read BEFORE any status check: a GraphQL parse or validation
222
+ // failure comes back as HTTP 400 carrying the standard { errors: [...] }
223
+ // body, and the raw path represents a non-2xx as ok:false with no err —
224
+ // so returning early on status would discard the server's own
225
+ // diagnostics, which are the only useful part of that response.
226
+ val errors = Struct.getpath(res, listOf("data", "errors")) as? List<Any?>
227
+
228
+ if (null != errors && errors.isNotEmpty()) {
229
+ val m = Struct.getprop(errors[0], "message") as? String
230
+ val msg = if (m.isNullOrEmpty()) "graphql error" else m
231
+ res["ok"] = false
232
+ res["err"] = SdkError("graphql_error",
233
+ "ProjectNameSDK: graphql: " + msg, null)
234
+ res["graphql"] = errors
235
+ }
236
+
237
+ return res
238
+ }
239
+
240
+ // Ungated request path shared by direct and graphql, each of which checks
241
+ // its own allow.op token first. Private, rather than a flag on fetchargs: a
242
+ // caller-supplied marker would let anyone opt straight back out of the gate
243
+ // by passing it.
244
+ private fun rawRequest(
245
+ fetchargsIn: MutableMap<String, Any?>?,
246
+ ): MutableMap<String, Any?> {
163
247
  val utility = this.util
164
248
 
165
249
  val fetchargs = fetchargsIn ?: linkedMapOf()
@@ -37,6 +37,8 @@ class Utility private constructor(register: Boolean) {
37
37
  lateinit var prepareParams: (Context) -> MutableMap<String, Any?>
38
38
  lateinit var preparePath: (Context) -> String
39
39
  lateinit var prepareQuery: (Context) -> MutableMap<String, Any?>
40
+ lateinit var graphqlBody: (Context) -> Any?
41
+ lateinit var graphqlErrors: (Context) -> Boolean
40
42
  lateinit var resultBasic: (Context) -> Result
41
43
  lateinit var resultBody: (Context) -> Result
42
44
  lateinit var resultHeaders: (Context) -> Result
@@ -4,6 +4,7 @@ import java.util.regex.Pattern
4
4
 
5
5
  import KOTLINPACKAGE.core.Context
6
6
  import KOTLINPACKAGE.core.SdkClient
7
+ import KOTLINPACKAGE.utility.struct.Struct
7
8
 
8
9
  // Pagination support for list operations. On the way out (PreRequest) it
9
10
  // stamps page/limit (or a cursor) into the request query; on the way back
@@ -40,6 +41,14 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
40
41
  paging = linkedMapOf()
41
42
  }
42
43
 
44
+ // GraphQL paginates through operation VARIABLES, not the query string.
45
+ // This hook runs after makeSpec, so spec.body already holds the
46
+ // { query, variables } envelope, and before makeFetchDef serialises it.
47
+ if ("graphql" == Struct.getprop(ctx.point, "kind") as? String) {
48
+ graphqlPreRequest(ctx, paging)
49
+ return
50
+ }
51
+
43
52
  val cursor = paging["cursor"]
44
53
  if (cursor != null) {
45
54
  spec.query[cursorParam] = cursor
@@ -57,6 +66,45 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
57
66
  }
58
67
  }
59
68
 
69
+ // Relay pagination: the cursor is the `after` variable (or whatever the
70
+ // model named it), and the page size is `first`.
71
+ private fun graphqlPreRequest(ctx: Context, paging: MutableMap<String, Any?>) {
72
+ val body = ctx.spec?.body as? MutableMap<String, Any?> ?: return
73
+
74
+ var variables = body["variables"] as? MutableMap<String, Any?>
75
+ if (variables == null) {
76
+ variables = linkedMapOf()
77
+ body["variables"] = variables
78
+ }
79
+
80
+ val afterVar = FeatureOptions.foptStr(this.options, "afterVar", "after")
81
+ val firstVar = FeatureOptions.foptStr(this.options, "firstVar", "first")
82
+
83
+ // Only bind variables the operation actually declares, or the server
84
+ // rejects the document.
85
+ val declared = mutableSetOf<String>()
86
+ val varlist = Struct.getpath(ctx.point, listOf("graphql", "vars")) as? List<Any?>
87
+ if (varlist != null) {
88
+ for (v in varlist) {
89
+ val name = Struct.getprop(v, "name") as? String
90
+ if (name != null) {
91
+ declared.add(name)
92
+ }
93
+ }
94
+ }
95
+
96
+ val cursor = paging["cursor"]
97
+ if (cursor != null && declared.contains(afterVar)) {
98
+ variables[afterVar] = cursor
99
+ }
100
+
101
+ if (this.options?.get("limit") != null && variables[firstVar] == null &&
102
+ declared.contains(firstVar)
103
+ ) {
104
+ variables[firstVar] = FeatureOptions.foptInt(this.options, "limit", 0)
105
+ }
106
+ }
107
+
60
108
  override fun preResult(ctx: Context) {
61
109
  if (!this.active || !isList(ctx)) {
62
110
  return
@@ -81,6 +129,43 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
81
129
  }
82
130
  }
83
131
 
132
+ // Set when the response states hasMore outright, rather than leaving it
133
+ // to be inferred from the presence of a cursor.
134
+ var explicitMore = false
135
+
136
+ // Relay connections carry the cursor in pageInfo, at the path the model
137
+ // recorded for this op.
138
+ val page = Struct.getpath(ctx.point, listOf("graphql", "page")) as? Map<*, *>
139
+ if (page != null && body is MutableMap<*, *>) {
140
+ // `connpath` locates the connection object inside the response
141
+ // envelope (data.<field>); the cursor/more paths are relative to it.
142
+ var conn: Any? = body
143
+ val connpath = page["connpath"] as? String
144
+ if (!connpath.isNullOrEmpty()) {
145
+ val sub = Struct.getpath(body, connpath)
146
+ if (sub != null) {
147
+ conn = sub
148
+ }
149
+ }
150
+
151
+ val cursorpath = page["cursor"] as? String
152
+ if (!cursorpath.isNullOrEmpty()) {
153
+ val c = Struct.getpath(conn, cursorpath)
154
+ if (c != null) {
155
+ paging["cursor"] = c
156
+ }
157
+ }
158
+
159
+ val morepath = page["more"] as? String
160
+ if (!morepath.isNullOrEmpty()) {
161
+ val more = Struct.getpath(conn, morepath)
162
+ if (more is Boolean) {
163
+ paging["hasMore"] = more
164
+ explicitMore = true
165
+ }
166
+ }
167
+ }
168
+
84
169
  // Body-level cursors.
85
170
  if (body is MutableMap<*, *>) {
86
171
  val bm = body as MutableMap<String, Any?>
@@ -95,10 +180,16 @@ class PagingFeature : BaseFeature("paging", "0.0.1", true) {
95
180
  }
96
181
  if (bm["hasMore"] is Boolean) {
97
182
  paging["hasMore"] = bm["hasMore"]
183
+ explicitMore = true
98
184
  }
99
185
  }
100
186
 
101
- if (paging["hasMore"] != true &&
187
+ // Cursor presence only INFERS another page. When the server stated the
188
+ // answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
189
+ // that wins: a final page normally carries both an end cursor and
190
+ // hasNextPage false, and inferring from the cursor there would send the
191
+ // caller back for a page that does not exist, forever.
192
+ if (!explicitMore && paging["hasMore"] != true &&
102
193
  (paging["next"] != null || paging["cursor"] != null || paging["nextPage"] != null)
103
194
  ) {
104
195
  paging["hasMore"] = true