@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
@@ -12,6 +12,7 @@
12
12
 
13
13
  #include <ctype.h>
14
14
  #include <stdlib.h>
15
+ #include <stdio.h>
15
16
  #include <string.h>
16
17
 
17
18
  typedef struct {
@@ -106,6 +107,93 @@ static void paging_init(Feature* f, Context* ctx, voxgig_value* options) {
106
107
  pf->active = fopt_bool(options, "active", false);
107
108
  }
108
109
 
110
+ // The model records connection/cursor/more as dot paths; getpath_c takes a
111
+ // NULL-terminated segment array. Both the copy and the segment array are
112
+ // sized from the path itself: a fixed buffer would silently truncate a long
113
+ // generated path and look up the wrong node, which reads as "no cursor" and
114
+ // stops pagination after one page.
115
+ static voxgig_value* getpath_dotted(voxgig_value* store, const char* path) {
116
+ if (!path || path[0] == '\0') return store;
117
+
118
+ size_t len = strlen(path);
119
+
120
+ // Segment count is bounded by dots + 1, so one allocation covers any path.
121
+ size_t maxseg = 2;
122
+ for (size_t i = 0; i < len; i++) {
123
+ if (path[i] == '.') maxseg++;
124
+ }
125
+
126
+ char* buf = (char*)malloc(len + 1);
127
+ const char** keys = (const char**)malloc(maxseg * sizeof(char*));
128
+ if (!buf || !keys) {
129
+ free(buf);
130
+ free(keys);
131
+ return voxgig_new_undef();
132
+ }
133
+ memcpy(buf, path, len + 1);
134
+
135
+ size_t n = 0;
136
+ char* save = NULL;
137
+ for (char* tok = strtok_r(buf, ".", &save); tok;
138
+ tok = strtok_r(NULL, ".", &save)) {
139
+ keys[n++] = tok;
140
+ }
141
+ keys[n] = NULL;
142
+
143
+ voxgig_value* out = getpath_c(store, keys);
144
+
145
+ // getpath_c does not retain the key strings, so both buffers are ours to
146
+ // release once the lookup has returned.
147
+ free(buf);
148
+ free(keys);
149
+
150
+ return out;
151
+ }
152
+
153
+ // Relay pagination: the cursor is the `after` variable (or whatever the
154
+ // model named it), and the page size is `first`.
155
+ static void paging_graphql_pre_request(PagingFeature* pf, Context* ctx,
156
+ voxgig_value* paging) {
157
+ voxgig_value* options = pf->options;
158
+ voxgig_value* body = ctx->spec->body;
159
+ if (!voxgig_is_map(body)) return;
160
+
161
+ voxgig_value* variables = getp(body, "variables");
162
+ if (!voxgig_is_map(variables)) {
163
+ variables = voxgig_new_map();
164
+ setp(body, "variables", variables);
165
+ }
166
+
167
+ const char* after_var = fopt_str(options, "afterVar", "after");
168
+ const char* first_var = fopt_str(options, "firstVar", "first");
169
+
170
+ // Only bind variables the operation actually declares, or the server
171
+ // rejects the document.
172
+ bool after_declared = false, first_declared = false;
173
+ voxgig_value* varlist = getpath2(ctx->point, "graphql", "vars");
174
+ if (voxgig_is_list(varlist)) {
175
+ voxgig_list* vl = voxgig_as_list(varlist);
176
+ for (size_t i = 0; i < vl->len; i++) {
177
+ voxgig_value* nv = getp(vl->items[i], "name");
178
+ if (!voxgig_is_string(nv)) continue;
179
+ const char* n = voxgig_as_string(nv);
180
+ if (strcmp(n, after_var) == 0) after_declared = true;
181
+ if (strcmp(n, first_var) == 0) first_declared = true;
182
+ }
183
+ }
184
+
185
+ voxgig_value* cursor = getp(paging, "cursor");
186
+ if (after_declared && !v_is_noval(cursor) && !v_is_null(cursor)) {
187
+ setp(variables, after_var, v_share(cursor));
188
+ }
189
+
190
+ voxgig_value* limit = getp(options, "limit");
191
+ if (first_declared && !v_is_noval(limit) && !v_is_null(limit) &&
192
+ v_is_noval(getp(variables, first_var))) {
193
+ setp(variables, first_var, v_num((double)fopt_int(options, "limit", 0)));
194
+ }
195
+ }
196
+
109
197
  static void paging_pre_request(PagingFeature* pf, Context* ctx) {
110
198
  voxgig_value* options = pf->options;
111
199
  if (!pf->active || !is_list_op(options, ctx->op->name)) return;
@@ -124,6 +212,15 @@ static void paging_pre_request(PagingFeature* pf, Context* ctx) {
124
212
  // A per-call cursor/page from ctrl takes priority (auto-iteration).
125
213
  voxgig_value* paging = ctx->ctrl->paging;
126
214
 
215
+ // GraphQL paginates through operation VARIABLES, not the query string.
216
+ // This hook runs after make_spec, so spec->body already holds the
217
+ // { query, variables } envelope, and before make_fetch_def serialises it.
218
+ voxgig_value* kind = getp(ctx->point, "kind");
219
+ if (voxgig_is_string(kind) && strcmp(voxgig_as_string(kind), "graphql") == 0) {
220
+ paging_graphql_pre_request(pf, ctx, paging);
221
+ return;
222
+ }
223
+
127
224
  voxgig_value* cursor = getp(paging, "cursor");
128
225
  if (!v_is_noval(cursor) && !v_is_null(cursor)) {
129
226
  setp(query, cursor_param, cursor);
@@ -165,6 +262,41 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
165
262
  }
166
263
  }
167
264
 
265
+ // Set when the response states hasMore outright, rather than leaving it to
266
+ // be inferred from the presence of a cursor.
267
+ bool explicit_more = false;
268
+
269
+ // Relay connections carry the cursor in pageInfo, at the path the model
270
+ // recorded for this op.
271
+ voxgig_value* page = getpath2(ctx->point, "graphql", "page");
272
+ if (voxgig_is_map(page) && voxgig_is_map(body)) {
273
+ // `connpath` locates the connection object inside the response envelope
274
+ // (data.<field>); the cursor/more paths are relative to it.
275
+ voxgig_value* conn = body;
276
+ voxgig_value* cpv = getp(page, "connpath");
277
+ if (voxgig_is_string(cpv) && voxgig_as_string(cpv)[0] != '\0') {
278
+ voxgig_value* sub = getpath_dotted(body, voxgig_as_string(cpv));
279
+ if (!v_is_noval(sub) && !v_is_null(sub)) conn = sub;
280
+ }
281
+
282
+ voxgig_value* curv = getp(page, "cursor");
283
+ if (voxgig_is_string(curv) && voxgig_as_string(curv)[0] != '\0') {
284
+ voxgig_value* cursor = getpath_dotted(conn, voxgig_as_string(curv));
285
+ if (!v_is_noval(cursor) && !v_is_null(cursor)) {
286
+ setp(paging, "cursor", v_share(cursor));
287
+ }
288
+ }
289
+
290
+ voxgig_value* morev = getp(page, "more");
291
+ if (voxgig_is_string(morev) && voxgig_as_string(morev)[0] != '\0') {
292
+ voxgig_value* mv = getpath_dotted(conn, voxgig_as_string(morev));
293
+ if (voxgig_is_bool(mv)) {
294
+ setp(paging, "hasMore", v_bool(voxgig_as_bool(mv)));
295
+ explicit_more = true;
296
+ }
297
+ }
298
+ }
299
+
168
300
  // Body-level cursors.
169
301
  if (voxgig_is_map(body)) {
170
302
  voxgig_value* next = getp(body, "next");
@@ -182,14 +314,21 @@ static void paging_pre_result(PagingFeature* pf, Context* ctx) {
182
314
  bool has_more;
183
315
  if (get_bool(body, "hasMore", &has_more)) {
184
316
  setp(paging, "hasMore", v_bool(has_more));
317
+ explicit_more = true;
185
318
  }
186
319
  }
187
320
 
321
+ // Cursor presence only INFERS another page. When the server stated the
322
+ // answer outright — relay's `hasNextPage: false`, or a body `hasMore` —
323
+ // that wins: a final page normally carries both an end cursor and
324
+ // hasNextPage false, and inferring from the cursor there would send the
325
+ // caller back for a page that does not exist, forever.
188
326
  bool hm = false;
189
327
  bool has = get_bool(paging, "hasMore", &hm);
190
328
  bool is_true = has && hm;
191
- if (!is_true && (!v_is_noval(getp(paging, "next")) || !v_is_noval(getp(paging, "cursor")) ||
192
- !v_is_noval(getp(paging, "nextPage")))) {
329
+ if (!explicit_more && !is_true &&
330
+ (!v_is_noval(getp(paging, "next")) || !v_is_noval(getp(paging, "cursor")) ||
331
+ !v_is_noval(getp(paging, "nextPage")))) {
193
332
  setp(paging, "hasMore", v_bool(true));
194
333
  }
195
334
 
@@ -0,0 +1,178 @@
1
+ // graphql utility (mirrors utility/GraphqlUtility.ts).
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_util — build { query, variables } for a point, binding
11
+ // the op's arguments to the document's declared
12
+ // variables.
13
+ //
14
+ // graphql_errors_util — lift a GraphQL failure into an SDK error.
15
+ // GraphQL reports failures as a top-level `errors`
16
+ // array under HTTP 200, so the status-driven path
17
+ // in result_basic never sees them.
18
+
19
+ #include "sdk.h"
20
+
21
+ #include <stdio.h>
22
+ #include <string.h>
23
+ #include <ctype.h>
24
+
25
+ // Uppercase copy, bounded. Used only to normalise an extension code.
26
+ static void upper_copy(const char* src, char* dst, size_t cap) {
27
+ size_t i = 0;
28
+ if (src != NULL) {
29
+ for (; src[i] != '\0' && i + 1 < cap; i++) {
30
+ dst[i] = (char)toupper((unsigned char)src[i]);
31
+ }
32
+ }
33
+ dst[i] = '\0';
34
+ }
35
+
36
+ // Map a GraphQL error to the same error codes the HTTP path produces, so a
37
+ // caller handles auth or rate limiting identically on both transports.
38
+ // Servers put the machine-readable code in `extensions.code`; Linear-style
39
+ // APIs use `extensions.type`.
40
+ const char* graphql_error_code(voxgig_value* gqlerr) {
41
+ voxgig_value* ext = getp(gqlerr, "extensions");
42
+
43
+ voxgig_value* code = getp(ext, "code");
44
+ if (!voxgig_is_string(code)) {
45
+ code = getp(ext, "type");
46
+ }
47
+
48
+ char raw[128];
49
+ upper_copy(voxgig_is_string(code) ? voxgig_as_string(code) : "", raw, sizeof(raw));
50
+
51
+ if (strstr(raw, "AUTH") || strstr(raw, "FORBIDDEN") ||
52
+ strstr(raw, "UNAUTHENTICATED")) {
53
+ return "request_auth";
54
+ }
55
+ if (strstr(raw, "RATELIMIT") || strstr(raw, "RATE_LIMIT") ||
56
+ strstr(raw, "TOO_MANY")) {
57
+ return "request_ratelimit";
58
+ }
59
+ if (strstr(raw, "BAD_USER_INPUT") || strstr(raw, "VALIDATION") ||
60
+ strstr(raw, "INVALID")) {
61
+ return "request_invalid";
62
+ }
63
+
64
+ return "request_graphql";
65
+ }
66
+
67
+ // Build the request body for a GraphQL point.
68
+ //
69
+ // Variables come from the op's own arguments: a named variable binds to the
70
+ // like-named argument (`from`), and the input-object variable (empty
71
+ // `from`) takes the request data as a whole — which is what makes a
72
+ // generated create/update call look exactly like its REST equivalent.
73
+ voxgig_value* graphql_body_util(Context* ctx) {
74
+ voxgig_value* gql = getp(ctx->point, "graphql");
75
+ if (!voxgig_is_map(gql)) return voxgig_new_noval();
76
+
77
+ // reqmatch/reqdata hold the caller's arguments for THIS call; data/match
78
+ // hold the entity's current state. Which pair depends on whether the op
79
+ // takes match or data input. A named variable falls back to the current
80
+ // state, so updating a loaded entity with just {title} still binds the
81
+ // stored id the mutation requires.
82
+ voxgig_value* reqsrc = ctx->reqmatch;
83
+ voxgig_value* datasrc = ctx->mtch;
84
+ if (ctx->op != NULL && ctx->op->input != NULL &&
85
+ strcmp(ctx->op->input, "data") == 0) {
86
+ reqsrc = ctx->reqdata;
87
+ datasrc = ctx->data;
88
+ }
89
+ if (!voxgig_is_map(reqsrc)) reqsrc = voxgig_new_map();
90
+ if (!voxgig_is_map(datasrc)) datasrc = voxgig_new_map();
91
+
92
+ voxgig_value* variables = voxgig_new_map();
93
+
94
+ voxgig_value* varlist = getp(gql, "vars");
95
+ if (voxgig_is_list(varlist)) {
96
+ voxgig_list* vl = voxgig_as_list(varlist);
97
+ for (size_t i = 0; i < vl->len; i++) {
98
+ voxgig_value* spec = vl->items[i];
99
+ if (!voxgig_is_map(spec)) continue;
100
+
101
+ voxgig_value* nameval = getp(spec, "name");
102
+ if (!voxgig_is_string(nameval)) continue;
103
+ const char* name = voxgig_as_string(nameval);
104
+
105
+ voxgig_value* fromval = getp(spec, "from");
106
+ const char* from = voxgig_is_string(fromval) ? voxgig_as_string(fromval) : "";
107
+
108
+ if (from[0] == '\0') {
109
+ // The input object IS the request body. Strip the action selector,
110
+ // which is an SDK-side point discriminator, not an API field.
111
+ voxgig_value* body = voxgig_new_map();
112
+ voxgig_map* rm = voxgig_as_map(reqsrc);
113
+ for (size_t j = 0; j < rm->len; j++) {
114
+ if (strcmp(rm->entries[j].key, "$action") != 0) {
115
+ setp(body, rm->entries[j].key, v_share(rm->entries[j].value));
116
+ }
117
+ }
118
+ setp(variables, name, body);
119
+ continue;
120
+ }
121
+
122
+ // Only send variables the caller actually supplied: sending an
123
+ // explicit null would clear a field on many APIs.
124
+ voxgig_value* val = getp(reqsrc, from);
125
+ if (v_is_noval(val) || v_is_null(val)) {
126
+ val = getp(datasrc, from);
127
+ }
128
+ if (!v_is_noval(val) && !v_is_null(val)) {
129
+ setp(variables, name, v_share(val));
130
+ }
131
+ }
132
+ }
133
+
134
+ voxgig_value* out = voxgig_new_map();
135
+ setp(out, "query", v_share(getp(gql, "doc")));
136
+ setp(out, "variables", variables);
137
+
138
+ return out;
139
+ }
140
+
141
+ // Inspect a decoded GraphQL response body and record a failure when the
142
+ // server reported one. Returns true when an error was recorded.
143
+ //
144
+ // Partial data (`data` alongside `errors`) is treated as failure: the REST
145
+ // surface has no partial-success concept, and silently returning half an
146
+ // object would be worse than failing. The raw envelope stays available on
147
+ // the result for callers that need it.
148
+ bool graphql_errors_util(Context* ctx) {
149
+ SdkResult* result = ctx->result;
150
+ if (result == NULL || ctx->point == NULL) return false;
151
+
152
+ voxgig_value* kind = getp(ctx->point, "kind");
153
+ if (!voxgig_is_string(kind) || strcmp(voxgig_as_string(kind), "graphql") != 0) {
154
+ return false;
155
+ }
156
+
157
+ voxgig_value* errors = getp(result->body, "errors");
158
+ if (!voxgig_is_list(errors)) return false;
159
+
160
+ voxgig_list* el = voxgig_as_list(errors);
161
+ if (el->len == 0) return false;
162
+
163
+ voxgig_value* first = el->items[0];
164
+ voxgig_value* msgval = getp(first, "message");
165
+ const char* msg = voxgig_is_string(msgval) ? voxgig_as_string(msgval) : "graphql error";
166
+
167
+ char buf[512];
168
+ if (el->len > 1) {
169
+ snprintf(buf, sizeof(buf), "graphql: %s (+%zu more)", msg, el->len - 1);
170
+ } else {
171
+ snprintf(buf, sizeof(buf), "graphql: %s", msg);
172
+ }
173
+
174
+ result->err = context_make_error(ctx, graphql_error_code(first), buf);
175
+ result->ok = false;
176
+
177
+ return true;
178
+ }
@@ -64,7 +64,7 @@ voxgig_value* make_options_util(Context* ctx) {
64
64
  "headers", cmap(1, "`$CHILD`", v_str("`$STRING`")),
65
65
  "allow", cmap(2,
66
66
  "method", v_str("GET,PUT,POST,PATCH,DELETE,OPTIONS"),
67
- "op", v_str("create,update,load,list,remove,command,direct")),
67
+ "op", v_str("create,update,load,list,remove,command,direct,graphql")),
68
68
  "entity", cmap(1, "`$CHILD`", cmap(3,
69
69
  "`$OPEN`", v_bool(true),
70
70
  "active", v_bool(false),
@@ -30,6 +30,12 @@ Response* make_response_util(Context* ctx, PNError** err) {
30
30
  result_basic_util(ctx);
31
31
  result_headers_util(ctx);
32
32
  result_body_util(ctx);
33
+
34
+ // GraphQL reports failures as a top-level `errors` array under HTTP 200,
35
+ // so result_basic's status check never sees them. Lift them here, before
36
+ // the response transform tries to unwrap data that is not there.
37
+ graphql_errors_util(ctx);
38
+
33
39
  transform_response_util(ctx);
34
40
 
35
41
  if (result->err == NULL) {
@@ -43,9 +43,29 @@ Spec* make_spec_util(Context* ctx, PNError** err) {
43
43
  spec->params = prepare_params_util(ctx);
44
44
  spec->query = prepare_query_util(ctx);
45
45
  spec->headers = prepare_headers_util(ctx);
46
- spec->body = prepare_body_util(ctx);
47
- char* path = prepare_path_util(ctx);
48
- spec_set_path(spec, path);
46
+
47
+ voxgig_value* pkind = getp(ctx->point, "kind");
48
+ bool is_graphql = voxgig_is_string(pkind) &&
49
+ strcmp(voxgig_as_string(pkind), "graphql") == 0;
50
+
51
+ if (is_graphql) {
52
+ // GraphQL addresses one endpoint: no path parts, no query string, and
53
+ // the body carries the operation. prepare_body is skipped deliberately
54
+ // — it only emits a body for data-input ops, whereas every GraphQL op
55
+ // posts one, including load/list/remove.
56
+ spec->body = graphql_body_util(ctx);
57
+ spec_set_path(spec, "");
58
+ // prepare_query already copied the op's match arguments into the query
59
+ // string. Those same values are bound as operation variables, so
60
+ // leaving them would send /graphql?id=i1.
61
+ spec->query = voxgig_new_map();
62
+ setp(spec->headers, "content-type",
63
+ voxgig_new_string(GRAPHQL_CONTENT_TYPE));
64
+ } else {
65
+ spec->body = prepare_body_util(ctx);
66
+ char* path = prepare_path_util(ctx);
67
+ spec_set_path(spec, path);
68
+ }
49
69
 
50
70
  Control* c = ctx->ctrl;
51
71
  if (control_has_explain(c)) {
@@ -83,7 +83,22 @@
83
83
  (when fd-err (core/sdk-throw fd-err))
84
84
  fetchdef)))
85
85
 
86
- (defn direct [client fetchargs]
86
+ ;; Is this raw-access op permitted by the SDK's allow.op option?
87
+ (defn- op-allowed? [client op]
88
+ (let [allow (vs/getpath (core/client-options-map client) "allow.op")]
89
+ (and (string? allow) (str/includes? allow op))))
90
+
91
+ (defn- op-denied [client op]
92
+ (let [allow (vs/getpath (core/client-options-map client) "allow.op")]
93
+ (vs/jm "ok" false
94
+ "err" (str "ProjectNameSDK: " op ": operation not allowed by"
95
+ " SDK option allow.op value: \"" (or allow "") "\""))))
96
+
97
+ ;; Ungated request path shared by direct and graphql, each of which checks its
98
+ ;; own allow.op token first. Private, rather than a flag on fetchargs: a
99
+ ;; caller-supplied marker would let anyone opt straight back out of the gate
100
+ ;; by passing it.
101
+ (defn- raw-request [client fetchargs]
87
102
  (let [utility (core/client-utility client)
88
103
  fetchargs (or fetchargs (vs/jm))
89
104
  pr (try {:fd (prepare client fetchargs)}
@@ -109,6 +124,54 @@
109
124
  (vs/jm "ok" (and (>= status 200) (< status 300)) "status" status "headers" headers "data" json-data))
110
125
  :else (vs/jm "ok" false "err" (core/ctx-error ctx "direct_invalid" "invalid response type")))))))
111
126
 
127
+ ;; Raw endpoint access is operator-controllable, like every entity op.
128
+ ;; Blocking it means denying BOTH the 'direct' and 'graphql' tokens, since
129
+ ;; either one reaches the same endpoint.
130
+ (defn direct [client fetchargs]
131
+ (if (op-allowed? client "direct")
132
+ (raw-request client fetchargs)
133
+ (op-denied client "direct")))
134
+
135
+ ;; Raw GraphQL access: the pressure valve that makes the generated surface's
136
+ ;; deliberate omissions (per-call selection sets, typed filter builders,
137
+ ;; batching, subscriptions) livable — the whole schema stays reachable.
138
+ ;;
139
+ ;; Thin wrapper over the same prepare/fetch path direct uses, with the one
140
+ ;; thing raw direct cannot do for GraphQL: a GraphQL failure rides HTTP 200 as
141
+ ;; a top-level `errors` array, so status alone would report a failed query as
142
+ ;; ok.
143
+ ;;
144
+ ;; NOTE: like direct, this bypasses the feature pipeline — no retry, ratelimit
145
+ ;; or paging features apply.
146
+ (defn graphql
147
+ ([client query] (graphql client query nil nil))
148
+ ([client query variables] (graphql client query variables nil))
149
+ ([client query variables ctrl]
150
+ (if-not (op-allowed? client "graphql")
151
+ (op-denied client "graphql")
152
+ (let [res (raw-request client
153
+ (vs/jm "method" "POST"
154
+ "headers" (vs/jm "content-type" "application/json")
155
+ "body" (vs/jm "query" query
156
+ "variables" (or variables (vs/jm)))
157
+ "ctrl" (or ctrl (vs/jm))))
158
+ ;; Errors are read BEFORE any status check: a GraphQL parse or
159
+ ;; validation failure comes back as HTTP 400 carrying the standard
160
+ ;; { errors: [...] } body, and the raw path represents a non-2xx as
161
+ ;; ok:false with no err — so returning early on status would
162
+ ;; discard the server's own diagnostics, which are the only useful
163
+ ;; part of that response.
164
+ errors (vs/getpath res "data.errors")
165
+ n (if (vs/islist errors) (vs/size errors) 0)]
166
+ (if (zero? n)
167
+ res
168
+ (let [m (vs/getprop (vs/getprop errors 0) "message")
169
+ m (if (and (string? m) (seq m)) m "graphql error")]
170
+ (.put ^java.util.Map res "ok" false)
171
+ (.put ^java.util.Map res "err" (str "ProjectNameSDK: graphql: " m))
172
+ (.put ^java.util.Map res "graphql" errors)
173
+ res))))))
174
+
112
175
  (defn test-sdk [testopts sdkopts]
113
176
  (let [sdkopts (let [c (vs/clone (or sdkopts (vs/jm)))] (if (vs/ismap c) c (vs/jm)))
114
177
  testopts (let [c (vs/clone (or testopts (vs/jm)))] (if (vs/ismap c) c (vs/jm)))]
@@ -471,6 +471,108 @@
471
471
  (defn u-prepare-body [ctx]
472
472
  (if (= "data" (op-input (oget ctx :op))) (ucall ctx :transform-request) nil))
473
473
 
474
+ ;; ---- graphql ---------------------------------------------------------------
475
+ ;;
476
+ ;; GraphQL transport. API-INDEPENDENT: every GraphQL SDK this generator
477
+ ;; produces uses this code unchanged. The API-specific part — which
478
+ ;; operations exist and what each one's document is — is model data, computed
479
+ ;; once by apidef and emitted into Config.
480
+ ;;
481
+ ;; Two jobs:
482
+ ;;
483
+ ;; u-graphql-body — build {query, variables} for a point, binding the op's
484
+ ;; arguments to the document's declared variables.
485
+ ;;
486
+ ;; u-graphql-errors — lift a GraphQL failure into an SDK error. GraphQL
487
+ ;; reports failures as a top-level `errors` array under
488
+ ;; HTTP 200, so the status-driven path in result-basic
489
+ ;; never sees them.
490
+
491
+ ;; Content type every GraphQL-over-HTTP request uses.
492
+ (def GRAPHQL-CONTENT-TYPE "application/json")
493
+
494
+ ;; Map a GraphQL error to the same error codes the HTTP path produces, so a
495
+ ;; caller handles auth or rate limiting identically on both transports.
496
+ ;; Servers put the machine-readable code in `extensions.code`; Linear-style
497
+ ;; APIs use `extensions.type`.
498
+ (defn graphql-error-code [gqlerr]
499
+ (let [ext (vs/getprop gqlerr "extensions")
500
+ code (let [c (vs/getprop ext "code")]
501
+ (if (and (string? c) (seq c)) c (vs/getprop ext "type")))
502
+ raw (str/upper-case (if (string? code) code ""))]
503
+ (cond
504
+ (or (str/includes? raw "AUTH") (str/includes? raw "FORBIDDEN")
505
+ (str/includes? raw "UNAUTHENTICATED")) "request_auth"
506
+ (or (str/includes? raw "RATELIMIT") (str/includes? raw "RATE_LIMIT")
507
+ (str/includes? raw "TOO_MANY")) "request_ratelimit"
508
+ (or (str/includes? raw "BAD_USER_INPUT") (str/includes? raw "VALIDATION")
509
+ (str/includes? raw "INVALID")) "request_invalid"
510
+ :else "request_graphql")))
511
+
512
+ ;; Build the request body for a GraphQL point.
513
+ ;;
514
+ ;; Variables come from the op's own arguments: a named variable binds to the
515
+ ;; like-named argument (`from`), and the input-object variable (empty `from`)
516
+ ;; takes the request data as a whole — which is what makes a generated
517
+ ;; create/update call look exactly like its REST equivalent.
518
+ (defn u-graphql-body [ctx]
519
+ (let [gql (vs/getprop (oget ctx :point) "graphql")]
520
+ (when (vs/ismap gql)
521
+ ;; reqmatch/reqdata hold the caller's arguments for this operation;
522
+ ;; which one depends on whether the op takes match or data input.
523
+ (let [datainput (= "data" (op-input (oget ctx :op)))
524
+ reqsrc (let [r (if datainput (oget ctx :reqdata) (oget ctx :reqmatch))]
525
+ (if (vs/ismap r) r (vs/jm)))
526
+ ;; data/match hold the entity's current state; a named variable
527
+ ;; falls back to it, so updating a loaded entity with just
528
+ ;; {title} still binds the stored id the mutation requires.
529
+ datasrc (let [d (if datainput (oget ctx :data) (oget ctx :match))]
530
+ (if (vs/ismap d) d (vs/jm)))
531
+ variables (vs/jm)
532
+ varlist (let [v (vs/getprop gql "vars")] (if (vs/islist v) v (vs/jt)))]
533
+ (doseq [spec (vec varlist)]
534
+ (when (vs/ismap spec)
535
+ (let [name (vs/getprop spec "name")
536
+ from (vs/getprop spec "from")]
537
+ (when (and (string? name) (seq name))
538
+ (if (or (nil? from) (not (string? from)) (empty? from))
539
+ ;; The input object IS the request body. Strip the action
540
+ ;; selector, which is an SDK-side point discriminator, not
541
+ ;; an API field.
542
+ (let [body (vs/jm)]
543
+ (doseq [item (or (vs/items reqsrc) [])]
544
+ (let [k (vs/getprop item 0) v (vs/getprop item 1)]
545
+ (when (not= "$action" k) (.put ^java.util.Map body k v))))
546
+ (.put ^java.util.Map variables name body))
547
+ ;; Only send variables the caller actually supplied: sending
548
+ ;; an explicit null would clear a field on many APIs.
549
+ (let [val (let [v (vs/getprop reqsrc from)]
550
+ (if (some? v) v (vs/getprop datasrc from)))]
551
+ (when (some? val) (.put ^java.util.Map variables name val))))))))
552
+ (vs/jm "query" (vs/getprop gql "doc") "variables" variables)))))
553
+
554
+ ;; Inspect a decoded GraphQL response body and record a failure when the
555
+ ;; server reported one. Returns true when an error was recorded.
556
+ ;;
557
+ ;; Partial data (`data` alongside `errors`) is treated as failure: the REST
558
+ ;; surface has no partial-success concept, and silently returning half an
559
+ ;; object would be worse than failing.
560
+ (defn u-graphql-errors [ctx]
561
+ (let [result (oget ctx :result)
562
+ errors (when result (vs/getprop (oget result :body) "errors"))
563
+ n (if (vs/islist errors) (vs/size errors) 0)]
564
+ (if (or (nil? result)
565
+ (not= "graphql" (vs/getprop (oget ctx :point) "kind"))
566
+ (zero? n))
567
+ false
568
+ (let [first-err (vs/getprop errors 0)
569
+ m (vs/getprop first-err "message")
570
+ m (if (and (string? m) (seq m)) m "graphql error")
571
+ m (if (< 1 n) (str m " (+" (dec n) " more)") m)]
572
+ (oset! result :err (ctx-error ctx (graphql-error-code first-err) (str "graphql: " m)))
573
+ (oset! result :ok false)
574
+ true))))
575
+
474
576
  (def HEADER-AUTH "authorization")
475
577
  (def OPTION-APIKEY "apikey")
476
578
  (def NOT-FOUND "__NOTFOUND__")
@@ -566,8 +668,23 @@
566
668
  (oset! spec :params (ucall ctx :prepare-params))
567
669
  (oset! spec :query (ucall ctx :prepare-query))
568
670
  (oset! spec :headers (ucall ctx :prepare-headers))
569
- (oset! spec :body (ucall ctx :prepare-body))
570
- (oset! spec :path (ucall ctx :prepare-path))
671
+ (if (= "graphql" (vs/getprop point "kind"))
672
+ ;; GraphQL addresses one endpoint: no path parts, no query
673
+ ;; string, and the body carries the operation. prepare-body is
674
+ ;; skipped deliberately — it only emits a body for data-input
675
+ ;; ops, whereas every GraphQL op posts one, including
676
+ ;; load/list/remove.
677
+ (do
678
+ (oset! spec :body (ucall ctx :graphql-body))
679
+ (oset! spec :path "")
680
+ ;; prepare-query already copied the op's match arguments into
681
+ ;; the query string. Those same values are bound as operation
682
+ ;; variables, so leaving them would send /graphql?id=i1.
683
+ (oset! spec :query (vs/jm))
684
+ (.put ^java.util.Map (oget spec :headers) "content-type" GRAPHQL-CONTENT-TYPE))
685
+ (do
686
+ (oset! spec :body (ucall ctx :prepare-body))
687
+ (oset! spec :path (ucall ctx :prepare-path))))
571
688
  (when-let [ex (oget (oget ctx :ctrl) :explain)] (.put ^java.util.Map ex "spec" spec))
572
689
  (let [[s err] (ucall ctx :prepare-auth)]
573
690
  (if err [nil err] (do (oset! ctx :spec s) [s nil])))))))))
@@ -696,6 +813,11 @@
696
813
  (ucall ctx :result-basic)
697
814
  (ucall ctx :result-headers)
698
815
  (ucall ctx :result-body)
816
+ ;; GraphQL reports failures as a top-level `errors` array under
817
+ ;; HTTP 200, so result-basic's status check never sees them. Lift
818
+ ;; them here, before the response transform tries to unwrap data
819
+ ;; that is not there.
820
+ (ucall ctx :graphql-errors)
699
821
  (ucall ctx :transform-response)
700
822
  (when (nil? (oget result :err)) (oset! result :ok true))
701
823
  (when-let [ex (oget (oget ctx :ctrl) :explain)] (.put ^java.util.Map ex "result" result))
@@ -800,7 +922,7 @@
800
922
  "auth" (vs/jm "prefix" "")
801
923
  "headers" (vs/jm "`$CHILD`" "`$STRING`")
802
924
  "allow" (vs/jm "method" "GET,PUT,POST,PATCH,DELETE,OPTIONS"
803
- "op" "create,update,load,list,remove,command,direct")
925
+ "op" "create,update,load,list,remove,command,direct,graphql")
804
926
  "entity" (vs/jm "`$CHILD`" (vs/jm "`$OPEN`" true "active" false "alias" (vs/jm)))
805
927
  "feature" (vs/jm "`$CHILD`" (vs/jm "`$OPEN`" true "active" false))
806
928
  "utility" (vs/jm)
@@ -901,6 +1023,7 @@
901
1023
  :param u-param :prepare-auth u-prepare-auth :prepare-body u-prepare-body
902
1024
  :prepare-headers u-prepare-headers :prepare-method u-prepare-method :prepare-params u-prepare-params
903
1025
  :prepare-path u-prepare-path :prepare-query u-prepare-query
1026
+ :graphql-body u-graphql-body :graphql-errors u-graphql-errors
904
1027
  :result-basic u-result-basic :result-body u-result-body :result-headers u-result-headers
905
1028
  :transform-request u-transform-request :transform-response u-transform-response})
906
1029