@voxgig/sdkgen 1.5.1 → 2.0.1

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 +8 -8
  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
@@ -1,6 +1,7 @@
1
1
  -- ProjectName SDK utility: make_spec
2
2
 
3
3
  local vs = require("utility.struct.struct")
4
+ local graphql = require("utility.graphql")
4
5
  local Spec = require("core.spec")
5
6
 
6
7
  local function make_spec_util(ctx)
@@ -53,8 +54,23 @@ local function make_spec_util(ctx)
53
54
  ctx.spec.params = utility.prepare_params(ctx)
54
55
  ctx.spec.query = utility.prepare_query(ctx)
55
56
  ctx.spec.headers = utility.prepare_headers(ctx)
56
- ctx.spec.body = utility.prepare_body(ctx)
57
- ctx.spec.path = utility.prepare_path(ctx)
57
+
58
+ if "graphql" == vs.getprop(ctx.point, "kind") then
59
+ -- GraphQL addresses one endpoint: no path parts, no query string, and
60
+ -- the body carries the operation. prepare_body 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.graphql_body(ctx)
64
+ ctx.spec.path = ""
65
+ -- prepare_query 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 = {}
69
+ ctx.spec.headers["content-type"] = graphql.CONTENT_TYPE
70
+ else
71
+ ctx.spec.body = utility.prepare_body(ctx)
72
+ ctx.spec.path = utility.prepare_path(ctx)
73
+ end
58
74
 
59
75
  if ctx.ctrl.explain ~= nil then
60
76
  ctx.ctrl.explain["spec"] = ctx.spec
@@ -26,6 +26,7 @@ local prepare_method = require("utility.prepare_method")
26
26
  local prepare_params = require("utility.prepare_params")
27
27
  local prepare_path = require("utility.prepare_path")
28
28
  local prepare_query = require("utility.prepare_query")
29
+ local graphql = require("utility.graphql")
29
30
  local result_basic = require("utility.result_basic")
30
31
  local result_body = require("utility.result_body")
31
32
  local result_headers = require("utility.result_headers")
@@ -58,6 +59,8 @@ local function register_all(u)
58
59
  u.prepare_params = prepare_params
59
60
  u.prepare_path = prepare_path
60
61
  u.prepare_query = prepare_query
62
+ u.graphql_body = graphql.body
63
+ u.graphql_errors = graphql.errors
61
64
  u.result_basic = result_basic
62
65
  u.result_body = result_body
63
66
  u.result_headers = result_headers
@@ -643,6 +643,37 @@ let clienttrack_feature () : feature =
643
643
  (* paging *)
644
644
  (* ------------------------------------------------------------------ *)
645
645
 
646
+ (* Relay pagination: the cursor is the `after` variable (or whatever the model
647
+ * named it), and the page size is `first`. *)
648
+ let graphql_pre_request (options : value) (spec : spec) (point : value)
649
+ (paging : value) : unit =
650
+ match spec.sp_body with
651
+ | Map _ as body ->
652
+ let variables = match getp body "variables" with
653
+ | Map _ as m -> m
654
+ | _ -> let m = empty_map () in setp body "variables" m; m in
655
+
656
+ let after_var = opt_str options "afterVar" ~default:"after" in
657
+ let first_var = opt_str options "firstVar" ~default:"first" in
658
+
659
+ (* Only bind variables the operation actually declares, or the server
660
+ * rejects the document. *)
661
+ let declared = match getpath_s point "graphql.vars" with
662
+ | List r -> List.filter_map (fun v -> match getp v "name" with
663
+ | Str n -> Some n | _ -> None) !r
664
+ | _ -> [] in
665
+
666
+ (match getp paging "cursor" with
667
+ | Noval | Null -> ()
668
+ | c -> if List.mem after_var declared then setp variables after_var c);
669
+
670
+ (match getp options "limit" with
671
+ | Noval | Null -> ()
672
+ | lim ->
673
+ if List.mem first_var declared && is_nullish (getp variables first_var)
674
+ then setp variables first_var lim)
675
+ | _ -> ()
676
+
646
677
  let paging_feature () : feature =
647
678
  let options = ref (empty_map ()) in
648
679
  let f = { f_name = "paging"; f_version = "0.0.1"; f_active = true; f_options = Noval;
@@ -676,19 +707,27 @@ let paging_feature () : feature =
676
707
  let limit_param = opt_str !options "limitParam" ~default:"limit" in
677
708
  let cursor_param = opt_str !options "cursorParam" ~default:"cursor" in
678
709
  let paging = match ctx.c_ctrl.ctrl_paging with Map _ as m -> m | _ -> empty_map () in
679
- (match getp paging "cursor" with
680
- | Noval | Null ->
681
- (match getp q page_param with
682
- | Noval ->
683
- let page = (match getp paging "page" with
684
- | Noval | Null -> (match getp !options "startPage" with Num n -> Num n | _ -> Num 1.)
685
- | p -> p) in
686
- setp q page_param page
687
- | _ -> ())
688
- | c -> setp q cursor_param c);
689
- (match getp !options "limit" with
690
- | Noval | Null -> ()
691
- | lim -> (match getp q limit_param with Noval -> setp q limit_param lim | _ -> ())))
710
+ (* GraphQL paginates through operation VARIABLES, not the query
711
+ * string. This hook runs after make_spec, so spec.sp_body already
712
+ * holds the { query, variables } envelope, and before
713
+ * make_fetch_def serialises it. *)
714
+ if get_str_d ctx.c_point "kind" "" = "graphql" then
715
+ graphql_pre_request !options spec ctx.c_point paging
716
+ else begin
717
+ (match getp paging "cursor" with
718
+ | Noval | Null ->
719
+ (match getp q page_param with
720
+ | Noval ->
721
+ let page = (match getp paging "page" with
722
+ | Noval | Null -> (match getp !options "startPage" with Num n -> Num n | _ -> Num 1.)
723
+ | p -> p) in
724
+ setp q page_param page
725
+ | _ -> ())
726
+ | c -> setp q cursor_param c);
727
+ (match getp !options "limit" with
728
+ | Noval | Null -> ()
729
+ | lim -> (match getp q limit_param with Noval -> setp q limit_param lim | _ -> ()))
730
+ end)
692
731
  | "PreResult" ->
693
732
  (match ctx.c_result with
694
733
  | None -> ()
@@ -703,18 +742,56 @@ let paging_feature () : feature =
703
742
  (match header_ci headers "link" with
704
743
  | Noval | Null -> ()
705
744
  | l -> (match extract_next (vstr_of l) with Some n -> setp paging "next" (Str n) | None -> ()));
745
+ (* Relay connections carry the cursor in pageInfo, at the path
746
+ * the model recorded for this op. `explicit_more` is set when
747
+ * the server states hasMore outright, rather than leaving it to
748
+ * be inferred from the presence of a cursor. *)
749
+ let explicit_more = ref false in
750
+ let gqlpage = getpath_s ctx.c_point "graphql.page" in
751
+ (match gqlpage, body with
752
+ | Map _, Map _ ->
753
+ (* `connpath` locates the connection object inside the
754
+ * response envelope (data.<field>); the cursor/more paths are
755
+ * relative to it. *)
756
+ let connpath = get_str_d gqlpage "connpath" "" in
757
+ let conn =
758
+ if connpath = "" then body
759
+ else (match getpath_s body connpath with
760
+ | Noval | Null -> body
761
+ | sub -> sub) in
762
+ let cursorpath = get_str_d gqlpage "cursor" "" in
763
+ if cursorpath <> "" then
764
+ (match getpath_s conn cursorpath with
765
+ | Noval | Null -> ()
766
+ | c -> setp paging "cursor" c);
767
+ let morepath = get_str_d gqlpage "more" "" in
768
+ if morepath <> "" then
769
+ (match getpath_s conn morepath with
770
+ | Bool b -> setp paging "hasMore" (Bool b); explicit_more := true
771
+ | _ -> ())
772
+ | _ -> ());
706
773
  (match body with
707
774
  | Map _ ->
708
775
  (match getp body "next" with Noval | Null -> () | n -> if is_nullish (getp paging "next") then setp paging "next" n);
709
776
  (match getp body "cursor" with Noval | Null -> () | c -> setp paging "cursor" c);
710
777
  (match getp body "nextCursor" with Noval | Null -> () | c -> setp paging "cursor" c);
711
- (match getp body "hasMore" with Bool b -> setp paging "hasMore" (Bool b) | _ -> ())
778
+ (match getp body "hasMore" with
779
+ | Bool b -> setp paging "hasMore" (Bool b); explicit_more := true
780
+ | _ -> ())
712
781
  | _ -> ());
713
- let hm = (getp paging "hasMore" = Bool true)
714
- || not (is_nullish (getp paging "next"))
715
- || not (is_nullish (getp paging "cursor"))
716
- || not (is_nullish (getp paging "nextPage")) in
717
- setp paging "hasMore" (Bool hm);
782
+ (* Cursor presence only INFERS another page. When the server
783
+ * stated the answer outright — relay's `hasNextPage: false`, or
784
+ * a body `hasMore` that wins: a final page normally carries
785
+ * both an end cursor and hasNextPage false, and inferring from
786
+ * the cursor there would send the caller back for a page that
787
+ * does not exist, forever. *)
788
+ if not !explicit_more then begin
789
+ let hm = (getp paging "hasMore" = Bool true)
790
+ || not (is_nullish (getp paging "next"))
791
+ || not (is_nullish (getp paging "cursor"))
792
+ || not (is_nullish (getp paging "nextPage")) in
793
+ setp paging "hasMore" (Bool hm)
794
+ end;
718
795
  result.rt_paging <- paging;
719
796
  track_set (cc ctx) "paging" (jo [("last", paging)]))
720
797
  | _ -> ());
@@ -1090,7 +1167,24 @@ let prepare (client : sdk_client) (fetchargs : value) : value =
1090
1167
  (match u.u_prepare_auth ctx with (_, Some err) -> raise (Sdk_error_exc err) | _ -> ());
1091
1168
  (match u.u_make_fetch_def ctx with (_, Some err) -> raise (Sdk_error_exc err) | (fd, None) -> fd)
1092
1169
 
1093
- let direct (client : sdk_client) (fetchargs : value) : value =
1170
+ (* Is this raw-access op permitted by the SDK's allow.op option? *)
1171
+ let op_allowed (client : sdk_client) (op : string) : bool =
1172
+ match getpath_s client.cl_options "allow.op" with
1173
+ | Str allow -> substr_contains allow op
1174
+ | _ -> false
1175
+
1176
+ let op_denied (client : sdk_client) (op : string) : value =
1177
+ let allow = match getpath_s client.cl_options "allow.op" with
1178
+ | Str s -> s | _ -> "" in
1179
+ jo [("ok", Bool false);
1180
+ ("err", Str ("ProjectNameSDK: " ^ op ^ ": operation not allowed by" ^
1181
+ " SDK option allow.op value: \"" ^ allow ^ "\""))]
1182
+
1183
+ (* Ungated request path shared by direct and graphql, each of which checks its
1184
+ * own allow.op token first. Separate, rather than a flag on fetchargs: a
1185
+ * caller-supplied marker would let anyone opt straight back out of the gate
1186
+ * by passing it. *)
1187
+ let raw_request (client : sdk_client) (fetchargs : value) : value =
1094
1188
  let u = client.cl_utility in
1095
1189
  let fetchargs = if is_noval fetchargs then empty_map () else fetchargs in
1096
1190
  match (try `Ok (prepare client fetchargs) with Sdk_error_exc e -> `Err e) with
@@ -1117,6 +1211,56 @@ let direct (client : sdk_client) (fetchargs : value) : value =
1117
1211
  ("headers", headers); ("data", json_data)]
1118
1212
  | _ -> jo [("ok", Bool false); ("err", err_to_value (ctx_make_error ctx "direct_invalid" "invalid response type"))]))
1119
1213
 
1214
+ (* Raw endpoint access is operator-controllable, like every entity op.
1215
+ * Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
1216
+ * either one reaches the same endpoint. *)
1217
+ let direct (client : sdk_client) (fetchargs : value) : value =
1218
+ if not (op_allowed client "direct") then op_denied client "direct"
1219
+ else raw_request client fetchargs
1220
+
1221
+ (* Raw GraphQL access: the pressure valve that makes the generated surface's
1222
+ * deliberate omissions (per-call selection sets, typed filter builders,
1223
+ * batching, subscriptions) livable — the whole schema stays reachable.
1224
+ *
1225
+ * Thin wrapper over the same prepare/fetch path direct uses, with the one
1226
+ * thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
1227
+ * a top-level `errors` array, so status alone would report a failed query as
1228
+ * ok.
1229
+ *
1230
+ * NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
1231
+ * or paging features apply. *)
1232
+ let graphql (client : sdk_client) (query : string) (variables : value)
1233
+ (ctrl : value) : value =
1234
+ if not (op_allowed client "graphql") then op_denied client "graphql"
1235
+ else begin
1236
+ let vars = match variables with Map _ as m -> m | _ -> empty_map () in
1237
+ let ctl = match ctrl with Map _ as m -> m | _ -> empty_map () in
1238
+ let res = raw_request client (jo [
1239
+ ("method", Str "POST");
1240
+ ("headers", jo [("content-type", Str "application/json")]);
1241
+ ("body", jo [("query", Str query); ("variables", vars)]);
1242
+ ("ctrl", ctl)]) in
1243
+
1244
+ (* Errors are read BEFORE any status check: a GraphQL parse or validation
1245
+ * failure comes back as HTTP 400 carrying the standard { errors: [...] }
1246
+ * body, and the raw path represents a non-2xx as ok:false with no err —
1247
+ * so returning early on status would discard the server's own
1248
+ * diagnostics, which are the only useful part of that response. *)
1249
+ let errors = match getpath_s res "data.errors" with
1250
+ | List r -> !r | _ -> [] in
1251
+
1252
+ (match errors with
1253
+ | [] -> ()
1254
+ | first :: _ ->
1255
+ let msg = get_str_d first "message" "" in
1256
+ let msg = if msg = "" then "graphql error" else msg in
1257
+ setp res "ok" (Bool false);
1258
+ setp res "err" (Str ("ProjectNameSDK: graphql: " ^ msg));
1259
+ setp res "graphql" (lst errors));
1260
+
1261
+ res
1262
+ end
1263
+
1120
1264
  let sdk_test ~(config : value) ~(make_feature : string -> feature) (testopts : value) (sdkopts : value) : sdk_client =
1121
1265
  let sdkopts = match clone (if is_noval sdkopts then empty_map () else sdkopts) with Map _ as m -> m | _ -> empty_map () in
1122
1266
  let testopts = match clone (if is_noval testopts then empty_map () else testopts) with Map _ as m -> m | _ -> empty_map () in
@@ -416,6 +416,120 @@ let prepare_query_util (ctx : ctx) : value =
416
416
  let prepare_body_util (ctx : ctx) : value =
417
417
  if ctx.c_op.op_input = "data" then (cu ctx).u_transform_request ctx else Noval
418
418
 
419
+ (* ---- graphql (transport) -------------------------------------------------
420
+ *
421
+ * GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
422
+ * produces uses this code unchanged. The API-specific part — which operations
423
+ * exist and what each one's document is — is model data, computed once by
424
+ * apidef and emitted into Config.
425
+ *
426
+ * Two jobs:
427
+ *
428
+ * graphql_body_util — build { query, variables } for a point, binding the
429
+ * op's arguments to the document's declared variables.
430
+ *
431
+ * graphql_errors_util — lift a GraphQL failure into an SDK error. GraphQL
432
+ * reports failures as a top-level `errors` array under
433
+ * HTTP 200, so the status-driven path in result_basic
434
+ * never sees them. *)
435
+
436
+ (* Content type every GraphQL-over-HTTP request uses. *)
437
+ let graphql_content_type = "application/json"
438
+
439
+ (* Map a GraphQL error to the same error codes the HTTP path produces, so a
440
+ * caller handles auth or rate limiting identically on both transports.
441
+ * Servers put the machine-readable code in `extensions.code`; Linear-style
442
+ * APIs use `extensions.type`. *)
443
+ let graphql_error_code (gqlerr : value) : string =
444
+ let ext = getp gqlerr "extensions" in
445
+ let code = get_str_d ext "code" "" in
446
+ let code = if code = "" then get_str_d ext "type" "" else code in
447
+ let raw = String.uppercase_ascii code in
448
+ if substr_contains raw "AUTH" || substr_contains raw "FORBIDDEN"
449
+ || substr_contains raw "UNAUTHENTICATED" then "request_auth"
450
+ else if substr_contains raw "RATELIMIT" || substr_contains raw "RATE_LIMIT"
451
+ || substr_contains raw "TOO_MANY" then "request_ratelimit"
452
+ else if substr_contains raw "BAD_USER_INPUT" || substr_contains raw "VALIDATION"
453
+ || substr_contains raw "INVALID" then "request_invalid"
454
+ else "request_graphql"
455
+
456
+ (* Build the request body for a GraphQL point.
457
+ *
458
+ * Variables come from the op's own arguments: a named variable binds to the
459
+ * like-named argument (`from`), and the input-object variable (empty `from`)
460
+ * takes the request data as a whole — which is what makes a generated
461
+ * create/update call look exactly like its REST equivalent. *)
462
+ let graphql_body_util (ctx : ctx) : value =
463
+ match getp ctx.c_point "graphql" with
464
+ | Map _ as gql ->
465
+ (* reqmatch/reqdata hold the caller's arguments for THIS call; data/match
466
+ * hold the entity's current state. Which pair depends on whether the op
467
+ * takes match or data input. A named variable falls back to the current
468
+ * state, so updating a loaded entity with just {title} still binds the
469
+ * stored id the mutation requires. *)
470
+ let datainput = ctx.c_op.op_input = "data" in
471
+ let reqsrc = if datainput then ctx.c_reqdata else ctx.c_reqmatch in
472
+ let datasrc = if datainput then ctx.c_data else ctx.c_match in
473
+ let reqsrc = match reqsrc with Map _ as m -> m | _ -> empty_map () in
474
+ let datasrc = match datasrc with Map _ as m -> m | _ -> empty_map () in
475
+ let variables = empty_map () in
476
+ let varlist = match getp gql "vars" with List r -> !r | _ -> [] in
477
+ List.iter (fun spec ->
478
+ match spec with
479
+ | Map _ ->
480
+ let name = get_str_d spec "name" "" in
481
+ let from = get_str_d spec "from" "" in
482
+ if name <> "" then begin
483
+ if from = "" then begin
484
+ (* The input object IS the request body. Strip the action
485
+ * selector, which is an SDK-side point discriminator, not an
486
+ * API field. *)
487
+ let body = empty_map () in
488
+ List.iter (fun k ->
489
+ if k <> "$action" then setp body k (getp reqsrc k))
490
+ (keysof reqsrc);
491
+ setp variables name body
492
+ end
493
+ else
494
+ (* Only send variables the caller actually supplied: sending an
495
+ * explicit null would clear a field on many APIs. *)
496
+ let v = getp reqsrc from in
497
+ let v = if is_nullish v then getp datasrc from else v in
498
+ if not (is_nullish v) then setp variables name v
499
+ end
500
+ | _ -> ())
501
+ varlist;
502
+ jo [("query", getp gql "doc"); ("variables", variables)]
503
+ | _ -> Noval
504
+
505
+ (* Inspect a decoded GraphQL response body and record a failure when the
506
+ * server reported one. Returns true when an error was recorded.
507
+ *
508
+ * Partial data (`data` alongside `errors`) is treated as failure: the REST
509
+ * surface has no partial-success concept, and silently returning half an
510
+ * object would be worse than failing. *)
511
+ let graphql_errors_util (ctx : ctx) : bool =
512
+ match ctx.c_result with
513
+ | None -> false
514
+ | Some result ->
515
+ if get_str_d ctx.c_point "kind" "" <> "graphql" then false
516
+ else
517
+ let errors = match getp result.rt_body "errors" with List r -> !r | _ -> [] in
518
+ let count = List.length errors in
519
+ if count = 0 then false
520
+ else begin
521
+ let first = List.hd errors in
522
+ let msg = get_str_d first "message" "" in
523
+ let msg = if msg = "" then "graphql error" else msg in
524
+ let msg =
525
+ if 1 < count then msg ^ " (+" ^ string_of_int (count - 1) ^ " more)"
526
+ else msg in
527
+ result.rt_err <-
528
+ Some (ctx_make_error ctx (graphql_error_code first) ("graphql: " ^ msg));
529
+ result.rt_ok <- false;
530
+ true
531
+ end
532
+
419
533
  let prepare_auth_util (ctx : ctx) : (spec option * sdk_error option) =
420
534
  match ctx.c_spec with
421
535
  | None -> (None, Some (ctx_make_error ctx "auth_no_spec" "Expected context spec property to be defined."))
@@ -584,8 +698,23 @@ let make_spec_util (ctx : ctx) : (spec option * sdk_error option) =
584
698
  sp.sp_params <- u.u_prepare_params ctx;
585
699
  sp.sp_query <- u.u_prepare_query ctx;
586
700
  sp.sp_headers <- u.u_prepare_headers ctx;
587
- sp.sp_body <- u.u_prepare_body ctx;
588
- sp.sp_path <- u.u_prepare_path ctx;
701
+ if get_str_d ctx.c_point "kind" "" = "graphql" then begin
702
+ (* GraphQL addresses one endpoint: no path parts, no query string, and
703
+ * the body carries the operation. prepare_body is skipped
704
+ * deliberately — it only emits a body for data-input ops, whereas
705
+ * every GraphQL op posts one, including load/list/remove. *)
706
+ sp.sp_body <- u.u_graphql_body ctx;
707
+ sp.sp_path <- "";
708
+ (* prepare_query already copied the op's match arguments into the query
709
+ * string. Those same values are bound as operation variables, so
710
+ * leaving them would send /graphql?id=i1. *)
711
+ sp.sp_query <- empty_map ();
712
+ setp sp.sp_headers "content-type" (Str graphql_content_type)
713
+ end
714
+ else begin
715
+ sp.sp_body <- u.u_prepare_body ctx;
716
+ sp.sp_path <- u.u_prepare_path ctx
717
+ end;
589
718
  (match ctx.c_ctrl.ctrl_explain with Map _ -> setp ctx.c_ctrl.ctrl_explain "spec" (spec_to_value sp) | _ -> ());
590
719
  match u.u_prepare_auth ctx with
591
720
  | (_, Some err) -> (None, Some err)
@@ -687,7 +816,13 @@ let make_response_util (ctx : ctx) : (response option * sdk_error option) =
687
816
  | None -> (None, Some (ctx_make_error ctx "response_no_result" "Expected context result property to be defined."))
688
817
  | Some result ->
689
818
  spec.sp_step <- "response";
690
- u.u_result_basic ctx; u.u_result_headers ctx; u.u_result_body ctx; ignore (u.u_transform_response ctx);
819
+ u.u_result_basic ctx; u.u_result_headers ctx; u.u_result_body ctx;
820
+ (* GraphQL reports failures as a top-level `errors` array under
821
+ * HTTP 200, so result_basic's status check never sees them. Lift
822
+ * them here, before the response transform tries to unwrap data
823
+ * that is not there. *)
824
+ ignore (u.u_graphql_errors ctx);
825
+ ignore (u.u_transform_response ctx);
691
826
  if result.rt_err = None then result.rt_ok <- true;
692
827
  (match ctx.c_ctrl.ctrl_explain with Map _ -> setp ctx.c_ctrl.ctrl_explain "result" (result_to_value result) | _ -> ());
693
828
  (Some response, None))))
@@ -763,7 +898,7 @@ let opt_spec_value () : value =
763
898
  ("auth", jo [("prefix", Str "")]);
764
899
  ("headers", jo [("`$CHILD`", Str "`$STRING`")]);
765
900
  ("allow", jo [("method", Str "GET,PUT,POST,PATCH,DELETE,OPTIONS");
766
- ("op", Str "create,update,load,list,remove,command,direct")]);
901
+ ("op", Str "create,update,load,list,remove,command,direct,graphql")]);
767
902
  ("entity", jo [("`$CHILD`", jo [("`$OPEN`", Bool true); ("active", Bool false); ("alias", empty_map ())])]);
768
903
  ("feature", jo [("`$CHILD`", jo [("`$OPEN`", Bool true); ("active", Bool false)])]);
769
904
  ("utility", empty_map ());
@@ -898,6 +1033,8 @@ let new_utility () : utility =
898
1033
  u_prepare_params = prepare_params_util;
899
1034
  u_prepare_path = prepare_path_util;
900
1035
  u_prepare_query = prepare_query_util;
1036
+ u_graphql_body = graphql_body_util;
1037
+ u_graphql_errors = graphql_errors_util;
901
1038
  u_result_basic = result_basic_util;
902
1039
  u_result_body = result_body_util;
903
1040
  u_result_headers = result_headers_util;
@@ -932,6 +1069,8 @@ let register (u : utility) : unit =
932
1069
  u.u_prepare_params <- prepare_params_util;
933
1070
  u.u_prepare_path <- prepare_path_util;
934
1071
  u.u_prepare_query <- prepare_query_util;
1072
+ u.u_graphql_body <- graphql_body_util;
1073
+ u.u_graphql_errors <- graphql_errors_util;
935
1074
  u.u_result_basic <- result_basic_util;
936
1075
  u.u_result_body <- result_body_util;
937
1076
  u.u_result_headers <- result_headers_util;
@@ -152,6 +152,8 @@ and utility = {
152
152
  mutable u_prepare_params : ctx -> value;
153
153
  mutable u_prepare_path : ctx -> string;
154
154
  mutable u_prepare_query : ctx -> value;
155
+ mutable u_graphql_body : ctx -> value;
156
+ mutable u_graphql_errors : ctx -> bool;
155
157
  mutable u_result_basic : ctx -> unit;
156
158
  mutable u_result_body : ctx -> unit;
157
159
  mutable u_result_headers : ctx -> unit;
@@ -64,6 +64,14 @@ sub PreRequest {
64
64
  $paging = $ctx->{ctrl}{paging};
65
65
  }
66
66
 
67
+ # GraphQL paginates through operation VARIABLES, not the query string.
68
+ # This hook runs after make_spec, so spec->{body} already holds the
69
+ # { query, variables } envelope, and before make_fetch_def serialises it.
70
+ if ('graphql' eq (ProjectNameHelpers::gp($ctx->{point}, 'kind') // '')) {
71
+ $self->_graphql_pre_request($ctx, $paging);
72
+ return;
73
+ }
74
+
67
75
  if (defined $paging->{cursor}) {
68
76
  $spec->{query}{$cursor_param} = $paging->{cursor};
69
77
  }
@@ -79,6 +87,45 @@ sub PreRequest {
79
87
  return;
80
88
  }
81
89
 
90
+ # Relay pagination: the cursor is the `after` variable (or whatever the model
91
+ # named it), and the page size is `first`.
92
+ sub _graphql_pre_request {
93
+ my ($self, $ctx, $paging) = @_;
94
+
95
+ my $body = $ctx->{spec}{body};
96
+ return unless ref($body) eq 'HASH';
97
+
98
+ my $variables = $body->{variables};
99
+ unless (ref($variables) eq 'HASH') {
100
+ $variables = {};
101
+ $body->{variables} = $variables;
102
+ }
103
+
104
+ my $after_var = defined $self->{options}{afterVar} ? $self->{options}{afterVar} : 'after';
105
+ my $first_var = defined $self->{options}{firstVar} ? $self->{options}{firstVar} : 'first';
106
+
107
+ # Only bind variables the operation actually declares, or the server
108
+ # rejects the document.
109
+ my %declared;
110
+ my $varlist = ProjectNameHelpers::gpath($ctx->{point}, 'graphql.vars');
111
+ if (ref($varlist) eq 'ARRAY') {
112
+ for my $v (@$varlist) {
113
+ my $name = ProjectNameHelpers::gp($v, 'name');
114
+ $declared{$name} = 1 if defined $name;
115
+ }
116
+ }
117
+
118
+ if (defined $paging->{cursor} && $declared{$after_var}) {
119
+ $variables->{$after_var} = $paging->{cursor};
120
+ }
121
+
122
+ if (defined $self->{options}{limit} && !defined $variables->{$first_var}
123
+ && $declared{$first_var}) {
124
+ $variables->{$first_var} = $self->{options}{limit};
125
+ }
126
+ return;
127
+ }
128
+
82
129
  sub PreResult {
83
130
  my ($self, $ctx) = @_;
84
131
  return unless $self->{active};
@@ -106,6 +153,39 @@ sub PreResult {
106
153
  }
107
154
  }
108
155
 
156
+ # Set when the response states hasMore outright, rather than leaving it to
157
+ # be inferred from the presence of a cursor.
158
+ my $explicit_more = 0;
159
+
160
+ # Relay connections carry the cursor in pageInfo, at the path the model
161
+ # recorded for this op.
162
+ my $page = ProjectNameHelpers::gpath($ctx->{point}, 'graphql.page');
163
+ if (ref($page) eq 'HASH' && Voxgig::Struct::ismap($body)) {
164
+ # `connpath` locates the connection object inside the response envelope
165
+ # (data.<field>); the cursor/more paths are relative to it.
166
+ my $conn = $body;
167
+ my $connpath = $page->{connpath};
168
+ if (defined $connpath && '' ne $connpath) {
169
+ my $sub = ProjectNameHelpers::gpath($body, $connpath);
170
+ $conn = $sub if defined $sub;
171
+ }
172
+
173
+ my $cursorpath = $page->{cursor};
174
+ if (defined $cursorpath && '' ne $cursorpath) {
175
+ my $cursor = ProjectNameHelpers::gpath($conn, $cursorpath);
176
+ $paging->{cursor} = $cursor if defined $cursor;
177
+ }
178
+
179
+ my $morepath = $page->{more};
180
+ if (defined $morepath && '' ne $morepath) {
181
+ my $more = ProjectNameHelpers::gpath($conn, $morepath);
182
+ if (ProjectNameHelpers::is_true($more) || ProjectNameHelpers::is_false($more)) {
183
+ $paging->{hasMore} = ProjectNameHelpers::is_true($more) ? 1 : 0;
184
+ $explicit_more = 1;
185
+ }
186
+ }
187
+ }
188
+
109
189
  # Body-level cursors.
110
190
  if (Voxgig::Struct::ismap($body)) {
111
191
  my $bnext = ProjectNameHelpers::gp($body, 'next');
@@ -118,12 +198,20 @@ sub PreResult {
118
198
  my $bhasmore = $body->{hasMore};
119
199
  if (ProjectNameHelpers::is_true($bhasmore) || ProjectNameHelpers::is_false($bhasmore)) {
120
200
  $paging->{hasMore} = ProjectNameHelpers::is_true($bhasmore) ? 1 : 0;
201
+ $explicit_more = 1;
121
202
  }
122
203
  }
123
204
 
124
- $paging->{hasMore} = ($paging->{hasMore}
125
- || defined $paging->{next} || defined $paging->{cursor}
126
- || defined $paging->{nextPage}) ? 1 : 0;
205
+ # Cursor presence only INFERS another page. When the server stated the
206
+ # answer outright - relay's `hasNextPage: false`, or a body `hasMore` -
207
+ # that wins: a final page normally carries both an end cursor and
208
+ # hasNextPage false, and inferring from the cursor there would send the
209
+ # caller back for a page that does not exist, forever.
210
+ unless ($explicit_more) {
211
+ $paging->{hasMore} = ($paging->{hasMore}
212
+ || defined $paging->{next} || defined $paging->{cursor}
213
+ || defined $paging->{nextPage}) ? 1 : 0;
214
+ }
127
215
 
128
216
  $result->{paging} = $paging;
129
217