@voxgig/sdkgen 3.5.1 → 3.7.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 (255) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/AgentGuideTop.js +17 -0
  3. package/dist/cmp/AgentGuideTop.js.map +1 -1
  4. package/dist/cmp/ExternalTarget.js +1 -1
  5. package/dist/cmp/ExternalTarget.js.map +1 -1
  6. package/dist/cmp/Readme.js +2 -0
  7. package/dist/cmp/Readme.js.map +1 -1
  8. package/dist/cmp/ReadmeStation.d.ts +2 -0
  9. package/dist/cmp/ReadmeStation.js +57 -0
  10. package/dist/cmp/ReadmeStation.js.map +1 -0
  11. package/dist/helpers/naming.d.ts +6 -1
  12. package/dist/helpers/naming.js +156 -5
  13. package/dist/helpers/naming.js.map +1 -1
  14. package/dist/helpers/opShape.js +72 -0
  15. package/dist/helpers/opShape.js.map +1 -1
  16. package/dist/helpers/packageMeta.d.ts +2 -1
  17. package/dist/helpers/packageMeta.js +57 -20
  18. package/dist/helpers/packageMeta.js.map +1 -1
  19. package/dist/sdkgen.d.ts +3 -3
  20. package/dist/sdkgen.js +63 -18
  21. package/dist/sdkgen.js.map +1 -1
  22. package/dist/testkit.d.ts +39 -0
  23. package/dist/testkit.js +663 -0
  24. package/dist/testkit.js.map +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/dist/utility.d.ts +1 -1
  27. package/dist/utility.js +16 -2
  28. package/dist/utility.js.map +1 -1
  29. package/package.json +32 -8
  30. package/project/.sdk/src/cmp/c/Config_c.ts +34 -13
  31. package/project/.sdk/src/cmp/c/Test_c.ts +5 -7
  32. package/project/.sdk/src/cmp/clojure/Test_clojure.ts +3 -7
  33. package/project/.sdk/src/cmp/cpp/Config_cpp.ts +9 -46
  34. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -7
  35. package/project/.sdk/src/cmp/csharp/Config_csharp.ts +7 -1
  36. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  37. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -7
  38. package/project/.sdk/src/cmp/dart/Config_dart.ts +14 -2
  39. package/project/.sdk/src/cmp/dart/Test_dart.ts +3 -4
  40. package/project/.sdk/src/cmp/dart/fragment/Config.data.fragment.dart +5 -0
  41. package/project/.sdk/src/cmp/dart/fragment/Config.fragment.dart +6 -0
  42. package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +13 -4
  43. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +12 -2
  44. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +42 -3
  45. package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
  46. package/project/.sdk/src/cmp/go/Config_go.ts +8 -1
  47. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +1 -1
  48. package/project/.sdk/src/cmp/go/Entity_go.ts +1 -1
  49. package/project/.sdk/src/cmp/go/Gitignore_go.ts +6 -0
  50. package/project/.sdk/src/cmp/go/Main_go.ts +2 -2
  51. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  52. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +59 -6
  53. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +1 -1
  54. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +1 -1
  55. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +1 -1
  56. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +1 -1
  57. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +1 -1
  58. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +1 -1
  59. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +1 -1
  60. package/project/.sdk/src/cmp/go/Test_go.ts +6 -8
  61. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +6 -2
  62. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +6 -2
  63. package/project/.sdk/src/cmp/java/Config_java.ts +8 -1
  64. package/project/.sdk/src/cmp/java/Test_java.ts +4 -7
  65. package/project/.sdk/src/cmp/js/Config_js.ts +9 -1
  66. package/project/.sdk/src/cmp/js/Package_js.ts +1 -1
  67. package/project/.sdk/src/cmp/js/Test_js.ts +4 -7
  68. package/project/.sdk/src/cmp/js/fragment/Config.data.fragment.js +7 -0
  69. package/project/.sdk/src/cmp/js/fragment/Config.fragment.js +8 -0
  70. package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +33 -5
  71. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +8 -1
  72. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +4 -7
  73. package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
  74. package/project/.sdk/src/cmp/lua/Config_lua.ts +11 -2
  75. package/project/.sdk/src/cmp/lua/Package_lua.ts +26 -3
  76. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -7
  77. package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +3 -7
  78. package/project/.sdk/src/cmp/perl/Config_perl.ts +8 -60
  79. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  80. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
  81. package/project/.sdk/src/cmp/php/Config_php.ts +11 -2
  82. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
  83. package/project/.sdk/src/cmp/php/Main_php.ts +25 -0
  84. package/project/.sdk/src/cmp/php/Package_php.ts +2 -2
  85. package/project/.sdk/src/cmp/php/Test_php.ts +5 -7
  86. package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +23 -6
  87. package/project/.sdk/src/cmp/py/Config_py.ts +11 -2
  88. package/project/.sdk/src/cmp/py/Main_py.ts +17 -7
  89. package/project/.sdk/src/cmp/py/Package_py.ts +2 -2
  90. package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
  91. package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +23 -6
  92. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
  93. package/project/.sdk/src/cmp/rb/Config_rb.ts +7 -1
  94. package/project/.sdk/src/cmp/rb/Package_rb.ts +3 -3
  95. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +1 -1
  96. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -7
  97. package/project/.sdk/src/cmp/rust/Config_rust.ts +5 -2
  98. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -7
  99. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -2
  100. package/project/.sdk/src/cmp/seneca-provider/Gitignore_seneca-provider.ts +161 -0
  101. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +20 -11
  102. package/project/.sdk/src/cmp/swift/Config_swift.ts +8 -54
  103. package/project/.sdk/src/cmp/swift/Package_swift.ts +52 -10
  104. package/project/.sdk/src/cmp/swift/Test_swift.ts +5 -7
  105. package/project/.sdk/src/cmp/ts/Config_ts.ts +9 -1
  106. package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +5 -2
  107. package/project/.sdk/src/cmp/ts/Entity_ts.ts +8 -2
  108. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  109. package/project/.sdk/src/cmp/ts/Test_ts.ts +8 -7
  110. package/project/.sdk/src/cmp/ts/fragment/Config.data.fragment.ts +7 -0
  111. package/project/.sdk/src/cmp/ts/fragment/Config.fragment.ts +8 -0
  112. package/project/.sdk/src/cmp/ts/fragment/Entity.fragment.ts +1 -1
  113. package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +12 -4
  114. package/project/.sdk/src/cmp/zig/Test_zig.ts +3 -7
  115. package/project/.sdk/tm/c/Makefile +6 -1
  116. package/project/.sdk/tm/c/utility/make_options.c +12 -1
  117. package/project/.sdk/tm/c/utility/make_point.c +64 -3
  118. package/project/.sdk/tm/clojure/src/sdk/core.clj +49 -14
  119. package/project/.sdk/tm/cpp/utility/pipeline.hpp +64 -4
  120. package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
  121. package/project/.sdk/tm/csharp/utility/Fetcher.cs +23 -5
  122. package/project/.sdk/tm/csharp/utility/MakeOptions.cs +13 -0
  123. package/project/.sdk/tm/csharp/utility/MakePoint.cs +65 -2
  124. package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +34 -4
  125. package/project/.sdk/tm/dart/lib/utility/FetcherUtility.dart +9 -0
  126. package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +17 -0
  127. package/project/.sdk/tm/dart/lib/utility/MakePointUtility.dart +48 -2
  128. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +109 -13
  129. package/project/.sdk/tm/elixir/test/feature_test.exs +19 -0
  130. package/project/.sdk/tm/elixir/test/primary_utility_test.exs +72 -0
  131. package/project/.sdk/tm/go/feature/test_feature.go +34 -2
  132. package/project/.sdk/tm/go/test/primary_utility_test.go +42 -20
  133. package/project/.sdk/tm/go/test/runner_test.go +1 -1
  134. package/project/.sdk/tm/go/utility/fetcher.go +14 -0
  135. package/project/.sdk/tm/go/utility/make_options.go +31 -4
  136. package/project/.sdk/tm/go/utility/make_point.go +66 -2
  137. package/project/.sdk/tm/java/feature/TestFeature.java +42 -2
  138. package/project/.sdk/tm/java/utility/Fetcher.java +11 -1
  139. package/project/.sdk/tm/java/utility/MakeOptions.java +11 -0
  140. package/project/.sdk/tm/java/utility/MakePoint.java +61 -2
  141. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
  142. package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +20 -2
  143. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +72 -2
  144. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +31 -2
  145. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +10 -1
  146. package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +11 -0
  147. package/project/.sdk/tm/kotlin/utility/MakePoint.kt +58 -2
  148. package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -0
  149. package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +13 -2
  150. package/project/.sdk/tm/lua/utility/make_options.lua +44 -0
  151. package/project/.sdk/tm/lua/utility/make_point.lua +65 -2
  152. package/project/.sdk/tm/ocaml/sdk_runtime.ml +40 -2
  153. package/project/.sdk/tm/perl/utility/fetcher.pm +10 -1
  154. package/project/.sdk/tm/perl/utility/make_options.pm +36 -0
  155. package/project/.sdk/tm/perl/utility/make_point.pm +55 -2
  156. package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
  157. package/project/.sdk/tm/php/test/PipelineTest.php +20 -0
  158. package/project/.sdk/tm/php/utility/Fetcher.php +12 -1
  159. package/project/.sdk/tm/php/utility/MakeOptions.php +58 -0
  160. package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
  161. package/project/.sdk/tm/py/pkg/utility/fetcher.py +13 -1
  162. package/project/.sdk/tm/py/pkg/utility/make_options.py +23 -0
  163. package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
  164. package/project/.sdk/tm/rb/utility/make_options.rb +18 -0
  165. package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
  166. package/project/.sdk/tm/rust/tests/common/mod.rs +1 -1
  167. package/project/.sdk/tm/rust/tests/feature_test.rs +27 -0
  168. package/project/.sdk/tm/rust/utility/fetcher.rs +8 -0
  169. package/project/.sdk/tm/rust/utility/make_options.rs +15 -0
  170. package/project/.sdk/tm/rust/utility/make_point.rs +69 -2
  171. package/project/.sdk/tm/scala/feature/TestFeature.scala +36 -1
  172. package/project/.sdk/tm/scala/utility/Make.scala +57 -3
  173. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +29 -1
  174. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +24 -1
  175. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +51 -2
  176. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +12 -0
  177. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
  178. package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +20 -2
  179. package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
  180. package/project/.sdk/tm/ts/test/utility/Corpus.test.ts +85 -0
  181. package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +10 -17
  182. package/project/.sdk/tm/zig/core/utility.zig +66 -3
  183. package/project/sdkgen-package.json +1 -2
  184. package/src/cmp/AgentGuideTop.ts +18 -0
  185. package/src/cmp/ExternalTarget.ts +1 -1
  186. package/src/cmp/Readme.ts +2 -0
  187. package/src/cmp/ReadmeStation.ts +69 -0
  188. package/src/helpers/naming.ts +172 -5
  189. package/src/helpers/opShape.ts +85 -0
  190. package/src/helpers/packageMeta.ts +59 -20
  191. package/src/sdkgen.ts +70 -20
  192. package/src/testkit.ts +863 -0
  193. package/src/utility.ts +17 -2
  194. package/testkit.d.ts +3 -0
  195. package/testkit.js +12 -0
  196. package/project/.sdk/model/target/haskell.aontu +0 -72
  197. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
  198. package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
  199. package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
  200. package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
  201. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
  202. package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
  203. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
  204. package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
  205. package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
  206. package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
  207. package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
  208. package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
  209. package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
  210. package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
  211. package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
  212. package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
  213. package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
  214. package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
  215. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
  216. package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
  217. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
  218. package/project/.sdk/tm/haskell/LICENSE +0 -21
  219. package/project/.sdk/tm/haskell/Makefile +0 -22
  220. package/project/.sdk/tm/haskell/VERSION +0 -1
  221. package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
  222. package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
  223. package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
  224. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
  225. package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
  226. package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
  227. package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
  228. package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
  229. package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
  230. package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
  231. package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
  232. package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
  233. package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
  234. package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
  235. package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
  236. package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
  237. package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
  238. package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
  239. package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
  240. package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
  241. package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
  242. package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
  243. package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
  244. package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
  245. package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
  246. package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
  247. package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
  248. package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
  249. package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
  250. package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
  251. package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
  252. package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
  253. package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
  254. package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
  255. package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
@@ -112,6 +112,78 @@ defmodule ProjectName.PrimaryUtilityTest do
112
112
  assert S.getpath(opts, "__derived__.clean.keyre") == "key|token|id"
113
113
  end
114
114
 
115
+ # --- feature add-order ----------------------------------------------------
116
+ # options.feature accepts an ordered LIST (developer add-order) or a map
117
+ # (defaults test-first); make_options records the resolved order in
118
+ # __derived__.featureorder. Driven with a synthetic ctx (empty config
119
+ # options) so the assertions do not depend on this SDK's own features.
120
+
121
+ defp resolve_order(feature) do
122
+ ctx =
123
+ S.jm([
124
+ "utility", Utility.new(),
125
+ "options", H.deep(%{"feature" => feature}),
126
+ "config", H.deep(%{"options" => %{}})
127
+ ])
128
+
129
+ fo = S.getpath(Utility.make_options(ctx), "__derived__.featureorder")
130
+ n = S.size(fo)
131
+ order = if n == 0, do: [], else: Enum.map(0..(n - 1), fn i -> S.getelem(fo, i) end)
132
+ Enum.join(order, ",")
133
+ end
134
+
135
+ test "feature order: map form is test-first" do
136
+ assert "test,metrics" ==
137
+ resolve_order(%{"metrics" => %{"active" => true}, "test" => %{"active" => true}})
138
+ end
139
+
140
+ test "feature order: an explicit list preserves its order" do
141
+ assert "metrics,test" ==
142
+ resolve_order([
143
+ %{"name" => "metrics", "active" => true},
144
+ %{"name" => "test", "active" => true}
145
+ ])
146
+ end
147
+
148
+ # Station special case, mirroring test's: its transport wrap must sit
149
+ # immediately outside the base transport, so map-form activation hoists
150
+ # it to just after test - or first, when no test entry exists.
151
+ test "feature order: map form hoists station after test" do
152
+ assert "test,station,metrics" ==
153
+ resolve_order(%{
154
+ "metrics" => %{"active" => true},
155
+ "station" => %{"active" => true},
156
+ "test" => %{"active" => true}
157
+ })
158
+ end
159
+
160
+ test "feature order: station first when no test entry exists" do
161
+ assert "station,metrics" ==
162
+ resolve_order(%{
163
+ "metrics" => %{"active" => true},
164
+ "station" => %{"active" => true}
165
+ })
166
+ end
167
+
168
+ # The extend seam (feature INSTANCES supplied at construction - the
169
+ # station adopt path): validate must accept the key verbatim, or the
170
+ # constructor's extend loop reads a stripped option and the seam is dead.
171
+ test "make_options preserves options.extend through validation" do
172
+ marker = S.jm(["name", "station"])
173
+
174
+ ctx =
175
+ S.jm([
176
+ "utility", Utility.new(),
177
+ "options", S.jm(["extend", S.jt([marker])]),
178
+ "config", H.deep(%{"options" => %{}})
179
+ ])
180
+
181
+ extend = S.getprop(Utility.make_options(ctx), "extend")
182
+ assert S.islist(extend)
183
+ assert S.size(extend) == 1
184
+ assert S.getprop(S.getelem(extend, 0), "name") == "station"
185
+ end
186
+
115
187
  test "make_error formats a namespaced message and raises by default" do
116
188
  c = client()
117
189
  ctx = ctx(c, "load")
@@ -4,6 +4,7 @@ import (
4
4
  "fmt"
5
5
  "math/rand"
6
6
  "regexp"
7
+ "strings"
7
8
 
8
9
  vs "github.com/voxgig/struct"
9
10
 
@@ -310,9 +311,40 @@ func (f *TestFeature) makeNetsim(net map[string]any, inner core.FetcherFunc) cor
310
311
  func (f *TestFeature) buildArgs(ctx *core.Context, op *core.Operation, args map[string]any) any {
311
312
  opname := op.Name
312
313
 
313
- // Get last point from config.
314
+ // Pick the entity's own endpoint from config, not a cross-reference from
315
+ // another resource that also returns it — the same rule makePoint falls
316
+ // back to, so the seed-data query is built from the endpoint the request
317
+ // will actually be sent to: a terminal `{id}` marks a record route, and
318
+ // failing that the shallower path wins.
314
319
  points := vs.GetPath([]any{"entity", ctx.Entity.GetName(), "op", opname, "points"}, ctx.Config)
315
- point := vs.GetElem(points, -1)
320
+ point := vs.GetElem(points, 0)
321
+ if plist, ok := points.([]any); ok {
322
+ partsLen := func(p any) int {
323
+ if parts, ok := vs.GetProp(p, "parts").([]any); ok {
324
+ return len(parts)
325
+ }
326
+ return 0
327
+ }
328
+ terminalParam := func(p any) bool {
329
+ parts, ok := vs.GetProp(p, "parts").([]any)
330
+ if !ok || 0 == len(parts) {
331
+ return false
332
+ }
333
+ last, _ := parts[len(parts)-1].(string)
334
+ return strings.HasPrefix(last, "{")
335
+ }
336
+ for _, cand := range plist {
337
+ candTerm := terminalParam(cand)
338
+ bestTerm := terminalParam(point)
339
+ if candTerm != bestTerm {
340
+ if candTerm {
341
+ point = cand
342
+ }
343
+ } else if partsLen(cand) < partsLen(point) {
344
+ point = cand
345
+ }
346
+ }
347
+ }
316
348
 
317
349
  // Get required params.
318
350
  paramsPath := vs.GetPath([]any{"args", "params"}, point)
@@ -1,6 +1,7 @@
1
1
  package sdktest
2
2
 
3
3
  import (
4
+ "errors"
4
5
  "fmt"
5
6
  "strings"
6
7
  "testing"
@@ -556,27 +557,48 @@ func TestPrimaryUtility(t *testing.T) {
556
557
  })
557
558
  })
558
559
 
560
+ // Was one hand-written case (the single-point path) covering one of this
561
+ // utility's seven branches, which is how the corpus fixture came to be
562
+ // marked deferred as "needs a real client". It does not: NewContext
563
+ // rebuilds Op from opname + entity + config, and Options can be supplied
564
+ // literally. Driven from the corpus now, so TS and Go assert the same
565
+ // branches.
566
+ //
567
+ // TS returns the error AS the value; Go returns it as a second result. The
568
+ // corpus says `match: out: code` for both, so the error is normalised to a
569
+ // map carrying its code here rather than forking the fixture per language.
559
570
  t.Run("makePoint-basic", func(t *testing.T) {
560
- ctx := makeTestCtx(client, utility, nil)
561
- point := map[string]any{
562
- "parts": []any{"items", "{id}"},
563
- "args": map[string]any{"params": []any{}},
564
- "params": []any{},
565
- "alias": map[string]any{},
566
- "select": map[string]any{},
567
- "active": true,
568
- "transform": map[string]any{},
569
- }
570
- ctx.Op.Points = []map[string]any{point}
571
-
572
- _, err := utility.MakePoint(ctx)
573
- if err != nil {
574
- t.Errorf("expected no error, got: %v", err)
575
- return
576
- }
577
- if ctx.Point == nil {
578
- t.Error("expected point to be set")
579
- }
571
+ runsetNamed(t, "makePoint", getSpec(primary, "makePoint", "basic"), func(entry map[string]any) (any, error) {
572
+ ctxmap, _ := entry["ctx"].(map[string]any)
573
+
574
+ // NewContext resolves Op from the ENTITY NAME, and reaches it
575
+ // through the Entity interface — a literal {name:...} map from the
576
+ // fixture is not one, so entname would be "" and every lookup would
577
+ // miss, reporting point_no_points for all seven cases. TS reads the
578
+ // same field with getprop and accepts the plain map. Swap in the
579
+ // package's minimal Entity so both ports resolve the same op.
580
+ if em, ok := ctxmap["entity"].(map[string]any); ok {
581
+ name, _ := em["name"].(string)
582
+ made := []any{}
583
+ swapped := map[string]any{}
584
+ for k, v := range ctxmap {
585
+ swapped[k] = v
586
+ }
587
+ swapped["entity"] = &plEntity{name: name, made: &made}
588
+ ctxmap = swapped
589
+ }
590
+
591
+ ctx := makeCtxFromMap(ctxmap, client, utility)
592
+ point, err := utility.MakePoint(ctx)
593
+ if err != nil {
594
+ var sdkErr *sdk.ProjectNameError
595
+ if errors.As(err, &sdkErr) {
596
+ return map[string]any{"code": sdkErr.Code}, nil
597
+ }
598
+ return nil, err
599
+ }
600
+ return point, nil
601
+ })
580
602
  })
581
603
 
582
604
  t.Run("makeUrl-basic", func(t *testing.T) {
@@ -218,7 +218,7 @@ type RunSubject func(entry map[string]any) (any, error)
218
218
  // PENDING sections are the ones deliberately left empty in the shared corpus
219
219
  // (.sdk/test/primary/<name>.aontu). Everything else MUST contribute cases.
220
220
  var pendingSections = map[string]bool{
221
- "fetcher": true, "makeFetchDef": true, "makePoint": true, "makeResult": true,
221
+ "fetcher": true, "makeFetchDef": true, "makeResult": true,
222
222
  "featureAdd": true, "featureHook": true, "featureInit": true,
223
223
  }
224
224
 
@@ -57,6 +57,20 @@ func defaultHTTPFetch(fullurl string, fetchdef map[string]any) (map[string]any,
57
57
  }
58
58
  }
59
59
 
60
+ // Honour a redirect annotation: "manual" surfaces a 3xx as an ordinary
61
+ // response instead of auto-following it. In the ts/js targets fetchdef
62
+ // rides straight into fetch(), where `redirect` is a native option -
63
+ // this is the same seam for Go's auto-following http.Client. The
64
+ // station feature sets it under a hosts egress policy, so a Location
65
+ // off the allowlist cannot pull an automatic credentialed follow-up.
66
+ if redirect, ok := fetchdef["redirect"].(string); ok && redirect == "manual" {
67
+ manual := *client
68
+ manual.CheckRedirect = func(req *http.Request, via []*http.Request) error {
69
+ return http.ErrUseLastResponse
70
+ }
71
+ client = &manual
72
+ }
73
+
60
74
  resp, err := client.Do(req)
61
75
  if err != nil {
62
76
  return nil, err
@@ -217,17 +217,44 @@ func makeOptionsUtil(ctx *core.Context) map[string]any {
217
217
  hasTest = true
218
218
  }
219
219
  }
220
+ ordered := make([]string, 0, len(names))
220
221
  if hasTest {
221
- featureorder = append(featureorder, "test")
222
+ ordered = append(ordered, "test")
222
223
  for _, n := range names {
223
224
  if n != "test" {
224
- featureorder = append(featureorder, n)
225
+ ordered = append(ordered, n)
225
226
  }
226
227
  }
227
228
  } else {
228
- for _, n := range names {
229
- featureorder = append(featureorder, n)
229
+ ordered = append(ordered, names...)
230
+ }
231
+ // Station special case, mirroring test's: its transport wrap must
232
+ // sit immediately outside the base transport (inside retry/cache/
233
+ // netsim), so map-form activation hoists it to just after test -
234
+ // or first, when no test entry exists. Without this the sorted
235
+ // default would init station last and wrap OUTSIDE the recording
236
+ // features, turning its wire-truth events into fiction.
237
+ si := -1
238
+ for i, n := range ordered {
239
+ if n == "station" {
240
+ si = i
241
+ break
242
+ }
243
+ }
244
+ if si >= 0 {
245
+ ordered = append(ordered[:si], ordered[si+1:]...)
246
+ ti := 0
247
+ for i, n := range ordered {
248
+ if n == "test" {
249
+ ti = i + 1
250
+ break
251
+ }
230
252
  }
253
+ ordered = append(ordered[:ti],
254
+ append([]string{"station"}, ordered[ti:]...)...)
255
+ }
256
+ for _, n := range ordered {
257
+ featureorder = append(featureorder, n)
231
258
  }
232
259
  }
233
260
 
@@ -8,6 +8,47 @@ import (
8
8
  "GOMODULE/core"
9
9
  )
10
10
 
11
+ // How many path segments a point has.
12
+ func partsLen(point map[string]any) int {
13
+ if parts, ok := vs.GetProp(point, "parts").([]any); ok {
14
+ return len(parts)
15
+ }
16
+ return 0
17
+ }
18
+
19
+ // Does this point's path end in a parameter? A record route ends in the
20
+ // record's identifier (`/boards/{id}`); a cross-reference that also returns
21
+ // the entity ends in the relationship's name (`/posts/{id}/author`).
22
+ func terminalParam(point map[string]any) bool {
23
+ parts, ok := vs.GetProp(point, "parts").([]any)
24
+ if !ok || 0 == len(parts) {
25
+ return false
26
+ }
27
+ last, _ := parts[len(parts)-1].(string)
28
+ return strings.HasPrefix(last, "{")
29
+ }
30
+
31
+ // The entity's OWN route among an op's points: a terminal parameter first,
32
+ // then the fewest path segments. Ties keep the earlier point, so the model's
33
+ // sorted-key order decides. The same rule runs at generation time, in
34
+ // helpers/opShape.ts — a template ships standalone, so both sides must move
35
+ // together.
36
+ func ownPoint(points []map[string]any) map[string]any {
37
+ best := points[0]
38
+ for _, cand := range points {
39
+ candTerm := terminalParam(cand)
40
+ bestTerm := terminalParam(best)
41
+ if candTerm != bestTerm {
42
+ if candTerm {
43
+ best = cand
44
+ }
45
+ } else if partsLen(cand) < partsLen(best) {
46
+ best = cand
47
+ }
48
+ }
49
+ return best
50
+ }
51
+
11
52
  func makePointUtil(ctx *core.Context) (map[string]any, error) {
12
53
  if ctx.Out["point"] != nil {
13
54
  // A PrePoint feature hook (e.g. rbac) may short-circuit the
@@ -52,9 +93,10 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
52
93
  }
53
94
 
54
95
  var point map[string]any
96
+ matched := false
55
97
  for i := 0; i < len(op.Points); i++ {
56
- point = op.Points[i]
57
- selectDef := core.ToMapAny(vs.GetProp(point, "select"))
98
+ cand := op.Points[i]
99
+ selectDef := core.ToMapAny(vs.GetProp(cand, "select"))
58
100
  found := true
59
101
 
60
102
  if selector != nil && selectDef != nil {
@@ -82,10 +124,32 @@ func makePointUtil(ctx *core.Context) (map[string]any, error) {
82
124
  }
83
125
 
84
126
  if found {
127
+ point = cand
128
+ matched = true
85
129
  break
86
130
  }
87
131
  }
88
132
 
133
+ // select.exist can list more than the params needed to pick a point,
134
+ // so nothing matches — fall back to the entity's own route rather
135
+ // than whichever point came last.
136
+ if !matched {
137
+ // A request naming an action reaches here only because that
138
+ // action's own point failed its exist test, so it is unbuildable
139
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the
140
+ // guard below compares the chosen point's $action and would wave
141
+ // the request through whenever the fallback lands on the action
142
+ // point itself.
143
+ if reqselector != nil && vs.GetProp(reqselector, "$action") != nil {
144
+ return nil, ctx.MakeError("point_action_invalid",
145
+ "Operation \""+op.Name+
146
+ "\" action \""+vs.Stringify(vs.GetProp(reqselector, "$action"))+
147
+ "\" is not valid.")
148
+ }
149
+
150
+ point = ownPoint(op.Points)
151
+ }
152
+
89
153
  if reqselector != nil {
90
154
  reqAction := vs.GetProp(reqselector, "$action")
91
155
  if reqAction != nil && point != nil {
@@ -26,6 +26,29 @@ public class TestFeature extends BaseFeature {
26
26
  private Map<String, Object> options;
27
27
  private int netcalls = 0;
28
28
 
29
+ // How many path segments a point has, and whether its path ends in a
30
+ // parameter — the rule makePoint uses to tell the entity's own route from a
31
+ // cross-reference that also returns it.
32
+ @SuppressWarnings("unchecked")
33
+ private static int pointPartsLen(Object point) {
34
+ Object parts = Struct.getprop(point, "parts");
35
+ return parts instanceof List ? ((List<Object>) parts).size() : 0;
36
+ }
37
+
38
+ @SuppressWarnings("unchecked")
39
+ private static boolean pointTerminalParam(Object point) {
40
+ Object parts = Struct.getprop(point, "parts");
41
+ if (!(parts instanceof List)) {
42
+ return false;
43
+ }
44
+ List<Object> list = (List<Object>) parts;
45
+ if (list.isEmpty()) {
46
+ return false;
47
+ }
48
+ Object last = list.get(list.size() - 1);
49
+ return last instanceof String && ((String) last).startsWith("{");
50
+ }
51
+
29
52
  public TestFeature() {
30
53
  super("test", "0.0.1", true);
31
54
  }
@@ -289,11 +312,28 @@ public class TestFeature extends BaseFeature {
289
312
  private Object buildArgs(Context ctx, Operation op, Map<String, Object> args) {
290
313
  String opname = op.name;
291
314
 
292
- // Get last point from config.
315
+ // Pick the entity's own endpoint from config, not a cross-reference from
316
+ // another resource that also returns it — the same rule makePoint falls
317
+ // back to, so the seed-data query is built from the endpoint the request
318
+ // will actually be sent to: a terminal `{id}` marks a record route, and
319
+ // failing that the shallower path wins.
293
320
  Object points = Struct.getpath(ctx.config,
294
321
  List.of("entity", ctx.entity == null ? "" : ctx.entity.getName(),
295
322
  "op", opname, "points"));
296
- Object point = Struct.getelem(points, -1);
323
+ Object point = Struct.getelem(points, 0);
324
+ if (points instanceof List) {
325
+ for (Object cand : (List<Object>) points) {
326
+ boolean candTerm = pointTerminalParam(cand);
327
+ boolean bestTerm = pointTerminalParam(point);
328
+ if (candTerm != bestTerm) {
329
+ if (candTerm) {
330
+ point = cand;
331
+ }
332
+ } else if (pointPartsLen(cand) < pointPartsLen(point)) {
333
+ point = cand;
334
+ }
335
+ }
336
+ }
297
337
 
298
338
  // Get required params.
299
339
  Object paramsPath = Struct.getpath(point, List.of("args", "params"));
@@ -59,10 +59,20 @@ final class Fetcher {
59
59
  reqb.setHeader("User-Agent", "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)");
60
60
  }
61
61
 
62
+ // Honour a redirect annotation on the fetch definition (set by the
63
+ // station feature's middleware under a hosts policy): "manual" returns
64
+ // a 3xx like any other response instead of following it — the ts
65
+ // donor's fetch honours the same key natively, and an automatic follow
66
+ // would carry injected credentials to a host no policy approved.
67
+ HttpClient.Redirect redirectPolicy =
68
+ "manual".equals(fetchdef.get("redirect"))
69
+ ? HttpClient.Redirect.NEVER
70
+ : HttpClient.Redirect.NORMAL;
71
+
62
72
  // Honour a proxy annotation on the fetch definition (set by the proxy
63
73
  // feature): route the request through a proxied HttpClient.
64
74
  HttpClient.Builder clientb = HttpClient.newBuilder()
65
- .followRedirects(HttpClient.Redirect.NORMAL);
75
+ .followRedirects(redirectPolicy);
66
76
  Object proxy = fetchdef.get("proxy");
67
77
  if (proxy instanceof String && !"".equals(proxy)) {
68
78
  try {
@@ -156,6 +156,17 @@ final class MakeOptions {
156
156
  else {
157
157
  featureorder.addAll(names);
158
158
  }
159
+ // Station special case, mirroring test's: its transport wrap must
160
+ // sit immediately outside the base transport (inside retry/cache/
161
+ // netsim), so map-form activation hoists it to just after test -
162
+ // or first, when no test entry exists. Without this the sorted
163
+ // default would init station last and wrap OUTSIDE the recording
164
+ // features, turning its wire-truth events into fiction.
165
+ int si = featureorder.indexOf("station");
166
+ if (0 <= si) {
167
+ featureorder.remove(si);
168
+ featureorder.add(featureorder.indexOf("test") + 1, "station");
169
+ }
159
170
  }
160
171
 
161
172
  Map<String, Object> derived = new LinkedHashMap<>();
@@ -14,6 +14,31 @@ final class MakePoint {
14
14
 
15
15
  private MakePoint() {}
16
16
 
17
+ // How many path segments a point has.
18
+ private static int partsLen(Map<String, Object> point) {
19
+ Object parts = Struct.getprop(point, "parts");
20
+ return parts instanceof List ? ((List<Object>) parts).size() : 0;
21
+ }
22
+
23
+ // Does this point's path end in a parameter? A record route ends in the
24
+ // record's identifier (/boards/{id}); a cross-reference that also returns
25
+ // the entity ends in the relationship's name (/posts/{id}/author). That,
26
+ // then fewest segments, is what tells the entity's own route from a
27
+ // cross-reference. The same rule runs at generation time, in
28
+ // helpers/opShape.ts — both sides must move together.
29
+ private static boolean terminalParam(Map<String, Object> point) {
30
+ Object parts = Struct.getprop(point, "parts");
31
+ if (!(parts instanceof List)) {
32
+ return false;
33
+ }
34
+ List<Object> list = (List<Object>) parts;
35
+ if (list.isEmpty()) {
36
+ return false;
37
+ }
38
+ Object last = list.get(list.size() - 1);
39
+ return last instanceof String && ((String) last).startsWith("{");
40
+ }
41
+
17
42
  static Map<String, Object> makePoint(Context ctx) {
18
43
  Object outPoint = ctx.out.get("point");
19
44
  if (outPoint != null) {
@@ -62,10 +87,11 @@ final class MakePoint {
62
87
  }
63
88
 
64
89
  Map<String, Object> point = null;
90
+ boolean matched = false;
65
91
  for (int i = 0; i < op.points.size(); i++) {
66
- point = op.points.get(i);
92
+ Map<String, Object> cand = op.points.get(i);
67
93
  Map<String, Object> selectDef =
68
- Helpers.toMapAny(Struct.getprop(point, "select"));
94
+ Helpers.toMapAny(Struct.getprop(cand, "select"));
69
95
  boolean found = true;
70
96
 
71
97
  if (selector != null && selectDef != null) {
@@ -92,10 +118,43 @@ final class MakePoint {
92
118
  }
93
119
 
94
120
  if (found) {
121
+ point = cand;
122
+ matched = true;
95
123
  break;
96
124
  }
97
125
  }
98
126
 
127
+ // select.exist can list more than the params needed to pick a point, so
128
+ // nothing matches — fall back to the entity's own route rather than
129
+ // whichever point came last.
130
+ if (!matched) {
131
+ // A request naming an action reaches here only because that action's
132
+ // own point failed its exist test, so it is unbuildable whatever we
133
+ // pick. Refuse it BEFORE choosing a fallback: the guard below
134
+ // compares the chosen point's $action and would wave the request
135
+ // through whenever the fallback lands on the action point itself.
136
+ Object unmatchedAction =
137
+ reqselector != null ? Struct.getprop(reqselector, "$action", null) : null;
138
+ if (unmatchedAction != null) {
139
+ throw ctx.makeError("point_action_invalid",
140
+ "Operation \"" + op.name
141
+ + "\" action \"" + Struct.stringify(unmatchedAction) + "\" is not valid.");
142
+ }
143
+
144
+ point = op.points.get(0);
145
+ for (Map<String, Object> cand : op.points) {
146
+ boolean candTerm = terminalParam(cand);
147
+ boolean bestTerm = terminalParam(point);
148
+ if (candTerm != bestTerm) {
149
+ if (candTerm) {
150
+ point = cand;
151
+ }
152
+ } else if (partsLen(cand) < partsLen(point)) {
153
+ point = cand;
154
+ }
155
+ }
156
+ }
157
+
99
158
  if (reqselector != null) {
100
159
  Object reqAction = Struct.getprop(reqselector, "$action", null);
101
160
  if (reqAction != null && point != null) {
@@ -251,9 +251,28 @@ class TestFeature extends BaseFeature {
251
251
  const isempty = struct.isempty
252
252
 
253
253
  const opname = getprop(op, 'name')
254
- const point =
255
- getelem(getpath(ctx.config, [
256
- 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']), -1)
254
+ const points = getpath(ctx.config, [
255
+ 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
256
+
257
+ // Pick the entity's own endpoint, not a cross-reference from another
258
+ // resource that also returns it — the same rule makePoint falls back to,
259
+ // so the seed-data query is built from the endpoint the request will
260
+ // actually be sent to: a terminal `{id}` marks a record route, and
261
+ // failing that the shallower path wins.
262
+ const isterm = (pt) => {
263
+ const parts = pt.parts
264
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
265
+ return 'string' === typeof last && 0 === last.indexOf('{')
266
+ }
267
+
268
+ let point = getelem(points, 0)
269
+ for (let i = 1; i < points.length; i++) {
270
+ const cand = getelem(points, i)
271
+ if (isterm(cand) !== isterm(point) ? isterm(cand) :
272
+ cand.parts.length < point.parts.length) {
273
+ point = cand
274
+ }
275
+ }
257
276
 
258
277
  const reqd = transform(
259
278
  select(getpath(point, ['args', 'params']), { reqd: true }),
@@ -68,6 +68,12 @@ function makeOptions(ctx) {
68
68
  }
69
69
  },
70
70
  utility: {},
71
+ // Feature INSTANCES supplied at construction (the station adopt
72
+ // path): consumed by the constructor's featureAdd loop, so they are
73
+ // class instances, not data - `$ANY` accepts them verbatim. Without
74
+ // this entry the seam is dead: the constructor reads
75
+ // options.extend, but validate rejected the key.
76
+ extend: '`$ANY`',
71
77
  system: {
72
78
  fetch: undefined
73
79
  },
@@ -131,10 +137,22 @@ function makeOptions(ctx) {
131
137
  // otherwise order the map test-first, then the remaining names sorted, so
132
138
  // the outcome is deterministic and `test` is always the base transport.
133
139
  if (0 === featureorder.length) {
134
- const names = Object.keys(opts.feature || {}).sort()
135
- featureorder = names.indexOf('test') < 0
140
+ let names = Object.keys(opts.feature || {}).sort()
141
+ names = names.indexOf('test') < 0
136
142
  ? names
137
143
  : ['test'].concat(names.filter((n) => 'test' !== n))
144
+ // Station special case, mirroring test's: its transport wrap must
145
+ // sit immediately outside the base transport (inside retry/cache/
146
+ // netsim), so map-form activation hoists it to just after test -
147
+ // or first, when no test entry exists. Without this the sorted
148
+ // default would init station last and wrap OUTSIDE the recording
149
+ // features, turning its wire-truth events into fiction.
150
+ const si = names.indexOf('station')
151
+ if (0 <= si) {
152
+ names.splice(si, 1)
153
+ names.splice(names.indexOf('test') + 1, 0, 'station')
154
+ }
155
+ featureorder = names
138
156
  }
139
157
 
140
158
  opts.__derived__ = {