@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
@@ -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
 
@@ -76,8 +76,11 @@ sub PreRequest {
76
76
  $spec->{query}{$cursor_param} = $paging->{cursor};
77
77
  }
78
78
  elsif (!defined $spec->{query}{$page_param}) {
79
- $spec->{query}{$page_param} = defined $paging->{page}
80
- ? $paging->{page}
79
+ # A record written back by PreResult holds the page just fetched as
80
+ # "page" and the one to fetch as "nextPage", so nextPage wins.
81
+ my $page = defined $paging->{nextPage} ? $paging->{nextPage} : $paging->{page};
82
+ $spec->{query}{$page_param} = defined $page
83
+ ? $page
81
84
  : (defined $self->{options}{startPage} ? $self->{options}{startPage} : 1);
82
85
  }
83
86
 
@@ -186,15 +189,27 @@ sub PreResult {
186
189
  }
187
190
  }
188
191
 
189
- # Body-level cursors.
192
+ # Body-level signals; snake_case forms are read first so camelCase wins.
190
193
  if (Voxgig::Struct::ismap($body)) {
191
194
  my $bnext = ProjectNameHelpers::gp($body, 'next');
192
195
  $paging->{next} = defined $paging->{next} ? $paging->{next} : $bnext
193
196
  if defined $bnext;
197
+ my $bsnakecursor = ProjectNameHelpers::gp($body, 'next_cursor');
198
+ $paging->{cursor} = $bsnakecursor if defined $bsnakecursor;
194
199
  my $bcursor = ProjectNameHelpers::gp($body, 'cursor');
195
200
  $paging->{cursor} = $bcursor if defined $bcursor;
196
201
  my $bnextcursor = ProjectNameHelpers::gp($body, 'nextCursor');
197
202
  $paging->{cursor} = $bnextcursor if defined $bnextcursor;
203
+ unless (defined $paging->{nextPage}) {
204
+ my $np = ProjectNameHelpers::gp($body, 'nextPage');
205
+ $np = ProjectNameHelpers::gp($body, 'next_page') unless defined $np;
206
+ $paging->{nextPage} = $np if defined $np && !ref($np);
207
+ }
208
+ my $bsnakemore = $body->{has_more};
209
+ if (ProjectNameHelpers::is_true($bsnakemore) || ProjectNameHelpers::is_false($bsnakemore)) {
210
+ $paging->{hasMore} = ProjectNameHelpers::is_true($bsnakemore) ? 1 : 0;
211
+ $explicit_more = 1;
212
+ }
198
213
  my $bhasmore = $body->{hasMore};
199
214
  if (ProjectNameHelpers::is_true($bhasmore) || ProjectNameHelpers::is_false($bhasmore)) {
200
215
  $paging->{hasMore} = ProjectNameHelpers::is_true($bhasmore) ? 1 : 0;
@@ -215,6 +230,17 @@ sub PreResult {
215
230
 
216
231
  $result->{paging} = $paging;
217
232
 
233
+ if ($ctx->{ctrl}) {
234
+ # The Context shares the caller's paging hash by reference, so refill it
235
+ # in place; a fresh hash on ctrl never reaches the caller.
236
+ if (Voxgig::Struct::ismap($ctx->{ctrl}{paging})) {
237
+ %{ $ctx->{ctrl}{paging} } = %$paging;
238
+ }
239
+ else {
240
+ $ctx->{ctrl}{paging} = $paging;
241
+ }
242
+ }
243
+
218
244
  $self->{client}{_paging} = { 'last' => $paging };
219
245
  return;
220
246
  }
@@ -66,10 +66,12 @@ sub init {
66
66
  my $tm = ProjectNameHelpers::gp($fctx->{point}, 'transform');
67
67
  my $spec = ProjectNameHelpers::gp($tm, 'res');
68
68
  return $data unless defined $spec && !ref($spec);
69
- # Exactly `body.<key>`; a deeper path is not an envelope this mock can
70
- # synthesise, so it is left alone rather than guessed at.
71
- return $data unless $spec =~ /^`body\.([^`.]+)`$/;
72
- return { $1 => $data };
69
+ # Rebuild whatever nesting the transform unwraps. Multi-segment on purpose:
70
+ # GraphQL ops unwrap `body.data.<field>`, not just one envelope property.
71
+ return $data unless $spec =~ /^`body\.(.+)`$/;
72
+ my $out = $data;
73
+ $out = { $_ => $out } for reverse split /\./, $1, -1;
74
+ return $out;
73
75
  };
74
76
 
75
77
  my $respond = sub {
@@ -152,18 +154,11 @@ sub init {
152
154
  my $args = $test_self->build_args($fctx, $op, $update_match);
153
155
  my $found = Voxgig::Struct::select($entmap, $args);
154
156
  my $ent = ProjectNameHelpers::ge($found, 0);
155
- if (!defined $ent && Voxgig::Struct::ismap($entmap) && keys %$entmap) {
156
- for my $k (sort keys %$entmap) {
157
- if (Voxgig::Struct::ismap($entmap->{$k})) {
158
- $ent = $entmap->{$k};
159
- last;
160
- }
161
- }
162
- }
157
+ # update miss: 404, never another record
163
158
  return $respond->($fctx, 404, undef, { 'statusText' => 'Not found' })
164
159
  unless ProjectNameHelpers::rb_truthy($ent);
165
- if (Voxgig::Struct::ismap($ent) && $fctx->{reqdata}) {
166
- $ent->{$_} = $fctx->{reqdata}{$_} for keys %{ $fctx->{reqdata} };
160
+ if (Voxgig::Struct::ismap($ent) && Voxgig::Struct::ismap($fctx->{reqdata})) {
161
+ Voxgig::Struct::merge([$ent, $fctx->{reqdata}]);
167
162
  }
168
163
  Voxgig::Struct::delprop($ent, '$KEY');
169
164
  my $out = Voxgig::Struct::clone($ent);
@@ -288,18 +283,55 @@ sub make_netsim {
288
283
  };
289
284
  }
290
285
 
286
+ sub _point_terminal {
287
+ my ($p) = @_;
288
+ my $parts = ProjectNameHelpers::gp($p, 'parts');
289
+ return 0 unless Voxgig::Struct::islist($parts) && @$parts;
290
+ my $last = $parts->[-1];
291
+ return (defined $last && !ref $last && 0 == index($last, '{')) ? 1 : 0;
292
+ }
293
+
294
+ sub _point_depth {
295
+ my ($p) = @_;
296
+ my $parts = ProjectNameHelpers::gp($p, 'parts');
297
+ return Voxgig::Struct::islist($parts) ? scalar @$parts : 0;
298
+ }
299
+
300
+ # The entity's own endpoint: a terminal `{param}` marks a record route, and
301
+ # among equals the shallower path wins (the same rule as make_point).
302
+ sub _pick_point {
303
+ my ($points) = @_;
304
+ return undef unless Voxgig::Struct::islist($points) && @$points;
305
+ my $point = $points->[0];
306
+ for my $cand (@$points[1 .. $#$points]) {
307
+ if (_point_terminal($cand) != _point_terminal($point)) {
308
+ $point = $cand if _point_terminal($cand);
309
+ }
310
+ elsif (_point_depth($cand) < _point_depth($point)) {
311
+ $point = $cand;
312
+ }
313
+ }
314
+ return $point;
315
+ }
316
+
291
317
  sub build_args {
292
318
  my ($self, $ctx, $op, $args) = @_;
293
319
  my $opname = $op->{name};
294
320
  my $entname = $ctx->{entity}->get_name;
295
321
  my $points = ProjectNameHelpers::gpath($ctx->{config},
296
322
  "entity.$entname.op.$opname.points");
297
- my $point = ProjectNameHelpers::ge($points, -1);
298
-
299
- my $params_path = ProjectNameHelpers::gpath($point, 'args.params');
300
- my $reqd_params = Voxgig::Struct::select($params_path,
301
- { 'reqd' => Voxgig::Struct::JTRUE() });
302
- my $reqd = Voxgig::Struct::transform($reqd_params, ['`$EACH`', '', '`$KEY.name`']);
323
+ my $point = _pick_point($points);
324
+
325
+ # Path AND query: a path-only read misses a query-addressed record
326
+ # (e.g. GET /result?trace_id=), which has no path param at all.
327
+ my $reqd = [];
328
+ for my $kind ('params', 'query') {
329
+ my $args_path = ProjectNameHelpers::gpath($point, "args.$kind");
330
+ my $reqd_args = Voxgig::Struct::select($args_path,
331
+ { 'reqd' => Voxgig::Struct::JTRUE() });
332
+ my $names = Voxgig::Struct::transform($reqd_args, ['`$EACH`', '', '`$KEY.name`']);
333
+ push @$reqd, @$names if Voxgig::Struct::islist($names);
334
+ }
303
335
 
304
336
  my $qand = [];
305
337
  my $q = { '`$AND`' => $qand };
@@ -3,9 +3,11 @@
3
3
  # Per-request timeout. Wraps the active transport with a deadline of "ms"
4
4
  # milliseconds (default 30000; <= 0 disables). The transport is synchronous
5
5
  # (HTTP::Tiny), so a hanging request is interrupted with a SIGALRM timer
6
- # (Time::HiRes::ualarm); when an injectable "now" clock is supplied the
7
- # elapsed wall-clock time is checked instead, so tests can assert the
8
- # deadline deterministically. Expiry yields an error with code "timeout".
6
+ # (Time::HiRes::ualarm) where the platform has one; where it has none
7
+ # (Windows) the deadline is checked once the call returns. When an
8
+ # injectable "now" clock is supplied the elapsed clock time is checked
9
+ # instead, so tests can assert the deadline deterministically. Expiry
10
+ # yields an error with code "timeout".
9
11
 
10
12
  use strict;
11
13
  use warnings;
@@ -75,24 +77,26 @@ sub with_timeout {
75
77
  # Live path: interrupt a hanging synchronous transport.
76
78
  my ($res, $err);
77
79
  my $timed_out = 0;
78
- my $ok = do {
79
- local $SIG{ALRM} = sub { $timed_out = 1; die "ProjectNameTimeout\n" };
80
- eval {
81
- Time::HiRes::ualarm(int($ms * 1000));
82
- ($res, $err) = $inner->($ctx, $url, $fetchdef);
83
- Time::HiRes::ualarm(0);
84
- 1;
85
- };
80
+ my $start = Time::HiRes::time();
81
+ local $SIG{ALRM} = sub { $timed_out = 1; die "ProjectNameTimeout\n" };
82
+ my $armed = eval { Time::HiRes::ualarm(int($ms * 1000)); 1 };
83
+ my $ok = eval {
84
+ ($res, $err) = $inner->($ctx, $url, $fetchdef);
85
+ 1;
86
86
  };
87
- Time::HiRes::ualarm(0);
87
+ my $e = $@;
88
+ eval { Time::HiRes::ualarm(0) } if $armed;
88
89
  if (!$ok) {
89
- my $e = $@;
90
90
  if ($timed_out) {
91
91
  $self->_track($ctx, $ms);
92
92
  return (undef, $ctx->make_error('timeout', "Request exceeded timeout of ${ms}ms"));
93
93
  }
94
94
  die $e;
95
95
  }
96
+ if (!$armed && (Time::HiRes::time() - $start) * 1000 > $ms) {
97
+ $self->_track($ctx, $ms);
98
+ return (undef, $ctx->make_error('timeout', "Request exceeded timeout of ${ms}ms"));
99
+ }
96
100
  return ($res, $err);
97
101
  }
98
102
 
@@ -267,6 +267,15 @@ sub subset {
267
267
  }
268
268
 
269
269
 
270
+ # Whether the SDK built the feature at all. The activity record is the
271
+ # wrong probe: most features create theirs on first use, so an idle client
272
+ # has none and every section but the eager ones read as inert.
273
+ sub present {
274
+ my ($client, $name) = @_;
275
+ return scalar grep { ref $_ && ($_->{name} // '') eq $name }
276
+ @{ $client->{features} || [] };
277
+ }
278
+
270
279
  sub record {
271
280
  my ($client, $name) = @_;
272
281
  return $client->{"_$name"};
@@ -295,7 +304,11 @@ for my $name (sort keys %{ $CORPUS->{feature} || {} }) {
295
304
  # Probed by ACTIVATING it: the feature defaults to inactive, so an idle
296
305
  # client never builds it and its absence says nothing.
297
306
  my $probe = build_client({ feature => [ { name => $name, active => 1 } ] });
298
- next unless defined record($probe, $name);
307
+ unless (present($probe, $name)) {
308
+ # The one line every runner prints for an inert section.
309
+ diag("feature.$name: inert (this SDK does not generate the feature)");
310
+ next;
311
+ }
299
312
 
300
313
  my %by_key = map { $_->{key} => $_ } @ops;
301
314
 
@@ -16,6 +16,16 @@ package ProjectNameUtilities;
16
16
 
17
17
  our %REGISTRY;
18
18
 
19
+ # One HTTP::Tiny per (proxy, redirect) setting: a client keeps its
20
+ # connection alive between requests, so one per request reused nothing.
21
+ my %HTTP_CLIENTS;
22
+
23
+ sub _http_client {
24
+ my (%new_args) = @_;
25
+ my $key = join "\n", map { "$_=$new_args{$_}" } sort keys %new_args;
26
+ return $HTTP_CLIENTS{$key} //= HTTP::Tiny->new(%new_args);
27
+ }
28
+
19
29
  our $DefaultHttpFetch = sub {
20
30
  my ($fullurl, $fetchdef) = @_;
21
31
  $fetchdef = {} unless defined $fetchdef;
@@ -47,8 +57,12 @@ our $DefaultHttpFetch = sub {
47
57
  $new_args{max_redirect} = 0
48
58
  if defined $fetchdef->{redirect} && !ref $fetchdef->{redirect}
49
59
  && 'manual' eq $fetchdef->{redirect};
60
+ # Both keys: HTTP::Tiny lets the environment override its generic proxy.
61
+ $new_args{http_proxy} = $new_args{https_proxy} = "$fetchdef->{proxy}"
62
+ if defined $fetchdef->{proxy} && !ref $fetchdef->{proxy}
63
+ && length $fetchdef->{proxy};
50
64
 
51
- my $res = eval { HTTP::Tiny->new(%new_args)->request($method, $fullurl, $opts) };
65
+ my $res = eval { _http_client(%new_args)->request($method, $fullurl, $opts) };
52
66
  if (!$res) {
53
67
  my $e = defined $@ ? "$@" : 'request failed';
54
68
  $e =~ s/\s+\z//;
@@ -30,6 +30,7 @@ $REGISTRY{prepare_query} = sub {
30
30
  for my $item (@$items) {
31
31
  my ($key, $val) = @$item;
32
32
  next unless ProjectNameHelpers::rb_truthy($val) && defined $key && !ref $key;
33
+ next if '$action' eq $key;
33
34
  next if grep { defined $_ && !ref $_ && $_ eq $key } @$params;
34
35
  $out->{$key} = $val;
35
36
  }
@@ -15,16 +15,26 @@ package ProjectNameUtilities;
15
15
 
16
16
  our %REGISTRY;
17
17
 
18
+ # `$action` selects the point (see make_point); it is never an API field, so
19
+ # the body is a copy without it. The caller's hash is left untouched.
20
+ my $strip_action = sub {
21
+ my ($reqdata) = @_;
22
+ return $reqdata unless 'HASH' eq ref $reqdata && exists $reqdata->{'$action'};
23
+ my %body = %$reqdata;
24
+ delete $body{'$action'};
25
+ return \%body;
26
+ };
27
+
18
28
  $REGISTRY{transform_request} = sub {
19
29
  my ($ctx) = @_;
20
30
  my $spec = $ctx->{spec};
21
31
  my $point = $ctx->{point};
22
32
  $spec->{step} = 'reqform' if $spec;
23
33
  my $transform = ProjectNameHelpers::to_map(ProjectNameHelpers::gp($point, 'transform'));
24
- return $ctx->{reqdata} unless $transform;
34
+ return $strip_action->($ctx->{reqdata}) unless $transform;
25
35
  my $reqform = ProjectNameHelpers::gp($transform, 'req');
26
- return $ctx->{reqdata} unless ProjectNameHelpers::rb_truthy($reqform);
27
- return Voxgig::Struct::transform({ 'reqdata' => $ctx->{reqdata} }, $reqform);
36
+ return $strip_action->($ctx->{reqdata}) unless ProjectNameHelpers::rb_truthy($reqform);
37
+ return $strip_action->(Voxgig::Struct::transform({ 'reqdata' => $ctx->{reqdata} }, $reqform));
28
38
  };
29
39
 
30
40
  1;
@@ -55,7 +55,11 @@ class ProjectNameContext
55
55
  if (array_key_exists('actor', $ctrl_raw)) {
56
56
  $this->ctrl->actor = $ctrl_raw['actor'];
57
57
  }
58
- } elseif ($basectx !== null && $basectx->ctrl !== null) {
58
+ if (isset($ctrl_raw['paging']) && is_array($ctrl_raw['paging'])) {
59
+ $this->ctrl->paging = $ctrl_raw['paging'];
60
+ }
61
+ } elseif ($basectx !== null && $basectx->ctrl !== null
62
+ && ProjectNameHelpers::get_ctx_prop($ctxmap, "opname") === null) {
59
63
  $this->ctrl = $basectx->ctrl;
60
64
  }
61
65
 
@@ -21,11 +21,16 @@ class ProjectNameControl
21
21
  // passed. The shared feature corpus caught it.
22
22
  public mixed $actor;
23
23
 
24
+ // Per-call paging record: read by the paging feature before the request
25
+ // and written back after it. Declared for the same reason as `$actor`.
26
+ public mixed $paging;
27
+
24
28
  public function __construct(array $opts = [])
25
29
  {
26
30
  $this->throw_err = $opts['throw_err'] ?? null;
27
31
  $this->err = null;
28
32
  $this->explain = $opts['explain'] ?? null;
29
33
  $this->actor = $opts['actor'] ?? null;
34
+ $this->paging = $opts['paging'] ?? null;
30
35
  }
31
36
  }
@@ -9,8 +9,8 @@ require_once __DIR__ . '/BaseFeature.php';
9
9
  // stamps page/limit (or a cursor) into the request query; on the way back
10
10
  // (PreResult) it reads the server's pagination signals — a `Link:
11
11
  // rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
12
- // `next`/`cursor`/`nextCursor`/`hasMore` fields in the body — and records
13
- // them on `$ctx->result->paging`. A per-call `ctrl->paging` (page or
12
+ // `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case
13
+ // forms too) — and records them on `$ctx->result->paging` and `ctrl->paging`. A per-call `ctrl->paging` (page or
14
14
  // cursor) takes priority over the stamped defaults; parameter names and
15
15
  // page size are configurable; `startPage` defaults to 1. Mirrors
16
16
  // ts/src/feature/paging/PagingFeature.ts.
@@ -71,8 +71,10 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
71
71
  } elseif (($spec->query[$page_param] ?? null) === null) {
72
72
  $start_page = is_numeric($this->options['startPage'] ?? null)
73
73
  ? $this->options['startPage'] : 1;
74
- $spec->query[$page_param] = ($paging['page'] ?? null) !== null
75
- ? $paging['page'] : $start_page;
74
+ // A record written back by PreResult holds the page just fetched
75
+ // as "page" and the one to fetch as "nextPage", so nextPage wins.
76
+ $page = $paging['nextPage'] ?? $paging['page'] ?? null;
77
+ $spec->query[$page_param] = $page !== null ? $page : $start_page;
76
78
  }
77
79
 
78
80
  if (($this->options['limit'] ?? null) !== null
@@ -194,17 +196,30 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
194
196
  }
195
197
  }
196
198
 
197
- // Body-level cursors.
199
+ // Body-level signals; snake_case forms are read first so camelCase wins.
198
200
  if (is_array($body)) {
199
201
  if (($body['next'] ?? null) !== null) {
200
202
  $paging['next'] = $paging['next'] ?? $body['next'];
201
203
  }
204
+ if (($body['next_cursor'] ?? null) !== null) {
205
+ $paging['cursor'] = $body['next_cursor'];
206
+ }
202
207
  if (($body['cursor'] ?? null) !== null) {
203
208
  $paging['cursor'] = $body['cursor'];
204
209
  }
205
210
  if (($body['nextCursor'] ?? null) !== null) {
206
211
  $paging['cursor'] = $body['nextCursor'];
207
212
  }
213
+ if ($paging['nextPage'] === null) {
214
+ $np = $body['nextPage'] ?? $body['next_page'] ?? null;
215
+ if (is_int($np) || is_float($np) || is_string($np)) {
216
+ $paging['nextPage'] = $np;
217
+ }
218
+ }
219
+ if (is_bool($body['has_more'] ?? null)) {
220
+ $paging['hasMore'] = $body['has_more'];
221
+ $explicit_more = true;
222
+ }
208
223
  if (is_bool($body['hasMore'] ?? null)) {
209
224
  $paging['hasMore'] = $body['hasMore'];
210
225
  $explicit_more = true;
@@ -225,6 +240,12 @@ class ProjectNamePagingFeature extends ProjectNameBaseFeature
225
240
 
226
241
  $result->paging = $paging;
227
242
 
243
+ // PHP arrays are values, so the record lands on this call's Control
244
+ // rather than on an array the caller passed.
245
+ if ($ctx->ctrl !== null) {
246
+ $ctx->ctrl->paging = $paging;
247
+ }
248
+
228
249
  $this->client->_paging = ['last' => $paging];
229
250
  }
230
251
 
@@ -35,8 +35,8 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
35
35
 
36
36
  $this->client->mode = 'test';
37
37
 
38
- // Ensure entity ids are correct.
39
- \Voxgig\Struct\Struct::walk($entity_data, function ($key, $val, $parent, $path) {
38
+ // Ensure entity ids are correct (walk is by value: keep its result).
39
+ $entity_data = \Voxgig\Struct\Struct::walk($entity_data, function ($key, $val, $parent, $path) {
40
40
  if (count($path) === 2 && is_array($val) && $key !== null) {
41
41
  $val['id'] = $key;
42
42
  }
@@ -103,9 +103,8 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
103
103
 
104
104
  // PHP-portable equivalent of TS buildArgs+select: a flat-key
105
105
  // filter that matches by exact-equality on each provided key,
106
- // with alias fallback. Empty match matches all entries — load
107
- // with empty match returns the first fixture entry (or last
108
- // create), list returns all entries.
106
+ // with alias fallback. An empty match matches every entry, as an
107
+ // empty `$AND` does in select; a non-empty miss matches nothing.
109
108
  $find_first = function (array $entmap, $match, $alias) {
110
109
  if (!is_array($match) || empty($match)) {
111
110
  foreach ($entmap as $e) {
@@ -158,6 +157,23 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
158
157
 
159
158
  $alias = is_object($op) ? ($op->alias ?? null) : \Voxgig\Struct\Struct::getprop($op, 'alias');
160
159
 
160
+ // Every op filters through buildArgs, which keeps `id` and the
161
+ // point's required params and drops the rest. find_first/find_all
162
+ // take a flat map, so take the primary key of each $OR clause;
163
+ // they do their own alias fallback.
164
+ $query_match = function ($args) use ($fctx, $op) {
165
+ $flat = [];
166
+ foreach (($this->buildArgs($fctx, $op, $args)['$AND'] ?? []) as $clause) {
167
+ $ors = $clause['$OR'] ?? [];
168
+ if (is_array($ors) && count($ors) > 0 && is_array($ors[0])) {
169
+ foreach ($ors[0] as $lk => $lv) {
170
+ $flat[$lk] = $lv;
171
+ }
172
+ }
173
+ }
174
+ return $flat;
175
+ };
176
+
161
177
  // For single-entity ops (load, remove) with an empty explicit
162
178
  // match, fall back to the id the entity client already knows from a
163
179
  // prior create/load (carried in $fctx->match / $fctx->data). This
@@ -176,7 +192,7 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
176
192
  };
177
193
 
178
194
  if ($op->name === 'load') {
179
- $ent = $find_first($entmap, $resolve_match($fctx->reqmatch), $alias);
195
+ $ent = $find_first($entmap, $query_match($resolve_match($fctx->reqmatch)), $alias);
180
196
  if ($ent === null) {
181
197
  return $respond(404, null, ['statusText' => 'Not found']);
182
198
  }
@@ -185,37 +201,7 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
185
201
  return $respond(200, $out);
186
202
 
187
203
  } elseif ($op->name === 'list') {
188
- // FILTER THE MATCH THE WAY THE TS MOCK DOES.
189
- //
190
- // This branch used the whole of reqmatch, while load goes
191
- // through resolve_match and the TS mock runs every op through
192
- // buildArgs — which keeps `id` and the point's REQUIRED params
193
- // and drops the rest. buildArgs is right here in this file and
194
- // its docblock says it mirrors the TS one; list simply did not
195
- // call it.
196
- //
197
- // So a match key that is neither `id` nor a required route
198
- // param matched NOTHING in php and was IGNORED in ts. trello's
199
- // board_star lists on `board_id`, which is not a field of that
200
- // entity (id, pos, member_id, id_board) and not a route param:
201
- // php returned an empty list and BoardStarEntityTest failed on
202
- // an assertion that ts, js and every other port passed.
203
- //
204
- // buildArgs returns a struct query; find_all takes a flat map,
205
- // so take the primary key of each $OR clause. find_all already
206
- // does its own alias fallback, which is what the second $OR
207
- // element carries.
208
- $listargs = $this->buildArgs($fctx, $op, $fctx->reqmatch);
209
- $listmatch = [];
210
- foreach (($listargs['$AND'] ?? []) as $clause) {
211
- $ors = $clause['$OR'] ?? [];
212
- if (is_array($ors) && count($ors) > 0 && is_array($ors[0])) {
213
- foreach ($ors[0] as $lk => $lv) {
214
- $listmatch[$lk] = $lv;
215
- }
216
- }
217
- }
218
- $found = $find_all($entmap, $listmatch, $alias);
204
+ $found = $find_all($entmap, $query_match($fctx->reqmatch), $alias);
219
205
  $cleaned = [];
220
206
  foreach ($found as $e) {
221
207
  if (is_array($e)) unset($e['$KEY']);
@@ -245,14 +231,13 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
245
231
  if (empty($update_match)) {
246
232
  $update_match = $resolve_match([]);
247
233
  }
248
- $ent = $find_first($entmap, $update_match, $alias);
234
+ $ent = $find_first($entmap, $query_match($update_match), $alias);
249
235
  if ($ent === null) {
236
+ // update miss: 404, never another record
250
237
  return $respond(404, null, ['statusText' => 'Not found']);
251
238
  }
252
239
  if (is_array($fctx->reqdata)) {
253
- foreach ($fctx->reqdata as $k => $v) {
254
- $ent[$k] = $v;
255
- }
240
+ $ent = \Voxgig\Struct\Struct::merge([$ent, $fctx->reqdata]);
256
241
  }
257
242
  $id = is_array($ent) ? ($ent['id'] ?? null) : null;
258
243
  if ($id !== null) {
@@ -264,7 +249,7 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
264
249
  return $respond(200, $out);
265
250
 
266
251
  } elseif ($op->name === 'remove') {
267
- $ent = $find_first($entmap, $resolve_match($fctx->reqmatch), $alias);
252
+ $ent = $find_first($entmap, $query_match($resolve_match($fctx->reqmatch)), $alias);
268
253
  // Remove only the first matched entity. If nothing matches,
269
254
  // succeed as a no-op rather than erroring.
270
255
  $id = is_array($ent) ? ($ent['id'] ?? null) : null;
@@ -267,6 +267,19 @@ class FeatureCorpusTest extends TestCase
267
267
  $this->assertSame($expect, $actual, $path);
268
268
  }
269
269
 
270
+ // Whether the SDK built the feature at all. The activity record is the
271
+ // wrong probe: most features create theirs on first use, so an idle
272
+ // client has none and every section but the eager ones read as inert.
273
+ private static function present($client, string $name): bool
274
+ {
275
+ foreach ($client->features ?? [] as $f) {
276
+ if (($f->name ?? null) === $name) {
277
+ return true;
278
+ }
279
+ }
280
+ return false;
281
+ }
282
+
270
283
  private static function record($client, string $name)
271
284
  {
272
285
  $prop = '_' . $name;
@@ -326,7 +339,10 @@ class FeatureCorpusTest extends TestCase
326
339
  // Probed by ACTIVATING it: the feature defaults to inactive, so an
327
340
  // idle client never builds it and its absence says nothing.
328
341
  $probe = self::buildClient(['feature' => [['name' => $name, 'active' => true]]]);
329
- if (null === self::record($probe, $name)) {
342
+ if (!self::present($probe, $name)) {
343
+ // The one line every runner prints for an inert section.
344
+ fwrite(STDERR, sprintf(
345
+ "feature.%s: inert (this SDK does not generate the feature)\n", $name));
330
346
  continue;
331
347
  }
332
348
 
@@ -1104,6 +1104,50 @@ class FeatureTest extends TestCase
1104
1104
  $this->assertTrue($res['result']->paging['hasMore']);
1105
1105
  }
1106
1106
 
1107
+ public function test_paging_snake_case_signals_and_ctrl_write_back(): void
1108
+ {
1109
+ $this->needs('paging');
1110
+ $rec = new FtRecorder(function (int $n) {
1111
+ return FtHarness::response(200, 1 === $n
1112
+ ? ['has_more' => true, 'next_cursor' => 'c2']
1113
+ : ['has_more' => false]);
1114
+ });
1115
+ $h = new FtHarness([
1116
+ ['name' => 'paging'],
1117
+ ], $rec->server());
1118
+ $res = $h->op(['op' => 'list', 'path' => '/w', 'ctrl' => ['paging' => []]]);
1119
+ $this->assertTrue($res['result']->paging['hasMore']);
1120
+ $this->assertSame('c2', $res['result']->paging['cursor']);
1121
+ $written = $res['ctx']->ctrl->paging;
1122
+ $this->assertSame('c2', $written['cursor']);
1123
+ $this->assertTrue($written['hasMore']);
1124
+ // Arrays are values here, so the written-back record is passed on by hand.
1125
+ $res = $h->op(['op' => 'list', 'path' => '/w', 'ctrl' => ['paging' => $written]]);
1126
+ $this->assertStringContainsString('cursor=c2', $rec->calls[1]['url']);
1127
+ $this->assertFalse($res['ctx']->ctrl->paging['hasMore']);
1128
+ $this->assertNull($res['ctx']->ctrl->paging['cursor']);
1129
+ }
1130
+
1131
+ public function test_paging_continues_from_written_back_next_page(): void
1132
+ {
1133
+ $this->needs('paging');
1134
+ $rec = new FtRecorder(function (int $n) {
1135
+ return FtHarness::response(200, 1 === $n ? ['next_page' => 2] : [],
1136
+ ['x-page' => (string) $n]);
1137
+ });
1138
+ $h = new FtHarness([
1139
+ ['name' => 'paging'],
1140
+ ], $rec->server());
1141
+ $res = $h->op(['op' => 'list', 'path' => '/w']);
1142
+ $written = $res['ctx']->ctrl->paging;
1143
+ $this->assertEquals(1, $written['page']);
1144
+ $this->assertEquals(2, $written['nextPage']);
1145
+ $this->assertTrue($written['hasMore']);
1146
+ $res = $h->op(['op' => 'list', 'path' => '/w', 'ctrl' => ['paging' => $written]]);
1147
+ $this->assertStringContainsString('page=2', $rec->calls[1]['url']);
1148
+ $this->assertFalse($res['ctx']->ctrl->paging['hasMore']);
1149
+ }
1150
+
1107
1151
  public function test_paging_non_list_op_is_not_paged(): void
1108
1152
  {
1109
1153
  $this->needs('paging');