@voxgig/sdkgen 4.22.0 → 4.24.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 (315) hide show
  1. package/bin/voxgig-sdkgen +18 -2
  2. package/dist/action/doctor.d.ts +1 -1
  3. package/dist/action/doctor.js +12 -10
  4. package/dist/action/doctor.js.map +1 -1
  5. package/dist/action/edition.js +4 -0
  6. package/dist/action/edition.js.map +1 -1
  7. package/dist/action/feature.js +5 -1
  8. package/dist/action/feature.js.map +1 -1
  9. package/dist/action/remove.d.ts +16 -0
  10. package/dist/action/remove.js +345 -0
  11. package/dist/action/remove.js.map +1 -0
  12. package/dist/action/target.js +6 -1
  13. package/dist/action/target.js.map +1 -1
  14. package/dist/cmp/ReadmeTop.js +1 -1
  15. package/dist/cmp/ReadmeTop.js.map +1 -1
  16. package/dist/cmp/ReadmeUnions.js +1 -1
  17. package/dist/cmp/ReadmeUnions.js.map +1 -1
  18. package/dist/cmp/Test.js +1 -1
  19. package/dist/cmp/Test.js.map +1 -1
  20. package/dist/helpers/buildIdNames.d.ts +3 -1
  21. package/dist/helpers/buildIdNames.js +15 -7
  22. package/dist/helpers/buildIdNames.js.map +1 -1
  23. package/dist/helpers/canonSpec.js +2 -2
  24. package/dist/helpers/canonSpec.js.map +1 -1
  25. package/dist/helpers/getMatchEntries.js +2 -2
  26. package/dist/helpers/getMatchEntries.js.map +1 -1
  27. package/dist/helpers/modelNames.js +5 -1
  28. package/dist/helpers/modelNames.js.map +1 -1
  29. package/dist/helpers/opShape.js +33 -33
  30. package/dist/helpers/opShape.js.map +1 -1
  31. package/dist/helpers/pointPath.js +5 -10
  32. package/dist/helpers/pointPath.js.map +1 -1
  33. package/dist/helpers/resolved.js +2 -29
  34. package/dist/helpers/resolved.js.map +1 -1
  35. package/dist/sdkgen.d.ts +2 -2
  36. package/dist/sdkgen.js +5 -3
  37. package/dist/sdkgen.js.map +1 -1
  38. package/dist/tsconfig.tsbuildinfo +1 -1
  39. package/dist/utility.js +15 -7
  40. package/dist/utility.js.map +1 -1
  41. package/package.json +3 -3
  42. package/project/.sdk/src/cmp/c/EntityTypes_c.ts +4 -4
  43. package/project/.sdk/src/cmp/c/ReadmeEntity_c.ts +3 -3
  44. package/project/.sdk/src/cmp/c/ReadmeModel_c.ts +3 -3
  45. package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +5 -5
  46. package/project/.sdk/src/cmp/c/Schema_c.ts +1 -1
  47. package/project/.sdk/src/cmp/c/TestDirect_c.ts +12 -12
  48. package/project/.sdk/src/cmp/clojure/ReadmeEntity_clojure.ts +3 -3
  49. package/project/.sdk/src/cmp/clojure/ReadmeModel_clojure.ts +3 -3
  50. package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +5 -5
  51. package/project/.sdk/src/cmp/clojure/Schema_clojure.ts +1 -1
  52. package/project/.sdk/src/cmp/cpp/EntityTypes_cpp.ts +3 -3
  53. package/project/.sdk/src/cmp/cpp/ReadmeEntity_cpp.ts +3 -3
  54. package/project/.sdk/src/cmp/cpp/ReadmeModel_cpp.ts +3 -3
  55. package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +5 -5
  56. package/project/.sdk/src/cmp/cpp/Schema_cpp.ts +1 -1
  57. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +14 -14
  58. package/project/.sdk/src/cmp/cpp/TestEntity_cpp.ts +51 -50
  59. package/project/.sdk/src/cmp/csharp/EntityTypes_csharp.ts +4 -4
  60. package/project/.sdk/src/cmp/csharp/ReadmeEntity_csharp.ts +3 -3
  61. package/project/.sdk/src/cmp/csharp/ReadmeModel_csharp.ts +3 -3
  62. package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +5 -5
  63. package/project/.sdk/src/cmp/csharp/Schema_csharp.ts +1 -1
  64. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -30
  65. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +66 -65
  66. package/project/.sdk/src/cmp/elixir/EntityTypes_elixir.ts +4 -4
  67. package/project/.sdk/src/cmp/elixir/ReadmeEntity_elixir.ts +3 -3
  68. package/project/.sdk/src/cmp/elixir/ReadmeModel_elixir.ts +3 -3
  69. package/project/.sdk/src/cmp/elixir/ReadmeRef_elixir.ts +4 -4
  70. package/project/.sdk/src/cmp/elixir/Schema_elixir.ts +1 -1
  71. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +4 -4
  72. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +3 -3
  73. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +3 -3
  74. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +5 -5
  75. package/project/.sdk/src/cmp/go/Schema_go.ts +1 -1
  76. package/project/.sdk/src/cmp/go/TestDirect_go.ts +38 -38
  77. package/project/.sdk/src/cmp/go/TestEntity_go.ts +75 -74
  78. package/project/.sdk/src/cmp/go/utility_go.ts +5 -5
  79. package/project/.sdk/src/cmp/java/EntityTypes_java.ts +4 -4
  80. package/project/.sdk/src/cmp/java/ReadmeEntity_java.ts +3 -3
  81. package/project/.sdk/src/cmp/java/ReadmeModel_java.ts +3 -3
  82. package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +5 -5
  83. package/project/.sdk/src/cmp/java/Schema_java.ts +1 -1
  84. package/project/.sdk/src/cmp/java/TestDirect_java.ts +30 -30
  85. package/project/.sdk/src/cmp/java/TestEntity_java.ts +69 -68
  86. package/project/.sdk/src/cmp/js/EntityTypes_js.ts +5 -5
  87. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +3 -3
  88. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +3 -3
  89. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +5 -5
  90. package/project/.sdk/src/cmp/js/Schema_js.ts +1 -1
  91. package/project/.sdk/src/cmp/js/TestDirect_js.ts +27 -27
  92. package/project/.sdk/src/cmp/js/TestEntity_js.ts +66 -69
  93. package/project/.sdk/src/cmp/js/TestLive_js.ts +3 -3
  94. package/project/.sdk/src/cmp/js/utility_js.ts +5 -5
  95. package/project/.sdk/src/cmp/kotlin/EntityTypes_kotlin.ts +4 -4
  96. package/project/.sdk/src/cmp/kotlin/ReadmeEntity_kotlin.ts +3 -3
  97. package/project/.sdk/src/cmp/kotlin/ReadmeModel_kotlin.ts +3 -3
  98. package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +5 -5
  99. package/project/.sdk/src/cmp/kotlin/Schema_kotlin.ts +1 -1
  100. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +30 -30
  101. package/project/.sdk/src/cmp/kotlin/TestEntity_kotlin.ts +67 -66
  102. package/project/.sdk/src/cmp/lua/EntityTypes_lua.ts +5 -5
  103. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +3 -3
  104. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +3 -3
  105. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +4 -4
  106. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +5 -5
  107. package/project/.sdk/src/cmp/lua/Schema_lua.ts +1 -1
  108. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +25 -25
  109. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +66 -65
  110. package/project/.sdk/src/cmp/ocaml/ReadmeEntity_ocaml.ts +3 -3
  111. package/project/.sdk/src/cmp/ocaml/ReadmeModel_ocaml.ts +3 -3
  112. package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +5 -5
  113. package/project/.sdk/src/cmp/ocaml/Schema_ocaml.ts +1 -1
  114. package/project/.sdk/src/cmp/perl/ReadmeEntity_perl.ts +3 -3
  115. package/project/.sdk/src/cmp/perl/ReadmeModel_perl.ts +3 -3
  116. package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +5 -5
  117. package/project/.sdk/src/cmp/perl/Schema_perl.ts +1 -1
  118. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -25
  119. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +60 -59
  120. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +4 -4
  121. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +3 -3
  122. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +3 -3
  123. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +4 -4
  124. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +5 -5
  125. package/project/.sdk/src/cmp/php/Schema_php.ts +1 -1
  126. package/project/.sdk/src/cmp/php/TestDirect_php.ts +25 -25
  127. package/project/.sdk/src/cmp/php/TestEntity_php.ts +66 -65
  128. package/project/.sdk/src/cmp/py/EntityTypes_py.ts +4 -4
  129. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +3 -3
  130. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +3 -3
  131. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +5 -5
  132. package/project/.sdk/src/cmp/py/Schema_py.ts +1 -1
  133. package/project/.sdk/src/cmp/py/TestDirect_py.ts +25 -25
  134. package/project/.sdk/src/cmp/py/TestEntity_py.ts +66 -65
  135. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +4 -4
  136. package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +4 -4
  137. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +3 -3
  138. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +3 -3
  139. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +4 -4
  140. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +5 -5
  141. package/project/.sdk/src/cmp/rb/Schema_rb.ts +1 -1
  142. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +25 -25
  143. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +66 -65
  144. package/project/.sdk/src/cmp/rust/EntityTypes_rust.ts +4 -4
  145. package/project/.sdk/src/cmp/rust/ReadmeEntity_rust.ts +3 -3
  146. package/project/.sdk/src/cmp/rust/ReadmeModel_rust.ts +3 -3
  147. package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +5 -5
  148. package/project/.sdk/src/cmp/rust/Schema_rust.ts +1 -1
  149. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +18 -18
  150. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +63 -62
  151. package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +4 -4
  152. package/project/.sdk/src/cmp/scala/ReadmeEntity_scala.ts +3 -3
  153. package/project/.sdk/src/cmp/scala/ReadmeModel_scala.ts +3 -3
  154. package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +5 -5
  155. package/project/.sdk/src/cmp/scala/Schema_scala.ts +1 -1
  156. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +14 -14
  157. package/project/.sdk/src/cmp/scala/TestEntity_scala.ts +60 -59
  158. package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +4 -4
  159. package/project/.sdk/src/cmp/swift/ReadmeEntity_swift.ts +3 -3
  160. package/project/.sdk/src/cmp/swift/ReadmeModel_swift.ts +3 -3
  161. package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +5 -5
  162. package/project/.sdk/src/cmp/swift/Schema_swift.ts +1 -1
  163. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -5
  164. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +3 -3
  165. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +3 -3
  166. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +1 -1
  167. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +5 -5
  168. package/project/.sdk/src/cmp/ts/Schema_ts.ts +1 -1
  169. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +40 -40
  170. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +72 -75
  171. package/project/.sdk/src/cmp/ts/TestLive_ts.ts +3 -3
  172. package/project/.sdk/src/cmp/ts/utility_ts.ts +5 -5
  173. package/project/.sdk/src/cmp/zig/ReadmeEntity_zig.ts +3 -3
  174. package/project/.sdk/src/cmp/zig/ReadmeModel_zig.ts +3 -3
  175. package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +5 -5
  176. package/project/.sdk/src/cmp/zig/Schema_zig.ts +1 -1
  177. package/project/.sdk/tm/c/core/context.c +3 -1
  178. package/project/.sdk/tm/c/feature/paging.c +37 -3
  179. package/project/.sdk/tm/c/feature/test.c +71 -31
  180. package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
  181. package/project/.sdk/tm/c/utility/transform_request.c +25 -3
  182. package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
  183. package/project/.sdk/tm/clojure/src/sdk/features.clj +74 -22
  184. package/project/.sdk/tm/cpp/core/types.hpp +1 -1
  185. package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
  186. package/project/.sdk/tm/cpp/feature/test.hpp +58 -19
  187. package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
  188. package/project/.sdk/tm/csharp/core/Context.cs +1 -1
  189. package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
  190. package/project/.sdk/tm/csharp/feature/TestFeature.cs +23 -25
  191. package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
  192. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
  193. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
  194. package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
  195. package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
  196. package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +58 -22
  197. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
  198. package/project/.sdk/tm/go/core/context.go +13 -13
  199. package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
  200. package/project/.sdk/tm/go/feature/test_feature.go +3 -15
  201. package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
  202. package/project/.sdk/tm/go/test/feature_test.go +54 -0
  203. package/project/.sdk/tm/go/utility/fetcher.go +41 -20
  204. package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
  205. package/project/.sdk/tm/go/utility/transform_request.go +22 -3
  206. package/project/.sdk/tm/java/core/Context.java +2 -1
  207. package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
  208. package/project/.sdk/tm/java/feature/TestFeature.java +21 -19
  209. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
  210. package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
  211. package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
  212. package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
  213. package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
  214. package/project/.sdk/tm/js/src/Context.js +7 -2
  215. package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
  216. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
  217. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
  218. package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
  219. package/project/.sdk/tm/js/test/live-entity.js +29 -29
  220. package/project/.sdk/tm/js/test/live-scenarios.js +4 -4
  221. package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
  222. package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
  223. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +19 -18
  224. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
  225. package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
  226. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
  227. package/project/.sdk/tm/lua/core/context.lua +8 -1
  228. package/project/.sdk/tm/lua/core/control.lua +3 -0
  229. package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
  230. package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
  231. package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
  232. package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
  233. package/project/.sdk/tm/ocaml/sdk_features.ml +78 -25
  234. package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
  235. package/project/.sdk/tm/perl/core/context.pm +2 -1
  236. package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
  237. package/project/.sdk/tm/perl/feature/test_feature.pm +52 -20
  238. package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
  239. package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
  240. package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
  241. package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
  242. package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
  243. package/project/.sdk/tm/php/core/Context.php +5 -1
  244. package/project/.sdk/tm/php/core/Control.php +5 -0
  245. package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
  246. package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
  247. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
  248. package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
  249. package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
  250. package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
  251. package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
  252. package/project/.sdk/tm/py/pkg/core/context.py +2 -1
  253. package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
  254. package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
  255. package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
  256. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
  257. package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
  258. package/project/.sdk/tm/py/test/test_feature.py +27 -0
  259. package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
  260. package/project/.sdk/tm/rb/core/context.rb +1 -1
  261. package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
  262. package/project/.sdk/tm/rb/feature/test_feature.rb +39 -11
  263. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
  264. package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
  265. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
  266. package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
  267. package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
  268. package/project/.sdk/tm/rust/core/context.rs +2 -6
  269. package/project/.sdk/tm/rust/feature/paging.rs +44 -3
  270. package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
  271. package/project/.sdk/tm/rust/feature/test.rs +74 -37
  272. package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
  273. package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
  274. package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
  275. package/project/.sdk/tm/scala/core/Context.scala +2 -1
  276. package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
  277. package/project/.sdk/tm/scala/feature/TestFeature.scala +21 -18
  278. package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
  279. package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
  280. package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
  281. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
  282. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
  283. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +22 -15
  284. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
  285. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
  286. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
  287. package/project/.sdk/tm/ts/src/Context.ts +7 -2
  288. package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
  289. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
  290. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
  291. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
  292. package/project/.sdk/tm/ts/test/live-entity.ts +26 -26
  293. package/project/.sdk/tm/ts/test/live-scenarios.ts +3 -3
  294. package/project/.sdk/tm/zig/core/context.zig +1 -1
  295. package/project/.sdk/tm/zig/core/utility.zig +19 -5
  296. package/project/.sdk/tm/zig/feature/paging.zig +34 -4
  297. package/project/.sdk/tm/zig/feature/test.zig +65 -30
  298. package/project/sdkgen-package.json +1 -1
  299. package/src/action/doctor.ts +12 -9
  300. package/src/action/edition.ts +8 -0
  301. package/src/action/feature.ts +7 -1
  302. package/src/action/remove.ts +492 -0
  303. package/src/action/target.ts +8 -1
  304. package/src/cmp/ReadmeTop.ts +1 -1
  305. package/src/cmp/ReadmeUnions.ts +1 -1
  306. package/src/cmp/Test.ts +1 -1
  307. package/src/helpers/buildIdNames.ts +17 -7
  308. package/src/helpers/canonSpec.ts +2 -2
  309. package/src/helpers/getMatchEntries.ts +2 -2
  310. package/src/helpers/modelNames.ts +6 -1
  311. package/src/helpers/opShape.ts +33 -33
  312. package/src/helpers/pointPath.ts +5 -10
  313. package/src/helpers/resolved.ts +3 -14
  314. package/src/sdkgen.ts +3 -1
  315. package/src/utility.ts +16 -7
@@ -12,6 +12,22 @@ class ProjectNameFetcher
12
12
  // Users can override by passing a User-Agent header in fetchdef.
13
13
  public const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; ProjectNameSDK/1.0)';
14
14
 
15
+ // One cURL handle per process, reset between calls rather than closed:
16
+ // curl_reset keeps the connection cache, so the next request to the
17
+ // same host reuses the open connection.
18
+ private static $curl = null;
19
+
20
+ private static function curlHandle(string $fullurl)
21
+ {
22
+ if (self::$curl === null) {
23
+ self::$curl = curl_init();
24
+ } else {
25
+ curl_reset(self::$curl);
26
+ }
27
+ curl_setopt(self::$curl, CURLOPT_URL, $fullurl);
28
+ return self::$curl;
29
+ }
30
+
15
31
  public static function defaultHttpFetch(string $fullurl, array $fetchdef): array
16
32
  {
17
33
  $method_str = strtoupper($fetchdef['method'] ?? 'GET');
@@ -130,7 +146,7 @@ class ProjectNameFetcher
130
146
  ];
131
147
  }
132
148
 
133
- $ch = curl_init($fullurl);
149
+ $ch = self::curlHandle($fullurl);
134
150
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
135
151
  curl_setopt($ch, CURLOPT_HEADER, false);
136
152
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
@@ -157,7 +173,6 @@ class ProjectNameFetcher
157
173
  $response_body = curl_exec($ch);
158
174
  if ($response_body === false) {
159
175
  $err = curl_error($ch) ?: 'curl_exec failed';
160
- curl_close($ch);
161
176
  return [
162
177
  [
163
178
  'status' => 0,
@@ -169,7 +184,6 @@ class ProjectNameFetcher
169
184
  $err,
170
185
  ];
171
186
  }
172
- curl_close($ch);
173
187
 
174
188
  $status = 0;
175
189
  $status_text = '';
@@ -22,7 +22,7 @@ class ProjectNamePrepareQuery
22
22
  foreach ($items as $item) {
23
23
  $key = $item[0];
24
24
  $val = $item[1];
25
- if ($val !== null && is_string($key) && !in_array($key, $params, true)) {
25
+ if ($val !== null && is_string($key) && '$action' !== $key && !in_array($key, $params, true)) {
26
26
  $out[$key] = $val;
27
27
  }
28
28
  }
@@ -16,12 +16,24 @@ class ProjectNameTransformRequest
16
16
  }
17
17
  $transform = ProjectNameHelpers::to_map(\Voxgig\Struct\Struct::getprop($point, 'transform'));
18
18
  if (!$transform) {
19
- return $ctx->reqdata;
19
+ return self::strip_action($ctx->reqdata);
20
20
  }
21
21
  $reqform = \Voxgig\Struct\Struct::getprop($transform, 'req');
22
22
  if (!$reqform) {
23
- return $ctx->reqdata;
23
+ return self::strip_action($ctx->reqdata);
24
24
  }
25
- return \Voxgig\Struct\Struct::transform(['reqdata' => $ctx->reqdata], $reqform);
25
+ return self::strip_action(\Voxgig\Struct\Struct::transform(['reqdata' => $ctx->reqdata], $reqform));
26
+ }
27
+
28
+ // `$action` selects the point (see MakePoint); it is never an API field,
29
+ // so the body is a copy without it. Arrays are values, so the unset
30
+ // never reaches the caller's copy.
31
+ private static function strip_action(mixed $reqdata): mixed
32
+ {
33
+ if (!is_array($reqdata) || !array_key_exists('$action', $reqdata)) {
34
+ return $reqdata;
35
+ }
36
+ unset($reqdata['$action']);
37
+ return $reqdata;
26
38
  }
27
39
  }
@@ -53,7 +53,8 @@ class ProjectNameContext:
53
53
  self.ctrl.actor = ctrl_raw["actor"]
54
54
  if isinstance(ctrl_raw.get("paging"), dict):
55
55
  self.ctrl.paging = ctrl_raw["paging"]
56
- elif basectx is not None and basectx.ctrl is not None:
56
+ elif (basectx is not None and basectx.ctrl is not None
57
+ and get_ctx_prop(ctxmap, "opname") is None):
57
58
  self.ctrl = basectx.ctrl
58
59
 
59
60
  # Meta
@@ -12,8 +12,8 @@ from projectname_sdk.feature.base_feature import ProjectNameBaseFeature
12
12
  # stamps page/limit (or a cursor) into the request query; on the way back
13
13
  # (PreResult) it reads the server's pagination signals — a `Link:
14
14
  # rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
15
- # `next`/`cursor`/`nextCursor`/`hasMore` fields in the body — and records
16
- # them on `ctx.result.paging`. A per-call cursor/page supplied via ctrl
15
+ # `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case forms
16
+ # too) — and records them on `ctx.result.paging` and the caller's ctrl paging. A per-call cursor/page supplied via ctrl
17
17
  # paging takes priority (used by auto-iteration). Parameter names
18
18
  # (`pageParam`/`limitParam`/`cursorParam`), `startPage` and page size
19
19
  # (`limit`) are configurable.
@@ -66,7 +66,11 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
66
66
  if paging.get("cursor") is not None:
67
67
  spec.query[cursor_param] = paging["cursor"]
68
68
  elif spec.query.get(page_param) is None:
69
- page = paging.get("page")
69
+ # A record written back by PreResult holds the page just fetched
70
+ # as "page" and the one to fetch as "nextPage", so nextPage wins.
71
+ page = paging.get("nextPage")
72
+ if page is None:
73
+ page = paging.get("page")
70
74
  if page is None:
71
75
  page = self.options.get("startPage") or 1
72
76
  spec.query[page_param] = page
@@ -166,14 +170,25 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
166
170
  paging["hasMore"] = more
167
171
  explicit_more = True
168
172
 
169
- # Body-level cursors.
173
+ # Body-level signals; snake_case forms are read first so camelCase wins.
170
174
  if isinstance(body, dict):
171
175
  if body.get("next") is not None:
172
176
  paging["next"] = paging["next"] or body["next"]
177
+ if body.get("next_cursor") is not None:
178
+ paging["cursor"] = body["next_cursor"]
173
179
  if body.get("cursor") is not None:
174
180
  paging["cursor"] = body["cursor"]
175
181
  if body.get("nextCursor") is not None:
176
182
  paging["cursor"] = body["nextCursor"]
183
+ if paging["nextPage"] is None:
184
+ np = body.get("nextPage")
185
+ if np is None:
186
+ np = body.get("next_page")
187
+ if isinstance(np, (int, float, str)) and not isinstance(np, bool):
188
+ paging["nextPage"] = np
189
+ if isinstance(body.get("has_more"), bool):
190
+ paging["hasMore"] = body["has_more"]
191
+ explicit_more = True
177
192
  if isinstance(body.get("hasMore"), bool):
178
193
  paging["hasMore"] = body["hasMore"]
179
194
  explicit_more = True
@@ -191,6 +206,16 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
191
206
 
192
207
  result.paging = paging
193
208
 
209
+ ctrl = ctx.ctrl
210
+ if ctrl is not None:
211
+ # The Context shares the caller's paging dict by reference, so
212
+ # refill it in place; a fresh dict on ctrl never reaches the caller.
213
+ if isinstance(getattr(ctrl, "paging", None), dict):
214
+ ctrl.paging.clear()
215
+ ctrl.paging.update(paging)
216
+ else:
217
+ ctrl.paging = paging
218
+
194
219
  self.client._paging = {"last": paging}
195
220
 
196
221
  def _is_list(self, ctx):
@@ -143,18 +143,11 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
143
143
  args = test_self.build_args(fctx, op, update_match)
144
144
  found = vs.select(entmap, args)
145
145
  ent = vs.getelem(found, 0)
146
- if ent is None and isinstance(entmap, dict):
147
- for e in entmap.values():
148
- if isinstance(e, dict):
149
- ent = e
150
- break
151
146
  if ent is None:
147
+ # update miss: 404, never another record
152
148
  return respond(404, None, {"statusText": "Not found"})
153
- if isinstance(ent, dict):
154
- reqdata = fctx.reqdata
155
- if reqdata is not None:
156
- for k, v in reqdata.items():
157
- ent[k] = v
149
+ if isinstance(ent, dict) and isinstance(fctx.reqdata, dict):
150
+ vs.merge([ent, fctx.reqdata])
158
151
  vs.delprop(ent, "$KEY")
159
152
  out = vs.clone(ent)
160
153
  return respond(200, out)
@@ -260,12 +253,36 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
260
253
 
261
254
  return netsim_fetcher
262
255
 
256
+ # The entity's own endpoint: a terminal `{param}` marks a record route,
257
+ # and among equals the shallower path wins (the same rule as make_point).
258
+ @staticmethod
259
+ def pick_point(points):
260
+ if not isinstance(points, list) or len(points) == 0:
261
+ return None
262
+
263
+ def terminal(p):
264
+ parts = vs.getprop(p, "parts")
265
+ return isinstance(parts, list) and len(parts) > 0 and \
266
+ isinstance(parts[-1], str) and parts[-1].startswith("{")
267
+
268
+ def depth(p):
269
+ parts = vs.getprop(p, "parts")
270
+ return len(parts) if isinstance(parts, list) else 0
271
+
272
+ point = points[0]
273
+ for cand in points[1:]:
274
+ if terminal(cand) != terminal(point):
275
+ if terminal(cand):
276
+ point = cand
277
+ elif depth(cand) < depth(point):
278
+ point = cand
279
+ return point
280
+
263
281
  def build_args(self, ctx, op, args):
264
282
  opname = op.name
265
283
 
266
- # Get last point from config.
267
284
  points = vs.getpath(ctx.config, "entity." + ctx.entity.get_name() + ".op." + opname + ".points")
268
- point = vs.getelem(points, -1)
285
+ point = self.pick_point(points)
269
286
 
270
287
  # Path AND query: a path-only read misses a query-addressed record
271
288
  # (e.g. GET /result?trace_id=), which has no path param at all.
@@ -2,21 +2,37 @@
2
2
 
3
3
  from __future__ import annotations
4
4
  import json
5
+ import threading
6
+ from http.cookiejar import DefaultCookiePolicy
5
7
  from projectname_sdk.utility.voxgig_struct import voxgig_struct as vs
6
8
 
7
9
 
8
10
  # Default User-Agent — many CDNs (notably Cloudflare) reject requests with
9
- # Python's default urllib UA ("Python-urllib/3.x"), returning 403 before
10
- # the request even reaches the origin. Set a Mozilla-shaped UA so the SDK
11
- # behaves like every other HTTP client by default. Users can still override
12
- # by passing a User-Agent header in fetchdef.
11
+ # a library's default UA, returning 403 before the request even reaches
12
+ # the origin. Set a Mozilla-shaped UA so the SDK behaves like every other
13
+ # HTTP client by default. Users can still override by passing a User-Agent
14
+ # header in fetchdef.
13
15
  _DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)"
14
16
 
15
17
 
16
- def _default_http_fetch(fullurl, fetchdef):
17
- import urllib.request
18
- import urllib.error
18
+ _SESSION = None
19
+ _SESSION_LOCK = threading.Lock()
20
+
21
+
22
+ def _session():
23
+ # Pool connections without retaining response cookies between calls.
24
+ global _SESSION
25
+ if _SESSION is None:
26
+ import requests
27
+ with _SESSION_LOCK:
28
+ if _SESSION is None:
29
+ session = requests.Session()
30
+ session.cookies.set_policy(DefaultCookiePolicy(allowed_domains=[]))
31
+ _SESSION = session
32
+ return _SESSION
33
+
19
34
 
35
+ def _default_http_fetch(fullurl, fetchdef):
20
36
  method = fetchdef.get("method", "GET")
21
37
  body_str = fetchdef.get("body")
22
38
  headers = fetchdef.get("headers", {})
@@ -26,76 +42,58 @@ def _default_http_fetch(fullurl, fetchdef):
26
42
 
27
43
  data = body_str.encode("utf-8") if body_str is not None else None
28
44
 
29
- req = urllib.request.Request(fullurl, data=data, method=method)
45
+ req_headers = {}
30
46
  has_ua = False
31
47
  for k, v in headers.items():
32
48
  if k.lower() == "user-agent":
33
49
  has_ua = True
34
- req.add_header(k, v)
50
+ req_headers[k] = v
35
51
  if not has_ua:
36
- req.add_header("User-Agent", _DEFAULT_USER_AGENT)
52
+ req_headers["User-Agent"] = _DEFAULT_USER_AGENT
37
53
 
38
54
  # Manual redirects: fetchdef["redirect"] == "manual" surfaces a 3xx as
39
- # an ordinary response instead of auto-following it — urllib would
40
- # otherwise replay the request, headers included, against whatever host
41
- # Location names. Set by the station feature's transport middleware
42
- # under an egress hosts policy (mirrors the ts fetch option).
43
- opener = None
44
- if fetchdef.get("redirect") == "manual":
45
- class _NoRedirect(urllib.request.HTTPRedirectHandler):
46
- def redirect_request(self, req, fp, code, msg, headers, newurl):
47
- return None
48
- opener = urllib.request.build_opener(_NoRedirect)
55
+ # an ordinary response instead of auto-following it, which would replay
56
+ # the request, headers included, against whatever host Location names.
57
+ # Set by the station feature's transport middleware under an egress
58
+ # hosts policy (mirrors the ts fetch option).
59
+ kwargs = {"allow_redirects": fetchdef.get("redirect") != "manual"}
60
+
61
+ proxy = fetchdef.get("proxy")
62
+ if isinstance(proxy, str) and proxy:
63
+ kwargs["proxies"] = {"http": proxy, "https": proxy}
64
+
65
+ timeout = fetchdef.get("timeout")
66
+ if isinstance(timeout, (int, float)) and timeout > 0:
67
+ kwargs["timeout"] = timeout
49
68
 
50
69
  try:
51
- resp = opener.open(req) if opener is not None else urllib.request.urlopen(req)
52
- body = resp.read().decode("utf-8")
53
- resp_headers = {}
54
- for k, v in resp.getheaders():
55
- resp_headers[k.lower()] = v
56
-
57
- json_body = None
58
- if len(body) > 0:
59
- try:
60
- json_body = json.loads(body)
61
- except Exception:
62
- pass
63
-
64
- status = resp.getcode()
65
- status_text = "OK" if status < 400 else "Error"
66
-
67
- return {
68
- "status": status,
69
- "statusText": status_text,
70
- "headers": resp_headers,
71
- "json": lambda: json_body,
72
- "body": body,
73
- }, None
74
- except urllib.error.HTTPError as e:
75
- body = e.read().decode("utf-8")
76
- resp_headers = {}
77
- for k, v in e.headers.items():
78
- resp_headers[k.lower()] = v
79
-
80
- json_body = None
81
- if len(body) > 0:
82
- try:
83
- json_body = json.loads(body)
84
- except Exception:
85
- pass
86
-
87
- status_text = "OK" if e.code < 400 else "Error"
88
-
89
- return {
90
- "status": e.code,
91
- "statusText": status_text,
92
- "headers": resp_headers,
93
- "json": lambda: json_body,
94
- "body": body,
95
- }, None
70
+ resp = _session().request(method, fullurl, data=data, headers=req_headers, **kwargs)
71
+ body = resp.content.decode("utf-8")
96
72
  except Exception as e:
97
73
  return None, str(e)
98
74
 
75
+ resp_headers = {}
76
+ for k, v in resp.headers.items():
77
+ resp_headers[k.lower()] = v
78
+
79
+ json_body = None
80
+ if len(body) > 0:
81
+ try:
82
+ json_body = json.loads(body)
83
+ except Exception:
84
+ pass
85
+
86
+ status = resp.status_code
87
+ status_text = resp.reason or ("OK" if status < 400 else "Error")
88
+
89
+ return {
90
+ "status": status,
91
+ "statusText": status_text,
92
+ "headers": resp_headers,
93
+ "json": lambda: json_body,
94
+ "body": body,
95
+ }, None
96
+
99
97
 
100
98
  def fetcher_util(ctx, fullurl, fetchdef):
101
99
  if ctx.client.mode != "live":
@@ -27,7 +27,8 @@ def prepare_query_util(ctx):
27
27
  for item in reqmatch_items:
28
28
  key = item[0]
29
29
  val = item[1]
30
- if val is not None and isinstance(key, str) and not _contains_param(params, key):
30
+ if val is not None and isinstance(key, str) and key != "$action" \
31
+ and not _contains_param(params, key):
31
32
  out[key] = val
32
33
 
33
34
  return out
@@ -5,6 +5,14 @@ from projectname_sdk.utility.voxgig_struct import voxgig_struct as vs
5
5
  from projectname_sdk.core.helpers import to_map
6
6
 
7
7
 
8
+ # `$action` selects the point (see make_point_util); it is never an API field,
9
+ # so the body is a copy without it. The caller's dict is left untouched.
10
+ def _strip_action(reqdata):
11
+ if not isinstance(reqdata, dict) or "$action" not in reqdata:
12
+ return reqdata
13
+ return {k: v for k, v in reqdata.items() if k != "$action"}
14
+
15
+
8
16
  def transform_request_util(ctx):
9
17
  spec = ctx.spec
10
18
  point = ctx.point
@@ -14,14 +22,14 @@ def transform_request_util(ctx):
14
22
 
15
23
  transform = to_map(vs.getprop(point, "transform"))
16
24
  if transform is None:
17
- return ctx.reqdata
25
+ return _strip_action(ctx.reqdata)
18
26
 
19
27
  reqform = vs.getprop(transform, "req")
20
28
  if reqform is None:
21
- return ctx.reqdata
29
+ return _strip_action(ctx.reqdata)
22
30
 
23
31
  reqdata = vs.transform({
24
32
  "reqdata": ctx.reqdata,
25
33
  }, reqform)
26
34
 
27
- return reqdata
35
+ return _strip_action(reqdata)
@@ -724,6 +724,33 @@ class TestPagingFeature:
724
724
  assert res["result"].paging["cursor"] == "abc"
725
725
  assert res["result"].paging["hasMore"] is True
726
726
 
727
+ def test_snake_case_signals_and_ctrl_write_back(self):
728
+ server, calls = recording_server(lambda n, _fd: make_response(
729
+ 200, {"has_more": True, "next_cursor": "c2"} if n == 1
730
+ else {"has_more": False}))
731
+ h = make_client([{"name": "paging"}], server=server)
732
+ pg = {}
733
+ ctrl = {"paging": pg}
734
+ res = h.op(op="list", path="/w", ctrl=ctrl)
735
+ assert res["result"].paging["hasMore"] is True
736
+ assert res["result"].paging["cursor"] == "c2"
737
+ assert pg["cursor"] == "c2" and pg["hasMore"] is True
738
+ h.op(op="list", path="/w", ctrl=ctrl)
739
+ assert re.search(r"[?&]cursor=c2(&|$)", calls[1]["url"])
740
+ assert pg["hasMore"] is False and pg["cursor"] is None
741
+
742
+ def test_continues_from_written_back_next_page(self):
743
+ server, calls = recording_server(lambda n, _fd: make_response(
744
+ 200, {"next_page": 2} if n == 1 else {}, {"x-page": str(n)}))
745
+ h = make_client([{"name": "paging"}], server=server)
746
+ pg = {}
747
+ ctrl = {"paging": pg}
748
+ h.op(op="list", path="/w", ctrl=ctrl)
749
+ assert pg["page"] == 1 and pg["nextPage"] == 2 and pg["hasMore"] is True
750
+ h.op(op="list", path="/w", ctrl=ctrl)
751
+ assert re.search(r"[?&]page=2(&|$)", calls[1]["url"])
752
+ assert pg["hasMore"] is False
753
+
727
754
  def test_non_list_op_is_not_paged(self):
728
755
  server, calls = recording_server()
729
756
  h = make_client([{"name": "paging"}], server=server)
@@ -240,6 +240,14 @@ def _subset(actual, expect, path):
240
240
  "{}: got {!r}, want {!r}".format(path, actual, expect)
241
241
 
242
242
 
243
+ # Whether the SDK built the feature at all. The activity record is the
244
+ # wrong probe: most features create theirs on first use, so an idle client
245
+ # has none and every section but the eager ones read as inert.
246
+ def _present(client, name):
247
+ return any(getattr(f, "name", None) == name
248
+ for f in (getattr(client, "features", None) or []))
249
+
250
+
243
251
  def _record(client, name):
244
252
  return getattr(client, "_" + name, None)
245
253
 
@@ -280,7 +288,9 @@ class TestFeatureCorpus:
280
288
  # Probed by ACTIVATING it: the feature defaults to inactive, so an
281
289
  # idle client never builds it and its absence says nothing.
282
290
  probe = _client({"feature": [{"name": name, "active": True}]})
283
- if _record(probe, name) is None:
291
+ if not _present(probe, name):
292
+ # The one line every runner prints for an inert section.
293
+ print("feature.{}: inert (this SDK does not generate the feature)".format(name))
284
294
  pytest.skip("this SDK was generated without the {} feature".format(name))
285
295
 
286
296
  ops = _usable_ops(2)
@@ -30,7 +30,7 @@ class ProjectNameContext
30
30
  @ctrl.explain = ctrl_raw["explain"] if ctrl_raw["explain"].is_a?(Hash)
31
31
  @ctrl.actor = ctrl_raw["actor"] if ctrl_raw.key?("actor")
32
32
  @ctrl.paging = ctrl_raw["paging"] if ctrl_raw["paging"].is_a?(Hash)
33
- elsif basectx&.ctrl
33
+ elsif basectx&.ctrl && ProjectNameHelpers.get_ctx_prop(ctxmap, "opname").nil?
34
34
  @ctrl = basectx.ctrl
35
35
  end
36
36
 
@@ -4,8 +4,8 @@
4
4
  # stamps page/limit (or a cursor) into the request query; on the way back
5
5
  # (PreResult) it reads the server's pagination signals - a Link rel="next"
6
6
  # header, X-Page/X-Next-Page/X-Total-Count headers, or next/cursor/
7
- # nextCursor/hasMore fields in the body - and records them on
8
- # result.paging. A per-call ctrl paging value (page or cursor) takes
7
+ # nextCursor/hasMore fields in the body (snake_case forms too) - and records
8
+ # them on result.paging and the caller's ctrl paging. A per-call ctrl paging value (page or cursor) takes
9
9
  # priority. Parameter names ("pageParam", "limitParam", "cursorParam"),
10
10
  # "startPage" (default 1) and page size ("limit") are configurable.
11
11
 
@@ -56,8 +56,10 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
56
56
  if !paging["cursor"].nil?
57
57
  spec.query[cursor_param] = paging["cursor"]
58
58
  elsif spec.query[page_param].nil?
59
- spec.query[page_param] =
60
- paging["page"].nil? ? (@options["startPage"] || 1) : paging["page"]
59
+ # A record written back by PreResult holds the page just fetched as
60
+ # "page" and the one to fetch as "nextPage", so nextPage wins.
61
+ page = paging["nextPage"].nil? ? paging["page"] : paging["nextPage"]
62
+ spec.query[page_param] = page.nil? ? (@options["startPage"] || 1) : page
61
63
  end
62
64
 
63
65
  if !@options["limit"].nil? && spec.query[limit_param].nil?
@@ -158,11 +160,20 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
158
160
  paging["next"] = m[1] if m
159
161
  end
160
162
 
161
- # Body-level cursors.
163
+ # Body-level signals; snake_case forms are read first so camelCase wins.
162
164
  if body.is_a?(Hash)
163
165
  paging["next"] = paging["next"] || body["next"] unless body["next"].nil?
166
+ paging["cursor"] = body["next_cursor"] unless body["next_cursor"].nil?
164
167
  paging["cursor"] = body["cursor"] unless body["cursor"].nil?
165
168
  paging["cursor"] = body["nextCursor"] unless body["nextCursor"].nil?
169
+ if paging["nextPage"].nil?
170
+ np = body["nextPage"].nil? ? body["next_page"] : body["nextPage"]
171
+ paging["nextPage"] = np if np.is_a?(Numeric) || np.is_a?(String)
172
+ end
173
+ if body["has_more"] == true || body["has_more"] == false
174
+ paging["hasMore"] = body["has_more"]
175
+ explicit_more = true
176
+ end
166
177
  if body["hasMore"] == true || body["hasMore"] == false
167
178
  paging["hasMore"] = body["hasMore"]
168
179
  explicit_more = true
@@ -181,6 +192,17 @@ class ProjectNamePagingFeature < ProjectNameBaseFeature
181
192
 
182
193
  result.paging = paging
183
194
 
195
+ if ctx.ctrl && ctx.ctrl.respond_to?(:paging=)
196
+ # The Context shares the caller's paging hash by reference, so refill
197
+ # it in place; a fresh hash on ctrl never reaches the caller.
198
+ if ctx.ctrl.paging.is_a?(Hash)
199
+ ctx.ctrl.paging.clear
200
+ ctx.ctrl.paging.merge!(paging)
201
+ else
202
+ ctx.ctrl.paging = paging
203
+ end
204
+ end
205
+
184
206
  @client.instance_variable_set(:@_paging, { "last" => paging })
185
207
  end
186
208
 
@@ -4,9 +4,10 @@ require_relative '../utility/struct/voxgig_struct'
4
4
  require_relative 'base_feature'
5
5
 
6
6
  class ProjectNameTestFeature < ProjectNameBaseFeature
7
- # The `body.<key>` form of an op's response transform: the mock wraps its
8
- # payload in <key> so the transform can unwrap it again.
9
- ENVELOPE_RES_RE = /\A`body\.([^`.]+)`\z/
7
+ # The `body.<path>` form of an op's response transform: the mock rebuilds
8
+ # whatever nesting the transform unwraps. Multi-segment on purpose: GraphQL
9
+ # ops unwrap body.data.<field>, not just one level.
10
+ ENVELOPE_RES_RE = /\A`body\.(.+)`\z/
10
11
 
11
12
  def initialize
12
13
  super
@@ -52,7 +53,8 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
52
53
  restf = transform["res"]
53
54
  next data unless restf.is_a?(String)
54
55
  m = ENVELOPE_RES_RE.match(restf)
55
- m.nil? ? data : { m[1] => data }
56
+ next data if m.nil?
57
+ m[1].split('.', -1).reverse.reduce(data) { |out, seg| { seg => out } }
56
58
  }
57
59
 
58
60
  respond = ->(status, data, extra) {
@@ -125,12 +127,10 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
125
127
  args = test_self.build_args(fctx, op, update_match)
126
128
  found = VoxgigStruct.select(entmap, args)
127
129
  ent = VoxgigStruct.getelem(found, 0)
128
- if ent.nil? && entmap.is_a?(Hash) && !entmap.empty?
129
- ent = entmap.values.find { |e| e.is_a?(Hash) }
130
- end
130
+ # update miss: 404, never another record
131
131
  return respond.call(404, nil, { "statusText" => "Not found" }) unless ent
132
- if ent.is_a?(Hash) && fctx.reqdata
133
- fctx.reqdata.each { |k, v| ent[k] = v }
132
+ if ent.is_a?(Hash) && fctx.reqdata.is_a?(Hash)
133
+ VoxgigStruct.merge([ent, fctx.reqdata])
134
134
  end
135
135
  VoxgigStruct.delprop(ent, "$KEY")
136
136
  out = VoxgigStruct.clone(ent)
@@ -235,14 +235,42 @@ class ProjectNameTestFeature < ProjectNameBaseFeature
235
235
  }
236
236
  end
237
237
 
238
+ # The entity's own endpoint: a terminal `{param}` marks a record route,
239
+ # and among equals the shallower path wins (the same rule as make_point).
240
+ def pick_point(points)
241
+ return nil unless points.is_a?(Array) && !points.empty?
242
+ terminal = ->(p) {
243
+ parts = VoxgigStruct.getprop(p, "parts")
244
+ parts.is_a?(Array) && !parts.empty? && parts[-1].is_a?(String) && parts[-1].start_with?("{")
245
+ }
246
+ depth = ->(p) {
247
+ parts = VoxgigStruct.getprop(p, "parts")
248
+ parts.is_a?(Array) ? parts.length : 0
249
+ }
250
+ point = points[0]
251
+ points[1..].each do |cand|
252
+ if terminal.call(cand) != terminal.call(point)
253
+ point = cand if terminal.call(cand)
254
+ elsif depth.call(cand) < depth.call(point)
255
+ point = cand
256
+ end
257
+ end
258
+ point
259
+ end
260
+
238
261
  def build_args(ctx, op, args)
239
262
  opname = op.name
240
263
  points = VoxgigStruct.getpath(ctx.config, "entity.#{ctx.entity.get_name}.op.#{opname}.points")
241
- point = VoxgigStruct.getelem(points, -1)
264
+ point = pick_point(points)
242
265
 
266
+ # Path AND query: a path-only read misses a query-addressed record
267
+ # (e.g. GET /result?trace_id=), which has no path param at all.
243
268
  params_path = VoxgigStruct.getpath(point, "args.params")
244
269
  reqd_params = VoxgigStruct.select(params_path, { "reqd" => true })
245
- reqd = VoxgigStruct.transform(reqd_params, ["`$EACH`", "", "`$KEY.name`"])
270
+ query_path = VoxgigStruct.getpath(point, "args.query")
271
+ reqd_query = VoxgigStruct.select(query_path, { "reqd" => true })
272
+ reqd = (VoxgigStruct.transform(reqd_params, ["`$EACH`", "", "`$KEY.name`"]) || []) +
273
+ (VoxgigStruct.transform(reqd_query, ["`$EACH`", "", "`$KEY.name`"]) || [])
246
274
 
247
275
  qand = []
248
276
  q = { "`$AND`" => qand }