@voxgig/sdkgen 4.34.1 → 4.34.2

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 (305) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ReadmeErrors.js +3 -3
  3. package/dist/cmp/ReadmeErrors.js.map +1 -1
  4. package/dist/cmp/ReadmeExplanation.js +3 -3
  5. package/dist/cmp/ReadmeExplanation.js.map +1 -1
  6. package/dist/cmp/ReadmeQuick.js +3 -1
  7. package/dist/cmp/ReadmeQuick.js.map +1 -1
  8. package/dist/cmp/ReadmeTop.js +9 -8
  9. package/dist/cmp/ReadmeTop.js.map +1 -1
  10. package/dist/helpers/definitionPlan.js +8 -4
  11. package/dist/helpers/definitionPlan.js.map +1 -1
  12. package/dist/helpers/naming.d.ts +2 -1
  13. package/dist/helpers/naming.js +11 -4
  14. package/dist/helpers/naming.js.map +1 -1
  15. package/dist/helpers/opExample.d.ts +16 -2
  16. package/dist/helpers/opExample.js +80 -21
  17. package/dist/helpers/opExample.js.map +1 -1
  18. package/dist/helpers/opShape.d.ts +3 -1
  19. package/dist/helpers/opShape.js +21 -8
  20. package/dist/helpers/opShape.js.map +1 -1
  21. package/dist/sdkgen.d.ts +4 -4
  22. package/dist/sdkgen.js +12 -4
  23. package/dist/sdkgen.js.map +1 -1
  24. package/dist/tsconfig.tsbuildinfo +1 -1
  25. package/dist/utility.d.ts +2 -1
  26. package/dist/utility.js +29 -3
  27. package/dist/utility.js.map +1 -1
  28. package/package.json +2 -2
  29. package/project/.sdk/src/cmp/c/ReadmeEntity_c.ts +3 -2
  30. package/project/.sdk/src/cmp/c/ReadmeHowto_c.ts +7 -3
  31. package/project/.sdk/src/cmp/c/ReadmeQuick_c.ts +3 -2
  32. package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +5 -4
  33. package/project/.sdk/src/cmp/c/ReadmeTopQuick_c.ts +3 -2
  34. package/project/.sdk/src/cmp/c/ReadmeTopTest_c.ts +8 -7
  35. package/project/.sdk/src/cmp/c/utility_c.ts +21 -2
  36. package/project/.sdk/src/cmp/clojure/ReadmeEntity_clojure.ts +4 -1
  37. package/project/.sdk/src/cmp/clojure/ReadmeHowto_clojure.ts +17 -6
  38. package/project/.sdk/src/cmp/clojure/ReadmeModel_clojure.ts +6 -6
  39. package/project/.sdk/src/cmp/clojure/ReadmeQuick_clojure.ts +13 -8
  40. package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +21 -18
  41. package/project/.sdk/src/cmp/clojure/ReadmeTopQuick_clojure.ts +6 -3
  42. package/project/.sdk/src/cmp/clojure/ReadmeTopTest_clojure.ts +16 -9
  43. package/project/.sdk/src/cmp/clojure/TestClean_clojure.ts +4 -4
  44. package/project/.sdk/src/cmp/clojure/fragment/Entity.fragment.clj +2 -1
  45. package/project/.sdk/src/cmp/clojure/utility_clojure.ts +14 -0
  46. package/project/.sdk/src/cmp/cpp/ReadmeEntity_cpp.ts +3 -2
  47. package/project/.sdk/src/cmp/cpp/ReadmeHowto_cpp.ts +7 -3
  48. package/project/.sdk/src/cmp/cpp/ReadmeQuick_cpp.ts +3 -2
  49. package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +6 -5
  50. package/project/.sdk/src/cmp/cpp/ReadmeTopQuick_cpp.ts +3 -2
  51. package/project/.sdk/src/cmp/cpp/ReadmeTopTest_cpp.ts +8 -7
  52. package/project/.sdk/src/cmp/cpp/utility_cpp.ts +13 -0
  53. package/project/.sdk/src/cmp/csharp/ReadmeEntity_csharp.ts +3 -2
  54. package/project/.sdk/src/cmp/csharp/ReadmeHowto_csharp.ts +20 -8
  55. package/project/.sdk/src/cmp/csharp/ReadmeQuick_csharp.ts +13 -8
  56. package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +10 -5
  57. package/project/.sdk/src/cmp/csharp/ReadmeTopQuick_csharp.ts +14 -7
  58. package/project/.sdk/src/cmp/csharp/ReadmeTopTest_csharp.ts +18 -10
  59. package/project/.sdk/src/cmp/csharp/utility_csharp.ts +13 -0
  60. package/project/.sdk/src/cmp/elixir/ReadmeEntity_elixir.ts +4 -4
  61. package/project/.sdk/src/cmp/elixir/ReadmeExplanation_elixir.ts +2 -1
  62. package/project/.sdk/src/cmp/elixir/ReadmeHowto_elixir.ts +13 -6
  63. package/project/.sdk/src/cmp/elixir/ReadmeModel_elixir.ts +5 -5
  64. package/project/.sdk/src/cmp/elixir/ReadmeQuick_elixir.ts +13 -10
  65. package/project/.sdk/src/cmp/elixir/ReadmeRef_elixir.ts +11 -11
  66. package/project/.sdk/src/cmp/elixir/ReadmeTopQuick_elixir.ts +8 -6
  67. package/project/.sdk/src/cmp/elixir/ReadmeTopTest_elixir.ts +14 -10
  68. package/project/.sdk/src/cmp/elixir/fragment/EntityCreateOp.fragment.ex +4 -3
  69. package/project/.sdk/src/cmp/elixir/fragment/EntityListOp.fragment.ex +4 -3
  70. package/project/.sdk/src/cmp/elixir/fragment/EntityLoadOp.fragment.ex +4 -3
  71. package/project/.sdk/src/cmp/elixir/fragment/EntityPatchOp.fragment.ex +4 -3
  72. package/project/.sdk/src/cmp/elixir/fragment/EntityRemoveOp.fragment.ex +4 -3
  73. package/project/.sdk/src/cmp/elixir/fragment/EntityUpdateOp.fragment.ex +4 -3
  74. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +12 -0
  75. package/project/.sdk/src/cmp/go/ReadmeEntity_go.ts +8 -5
  76. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +3 -1
  77. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +20 -8
  78. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +16 -13
  79. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +14 -13
  80. package/project/.sdk/src/cmp/go/ReadmeRef_go.ts +14 -13
  81. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +10 -8
  82. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +6 -2
  83. package/project/.sdk/src/cmp/go/utility_go.ts +1 -1
  84. package/project/.sdk/src/cmp/java/ReadmeEntity_java.ts +8 -19
  85. package/project/.sdk/src/cmp/java/ReadmeHowto_java.ts +19 -22
  86. package/project/.sdk/src/cmp/java/ReadmeModel_java.ts +10 -9
  87. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +24 -28
  88. package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +27 -35
  89. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +22 -25
  90. package/project/.sdk/src/cmp/java/ReadmeTopTest_java.ts +18 -24
  91. package/project/.sdk/src/cmp/java/utility_java.ts +25 -0
  92. package/project/.sdk/src/cmp/js/EntityOperation_js.ts +3 -1
  93. package/project/.sdk/src/cmp/js/ReadmeEntity_js.ts +2 -8
  94. package/project/.sdk/src/cmp/js/ReadmeHowto_js.ts +11 -9
  95. package/project/.sdk/src/cmp/js/ReadmeModel_js.ts +9 -9
  96. package/project/.sdk/src/cmp/js/ReadmeQuick_js.ts +7 -13
  97. package/project/.sdk/src/cmp/js/ReadmeRef_js.ts +14 -15
  98. package/project/.sdk/src/cmp/js/ReadmeTopQuick_js.ts +16 -11
  99. package/project/.sdk/src/cmp/js/ReadmeTopTest_js.ts +11 -7
  100. package/project/.sdk/src/cmp/js/fragment/EntityCreateOp.fragment.js +1 -1
  101. package/project/.sdk/src/cmp/js/fragment/EntityListOp.fragment.js +1 -1
  102. package/project/.sdk/src/cmp/js/fragment/EntityLoadOp.fragment.js +1 -1
  103. package/project/.sdk/src/cmp/js/fragment/EntityPatchOp.fragment.js +1 -1
  104. package/project/.sdk/src/cmp/js/fragment/EntityRemoveOp.fragment.js +1 -1
  105. package/project/.sdk/src/cmp/js/fragment/EntityUpdateOp.fragment.js +1 -1
  106. package/project/.sdk/src/cmp/js/utility_js.ts +2 -1
  107. package/project/.sdk/src/cmp/kotlin/ReadmeEntity_kotlin.ts +3 -2
  108. package/project/.sdk/src/cmp/kotlin/ReadmeHowto_kotlin.ts +19 -9
  109. package/project/.sdk/src/cmp/kotlin/ReadmeModel_kotlin.ts +10 -9
  110. package/project/.sdk/src/cmp/kotlin/ReadmeQuick_kotlin.ts +14 -9
  111. package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +18 -15
  112. package/project/.sdk/src/cmp/kotlin/ReadmeTopQuick_kotlin.ts +14 -8
  113. package/project/.sdk/src/cmp/kotlin/ReadmeTopTest_kotlin.ts +17 -10
  114. package/project/.sdk/src/cmp/kotlin/utility_kotlin.ts +21 -1
  115. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +3 -2
  116. package/project/.sdk/src/cmp/lua/ReadmeExamplesTest_lua.ts +7 -4
  117. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +9 -7
  118. package/project/.sdk/src/cmp/lua/ReadmeModel_lua.ts +13 -13
  119. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +20 -9
  120. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +9 -8
  121. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +15 -7
  122. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +6 -5
  123. package/project/.sdk/src/cmp/lua/fragment/Entity.fragment.lua +1 -0
  124. package/project/.sdk/src/cmp/lua/fragment/EntityCreateOp.fragment.lua +1 -1
  125. package/project/.sdk/src/cmp/lua/fragment/EntityListOp.fragment.lua +1 -1
  126. package/project/.sdk/src/cmp/lua/fragment/EntityLoadOp.fragment.lua +1 -1
  127. package/project/.sdk/src/cmp/lua/fragment/EntityPatchOp.fragment.lua +1 -1
  128. package/project/.sdk/src/cmp/lua/fragment/EntityRemoveOp.fragment.lua +1 -1
  129. package/project/.sdk/src/cmp/lua/fragment/EntityUpdateOp.fragment.lua +1 -1
  130. package/project/.sdk/src/cmp/ocaml/ReadmeEntity_ocaml.ts +3 -2
  131. package/project/.sdk/src/cmp/ocaml/ReadmeHowto_ocaml.ts +3 -2
  132. package/project/.sdk/src/cmp/ocaml/ReadmeModel_ocaml.ts +1 -0
  133. package/project/.sdk/src/cmp/ocaml/ReadmeQuick_ocaml.ts +5 -3
  134. package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +10 -8
  135. package/project/.sdk/src/cmp/ocaml/ReadmeTopQuick_ocaml.ts +4 -2
  136. package/project/.sdk/src/cmp/ocaml/ReadmeTopTest_ocaml.ts +3 -2
  137. package/project/.sdk/src/cmp/ocaml/utility_ocaml.ts +13 -1
  138. package/project/.sdk/src/cmp/perl/ReadmeEntity_perl.ts +4 -1
  139. package/project/.sdk/src/cmp/perl/ReadmeHowto_perl.ts +10 -5
  140. package/project/.sdk/src/cmp/perl/ReadmeModel_perl.ts +6 -6
  141. package/project/.sdk/src/cmp/perl/ReadmeQuick_perl.ts +8 -5
  142. package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +21 -18
  143. package/project/.sdk/src/cmp/perl/ReadmeTopQuick_perl.ts +6 -3
  144. package/project/.sdk/src/cmp/perl/ReadmeTopTest_perl.ts +11 -8
  145. package/project/.sdk/src/cmp/perl/fragment/Entity.fragment.pm +1 -1
  146. package/project/.sdk/src/cmp/perl/fragment/EntityCreateOp.fragment.pm +2 -2
  147. package/project/.sdk/src/cmp/perl/fragment/EntityLoadOp.fragment.pm +2 -2
  148. package/project/.sdk/src/cmp/perl/fragment/EntityPatchOp.fragment.pm +2 -2
  149. package/project/.sdk/src/cmp/perl/fragment/EntityRemoveOp.fragment.pm +1 -1
  150. package/project/.sdk/src/cmp/perl/fragment/EntityUpdateOp.fragment.pm +2 -2
  151. package/project/.sdk/src/cmp/perl/utility_perl.ts +11 -1
  152. package/project/.sdk/src/cmp/php/EntityOperation_php.ts +3 -1
  153. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +4 -3
  154. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +12 -6
  155. package/project/.sdk/src/cmp/php/ReadmeModel_php.ts +9 -9
  156. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +3 -2
  157. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +15 -14
  158. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +5 -4
  159. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +13 -7
  160. package/project/.sdk/src/cmp/php/fragment/EntityCreateOp.fragment.php +2 -2
  161. package/project/.sdk/src/cmp/php/fragment/EntityListOp.fragment.php +2 -2
  162. package/project/.sdk/src/cmp/php/fragment/EntityLoadOp.fragment.php +2 -2
  163. package/project/.sdk/src/cmp/php/fragment/EntityPatchOp.fragment.php +2 -2
  164. package/project/.sdk/src/cmp/php/fragment/EntityRemoveOp.fragment.php +2 -2
  165. package/project/.sdk/src/cmp/php/fragment/EntityUpdateOp.fragment.php +2 -2
  166. package/project/.sdk/src/cmp/py/EntityOperation_py.ts +3 -1
  167. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +3 -8
  168. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +9 -9
  169. package/project/.sdk/src/cmp/py/ReadmeModel_py.ts +9 -9
  170. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +9 -14
  171. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +23 -30
  172. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +7 -12
  173. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +8 -7
  174. package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +1 -1
  175. package/project/.sdk/src/cmp/py/fragment/EntityListOp.fragment.py +1 -1
  176. package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +1 -1
  177. package/project/.sdk/src/cmp/py/fragment/EntityPatchOp.fragment.py +1 -1
  178. package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +1 -1
  179. package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +1 -1
  180. package/project/.sdk/src/cmp/rb/EntityOperation_rb.ts +3 -1
  181. package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +2 -1
  182. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +4 -1
  183. package/project/.sdk/src/cmp/rb/ReadmeExamplesTest_rb.ts +3 -2
  184. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +20 -10
  185. package/project/.sdk/src/cmp/rb/ReadmeModel_rb.ts +10 -9
  186. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +6 -3
  187. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +14 -11
  188. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +6 -3
  189. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +14 -10
  190. package/project/.sdk/src/cmp/rb/fragment/EntityCreateOp.fragment.rb +2 -1
  191. package/project/.sdk/src/cmp/rb/fragment/EntityListOp.fragment.rb +1 -1
  192. package/project/.sdk/src/cmp/rb/fragment/EntityLoadOp.fragment.rb +2 -1
  193. package/project/.sdk/src/cmp/rb/fragment/EntityPatchOp.fragment.rb +2 -1
  194. package/project/.sdk/src/cmp/rb/fragment/EntityRemoveOp.fragment.rb +2 -1
  195. package/project/.sdk/src/cmp/rb/fragment/EntityUpdateOp.fragment.rb +2 -1
  196. package/project/.sdk/src/cmp/rb/utility_rb.ts +10 -0
  197. package/project/.sdk/src/cmp/rust/ReadmeEntity_rust.ts +3 -2
  198. package/project/.sdk/src/cmp/rust/ReadmeHowto_rust.ts +6 -2
  199. package/project/.sdk/src/cmp/rust/ReadmeQuick_rust.ts +3 -2
  200. package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +5 -4
  201. package/project/.sdk/src/cmp/rust/ReadmeTopQuick_rust.ts +3 -2
  202. package/project/.sdk/src/cmp/rust/ReadmeTopTest_rust.ts +7 -6
  203. package/project/.sdk/src/cmp/rust/utility_rust.ts +13 -1
  204. package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +5 -2
  205. package/project/.sdk/src/cmp/scala/ReadmeEntity_scala.ts +8 -19
  206. package/project/.sdk/src/cmp/scala/ReadmeHowto_scala.ts +17 -22
  207. package/project/.sdk/src/cmp/scala/ReadmeModel_scala.ts +10 -9
  208. package/project/.sdk/src/cmp/scala/ReadmeQuick_scala.ts +18 -28
  209. package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +25 -35
  210. package/project/.sdk/src/cmp/scala/ReadmeTopQuick_scala.ts +14 -25
  211. package/project/.sdk/src/cmp/scala/ReadmeTopTest_scala.ts +16 -24
  212. package/project/.sdk/src/cmp/scala/utility_scala.ts +25 -0
  213. package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +1 -1
  214. package/project/.sdk/src/cmp/swift/ReadmeEntity_swift.ts +3 -2
  215. package/project/.sdk/src/cmp/swift/ReadmeHowto_swift.ts +22 -8
  216. package/project/.sdk/src/cmp/swift/ReadmeModel_swift.ts +10 -9
  217. package/project/.sdk/src/cmp/swift/ReadmeQuick_swift.ts +16 -8
  218. package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +20 -15
  219. package/project/.sdk/src/cmp/swift/ReadmeTopQuick_swift.ts +12 -7
  220. package/project/.sdk/src/cmp/swift/ReadmeTopTest_swift.ts +21 -10
  221. package/project/.sdk/src/cmp/swift/utility_swift.ts +14 -1
  222. package/project/.sdk/src/cmp/ts/ReadmeEntity_ts.ts +2 -8
  223. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +11 -9
  224. package/project/.sdk/src/cmp/ts/ReadmeModel_ts.ts +9 -9
  225. package/project/.sdk/src/cmp/ts/ReadmeQuick_ts.ts +7 -13
  226. package/project/.sdk/src/cmp/ts/ReadmeRef_ts.ts +14 -23
  227. package/project/.sdk/src/cmp/ts/ReadmeTopQuick_ts.ts +8 -14
  228. package/project/.sdk/src/cmp/ts/ReadmeTopTest_ts.ts +29 -14
  229. package/project/.sdk/src/cmp/ts/fragment/EntityCreateOp.fragment.ts +1 -1
  230. package/project/.sdk/src/cmp/ts/fragment/EntityListOp.fragment.ts +1 -1
  231. package/project/.sdk/src/cmp/ts/fragment/EntityLoadOp.fragment.ts +1 -1
  232. package/project/.sdk/src/cmp/ts/fragment/EntityPatchOp.fragment.ts +1 -1
  233. package/project/.sdk/src/cmp/ts/fragment/EntityUpdateOp.fragment.ts +1 -1
  234. package/project/.sdk/src/cmp/ts/utility_ts.ts +2 -1
  235. package/project/.sdk/src/cmp/zig/ReadmeEntity_zig.ts +3 -2
  236. package/project/.sdk/src/cmp/zig/ReadmeHowto_zig.ts +6 -2
  237. package/project/.sdk/src/cmp/zig/ReadmeQuick_zig.ts +4 -3
  238. package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +5 -4
  239. package/project/.sdk/src/cmp/zig/ReadmeTopQuick_zig.ts +3 -2
  240. package/project/.sdk/src/cmp/zig/ReadmeTopTest_zig.ts +7 -6
  241. package/project/.sdk/src/cmp/zig/utility_zig.ts +13 -0
  242. package/project/.sdk/tm/c/utility/transform_request.c +1 -1
  243. package/project/.sdk/tm/clojure/src/sdk/core.clj +32 -26
  244. package/project/.sdk/tm/clojure/test/sdk/test/concurrency.clj +136 -0
  245. package/project/.sdk/tm/clojure/test/sdk/test_runner.clj +3 -1
  246. package/project/.sdk/tm/cpp/core/types.hpp +36 -6
  247. package/project/.sdk/tm/cpp/test/concurrency_test.cpp +348 -0
  248. package/project/.sdk/tm/cpp/utility/pipeline.hpp +42 -25
  249. package/project/.sdk/tm/csharp/core/Context.cs +8 -5
  250. package/project/.sdk/tm/csharp/test/ConcurrencyTest.cs +238 -0
  251. package/project/.sdk/tm/csharp/utility/Clean.cs +12 -10
  252. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +1 -1
  253. package/project/.sdk/tm/elixir/lib/projectname/context.ex +3 -0
  254. package/project/.sdk/tm/elixir/lib/projectname/entity_base.ex +2 -1
  255. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +31 -10
  256. package/project/.sdk/tm/elixir/test/concurrency_test.exs +176 -0
  257. package/project/.sdk/tm/go/core/context.go +5 -1
  258. package/project/.sdk/tm/go/test/concurrency_test.go +214 -0
  259. package/project/.sdk/tm/go/utility/transform_request.go +1 -1
  260. package/project/.sdk/tm/java/core/Context.java +6 -3
  261. package/project/.sdk/tm/java/test/ConcurrencyTest.java +221 -0
  262. package/project/.sdk/tm/java/utility/Clean.java +22 -17
  263. package/project/.sdk/tm/java/utility/TransformRequest.java +1 -1
  264. package/project/.sdk/tm/js/src/utility/TransformRequestUtility.js +1 -1
  265. package/project/.sdk/tm/kotlin/core/Context.kt +5 -3
  266. package/project/.sdk/tm/kotlin/test/ConcurrencyTest.kt +193 -0
  267. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +1 -1
  268. package/project/.sdk/tm/lua/utility/transform_request.lua +1 -1
  269. package/project/.sdk/tm/ocaml/Makefile +21 -8
  270. package/project/.sdk/tm/ocaml/sdk_runtime.ml +27 -12
  271. package/project/.sdk/tm/ocaml/sdk_types.ml +6 -2
  272. package/project/.sdk/tm/ocaml/test/concurrency_test.ml +148 -0
  273. package/project/.sdk/tm/perl/utility/transform_request.pm +1 -1
  274. package/project/.sdk/tm/php/feature/TestFeature.php +9 -9
  275. package/project/.sdk/tm/php/utility/TransformRequest.php +1 -1
  276. package/project/.sdk/tm/py/pkg/core/context.py +2 -2
  277. package/project/.sdk/tm/py/pkg/utility/clean.py +12 -8
  278. package/project/.sdk/tm/py/pkg/utility/transform_request.py +1 -1
  279. package/project/.sdk/tm/py/test/test_concurrency.py +177 -0
  280. package/project/.sdk/tm/rb/core/context.rb +8 -3
  281. package/project/.sdk/tm/rb/test/concurrency_test.rb +160 -0
  282. package/project/.sdk/tm/rb/utility/clean.rb +8 -7
  283. package/project/.sdk/tm/rb/utility/transform_request.rb +1 -1
  284. package/project/.sdk/tm/rust/utility/transform_request.rs +1 -1
  285. package/project/.sdk/tm/scala/Makefile +2 -0
  286. package/project/.sdk/tm/scala/core/Context.scala +6 -4
  287. package/project/.sdk/tm/scala/sdktest/ConcurrencyTest.scala +186 -0
  288. package/project/.sdk/tm/scala/utility/Transform.scala +1 -1
  289. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +2 -0
  290. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +35 -12
  291. package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/ConcurrencyTest.swift +214 -0
  292. package/project/.sdk/tm/ts/src/utility/TransformRequestUtility.ts +1 -1
  293. package/project/.sdk/tm/zig/core/utility.zig +1 -1
  294. package/project/.sdk/tm/zig/feature/test.zig +10 -8
  295. package/project/sdkgen-package.json +1 -1
  296. package/src/cmp/ReadmeErrors.ts +5 -5
  297. package/src/cmp/ReadmeExplanation.ts +4 -5
  298. package/src/cmp/ReadmeQuick.ts +3 -1
  299. package/src/cmp/ReadmeTop.ts +10 -12
  300. package/src/helpers/definitionPlan.ts +8 -4
  301. package/src/helpers/naming.ts +12 -4
  302. package/src/helpers/opExample.ts +97 -25
  303. package/src/helpers/opShape.ts +25 -9
  304. package/src/sdkgen.ts +14 -3
  305. package/src/utility.ts +31 -4
@@ -0,0 +1,193 @@
1
+ package KOTLINPACKAGE.sdktest
2
+
3
+ // Requests in flight at once on one client. Each resolves its operation
4
+ // through the cache the client's root context shares with every request, and
5
+ // registers and cleans secrets through the one registry the client holds.
6
+
7
+ import java.util.concurrent.ConcurrentLinkedQueue
8
+ import java.util.concurrent.CountDownLatch
9
+ import java.util.concurrent.CyclicBarrier
10
+ import java.util.function.BiFunction
11
+ import java.util.function.Supplier
12
+
13
+ import org.junit.jupiter.api.Assertions.assertEquals
14
+ import org.junit.jupiter.api.Assertions.assertSame
15
+ import org.junit.jupiter.api.Assertions.assertTrue
16
+ import org.junit.jupiter.api.Test
17
+
18
+ import KOTLINPACKAGE.core.Context
19
+ import KOTLINPACKAGE.core.Operation
20
+ import KOTLINPACKAGE.core.ProjectNameSDK
21
+ import KOTLINPACKAGE.core.Utility
22
+
23
+ class ConcurrencyTest {
24
+
25
+ private val rounds = 200
26
+ private val width = 8
27
+ private val ops = 32
28
+
29
+ // A live client whose transport answers at once.
30
+ private fun liveClient(): ProjectNameSDK {
31
+ val fetch = BiFunction<String, MutableMap<String, Any?>, MutableMap<String, Any?>> { _, _ ->
32
+ linkedMapOf<String, Any?>(
33
+ "status" to 200,
34
+ "statusText" to "OK",
35
+ "headers" to linkedMapOf<String, Any?>(),
36
+ "json" to Supplier<Any?> { linkedMapOf<String, Any?>("ok" to true) },
37
+ )
38
+ }
39
+ return ProjectNameSDK(linkedMapOf<String, Any?>(
40
+ "base" to "http://concurrency.test/api",
41
+ "allow" to linkedMapOf<String, Any?>("op" to "direct"),
42
+ "system" to linkedMapOf<String, Any?>("fetch" to fetch),
43
+ ))
44
+ }
45
+
46
+ // Runs body on `width` threads released together, and returns what they threw.
47
+ private fun atOnce(body: (Int) -> Unit): List<Throwable> {
48
+ val start = CyclicBarrier(width)
49
+ val thrown = ConcurrentLinkedQueue<Throwable>()
50
+ val threads = (0 until width).map { n ->
51
+ Thread {
52
+ try {
53
+ start.await()
54
+ body(n)
55
+ } catch (t: Throwable) {
56
+ thrown.add(t)
57
+ }
58
+ }.also { it.start() }
59
+ }
60
+ threads.forEach { it.join() }
61
+ return thrown.toList()
62
+ }
63
+
64
+ @Test
65
+ fun concurrentFirstRequestsSucceed() {
66
+ repeat(rounds) { round ->
67
+ // A fresh client each round, so every request in it is a first request.
68
+ val client = liveClient()
69
+ val results = arrayOfNulls<MutableMap<String, Any?>>(width)
70
+ val thrown = atOnce { n -> results[n] = client.direct(linkedMapOf<String, Any?>("path" to "p$n")) }
71
+
72
+ assertTrue(thrown.isEmpty(), "round $round threw: $thrown")
73
+ results.forEachIndexed { n, res ->
74
+ assertEquals(true, res?.get("ok"), "round $round, request $n failed: $res")
75
+ }
76
+ }
77
+ }
78
+
79
+ @Test
80
+ fun concurrentResolutionsShareOneCachedOperation() {
81
+ repeat(rounds) { round ->
82
+ val client = liveClient()
83
+ val utility = client.getUtility()
84
+ val root = client.getRootCtx()
85
+ val got = Array(width) { arrayOfNulls<Operation>(ops) }
86
+ val thrown = atOnce { n ->
87
+ for (k in 0 until ops) {
88
+ got[n][k] = utility.makeContext(linkedMapOf<String, Any?>("opname" to "op$k"), root).op
89
+ }
90
+ }
91
+
92
+ assertTrue(thrown.isEmpty(), "round $round threw: $thrown")
93
+ for (k in 0 until ops) {
94
+ val cached = utility.makeContext(linkedMapOf<String, Any?>("opname" to "op$k"), root).op
95
+ for (n in 0 until width) {
96
+ assertSame(cached, got[n][k], "round $round: op$k resolved to more than one Operation")
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ private fun addedSecret(round: Int, n: Int, k: Int) = "ADDED-SECRET-$round-$n-$k"
103
+
104
+ // Registers thread n's secrets, then counts the thread out of registering.
105
+ private fun registerSecrets(
106
+ utility: Utility, root: Context, round: Int, n: Int, registering: CountDownLatch,
107
+ ) {
108
+ try {
109
+ for (k in 0 until ops) {
110
+ utility.cleanAdd(root, addedSecret(round, n, k))
111
+ }
112
+ } finally {
113
+ registering.countDown()
114
+ }
115
+ }
116
+
117
+ private fun assertEverySecretMasked(utility: Utility, root: Context, round: Int) {
118
+ for (n in 0 until width / 2) {
119
+ for (k in 0 until ops) {
120
+ val added = addedSecret(round, n, k)
121
+ assertEquals("[redacted]", utility.clean(root, added),
122
+ "round $round: $added was registered but not masked")
123
+ }
124
+ }
125
+ }
126
+
127
+ // Secrets registered on some threads while others clean: every clean masks
128
+ // what was registered before it, the longer secret whole, and no
129
+ // registration is lost.
130
+ @Test
131
+ fun concurrentRegistrationKeepsEverySecretMasked() {
132
+ repeat(rounds / 4) { round ->
133
+ val client = liveClient()
134
+ val utility = client.getUtility()
135
+ val root = client.getRootCtx()
136
+ val inner = "INNER-SECRET-$round"
137
+ utility.cleanAdd(root, inner)
138
+ utility.cleanAdd(root, "OUTER-$inner-TAIL")
139
+ val text = "a $inner b OUTER-$inner-TAIL c"
140
+ val masked = "a [redacted] b [redacted] c"
141
+ assertEquals(masked, utility.clean(root, text))
142
+
143
+ val registering = CountDownLatch(width / 2)
144
+ val wrong = ConcurrentLinkedQueue<Any?>()
145
+ val thrown = atOnce { n ->
146
+ if (n < width / 2) {
147
+ registerSecrets(utility, root, round, n, registering)
148
+ } else {
149
+ while (0L < registering.count) {
150
+ val cleaned = utility.clean(root, text)
151
+ if (masked != cleaned) {
152
+ wrong.add(cleaned)
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+ assertTrue(thrown.isEmpty(), "round $round threw: $thrown")
159
+ assertTrue(wrong.isEmpty(), "round $round cleaned to: $wrong")
160
+ assertEverySecretMasked(utility, root, round)
161
+ }
162
+ }
163
+
164
+ // Requests on one client while secrets register on it: each request copies
165
+ // the client's options, the registry among them.
166
+ @Test
167
+ fun concurrentRequestsSurviveRegistration() {
168
+ repeat(rounds / 4) { round ->
169
+ val client = liveClient()
170
+ val utility = client.getUtility()
171
+ val root = client.getRootCtx()
172
+ val registering = CountDownLatch(width / 2)
173
+ val failed = ConcurrentLinkedQueue<Any?>()
174
+ val thrown = atOnce { n ->
175
+ if (n < width / 2) {
176
+ registerSecrets(utility, root, round, n, registering)
177
+ } else {
178
+ while (0L < registering.count) {
179
+ val res = client.direct(linkedMapOf<String, Any?>("path" to "p$n"))
180
+ if (true != res["ok"]) {
181
+ failed.add(res)
182
+ break
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ assertTrue(thrown.isEmpty(), "round $round threw: $thrown")
189
+ assertTrue(failed.isEmpty(), "round $round, a request failed: $failed")
190
+ assertEverySecretMasked(utility, root, round)
191
+ }
192
+ }
193
+ }
@@ -68,7 +68,7 @@ private fun stripAction(reqdata: Any?): Any? = omitKeys(reqdata, listOf("\$actio
68
68
 
69
69
  // A header, cookie or query argument travels where prepareHeaders or
70
70
  // prepareQuery sends it, so the body is built from the request data without
71
- // it, unless the entity declares it as a field too.
71
+ // it, unless the point marks it as a field the body keeps.
72
72
  private fun routedArgNames(ctx: Context): List<String> =
73
73
  (callArgs(ctx, "header") + callArgs(ctx, "cookie") + callArgs(ctx, "query")).map { it.name }
74
74
  .filter { !fieldArg(ctx, it) }
@@ -48,7 +48,7 @@ end
48
48
 
49
49
  -- A header, cookie or query argument travels where prepare_headers_util or
50
50
  -- prepare_query_util sends it, so the body is built from the request data
51
- -- without it, unless the entity declares it as a field too.
51
+ -- without it, unless the point marks it as a field the body keeps.
52
52
  local function routed_arg_names(ctx)
53
53
  local names = {}
54
54
  for _, kind in ipairs({ "header", "cookie", "query" }) do
@@ -1,11 +1,12 @@
1
1
  # Makefile for the ProjectName OCaml SDK (generated by @voxgig/sdkgen).
2
2
  #
3
3
  # The SDK is dependency-free: it compiles with the stock bytecode compiler
4
- # (ocamlc) — no opam packages, no dune. Four test executables are built:
5
- # run_sdk_test — the SDK pipeline + feature suite (Testutil harness)
6
- # run_omni_smoke — the vendored omni runner's own failure paths
7
- # run_struct_corpus — the shared voxgig/struct JSON corpus
8
- # run_primary_corpus — the shared primary-utility JSON corpus
4
+ # (ocamlc) — no opam packages, no dune. The test executables:
5
+ # run_sdk_test — the SDK pipeline + feature suite (Testutil harness)
6
+ # run_omni_smoke — the vendored omni runner's own failure paths
7
+ # run_struct_corpus — the shared voxgig/struct JSON corpus
8
+ # run_primary_corpus — the shared primary-utility JSON corpus
9
+ # run_concurrency_test — requests on several threads at once on one client
9
10
  #
10
11
  # The two corpus suites run on the VENDORED @voxgig/omni engine
11
12
  # (test/vendor/omni/omni.ml), driven through its native API by the local
@@ -95,6 +96,11 @@ CLEAN_TESTS = $(sort $(wildcard test/clean_test.ml))
95
96
  # Wildcard so an absent file is simply skipped, never a missing-target error.
96
97
  DOC_TESTS = $(sort $(wildcard test/readme_examples_test.ml))
97
98
 
99
+ # Requests on several threads at once on one client. Only this executable
100
+ # links the threads library; the SDK itself never does.
101
+ CONCURRENCY_SRC = $(RUNTIME) sdk_config.ml $(ENTITIES) sdk_client.ml sdk_error.ml \
102
+ test/testutil.ml test/concurrency_test.ml
103
+
98
104
  # Fixed test order: harness deps first, generated/behaviour tests, the gated
99
105
  # feature suites, summary last.
100
106
  SDK_TEST = $(RUNTIME) sdk_config.ml $(ENTITIES) sdk_client.ml sdk_error.ml \
@@ -117,9 +123,9 @@ PRIMARY_SRC = $(RUNTIME) sdk_config.ml $(ENTITIES) sdk_client.ml sdk_error.ml \
117
123
  SMOKE_SRC = utility/vregex.ml utility/voxgig_struct.ml \
118
124
  $(OMNI) test/omni_smoke_test.ml
119
125
 
120
- .PHONY: test test-sdk test-omni test-corpus test-primary build lint clean
126
+ .PHONY: test test-sdk test-omni test-corpus test-primary test-concurrency build lint clean
121
127
 
122
- test: test-sdk test-omni test-corpus test-primary
128
+ test: test-sdk test-omni test-corpus test-primary test-concurrency
123
129
 
124
130
  test-sdk: run_sdk_test
125
131
  ./run_sdk_test
@@ -133,6 +139,9 @@ test-corpus: run_struct_corpus
133
139
  test-primary: run_primary_corpus
134
140
  ./run_primary_corpus $(CORPUS)
135
141
 
142
+ test-concurrency: run_concurrency_test
143
+ ./run_concurrency_test
144
+
136
145
  # Every executable that links the RUNTIME links the gated feature tier with
137
146
  # it: library archives FIRST (an archive must precede the modules that use
138
147
  # it), stub objects and link flags LAST.
@@ -148,6 +157,10 @@ run_struct_corpus: $(CORPUS_SRC)
148
157
  run_primary_corpus: $(PRIMARY_SRC) $(FEATURE_OBJ)
149
158
  $(OCAMLC) $(INC) $(FEATURE_LIB) $(PRIMARY_SRC) $(FEATURE_OBJ) $(FEATURE_LINK) -o run_primary_corpus
150
159
 
160
+ run_concurrency_test: $(CONCURRENCY_SRC) $(FEATURE_OBJ)
161
+ $(OCAMLC) $(INC) -I +unix -I +threads unix.cma threads.cma $(filter-out unix.cma,$(FEATURE_LIB)) \
162
+ $(CONCURRENCY_SRC) $(FEATURE_OBJ) $(FEATURE_LINK) -o run_concurrency_test
163
+
151
164
  # Type-check the SDK library (a clean compile means it is sound).
152
165
  build: lint
153
166
  lint:
@@ -159,4 +172,4 @@ clean:
159
172
  test/vendor/omni/*.cmi test/vendor/omni/*.cmo \
160
173
  feature/*.cmi feature/*.cmo feature/*/*/*.cmi feature/*/*/*.cmo \
161
174
  feature/*/*/*.o test/feature/*/*.cmi test/feature/*/*.cmo \
162
- run_sdk_test run_omni_smoke run_struct_corpus run_primary_corpus a.out
175
+ run_sdk_test run_omni_smoke run_struct_corpus run_primary_corpus run_concurrency_test a.out
@@ -159,10 +159,19 @@ let default_ctxspec () : ctxspec =
159
159
  cs_reqdata = None; cs_match = None; cs_reqmatch = None; cs_point = None;
160
160
  cs_spec = None; cs_result = None; cs_response = None }
161
161
 
162
+ (* Every request racing to build this Operation gets the one stored first. *)
163
+ let rec store_op (opmap : operation OpMap.t Atomic.t) (key : string) (op : operation) : operation =
164
+ let seen = Atomic.get opmap in
165
+ match OpMap.find_opt key seen with
166
+ | Some stored -> stored
167
+ | None ->
168
+ if Atomic.compare_and_set opmap seen (OpMap.add key op seen) then op
169
+ else store_op opmap key op
170
+
162
171
  let resolve_op (ctx : ctx) (opname : string) : operation =
163
172
  let entname = match ctx.c_entity with Some e -> e.e_name | None -> "_" in
164
173
  let cache_key = entname ^ ":" ^ opname in
165
- match Hashtbl.find_opt ctx.c_opmap cache_key with
174
+ match OpMap.find_opt cache_key (Atomic.get ctx.c_opmap) with
166
175
  | Some op -> op
167
176
  | None ->
168
177
  if opname = "" then new_operation (empty_map ())
@@ -177,8 +186,7 @@ let resolve_op (ctx : ctx) (opname : string) : operation =
177
186
  let op = new_operation
178
187
  (jo [("entity", Str entname); ("name", Str opname);
179
188
  ("input", Str inpt); ("points", points)]) in
180
- Hashtbl.replace ctx.c_opmap cache_key op;
181
- op
189
+ store_op ctx.c_opmap cache_key op
182
190
  end
183
191
 
184
192
  let make_context_impl (cs : ctxspec) (basectx : ctx option) : ctx =
@@ -226,7 +234,7 @@ let make_context_impl (cs : ctxspec) (basectx : ctx option) : ctx =
226
234
  | _ -> (match basectx with Some b -> b.c_shared | None -> Noval) in
227
235
  let opmap =
228
236
  match cs.cs_opmap with Some h -> h
229
- | None -> (match basectx with Some b -> b.c_opmap | None -> Hashtbl.create 16) in
237
+ | None -> (match basectx with Some b -> b.c_opmap | None -> Atomic.make OpMap.empty) in
230
238
  let mapof = function Some d -> (match to_map d with Map _ as m -> m | _ -> empty_map ()) | None -> empty_map () in
231
239
  let data = mapof cs.cs_data and reqdata = mapof cs.cs_reqdata
232
240
  and mtch = mapof cs.cs_match and reqmatch = mapof cs.cs_reqmatch in
@@ -321,18 +329,25 @@ let clean_forms (value : string) : string list =
321
329
  if String.length j >= 2 then add (String.sub j 1 (String.length j - 2)));
322
330
  !out
323
331
 
332
+ (* A registration reads the registry list and writes a new one, so requests
333
+ * on other domains registering at once take turns, or one would be dropped.
334
+ * Cleaning reads the list whole and needs no turn. *)
335
+ let clean_registering = Atomic.make false
336
+
324
337
  let clean_add_util (ctx : ctx) (value : string) : unit =
325
338
  let cfg = clean_config ctx in
326
339
  let minlen = match getp cfg "min" with Num n -> int_of_float n | _ -> 4 in
327
340
  match getp cfg "values" with
328
341
  | List r when String.length value >= minlen ->
329
- let have = str_values (List r) in
330
- let add = List.filter (fun f -> String.length f >= minlen && not (List.mem f have))
331
- (clean_forms value) in
332
- (* Longest first, so a value is never masked by a substring of itself. *)
333
- if add <> [] then
334
- r := List.map (fun s -> Str s)
335
- (List.stable_sort (fun a b -> compare (String.length b) (String.length a)) (have @ add))
342
+ while not (Atomic.compare_and_set clean_registering false true) do () done;
343
+ Fun.protect ~finally:(fun () -> Atomic.set clean_registering false) (fun () ->
344
+ let have = str_values (List r) in
345
+ let add = List.filter (fun f -> String.length f >= minlen && not (List.mem f have))
346
+ (clean_forms value) in
347
+ (* Longest first, so a value is never masked by a substring of itself. *)
348
+ if add <> [] then
349
+ r := List.map (fun s -> Str s)
350
+ (List.stable_sort (fun a b -> compare (String.length b) (String.length a)) (have @ add)))
336
351
  | _ -> ()
337
352
 
338
353
  let mask_value (cfg : value) (value : string) : string =
@@ -1011,7 +1026,7 @@ let field_arg (ctx : ctx) (name : string) : bool =
1011
1026
 
1012
1027
  (* A header, cookie or query argument travels where prepare_headers_util or
1013
1028
  prepare_query_util sends it, so the body is built from the request data
1014
- without it, unless the entity declares it as a field too. *)
1029
+ without it, unless the point marks it as a field the body keeps. *)
1015
1030
  let routed_arg_names (ctx : ctx) : string list =
1016
1031
  List.filter (fun name -> not (field_arg ctx name))
1017
1032
  (List.map (fun (name, _, _) -> name)
@@ -17,6 +17,8 @@
17
17
 
18
18
  open Voxgig_struct
19
19
 
20
+ module OpMap = Map.Make (String)
21
+
20
22
  (* Branded SDK error. Raised as `Sdk_error_exc` on the throwing path; carried
21
23
  * as `sdk_error option` on the (value, err) pipeline return tuples. *)
22
24
  type sdk_error = {
@@ -238,7 +240,7 @@ and ctxspec = {
238
240
  mutable cs_options : value option;
239
241
  mutable cs_entity : entity_obj option;
240
242
  mutable cs_shared : value option;
241
- mutable cs_opmap : (string, operation) Hashtbl.t option;
243
+ mutable cs_opmap : operation OpMap.t Atomic.t option;
242
244
  mutable cs_data : value option;
243
245
  mutable cs_reqdata : value option;
244
246
  mutable cs_match : value option;
@@ -261,7 +263,9 @@ and ctx = {
261
263
  mutable c_config : value;
262
264
  mutable c_entopts : value;
263
265
  mutable c_options : value;
264
- mutable c_opmap : (string, operation) Hashtbl.t;
266
+ (* Shared by every context of one client, so by requests on other domains:
267
+ * a map replaced whole, never changed in place. *)
268
+ mutable c_opmap : operation OpMap.t Atomic.t;
265
269
  mutable c_response : response option;
266
270
  mutable c_result : result option;
267
271
  mutable c_spec : spec option;
@@ -0,0 +1,148 @@
1
+ (* Requests in flight at once on one client. Each resolves its operation
2
+ * through the cache the client's root context shares with every request, and
3
+ * registers and cleans secrets through the one registry the client holds.
4
+ *
5
+ * Threads take turns on one domain and switch when the runtime's tick lands,
6
+ * so each test runs long enough to be switched inside a resolution or a
7
+ * registration. *)
8
+
9
+ open Voxgig_struct
10
+ open Sdk_types
11
+ open Testutil
12
+
13
+ let rounds = 3
14
+ let width = 8
15
+ let ops = 4000
16
+ let seeded = 600
17
+ let added = 100
18
+ let masked = "a [redacted] b [redacted] c"
19
+
20
+ (* Runs body on width threads released together, and returns what they raised. *)
21
+ let at_once (body : int -> unit) : string list =
22
+ let lock = Mutex.create () in
23
+ let ready = Condition.create () in
24
+ let waiting = ref 0 in
25
+ let raised = ref [] in
26
+ let run n =
27
+ Mutex.lock lock;
28
+ incr waiting;
29
+ if !waiting = width then Condition.broadcast ready
30
+ else while !waiting < width do Condition.wait ready lock done;
31
+ Mutex.unlock lock;
32
+ try body n
33
+ with e ->
34
+ Mutex.lock lock;
35
+ raised := fail_msg e :: !raised;
36
+ Mutex.unlock lock
37
+ in
38
+ List.iter Thread.join (List.init width (Thread.create run));
39
+ !raised
40
+
41
+ let root_of (cl : sdk_client) : ctx =
42
+ match cl.cl_rootctx with Some c -> c | None -> failwith "no root context"
43
+
44
+ let resolve (cl : sdk_client) (k : int) : operation =
45
+ let ctx = cl.cl_utility.u_make_context
46
+ { (Sdk_runtime.default_ctxspec ()) with cs_opname = Some ("op" ^ string_of_int k) }
47
+ cl.cl_rootctx in
48
+ ctx.c_op
49
+
50
+ let () = test "concurrent resolutions share one cached operation" (fun () ->
51
+ for round = 1 to rounds do
52
+ let cl = Sdk_client.test () in
53
+ let got = Array.init width (fun _ -> Array.make ops None) in
54
+ let raised = at_once (fun n ->
55
+ for k = 0 to ops - 1 do got.(n).(k) <- Some (resolve cl k) done) in
56
+ check (Printf.sprintf "round %d raised: %s" round (String.concat "; " raised)) (raised = []);
57
+ for k = 0 to ops - 1 do
58
+ let cached = resolve cl k in
59
+ for n = 0 to width - 1 do
60
+ check (Printf.sprintf "round %d: op%d resolved to more than one Operation" round k)
61
+ (match got.(n).(k) with Some op -> op == cached | None -> false)
62
+ done
63
+ done
64
+ done)
65
+
66
+ let clean_str (cl : sdk_client) (text : string) : string =
67
+ match cl.cl_utility.u_clean (root_of cl) (Str text) with Str s -> s | v -> stringify v
68
+
69
+ (* A live client whose transport answers at once. *)
70
+ let live_client () : sdk_client =
71
+ let fetch = Func (fun _ _ _ _ ->
72
+ Sdk_helpers.jo [
73
+ ("status", Num 200.); ("statusText", Str "OK"); ("headers", Sdk_helpers.jo []);
74
+ ("json", Sdk_helpers.json_thunk (Sdk_helpers.jo [("ok", Bool true)]))]) in
75
+ Sdk_client.make (Sdk_helpers.jo [
76
+ ("base", Str "http://concurrency.test/api");
77
+ ("allow", Sdk_helpers.jo [("op", Str "direct")]);
78
+ ("system", Sdk_helpers.jo [("fetch", fetch)])])
79
+
80
+ (* Registers thread n's secrets, then counts the thread out of registering. *)
81
+ let register_secrets (cl : sdk_client) (round : int) (n : int) (registering : int Atomic.t) : unit =
82
+ Fun.protect ~finally:(fun () -> Atomic.decr registering) (fun () ->
83
+ for k = 1 to added do
84
+ cl.cl_utility.u_clean_add (root_of cl) (Printf.sprintf "ADDED-SECRET-%d-%d-%d" round n k)
85
+ done)
86
+
87
+ let every_secret_masked (cl : sdk_client) (round : int) : unit =
88
+ for n = 0 to width / 2 - 1 do
89
+ for k = 1 to added do
90
+ let secret = Printf.sprintf "ADDED-SECRET-%d-%d-%d" round n k in
91
+ check_str (Printf.sprintf "round %d: %s was registered but not masked" round secret)
92
+ (clean_str cl secret) "[redacted]"
93
+ done
94
+ done
95
+
96
+ (* Secrets registered on some threads while others clean: every clean masks
97
+ * what was registered before it, the longer secret whole, and no
98
+ * registration is lost. *)
99
+ let () = test "concurrent registration keeps every secret masked" (fun () ->
100
+ for round = 1 to rounds do
101
+ let cl = Sdk_client.test () in
102
+ let add v = cl.cl_utility.u_clean_add (root_of cl) v in
103
+ for k = 1 to seeded do add (Printf.sprintf "SEEDED-SECRET-%d-%d" round k) done;
104
+ let inner = Printf.sprintf "INNER-SECRET-%d" round in
105
+ add inner;
106
+ add ("OUTER-" ^ inner ^ "-TAIL");
107
+ let text = "a " ^ inner ^ " b OUTER-" ^ inner ^ "-TAIL c" in
108
+ check_str (Printf.sprintf "round %d" round) (clean_str cl text) masked;
109
+ let registering = Atomic.make (width / 2) in
110
+ let raised = at_once (fun n ->
111
+ if n < width / 2 then register_secrets cl round n registering
112
+ else
113
+ while 0 < Atomic.get registering do
114
+ let got = clean_str cl text in
115
+ if got <> masked then failwith ("cleaned to: " ^ got);
116
+ Thread.yield ()
117
+ done) in
118
+ check (Printf.sprintf "round %d raised: %s" round (String.concat "; " raised)) (raised = []);
119
+ every_secret_masked cl round
120
+ done)
121
+
122
+ (* Requests on one client while secrets register on it: each request copies
123
+ * the client's options, the registry among them. *)
124
+ let () = test "concurrent requests survive registration" (fun () ->
125
+ for round = 1 to rounds do
126
+ let cl = live_client () in
127
+ for k = 1 to seeded do
128
+ cl.cl_utility.u_clean_add (root_of cl) (Printf.sprintf "SEEDED-SECRET-%d-%d" round k)
129
+ done;
130
+ let registering = Atomic.make (width / 2) in
131
+ let raised = at_once (fun n ->
132
+ if n < width / 2 then register_secrets cl round n registering
133
+ else
134
+ while 0 < Atomic.get registering do
135
+ let res = Sdk_client.direct cl
136
+ (Sdk_helpers.jo [("path", Str (Printf.sprintf "p%d" n))]) in
137
+ if Sdk_helpers.getp res "ok" <> Bool true then
138
+ failwith ("a request failed: " ^ stringify res);
139
+ Thread.yield ()
140
+ done) in
141
+ check (Printf.sprintf "round %d raised: %s" round (String.concat "; " raised)) (raised = []);
142
+ every_secret_masked cl round
143
+ done)
144
+
145
+ let () =
146
+ List.iter (fun m -> print_endline ("FAIL " ^ m)) (List.rev !failures);
147
+ Printf.printf "concurrency_test: %d passed, %d failed\n" !npass !nfail;
148
+ if !nfail > 0 then exit 1
@@ -44,7 +44,7 @@ my $field_arg = sub {
44
44
 
45
45
  # A header, cookie or query argument travels where prepare_headers or
46
46
  # prepare_query sends it, so the body is built from the request data without
47
- # it, unless the entity declares it as a field too.
47
+ # it, unless the point marks it as a field the body keeps.
48
48
  my $routed_arg_names = sub {
49
49
  my ($ctx) = @_;
50
50
  return grep { !$field_arg->($ctx, $_) } map { $_->[0] } ProjectNameUtilities::call_args($ctx, 'header'),
@@ -392,7 +392,7 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
392
392
 
393
393
  /**
394
394
  * Build a structured `$AND` query from the request match/data dict,
395
- * matching the TS test feature's buildArgs. Mirrors ts/src/feature/test/TestFeature.ts:158-204.
395
+ * matching the TS test feature's buildArgs in ts/src/feature/test/TestFeature.ts.
396
396
  *
397
397
  * For each key in $args that is 'id' OR a required-param key on the
398
398
  * current operation point, emit a `$OR` clause matching the key (and
@@ -409,12 +409,12 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
409
409
  }
410
410
 
411
411
  $opname = is_object($op) ? ($op->name ?? null) : (\Voxgig\Struct\Struct::getprop($op, 'name'));
412
- $entityName = null;
413
- if (isset($ctx->entity)) {
414
- $entityName = is_object($ctx->entity)
415
- ? ($ctx->entity->name ?? null)
416
- : (is_array($ctx->entity) ? ($ctx->entity['name'] ?? null) : null);
417
- }
412
+
413
+ // An entity keeps its name private, behind get_name(); a context
414
+ // with no entity has the name its operation was resolved for.
415
+ $entityName = (is_object($ctx->entity) && method_exists($ctx->entity, 'get_name'))
416
+ ? $ctx->entity->get_name()
417
+ : (is_object($op) ? ($op->entity ?? null) : \Voxgig\Struct\Struct::getprop($op, 'entity'));
418
418
 
419
419
  // Resolve required-param names from the op's last point. Defensive:
420
420
  // any missing piece falls back to "no required params".
@@ -427,8 +427,8 @@ class ProjectNameTestFeature extends ProjectNameBaseFeature
427
427
  // `{id}` marks a record route, and failing that the shallower
428
428
  // path wins.
429
429
  $points = \Voxgig\Struct\Struct::getpath(
430
- ['entity', $entityName, 'op', $opname, 'points'],
431
- $ctx->config
430
+ $ctx->config,
431
+ ['entity', $entityName, 'op', $opname, 'points']
432
432
  );
433
433
  $point = \Voxgig\Struct\Struct::getelem($points, 0);
434
434
  if (is_array($points)) {
@@ -37,7 +37,7 @@ class ProjectNameTransformRequest
37
37
 
38
38
  // A header, cookie or query argument travels where PrepareHeaders or
39
39
  // PrepareQuery sends it, so the body is built from the request data
40
- // without it, unless the entity declares it as a field too.
40
+ // without it, unless the point marks it as a field the body keeps.
41
41
  private static function routed_arg_names(ProjectNameContext $ctx): array
42
42
  {
43
43
  $args = array_merge(ProjectNameParam::callArgs($ctx, 'header'),
@@ -202,8 +202,8 @@ class ProjectNameContext:
202
202
  "points": points,
203
203
  })
204
204
 
205
- self.opmap[cache_key] = op
206
- return op
205
+ # Every request racing to build this Operation gets the one stored first.
206
+ return self.opmap.setdefault(cache_key, op)
207
207
 
208
208
  def make_error(self, code, msg):
209
209
  return ProjectNameError(code, msg, self)
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
  import base64
5
5
  import json
6
+ import threading
6
7
  from urllib.parse import quote
7
8
 
8
9
  from projectname_sdk.schema import OPTSPEC
@@ -113,20 +114,23 @@ def _forms(value):
113
114
  return out
114
115
 
115
116
 
117
+ # Requests on other threads clean while one registers, so a registration
118
+ # publishes a new list and never changes a published one.
119
+ _REGISTERING = threading.Lock()
120
+
121
+
116
122
  # Register a secret value. Idempotent; shorter than `min` is not a secret
117
123
  # the SDK can mask without blanking ordinary text.
118
124
  def clean_add_util(ctx, value):
119
125
  cfg = _clean_config(ctx)
120
126
  if not isinstance(value, str) or len(value) < cfg["min"]:
121
127
  return
122
- values = cfg["values"]
123
- changed = False
124
- for form in _forms(value):
125
- if len(form) >= cfg["min"] and form not in values:
126
- values.append(form)
127
- changed = True
128
- if changed:
129
- values.sort(key=len, reverse=True)
128
+ with _REGISTERING:
129
+ values = cfg["values"]
130
+ added = [form for form in _forms(value)
131
+ if len(form) >= cfg["min"] and form not in values]
132
+ if added:
133
+ cfg["values"] = sorted(values + added, key=len, reverse=True)
130
134
 
131
135
 
132
136
  # Every scalar under a sensitive name, at any depth and of any shape: a
@@ -14,7 +14,7 @@ def _strip_action(reqdata):
14
14
 
15
15
  # A header, cookie or query argument travels where prepare_headers_util or
16
16
  # prepare_query_util sends it, so the body is built from the request data
17
- # without it, unless the entity declares it as a field too.
17
+ # without it, unless the point marks it as a field the body keeps.
18
18
  def _routed_arg_names(ctx):
19
19
  return [name for name, _orig, _val in
20
20
  call_args(ctx, "header") + call_args(ctx, "cookie") + call_args(ctx, "query")