@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
@@ -0,0 +1,145 @@
1
+ package KOTLINPACKAGE.utility
2
+
3
+ import KOTLINPACKAGE.core.Context
4
+ import KOTLINPACKAGE.core.Result
5
+ import KOTLINPACKAGE.utility.struct.Struct
6
+
7
+ // GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
8
+ // produces uses this file unchanged. The API-specific part — which
9
+ // operations exist and what each one's document is — is model data,
10
+ // computed once by apidef and emitted into Config.
11
+ //
12
+ // Two jobs:
13
+ //
14
+ // graphqlBody — build { query, variables } for a point, binding the
15
+ // op's arguments to the document's declared variables.
16
+ //
17
+ // graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL
18
+ // reports failures as a top-level `errors` array under
19
+ // HTTP 200, so the status-driven path in resultBasic
20
+ // never sees them.
21
+
22
+ // Content type every GraphQL-over-HTTP request uses.
23
+ const val GRAPHQL_CONTENT_TYPE = "application/json"
24
+
25
+ // Map a GraphQL error to the same error codes the HTTP path produces, so a
26
+ // caller handles auth or rate limiting identically on both transports.
27
+ // Servers put the machine-readable code in `extensions.code`; Linear-style
28
+ // APIs use `extensions.type`.
29
+ fun graphqlErrorCode(gqlerr: Any?): String {
30
+ val ext = Struct.getprop(gqlerr, "extensions")
31
+
32
+ var code = Struct.getprop(ext, "code") as? String
33
+ if (code.isNullOrEmpty()) {
34
+ code = Struct.getprop(ext, "type") as? String
35
+ }
36
+ val raw = (code ?: "").uppercase()
37
+
38
+ if (raw.contains("AUTH") || raw.contains("FORBIDDEN") ||
39
+ raw.contains("UNAUTHENTICATED")
40
+ ) {
41
+ return "request_auth"
42
+ }
43
+ if (raw.contains("RATELIMIT") || raw.contains("RATE_LIMIT") ||
44
+ raw.contains("TOO_MANY")
45
+ ) {
46
+ return "request_ratelimit"
47
+ }
48
+ if (raw.contains("BAD_USER_INPUT") || raw.contains("VALIDATION") ||
49
+ raw.contains("INVALID")
50
+ ) {
51
+ return "request_invalid"
52
+ }
53
+
54
+ return "request_graphql"
55
+ }
56
+
57
+ // Build the request body for a GraphQL point.
58
+ //
59
+ // Variables come from the op's own arguments: a named variable binds to the
60
+ // like-named argument (`from`), and the input-object variable (empty
61
+ // `from`) takes the request data as a whole — which is what makes a
62
+ // generated create/update call look exactly like its REST equivalent.
63
+ @Suppress("UNCHECKED_CAST")
64
+ fun graphqlBody(ctx: Context): Any? {
65
+ val gql = Struct.getprop(ctx.point, "graphql") as? Map<String, Any?>
66
+ ?: return null
67
+
68
+ // reqmatch/reqdata hold the caller's arguments for THIS call; data/match
69
+ // hold the entity's current state. Which pair depends on whether the op
70
+ // takes match or data input. A named variable falls back to the current
71
+ // state, so updating a loaded entity with just {title} still binds the
72
+ // stored id the mutation requires.
73
+ val datainput = null != ctx.op && "data" == ctx.op!!.input
74
+ val src = (if (datainput) ctx.reqdata else ctx.reqmatch) ?: mutableMapOf()
75
+ val statesrc = (if (datainput) ctx.data else ctx.match) ?: mutableMapOf()
76
+
77
+ val variables = mutableMapOf<String, Any?>()
78
+
79
+ val varlist = Struct.getprop(gql, "vars") as? List<Any?> ?: emptyList()
80
+
81
+ for (entry in varlist) {
82
+ val spec = entry as? Map<String, Any?> ?: continue
83
+
84
+ val name = Struct.getprop(spec, "name") as? String ?: continue
85
+ val from = Struct.getprop(spec, "from") as? String
86
+
87
+ if (from.isNullOrEmpty()) {
88
+ // The input object IS the request body. Strip the action selector,
89
+ // which is an SDK-side point discriminator, not an API field.
90
+ val body = mutableMapOf<String, Any?>()
91
+ for ((k, v) in src) {
92
+ if ("\$action" != k) {
93
+ body[k] = v
94
+ }
95
+ }
96
+ variables[name] = body
97
+ continue
98
+ }
99
+
100
+ // Only send variables the caller actually supplied: sending an explicit
101
+ // null would clear a field on many APIs.
102
+ val value = Struct.getprop(src, from) ?: Struct.getprop(statesrc, from)
103
+ if (null != value) {
104
+ variables[name] = value
105
+ }
106
+ }
107
+
108
+ return mutableMapOf<String, Any?>(
109
+ "query" to Struct.getprop(gql, "doc"),
110
+ "variables" to variables,
111
+ )
112
+ }
113
+
114
+ // Inspect a decoded GraphQL response body and record a failure when the
115
+ // server reported one. Returns true when an error was recorded.
116
+ //
117
+ // Partial data (`data` alongside `errors`) is treated as failure: the REST
118
+ // surface has no partial-success concept, and silently returning half an
119
+ // object would be worse than failing.
120
+ fun graphqlErrors(ctx: Context): Boolean {
121
+ val result = ctx.result ?: return false
122
+ if (null == ctx.point) return false
123
+
124
+ if ("graphql" != Struct.getprop(ctx.point, "kind") as? String) {
125
+ return false
126
+ }
127
+
128
+ val errors = Struct.getprop(result.body, "errors") as? List<Any?>
129
+ ?: return false
130
+ if (errors.isEmpty()) return false
131
+
132
+ val first = errors[0]
133
+ var msg = Struct.getprop(first, "message") as? String
134
+ if (msg.isNullOrEmpty()) {
135
+ msg = "graphql error"
136
+ }
137
+ if (1 < errors.size) {
138
+ msg = msg + " (+" + (errors.size - 1) + " more)"
139
+ }
140
+
141
+ result.err = ctx.makeError(graphqlErrorCode(first), "graphql: " + msg)
142
+ result.ok = false
143
+
144
+ return true
145
+ }
@@ -63,7 +63,7 @@ fun makeOptions(ctx: Context): MutableMap<String, Any?> {
63
63
  "\"headers\": { \"`\$CHILD`\": \"`\$STRING`\" }," +
64
64
  "\"allow\": {" +
65
65
  " \"method\": \"GET,PUT,POST,PATCH,DELETE,OPTIONS\"," +
66
- " \"op\": \"create,update,load,list,remove,command,direct\"" +
66
+ " \"op\": \"create,update,load,list,remove,command,direct,graphql\"" +
67
67
  "}," +
68
68
  "\"entity\": { \"`\$CHILD`\": {" +
69
69
  " \"`\$OPEN`\": true, \"active\": false, \"alias\": {} } }," +
@@ -90,6 +90,12 @@ fun makeResponse(ctx: Context): Response {
90
90
  utility.resultBasic(ctx)
91
91
  utility.resultHeaders(ctx)
92
92
  utility.resultBody(ctx)
93
+
94
+ // GraphQL reports failures as a top-level `errors` array under HTTP 200,
95
+ // so resultBasic's status check never sees them. Lift them here, before
96
+ // the response transform tries to unwrap data that is not there.
97
+ utility.graphqlErrors(ctx)
98
+
93
99
  utility.transformResponse(ctx)
94
100
 
95
101
  if (result.err == null) {
@@ -49,8 +49,23 @@ fun makeSpec(ctx: Context): Spec {
49
49
  spec.params = utility.prepareParams(ctx)
50
50
  spec.query = utility.prepareQuery(ctx)
51
51
  spec.headers = utility.prepareHeaders(ctx)
52
- spec.body = utility.prepareBody(ctx)
53
- spec.path = utility.preparePath(ctx)
52
+
53
+ if ("graphql" == Struct.getprop(ctx.point, "kind") as? String) {
54
+ // GraphQL addresses one endpoint: no path parts, no query string, and
55
+ // the body carries the operation. prepareBody is skipped deliberately
56
+ // — it only emits a body for data-input ops, whereas every GraphQL op
57
+ // posts one, including load/list/remove.
58
+ spec.body = utility.graphqlBody(ctx)
59
+ spec.path = ""
60
+ // prepareQuery already copied the op's match arguments into the query
61
+ // string. Those same values are bound as operation variables, so
62
+ // leaving them would send /graphql?id=i1.
63
+ spec.query = mutableMapOf()
64
+ spec.headers["content-type"] = GRAPHQL_CONTENT_TYPE
65
+ } else {
66
+ spec.body = utility.prepareBody(ctx)
67
+ spec.path = utility.preparePath(ctx)
68
+ }
54
69
 
55
70
  if (ctx.ctrl.explain != null) {
56
71
  ctx.ctrl.explain!!["spec"] = spec
@@ -30,6 +30,8 @@ object Register {
30
30
  u.prepareParams = ::prepareParams
31
31
  u.preparePath = ::preparePath
32
32
  u.prepareQuery = ::prepareQuery
33
+ u.graphqlBody = ::graphqlBody
34
+ u.graphqlErrors = ::graphqlErrors
33
35
  u.resultBasic = ::resultBasic
34
36
  u.resultBody = ::resultBody
35
37
  u.resultHeaders = ::resultHeaders
@@ -328,6 +328,122 @@ def prepareBody (ctx : Value) : SIO Value := do
328
328
  if opInputOf (← opnameOf ctx) == "data" then transformRequest ctx
329
329
  else pure .noval
330
330
 
331
+ -- ---------------------------------------------------------------------------
332
+ -- graphql (transport)
333
+ --
334
+ -- GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
335
+ -- produces uses this code unchanged. The API-specific part — which operations
336
+ -- exist and what each one's document is — is model data, computed once by
337
+ -- apidef and emitted into Config.
338
+ --
339
+ -- Two jobs:
340
+ --
341
+ -- graphqlBody — build { query, variables } for a point, binding the op's
342
+ -- arguments to the document's declared variables.
343
+ --
344
+ -- graphqlErrors — lift a GraphQL failure into an SDK error. GraphQL reports
345
+ -- failures as a top-level `errors` array under HTTP 200, so
346
+ -- the status-driven path in resultBasic never sees them.
347
+ -- ---------------------------------------------------------------------------
348
+
349
+ /-- Content type every GraphQL-over-HTTP request uses. -/
350
+ def graphqlContentType : String := "application/json"
351
+
352
+ private def hasSub (hay needle : String) : Bool :=
353
+ 1 < (hay.splitOn needle).length
354
+
355
+ /-- Map a GraphQL error to the same error codes the HTTP path produces, so a
356
+ caller handles auth or rate limiting identically on both transports. Servers
357
+ put the machine-readable code in `extensions.code`; Linear-style APIs use
358
+ `extensions.type`. -/
359
+ def graphqlErrorCode (gqlerr : Value) : SIO String := do
360
+ let ext ← gp gqlerr "extensions"
361
+ let c ← gpS ext "code"
362
+ let code ← if c == "" then gpS ext "type" else pure c
363
+ let raw := code.toUpper
364
+ if hasSub raw "AUTH" || hasSub raw "FORBIDDEN" || hasSub raw "UNAUTHENTICATED" then
365
+ pure "request_auth"
366
+ else if hasSub raw "RATELIMIT" || hasSub raw "RATE_LIMIT" || hasSub raw "TOO_MANY" then
367
+ pure "request_ratelimit"
368
+ else if hasSub raw "BAD_USER_INPUT" || hasSub raw "VALIDATION" || hasSub raw "INVALID" then
369
+ pure "request_invalid"
370
+ else
371
+ pure "request_graphql"
372
+
373
+ /-- Build the request body for a GraphQL point.
374
+
375
+ Variables come from the op's own arguments: a named variable binds to the
376
+ like-named argument (`from`), and the input-object variable (empty `from`)
377
+ takes the request data as a whole — which is what makes a generated
378
+ create/update call look exactly like its REST equivalent. -/
379
+ def graphqlBody (ctx : Value) : SIO Value := do
380
+ let gql ← gp (← gp ctx "point") "graphql"
381
+ match gql with
382
+ | .map _ => do
383
+ -- reqmatch/reqdata hold the caller's arguments for THIS call; data/match
384
+ -- hold the entity's current state. Which pair depends on whether the op
385
+ -- takes match or data input. A named variable falls back to the current
386
+ -- state, so updating a loaded entity with just {title} still binds the
387
+ -- stored id the mutation requires.
388
+ let datainput := opInputOf (← opnameOf ctx) == "data"
389
+ let reqsrc ← asMap (← gp ctx (if datainput then "reqdata" else "reqmatch"))
390
+ let datasrc ← asMap (← gp ctx (if datainput then "data" else "match"))
391
+ let variables ← emptyMap
392
+ let varlist ← match (← gp gql "vars") with
393
+ | .list i => listItems i
394
+ | _ => pure #[]
395
+ for spec in varlist do
396
+ match spec with
397
+ | .map _ => do
398
+ let name ← gpS spec "name"
399
+ let from ← gpS spec "from"
400
+ if name != "" then
401
+ if from == "" then do
402
+ -- The input object IS the request body. Strip the action
403
+ -- selector, which is an SDK-side point discriminator, not an API
404
+ -- field.
405
+ let body ← emptyMap
406
+ for k in (← keysof reqsrc) do
407
+ if k != "$action" then sp body k (← gp reqsrc k)
408
+ sp variables name body
409
+ else do
410
+ -- Only send variables the caller actually supplied: sending an
411
+ -- explicit null would clear a field on many APIs.
412
+ let v0 ← gp reqsrc from
413
+ let v ← if isNullish v0 then gp datasrc from else pure v0
414
+ if !(isNullish v) then sp variables name v
415
+ | _ => pure ()
416
+ newMap #[("query", ← gp gql "doc"), ("variables", variables)]
417
+ | _ => pure .noval
418
+
419
+ /-- Inspect a decoded GraphQL response body and record a failure when the
420
+ server reported one. Returns true when an error was recorded.
421
+
422
+ Partial data (`data` alongside `errors`) is treated as failure: the REST
423
+ surface has no partial-success concept, and silently returning half an object
424
+ would be worse than failing. -/
425
+ def graphqlErrors (ctx : Value) : SIO Bool := do
426
+ let resultV ← gp ctx "result"
427
+ match resultV with
428
+ | .map _ => do
429
+ if (← gpS (← gp ctx "point") "kind") != "graphql" then pure false
430
+ else
431
+ let errors ← match (← gp (← gp resultV "body") "errors") with
432
+ | .list i => listItems i
433
+ | _ => pure #[]
434
+ if errors.size == 0 then pure false
435
+ else do
436
+ let first := errors[0]!
437
+ let m0 ← gpS first "message"
438
+ let m1 := if m0 == "" then "graphql error" else m0
439
+ let msg := if 1 < errors.size then
440
+ m1 ++ " (+" ++ toString (errors.size - 1) ++ " more)" else m1
441
+ let e ← mkErr (← graphqlErrorCode first) ("graphql: " ++ msg)
442
+ sp resultV "err" e
443
+ sp resultV "ok" (.bool false)
444
+ pure true
445
+ | _ => pure false
446
+
331
447
  -- ---------------------------------------------------------------------------
332
448
  -- result assembly
333
449
  -- ---------------------------------------------------------------------------
@@ -404,7 +520,20 @@ def makeSpec (ctx : Value) : SIO (Value × Option Value) := do
404
520
  sp spec "path" (.str (← preparePath ctx))
405
521
  sp spec "params" (← prepareParams ctx)
406
522
  sp spec "query" (← prepareQuery ctx)
407
- sp spec "headers" (← prepareHeaders ctx)
523
+ let headers ← prepareHeaders ctx
524
+ sp spec "headers" headers
525
+ if (← gpS point "kind") == "graphql" then do
526
+ -- GraphQL addresses one endpoint: no path parts, no query string, and the
527
+ -- body carries the operation. prepareBody is skipped deliberately — it
528
+ -- only emits a body for data-input ops, whereas every GraphQL op posts
529
+ -- one, including load/list/remove.
530
+ sp spec "body" (← graphqlBody ctx)
531
+ sp spec "path" (.str "")
532
+ -- prepareQuery already copied the op's match arguments into the query
533
+ -- string. Those same values are bound as operation variables, so leaving
534
+ -- them would send /graphql?id=i1.
535
+ sp spec "query" (← emptyMap)
536
+ sp headers "content-type" (.str graphqlContentType)
408
537
  pure (spec, none)
409
538
 
410
539
  /-- base/prefix/path/suffix joined, `{param}` substituted, query appended. -/
@@ -613,6 +742,10 @@ def makeResponse (ctx : Value) : SIO (Value × Option Value) := do
613
742
  resultBasic ctx
614
743
  resultHeaders ctx
615
744
  resultBody ctx
745
+ -- GraphQL reports failures as a top-level `errors` array under HTTP 200,
746
+ -- so resultBasic's status check never sees them. Lift them here, before
747
+ -- the response transform tries to unwrap data that is not there.
748
+ let _ ← graphqlErrors ctx
616
749
  let _ ← transformResponse ctx
617
750
  let errv ← gp resultV "err"
618
751
  if !(← isErrV errv) then sp resultV "ok" (.bool true)
@@ -13,6 +13,7 @@
13
13
  -- configurable.
14
14
 
15
15
  local BaseFeature = require("feature.base_feature")
16
+ local vs = require("utility.struct.struct")
16
17
 
17
18
  local PagingFeature = {}
18
19
  PagingFeature.__index = PagingFeature
@@ -83,6 +84,14 @@ function PagingFeature:PreRequest(ctx)
83
84
  paging = ctx.ctrl.paging
84
85
  end
85
86
 
87
+ -- GraphQL paginates through operation VARIABLES, not the query string.
88
+ -- This hook runs after make_spec, so spec.body already holds the
89
+ -- { query, variables } envelope, and before make_fetch_def serialises it.
90
+ if vs.getprop(ctx.point, "kind") == "graphql" then
91
+ self:_graphql_pre_request(ctx, paging)
92
+ return
93
+ end
94
+
86
95
  if paging["cursor"] ~= nil then
87
96
  spec.query[cursor_param] = paging["cursor"]
88
97
  elseif spec.query[page_param] == nil then
@@ -99,6 +108,47 @@ function PagingFeature:PreRequest(ctx)
99
108
  end
100
109
 
101
110
 
111
+ -- Relay pagination: the cursor is the `after` variable (or whatever the model
112
+ -- named it), and the page size is `first`.
113
+ function PagingFeature:_graphql_pre_request(ctx, paging)
114
+ local body = ctx.spec.body
115
+ if type(body) ~= "table" then
116
+ return
117
+ end
118
+
119
+ local variables = body["variables"]
120
+ if type(variables) ~= "table" then
121
+ variables = {}
122
+ body["variables"] = variables
123
+ end
124
+
125
+ local after_var = self.options["afterVar"] or "after"
126
+ local first_var = self.options["firstVar"] or "first"
127
+
128
+ -- Only bind variables the operation actually declares, or the server
129
+ -- rejects the document.
130
+ local declared = {}
131
+ local varlist = vs.getpath(ctx.point, "graphql.vars")
132
+ if type(varlist) == "table" then
133
+ for _, v in ipairs(varlist) do
134
+ local name = vs.getprop(v, "name")
135
+ if name ~= nil then
136
+ declared[name] = true
137
+ end
138
+ end
139
+ end
140
+
141
+ if paging["cursor"] ~= nil and declared[after_var] then
142
+ variables[after_var] = paging["cursor"]
143
+ end
144
+
145
+ if self.options["limit"] ~= nil and variables[first_var] == nil
146
+ and declared[first_var] then
147
+ variables[first_var] = self.options["limit"]
148
+ end
149
+ end
150
+
151
+
102
152
  function PagingFeature:PreResult(ctx)
103
153
  if not self.active then
104
154
  return
@@ -131,6 +181,43 @@ function PagingFeature:PreResult(ctx)
131
181
  end
132
182
  end
133
183
 
184
+ -- Set when the response states hasMore outright, rather than leaving it to
185
+ -- be inferred from the presence of a cursor.
186
+ local explicit_more = false
187
+
188
+ -- Relay connections carry the cursor in pageInfo, at the path the model
189
+ -- recorded for this op.
190
+ local page = vs.getpath(ctx.point, "graphql.page")
191
+ if type(page) == "table" and type(body) == "table" then
192
+ -- `connpath` locates the connection object inside the response envelope
193
+ -- (data.<field>); the cursor/more paths are relative to it.
194
+ local conn = body
195
+ local connpath = page["connpath"]
196
+ if type(connpath) == "string" and connpath ~= "" then
197
+ local sub = vs.getpath(body, connpath)
198
+ if sub ~= nil then
199
+ conn = sub
200
+ end
201
+ end
202
+
203
+ local cursorpath = page["cursor"]
204
+ if type(cursorpath) == "string" and cursorpath ~= "" then
205
+ local cursor = vs.getpath(conn, cursorpath)
206
+ if cursor ~= nil then
207
+ paging.cursor = cursor
208
+ end
209
+ end
210
+
211
+ local morepath = page["more"]
212
+ if type(morepath) == "string" and morepath ~= "" then
213
+ local more = vs.getpath(conn, morepath)
214
+ if type(more) == "boolean" then
215
+ paging.hasMore = more
216
+ explicit_more = true
217
+ end
218
+ end
219
+ end
220
+
134
221
  -- Body-level cursors.
135
222
  if type(body) == "table" then
136
223
  if body["next"] ~= nil and paging.next == nil then
@@ -144,11 +231,19 @@ function PagingFeature:PreResult(ctx)
144
231
  end
145
232
  if type(body["hasMore"]) == "boolean" then
146
233
  paging.hasMore = body["hasMore"]
234
+ explicit_more = true
147
235
  end
148
236
  end
149
237
 
150
- paging.hasMore = paging.hasMore or
151
- paging.next ~= nil or paging.cursor ~= nil or paging.nextPage ~= nil
238
+ -- Cursor presence only INFERS another page. When the server stated the
239
+ -- answer outright relay's `hasNextPage: false`, or a body `hasMore` —
240
+ -- that wins: a final page normally carries both an end cursor and
241
+ -- hasNextPage false, and inferring from the cursor there would send the
242
+ -- caller back for a page that does not exist, forever.
243
+ if not explicit_more then
244
+ paging.hasMore = paging.hasMore or
245
+ paging.next ~= nil or paging.cursor ~= nil or paging.nextPage ~= nil
246
+ end
152
247
 
153
248
  result.paging = paging
154
249
 
@@ -0,0 +1,163 @@
1
+ -- ProjectName SDK utility: graphql
2
+ --
3
+ -- GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
4
+ -- produces uses this file unchanged. The API-specific part — which
5
+ -- operations exist and what each one's document is — is model data,
6
+ -- computed once by apidef and emitted into Config.
7
+ --
8
+ -- Two jobs:
9
+ --
10
+ -- graphql_body — build { query, variables } for a point, binding the
11
+ -- op's arguments to the document's declared variables.
12
+ --
13
+ -- graphql_errors — lift a GraphQL failure into an SDK error. GraphQL
14
+ -- reports failures as a top-level `errors` array under
15
+ -- HTTP 200, so the status-driven path in result_basic
16
+ -- never sees them.
17
+
18
+ local vs = require("utility.struct.struct")
19
+
20
+ local M = {}
21
+
22
+ -- Content type every GraphQL-over-HTTP request uses.
23
+ M.CONTENT_TYPE = "application/json"
24
+
25
+ -- Map a GraphQL error to the same error codes the HTTP path produces, so a
26
+ -- caller handles auth or rate limiting identically on both transports.
27
+ -- Servers put the machine-readable code in `extensions.code`; Linear-style
28
+ -- APIs use `extensions.type`.
29
+ function M.error_code(gqlerr)
30
+ local ext = vs.getprop(gqlerr, "extensions")
31
+
32
+ local raw = vs.getprop(ext, "code")
33
+ if raw == nil or raw == "" then
34
+ raw = vs.getprop(ext, "type")
35
+ end
36
+ raw = type(raw) == "string" and raw:upper() or ""
37
+
38
+ if raw:find("AUTH", 1, true) or raw:find("FORBIDDEN", 1, true)
39
+ or raw:find("UNAUTHENTICATED", 1, true) then
40
+ return "request_auth"
41
+ end
42
+ if raw:find("RATELIMIT", 1, true) or raw:find("RATE_LIMIT", 1, true)
43
+ or raw:find("TOO_MANY", 1, true) then
44
+ return "request_ratelimit"
45
+ end
46
+ if raw:find("BAD_USER_INPUT", 1, true) or raw:find("VALIDATION", 1, true)
47
+ or raw:find("INVALID", 1, true) then
48
+ return "request_invalid"
49
+ end
50
+
51
+ return "request_graphql"
52
+ end
53
+
54
+ -- Build the request body for a GraphQL point.
55
+ --
56
+ -- Variables come from the op's own arguments: a named variable binds to the
57
+ -- like-named argument (`from`), and the input-object variable (empty
58
+ -- `from`) takes the request data as a whole — which is what makes a
59
+ -- generated create/update call look exactly like its REST equivalent.
60
+ function M.body(ctx)
61
+ local gql = vs.getprop(ctx.point, "graphql")
62
+ if type(gql) ~= "table" then
63
+ return nil
64
+ end
65
+
66
+ -- reqmatch/reqdata hold the caller's arguments for THIS call; data/match
67
+ -- hold the entity's current state. Which pair depends on whether the op
68
+ -- takes match or data input. A named variable falls back to the current
69
+ -- state, so updating a loaded entity with just {title} still binds the
70
+ -- stored id the mutation requires.
71
+ local reqsrc = ctx.reqmatch
72
+ local datasrc = ctx.match
73
+ if ctx.op ~= nil and ctx.op.input == "data" then
74
+ reqsrc = ctx.reqdata
75
+ datasrc = ctx.data
76
+ end
77
+ if type(reqsrc) ~= "table" then
78
+ reqsrc = {}
79
+ end
80
+ if type(datasrc) ~= "table" then
81
+ datasrc = {}
82
+ end
83
+
84
+ local variables = {}
85
+
86
+ local varlist = vs.getprop(gql, "vars")
87
+ if type(varlist) == "table" then
88
+ for _, spec in ipairs(varlist) do
89
+ if type(spec) == "table" then
90
+ local name = vs.getprop(spec, "name")
91
+ local from = vs.getprop(spec, "from")
92
+
93
+ if name ~= nil then
94
+ if from == nil or from == "" then
95
+ -- The input object IS the request body. Strip the action
96
+ -- selector, which is an SDK-side point discriminator, not an
97
+ -- API field.
98
+ local body = {}
99
+ for k, v in pairs(reqsrc) do
100
+ if k ~= "$action" then
101
+ body[k] = v
102
+ end
103
+ end
104
+ variables[name] = body
105
+ else
106
+ -- Only send variables the caller actually supplied: sending an
107
+ -- explicit null would clear a field on many APIs.
108
+ local val = vs.getprop(reqsrc, from)
109
+ if val == nil then
110
+ val = vs.getprop(datasrc, from)
111
+ end
112
+ if val ~= nil then
113
+ variables[name] = val
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ return {
122
+ query = vs.getprop(gql, "doc"),
123
+ variables = variables,
124
+ }
125
+ end
126
+
127
+ -- Inspect a decoded GraphQL response body and record a failure when the
128
+ -- server reported one. Returns true when an error was recorded.
129
+ --
130
+ -- Partial data (`data` alongside `errors`) is treated as failure: the REST
131
+ -- surface has no partial-success concept, and silently returning half an
132
+ -- object would be worse than failing.
133
+ function M.errors(ctx)
134
+ local result = ctx.result
135
+ if result == nil or ctx.point == nil then
136
+ return false
137
+ end
138
+
139
+ if vs.getprop(ctx.point, "kind") ~= "graphql" then
140
+ return false
141
+ end
142
+
143
+ local errors = vs.getprop(result.body, "errors")
144
+ if type(errors) ~= "table" or #errors == 0 then
145
+ return false
146
+ end
147
+
148
+ local first = errors[1]
149
+ local msg = vs.getprop(first, "message")
150
+ if type(msg) ~= "string" or msg == "" then
151
+ msg = "graphql error"
152
+ end
153
+ if 1 < #errors then
154
+ msg = msg .. " (+" .. tostring(#errors - 1) .. " more)"
155
+ end
156
+
157
+ result.err = ctx:make_error(M.error_code(first), "graphql: " .. msg)
158
+ result.ok = false
159
+
160
+ return true
161
+ end
162
+
163
+ return M
@@ -66,7 +66,7 @@ local function make_options_util(ctx)
66
66
  },
67
67
  allow = {
68
68
  method = "GET,PUT,POST,PATCH,DELETE,OPTIONS",
69
- op = "create,update,load,list,remove,command,direct",
69
+ op = "create,update,load,list,remove,command,direct,graphql",
70
70
  },
71
71
  entity = {
72
72
  ["`$CHILD`"] = {
@@ -28,6 +28,12 @@ local function make_response_util(ctx)
28
28
  utility.result_basic(ctx)
29
29
  utility.result_headers(ctx)
30
30
  utility.result_body(ctx)
31
+
32
+ -- GraphQL reports failures as a top-level `errors` array under HTTP 200,
33
+ -- so result_basic's status check never sees them. Lift them here, before
34
+ -- the response transform tries to unwrap data that is not there.
35
+ utility.graphql_errors(ctx)
36
+
31
37
  utility.transform_response(ctx)
32
38
 
33
39
  if result.err == nil then