@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
@@ -480,6 +480,29 @@
480
480
  (or (try (Long/parseLong s) (catch Exception _ nil))
481
481
  (try (Double/parseDouble s) (catch Exception _ nil))))))
482
482
 
483
+ ;; Relay pagination: the cursor is the `after` variable (or whatever the model
484
+ ;; named it), and the page size is `first`.
485
+ (defn- graphql-pre-request [fa spec point paging]
486
+ (let [body (core/oget spec :body)]
487
+ (when (vs/ismap body)
488
+ (let [variables (let [v (mget body "variables")]
489
+ (if (vs/ismap v)
490
+ v
491
+ (let [nv (vs/jm)] (.put ^java.util.Map body "variables" nv) nv)))
492
+ after-var (opt fa "afterVar" "after")
493
+ first-var (opt fa "firstVar" "first")
494
+ ;; Only bind variables the operation actually declares, or the
495
+ ;; server rejects the document.
496
+ declared (let [vl (vs/getpath point "graphql.vars")]
497
+ (if (vs/islist vl)
498
+ (into #{} (keep #(vs/getprop % "name") (vec vl)))
499
+ #{}))]
500
+ (when (and (some? (mget paging "cursor")) (declared after-var))
501
+ (.put ^java.util.Map variables after-var (mget paging "cursor")))
502
+ (when (and (some? (opt fa "limit")) (nil? (mget variables first-var))
503
+ (declared first-var))
504
+ (.put ^java.util.Map variables first-var (opt fa "limit")))))))
505
+
483
506
  (defn paging-feature []
484
507
  (let [fa (new-feature "paging" false "0.0.1")
485
508
  list? (fn [ctx] (let [ops (or (opt fa "ops") (vs/jt "list"))
@@ -501,12 +524,19 @@
501
524
  cursor-param (opt fa "cursorParam" "cursor")
502
525
  ctrl (core/oget ctx :ctrl)
503
526
  paging (let [p (when ctrl (core/oget ctrl :paging))] (if (vs/ismap p) p (vs/jm)))]
504
- (cond
505
- (some? (mget paging "cursor")) (.put ^java.util.Map q cursor-param (mget paging "cursor"))
506
- (nil? (mget q page-param))
507
- (.put ^java.util.Map q page-param (if (nil? (mget paging "page")) (opt fa "startPage" 1) (mget paging "page"))))
508
- (when (and (some? (opt fa "limit")) (nil? (mget q limit-param)))
509
- (.put ^java.util.Map q limit-param (opt fa "limit"))))))))
527
+ ;; GraphQL paginates through operation VARIABLES, not
528
+ ;; the query string. This hook runs after make-spec, so
529
+ ;; spec.body already holds the { query, variables }
530
+ ;; envelope, and before make-fetch-def serialises it.
531
+ (if (= "graphql" (vs/getprop (core/oget ctx :point) "kind"))
532
+ (graphql-pre-request fa spec (core/oget ctx :point) paging)
533
+ (do
534
+ (cond
535
+ (some? (mget paging "cursor")) (.put ^java.util.Map q cursor-param (mget paging "cursor"))
536
+ (nil? (mget q page-param))
537
+ (.put ^java.util.Map q page-param (if (nil? (mget paging "page")) (opt fa "startPage" 1) (mget paging "page"))))
538
+ (when (and (some? (opt fa "limit")) (nil? (mget q limit-param)))
539
+ (.put ^java.util.Map q limit-param (opt fa "limit"))))))))))
510
540
  "PreResult"
511
541
  (fn [ctx]
512
542
  (when (and (active? fa) (list? ctx))
@@ -522,14 +552,50 @@
522
552
  (when (some? link)
523
553
  (when-let [m (re-find #"(?i)<([^>]+)>\s*;\s*rel=\"?next\"?" (str link))]
524
554
  (.put ^java.util.Map paging "next" (nth m 1))))
525
- (when (vs/ismap body)
526
- (when (some? (mget body "next")) (when (nil? (mget paging "next")) (.put ^java.util.Map paging "next" (mget body "next"))))
527
- (when (some? (mget body "cursor")) (.put ^java.util.Map paging "cursor" (mget body "cursor")))
528
- (when (some? (mget body "nextCursor")) (.put ^java.util.Map paging "cursor" (mget body "nextCursor")))
529
- (let [hm (mget body "hasMore")] (when (or (= hm true) (= hm false)) (.put ^java.util.Map paging "hasMore" hm))))
530
- (.put ^java.util.Map paging "hasMore"
531
- (boolean (or (mget paging "hasMore") (some? (mget paging "next"))
532
- (some? (mget paging "cursor")) (some? (mget paging "nextPage")))))
555
+ ;; Set when the response states hasMore outright, rather
556
+ ;; than leaving it to be inferred from a cursor.
557
+ (let [explicit-more (atom false)
558
+ point (core/oget ctx :point)
559
+ page (vs/getpath point "graphql.page")]
560
+ ;; Relay connections carry the cursor in pageInfo, at
561
+ ;; the path the model recorded for this op.
562
+ (when (and (vs/ismap page) (vs/ismap body))
563
+ ;; `connpath` locates the connection object inside the
564
+ ;; response envelope (data.<field>); the cursor/more
565
+ ;; paths are relative to it.
566
+ (let [connpath (mget page "connpath")
567
+ conn (if (and (string? connpath) (seq connpath))
568
+ (or (vs/getpath body connpath) body)
569
+ body)
570
+ cursorpath (mget page "cursor")
571
+ morepath (mget page "more")]
572
+ (when (and (string? cursorpath) (seq cursorpath))
573
+ (when-let [c (vs/getpath conn cursorpath)]
574
+ (.put ^java.util.Map paging "cursor" c)))
575
+ (when (and (string? morepath) (seq morepath))
576
+ (let [more (vs/getpath conn morepath)]
577
+ (when (or (= more true) (= more false))
578
+ (.put ^java.util.Map paging "hasMore" more)
579
+ (reset! explicit-more true))))))
580
+ (when (vs/ismap body)
581
+ (when (some? (mget body "next")) (when (nil? (mget paging "next")) (.put ^java.util.Map paging "next" (mget body "next"))))
582
+ (when (some? (mget body "cursor")) (.put ^java.util.Map paging "cursor" (mget body "cursor")))
583
+ (when (some? (mget body "nextCursor")) (.put ^java.util.Map paging "cursor" (mget body "nextCursor")))
584
+ (let [hm (mget body "hasMore")]
585
+ (when (or (= hm true) (= hm false))
586
+ (.put ^java.util.Map paging "hasMore" hm)
587
+ (reset! explicit-more true))))
588
+ ;; Cursor presence only INFERS another page. When the
589
+ ;; server stated the answer outright — relay's
590
+ ;; `hasNextPage: false`, or a body `hasMore` — that
591
+ ;; wins: a final page normally carries both an end
592
+ ;; cursor and hasNextPage false, and inferring from the
593
+ ;; cursor there would send the caller back for a page
594
+ ;; that does not exist, forever.
595
+ (when-not @explicit-more
596
+ (.put ^java.util.Map paging "hasMore"
597
+ (boolean (or (mget paging "hasMore") (some? (mget paging "next"))
598
+ (some? (mget paging "cursor")) (some? (mget paging "nextPage")))))))
533
599
  (core/oset! result :paging paging)
534
600
  (track-set! fa "_paging" (vs/jm "last" paging)))))))
535
601
  )
@@ -560,6 +560,15 @@ public:
560
560
 
561
561
  Value prepare(const Value& fetchargs);
562
562
  Value direct(const Value& fetchargs);
563
+ Value graphql(const std::string& query, const Value& variables = Value::undef(),
564
+ const Value& ctrl = Value::undef());
565
+
566
+ private:
567
+ bool opAllowed(const std::string& op);
568
+ Value opDenied(const std::string& op);
569
+ Value rawRequest(const Value& fetchargs);
570
+
571
+ public:
563
572
 
564
573
  static Value testOptions(const Value& testopts, const Value& sdkopts);
565
574
  };
@@ -868,7 +877,81 @@ inline Value SdkClient::prepare(const Value& fetchargs_) {
868
877
  return u->makeFetchDef(ctx);
869
878
  }
870
879
 
880
+ // Is this raw-access op permitted by the SDK's allow.op option?
881
+ inline bool SdkClient::opAllowed(const std::string& op) {
882
+ Value allow = Struct::getpath(options, {"allow", "op"});
883
+ if (!allow.is_string()) return false;
884
+ return std::string::npos != allow.as_string().find(op);
885
+ }
886
+
887
+ inline Value SdkClient::opDenied(const std::string& op) {
888
+ Value allow = Struct::getpath(options, {"allow", "op"});
889
+ std::string a = allow.is_string() ? allow.as_string() : "";
890
+ Value out = vmap();
891
+ map_put(out, "ok", Value(false));
892
+ map_put(out, "err", vmap({{"message", Value(
893
+ "ProjectNameSDK: " + op + ": operation not allowed by"
894
+ " SDK option allow.op value: \"" + a + "\"")}}));
895
+ return out;
896
+ }
897
+
898
+ // Raw endpoint access is operator-controllable, like every entity op.
899
+ // Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
900
+ // either one reaches the same endpoint.
871
901
  inline Value SdkClient::direct(const Value& fetchargs_) {
902
+ if (!opAllowed("direct")) return opDenied("direct");
903
+ return rawRequest(fetchargs_);
904
+ }
905
+
906
+ // Raw GraphQL access: the pressure valve that makes the generated surface's
907
+ // deliberate omissions (per-call selection sets, typed filter builders,
908
+ // batching, subscriptions) livable — the whole schema stays reachable.
909
+ //
910
+ // Thin wrapper over the same prepare/fetch path direct uses, with the one
911
+ // thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
912
+ // a top-level `errors` array, so status alone would report a failed query as
913
+ // ok.
914
+ //
915
+ // NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
916
+ // or paging features apply.
917
+ inline Value SdkClient::graphql(const std::string& query, const Value& variables,
918
+ const Value& ctrl) {
919
+ if (!opAllowed("graphql")) return opDenied("graphql");
920
+
921
+ Value res = rawRequest(vmap({
922
+ {"method", Value(std::string("POST"))},
923
+ {"headers", vmap({{"content-type", Value(std::string("application/json"))}})},
924
+ {"body", vmap({
925
+ {"query", Value(query)},
926
+ {"variables", variables.is_map() ? variables : vmap()}})},
927
+ {"ctrl", ctrl.is_map() ? ctrl : vmap()}}));
928
+
929
+ // Errors are read BEFORE any status check: a GraphQL parse or validation
930
+ // failure comes back as HTTP 400 carrying the standard { errors: [...] }
931
+ // body, and the raw path represents a non-2xx as ok:false with no err — so
932
+ // returning early on status would discard the server's own diagnostics,
933
+ // which are the only useful part of that response.
934
+ Value errors = getp(getp(res, "data"), "errors");
935
+
936
+ if (errors.is_list() && !errors.as_list()->empty()) {
937
+ Value first = (*errors.as_list())[0];
938
+ Value m = getp(first, "message");
939
+ std::string msg = m.is_string() && !m.as_string().empty()
940
+ ? m.as_string() : "graphql error";
941
+ map_put(res, "ok", Value(false));
942
+ map_put(res, "err", vmap({{"message",
943
+ Value("ProjectNameSDK: graphql: " + msg)}}));
944
+ map_put(res, "graphql", errors);
945
+ }
946
+
947
+ return res;
948
+ }
949
+
950
+ // Ungated request path shared by direct and graphql, each of which checks its
951
+ // own allow.op token first. Private, rather than a flag on fetchargs: a
952
+ // caller-supplied marker would let anyone opt straight back out of the gate
953
+ // by passing it.
954
+ inline Value SdkClient::rawRequest(const Value& fetchargs_) {
872
955
  UtilityPtr u = utility;
873
956
  Value fetchargs = fetchargs_.is_map() ? fetchargs_ : vmap();
874
957
 
@@ -49,6 +49,14 @@ public:
49
49
  Value paging = ctx->ctrl ? ctx->ctrl->paging : Value::undef();
50
50
  if (!paging.is_map()) paging = vmap();
51
51
 
52
+ // GraphQL paginates through operation VARIABLES, not the query string.
53
+ // This hook runs after makeSpec, so spec->body already holds the
54
+ // { query, variables } envelope, and before makeFetchDef serialises it.
55
+ if ("graphql" == as_str(getp(ctx->point, "kind"))) {
56
+ graphqlPreRequest(ctx, paging);
57
+ return;
58
+ }
59
+
52
60
  Value cursor = getp(paging, "cursor");
53
61
  if (!is_nullish(cursor)) {
54
62
  map_put(spec->query, cursorParam, cursor);
@@ -66,6 +74,44 @@ public:
66
74
  }
67
75
  }
68
76
 
77
+ // Relay pagination: the cursor is the `after` variable (or whatever the
78
+ // model named it), and the page size is `first`.
79
+ void graphqlPreRequest(CtxPtr ctx, const Value& paging) {
80
+ Value body = ctx->spec->body;
81
+ if (!body.is_map()) return;
82
+
83
+ Value variables = getp(body, "variables");
84
+ if (!variables.is_map()) {
85
+ variables = vmap();
86
+ map_put(body, "variables", variables);
87
+ }
88
+
89
+ std::string afterVar = fopt::foptStr(options, "afterVar", "after");
90
+ std::string firstVar = fopt::foptStr(options, "firstVar", "first");
91
+
92
+ // Only bind variables the operation actually declares, or the server
93
+ // rejects the document.
94
+ bool afterDeclared = false, firstDeclared = false;
95
+ Value varlist = Struct::getpath(ctx->point, {"graphql", "vars"});
96
+ if (varlist.is_list()) {
97
+ for (const auto& v : *varlist.as_list()) {
98
+ std::string n = as_str(getp(v, "name"));
99
+ if (n == afterVar) afterDeclared = true;
100
+ if (n == firstVar) firstDeclared = true;
101
+ }
102
+ }
103
+
104
+ Value cursor = getp(paging, "cursor");
105
+ if (afterDeclared && !is_nullish(cursor)) {
106
+ map_put(variables, afterVar, cursor);
107
+ }
108
+
109
+ if (firstDeclared && !is_nullish(getp(options, "limit")) &&
110
+ is_nullish(getp(variables, firstVar))) {
111
+ map_put(variables, firstVar, Value(fopt::foptInt(options, "limit", 0)));
112
+ }
113
+ }
114
+
69
115
  void preResult(CtxPtr ctx) override {
70
116
  if (!active || !isList(ctx)) return;
71
117
  ResultPtr result = ctx->result;
@@ -92,6 +138,39 @@ public:
92
138
  }
93
139
  }
94
140
 
141
+ // Set when the response states hasMore outright, rather than leaving it
142
+ // to be inferred from the presence of a cursor.
143
+ bool explicitMore = false;
144
+
145
+ // Relay connections carry the cursor in pageInfo, at the path the model
146
+ // recorded for this op.
147
+ Value page = Struct::getpath(ctx->point, {"graphql", "page"});
148
+ if (page.is_map() && body.is_map()) {
149
+ // `connpath` locates the connection object inside the response
150
+ // envelope (data.<field>); the cursor/more paths are relative to it.
151
+ Value conn = body;
152
+ std::string connpath = as_str(getp(page, "connpath"));
153
+ if (!connpath.empty()) {
154
+ Value sub = Struct::getpath(body, splitDots(connpath));
155
+ if (!is_nullish(sub)) conn = sub;
156
+ }
157
+
158
+ std::string cursorpath = as_str(getp(page, "cursor"));
159
+ if (!cursorpath.empty()) {
160
+ Value c = Struct::getpath(conn, splitDots(cursorpath));
161
+ if (!is_nullish(c)) map_put(paging, "cursor", c);
162
+ }
163
+
164
+ std::string morepath = as_str(getp(page, "more"));
165
+ if (!morepath.empty()) {
166
+ Value m = Struct::getpath(conn, splitDots(morepath));
167
+ if (m.is_bool()) {
168
+ map_put(paging, "hasMore", m);
169
+ explicitMore = true;
170
+ }
171
+ }
172
+ }
173
+
95
174
  // Body-level cursors.
96
175
  if (body.is_map()) {
97
176
  Value bnext = getp(body, "next");
@@ -109,10 +188,16 @@ public:
109
188
  Value bhasMore = getp(body, "hasMore");
110
189
  if (bhasMore.is_bool()) {
111
190
  map_put(paging, "hasMore", bhasMore);
191
+ explicitMore = true;
112
192
  }
113
193
  }
114
194
 
115
- if (!is_true(getp(paging, "hasMore")) &&
195
+ // Cursor presence only INFERS another page. When the server stated the
196
+ // answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
197
+ // that wins: a final page normally carries both an end cursor and
198
+ // hasNextPage false, and inferring from the cursor there would send the
199
+ // caller back for a page that does not exist, forever.
200
+ if (!explicitMore && !is_true(getp(paging, "hasMore")) &&
116
201
  (!is_nullish(getp(paging, "next")) || !is_nullish(getp(paging, "cursor")) ||
117
202
  !is_nullish(getp(paging, "nextPage")))) {
118
203
  map_put(paging, "hasMore", Value(true));
@@ -123,6 +208,23 @@ public:
123
208
  }
124
209
 
125
210
  private:
211
+ // The model records connection/cursor/more as dot paths; Struct::getpath
212
+ // takes pre-split segments.
213
+ static std::vector<std::string> splitDots(const std::string& path) {
214
+ std::vector<std::string> out;
215
+ size_t start = 0;
216
+ while (true) {
217
+ size_t dot = path.find('.', start);
218
+ if (std::string::npos == dot) {
219
+ out.push_back(path.substr(start));
220
+ break;
221
+ }
222
+ out.push_back(path.substr(start, dot - start));
223
+ start = dot + 1;
224
+ }
225
+ return out;
226
+ }
227
+
126
228
  bool isList(CtxPtr ctx) {
127
229
  std::string opname = "";
128
230
  if (ctx->op) opname = ctx->op->name;
@@ -9,6 +9,7 @@
9
9
 
10
10
  #include <algorithm>
11
11
  #include <memory>
12
+ #include <cctype>
12
13
  #include <string>
13
14
  #include <vector>
14
15
 
@@ -376,6 +377,133 @@ inline Value makePoint(CtxPtr ctx) {
376
377
  return ctx->point;
377
378
  }
378
379
 
380
+ // ---- graphql ----------------------------------------------------------
381
+ //
382
+ // GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
383
+ // produces uses this unchanged. The API-specific part — which operations
384
+ // exist and what each one's document is — is model data, computed once by
385
+ // apidef and emitted into Config.
386
+
387
+ // Content type every GraphQL-over-HTTP request uses.
388
+ inline const char* graphqlContentType() { return "application/json"; }
389
+
390
+ // Map a GraphQL error to the same error codes the HTTP path produces, so a
391
+ // caller handles auth or rate limiting identically on both transports.
392
+ // Servers put the machine-readable code in `extensions.code`; Linear-style
393
+ // APIs use `extensions.type`.
394
+ inline std::string graphqlErrorCode(const Value& gqlerr) {
395
+ Value ext = getp(gqlerr, "extensions");
396
+
397
+ std::string raw = as_str(getp(ext, "code"));
398
+ if (raw.empty()) raw = as_str(getp(ext, "type"));
399
+ for (auto& ch : raw) ch = (char)std::toupper((unsigned char)ch);
400
+
401
+ if (raw.find("AUTH") != std::string::npos ||
402
+ raw.find("FORBIDDEN") != std::string::npos ||
403
+ raw.find("UNAUTHENTICATED") != std::string::npos) {
404
+ return "request_auth";
405
+ }
406
+ if (raw.find("RATELIMIT") != std::string::npos ||
407
+ raw.find("RATE_LIMIT") != std::string::npos ||
408
+ raw.find("TOO_MANY") != std::string::npos) {
409
+ return "request_ratelimit";
410
+ }
411
+ if (raw.find("BAD_USER_INPUT") != std::string::npos ||
412
+ raw.find("VALIDATION") != std::string::npos ||
413
+ raw.find("INVALID") != std::string::npos) {
414
+ return "request_invalid";
415
+ }
416
+
417
+ return "request_graphql";
418
+ }
419
+
420
+ // Build the request body for a GraphQL point.
421
+ //
422
+ // Variables come from the op's own arguments: a named variable binds to the
423
+ // like-named argument (`from`), and the input-object variable (empty
424
+ // `from`) takes the request data as a whole — which is what makes a
425
+ // generated create/update call look exactly like its REST equivalent.
426
+ inline Value graphqlBody(CtxPtr ctx) {
427
+ Value gql = getp(ctx->point, "graphql");
428
+ if (!gql.is_map()) return Value::undef();
429
+
430
+ // reqmatch/reqdata hold the caller's arguments for this operation; which
431
+ // one depends on whether the op takes match or data input.
432
+ Value reqsrc = ctx->reqmatch;
433
+ Value datasrc = ctx->match;
434
+ if (ctx->op && ctx->op->input == "data") {
435
+ reqsrc = ctx->reqdata;
436
+ datasrc = ctx->data;
437
+ }
438
+ if (!reqsrc.is_map()) reqsrc = vmap();
439
+ if (!datasrc.is_map()) datasrc = vmap();
440
+
441
+ Value variables = vmap();
442
+
443
+ Value varlist = getp(gql, "vars");
444
+ if (varlist.is_list()) {
445
+ for (const auto& spec : *varlist.as_list()) {
446
+ if (!spec.is_map()) continue;
447
+
448
+ std::string name = as_str(getp(spec, "name"));
449
+ std::string from = as_str(getp(spec, "from"));
450
+
451
+ if (from.empty()) {
452
+ // The input object IS the request body. Strip the action selector,
453
+ // which is an SDK-side point discriminator, not an API field.
454
+ Value body = vmap();
455
+ for (const auto& item : Struct::items(reqsrc)) {
456
+ std::string key = as_str(pair_key(item));
457
+ if ("$action" != key) map_put(body, key, pair_val(item));
458
+ }
459
+ map_put(variables, name, body);
460
+ continue;
461
+ }
462
+
463
+ // Only send variables the caller actually supplied: sending an
464
+ // explicit null would clear a field on many APIs.
465
+ Value val = getp(reqsrc, from);
466
+ if (is_nullish(val)) val = getp(datasrc, from);
467
+ if (!is_nullish(val)) map_put(variables, name, val);
468
+ }
469
+ }
470
+
471
+ Value out = vmap();
472
+ map_put(out, "query", getp(gql, "doc"));
473
+ map_put(out, "variables", variables);
474
+
475
+ return out;
476
+ }
477
+
478
+ // Inspect a decoded GraphQL response body and record a failure when the
479
+ // server reported one. Returns true when an error was recorded.
480
+ //
481
+ // Partial data (`data` alongside `errors`) is treated as failure: the REST
482
+ // surface has no partial-success concept, and silently returning half an
483
+ // object would be worse than failing.
484
+ inline bool graphqlErrors(CtxPtr ctx) {
485
+ if (!ctx->result) return false;
486
+ if ("graphql" != as_str(getp(ctx->point, "kind"))) return false;
487
+
488
+ Value errors = getp(ctx->result->body, "errors");
489
+ if (!errors.is_list()) return false;
490
+
491
+ const auto& el = *errors.as_list();
492
+ if (el.empty()) return false;
493
+
494
+ Value first = el[0];
495
+ std::string msg = as_str(getp(first, "message"));
496
+ if (msg.empty()) msg = "graphql error";
497
+ if (1 < el.size()) {
498
+ msg = msg + " (+" + std::to_string(el.size() - 1) + " more)";
499
+ }
500
+
501
+ ctx->result->err = ctx->makeError(graphqlErrorCode(first), "graphql: " + msg);
502
+ ctx->result->ok = false;
503
+
504
+ return true;
505
+ }
506
+
379
507
  // ---- makeSpec ---------------------------------------------------------
380
508
 
381
509
  inline SpecPtr makeSpec(CtxPtr ctx) {
@@ -412,8 +540,23 @@ inline SpecPtr makeSpec(CtxPtr ctx) {
412
540
  ctx->spec->params = utility->prepareParams(ctx);
413
541
  ctx->spec->query = utility->prepareQuery(ctx);
414
542
  ctx->spec->headers = utility->prepareHeaders(ctx);
415
- ctx->spec->body = utility->prepareBody(ctx);
416
- ctx->spec->path = utility->preparePath(ctx);
543
+
544
+ if ("graphql" == as_str(getp(ctx->point, "kind"))) {
545
+ // GraphQL addresses one endpoint: no path parts, no query string, and
546
+ // the body carries the operation. prepareBody is skipped deliberately
547
+ // — it only emits a body for data-input ops, whereas every GraphQL op
548
+ // posts one, including load/list/remove.
549
+ ctx->spec->body = graphqlBody(ctx);
550
+ ctx->spec->path = "";
551
+ // prepareQuery already copied the op's match arguments into the query
552
+ // string. Those same values are bound as operation variables, so
553
+ // leaving them would send /graphql?id=i1.
554
+ ctx->spec->query = vmap();
555
+ map_put(ctx->spec->headers, "content-type", Value(graphqlContentType()));
556
+ } else {
557
+ ctx->spec->body = utility->prepareBody(ctx);
558
+ ctx->spec->path = utility->preparePath(ctx);
559
+ }
417
560
 
418
561
  if (ctx->ctrl->explain.is_map()) {
419
562
  map_put(ctx->ctrl->explain, "spec", ctx->spec->toValue());
@@ -591,6 +734,12 @@ inline ResponsePtr makeResponse(CtxPtr ctx) {
591
734
  utility->resultBasic(ctx);
592
735
  utility->resultHeaders(ctx);
593
736
  utility->resultBody(ctx);
737
+
738
+ // GraphQL reports failures as a top-level `errors` array under HTTP 200,
739
+ // so resultBasic's status check never sees them. Lift them here, before
740
+ // the response transform tries to unwrap data that is not there.
741
+ graphqlErrors(ctx);
742
+
594
743
  utility->transformResponse(ctx);
595
744
 
596
745
  if (!result->err) result->ok = true;
@@ -879,7 +1028,7 @@ inline const char* OPTSPEC_JSON() {
879
1028
  "\"headers\": { \"`$CHILD`\": \"`$STRING`\" },"
880
1029
  "\"allow\": {"
881
1030
  " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\","
882
- " \"op\": \"create,update,load,list,remove,command,direct\""
1031
+ " \"op\": \"create,update,load,list,remove,command,direct,graphql\""
883
1032
  "},"
884
1033
  "\"entity\": { \"`$CHILD`\": {"
885
1034
  " \"`$OPEN`\": true, \"active\": false, \"alias\": {} } },"
@@ -30,6 +30,8 @@ public class Utility
30
30
  public Func<Context, Dictionary<string, object?>> PrepareParams = null!;
31
31
  public Func<Context, string> PreparePath = null!;
32
32
  public Func<Context, Dictionary<string, object?>> PrepareQuery = null!;
33
+ public Func<Context, object?> GraphqlBody = null!;
34
+ public Func<Context, bool> GraphqlErrors = null!;
33
35
  public Func<Context, Result> ResultBasic = null!;
34
36
  public Func<Context, Result> ResultBody = null!;
35
37
  public Func<Context, Result> ResultHeaders = null!;
@@ -76,6 +78,8 @@ public class Utility
76
78
  PrepareParams = src.PrepareParams,
77
79
  PreparePath = src.PreparePath,
78
80
  PrepareQuery = src.PrepareQuery,
81
+ GraphqlBody = src.GraphqlBody,
82
+ GraphqlErrors = src.GraphqlErrors,
79
83
  ResultBasic = src.ResultBasic,
80
84
  ResultBody = src.ResultBody,
81
85
  ResultHeaders = src.ResultHeaders,