@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
@@ -35,7 +35,7 @@ describe('PrimaryUtility', async () => {
35
35
  // (.sdk/test/primary/<name>.aontu carries a PENDING header). Everything
36
36
  // else MUST contribute cases.
37
37
  const PENDING = new Set([
38
- 'fetcher', 'makeFetchDef', 'makePoint', 'makeResult',
38
+ 'fetcher', 'makeFetchDef', 'makeResult',
39
39
  'featureAdd', 'featureHook', 'featureInit',
40
40
  ])
41
41
 
@@ -279,22 +279,15 @@ describe('PrimaryUtility', async () => {
279
279
  })
280
280
 
281
281
 
282
- test('makePoint-single', () => {
283
- const ctx = makeCtx()
284
- const point = {
285
- parts: ['items', '{id}'],
286
- args: { params: [] },
287
- params: [],
288
- alias: {},
289
- select: {},
290
- active: true,
291
- transform: { req: undefined, res: undefined },
292
- }
293
- ctx.op.points = [point]
294
-
295
- const result = utility.makePoint(ctx)
296
- ok(!(result instanceof Error))
297
- equal(ctx.point, point)
282
+ // Was one hand-written case (the single-point path) covering one of this
283
+ // utility's seven branches, which is how the corpus fixture came to be
284
+ // marked deferred as "needs a real client". It does not: Context rebuilds
285
+ // `op` from opname + entity + config, and `options` can be supplied
286
+ // literally, so allow.op, the empty-points error, exist-selection,
287
+ // $action selection and the invalid-$action error are all expressible.
288
+ // Driven from the corpus now, so every port asserts the same branches.
289
+ test('makePoint-basic', async () => {
290
+ await runsection('makePoint', utility.makePoint)
298
291
  })
299
292
 
300
293
 
@@ -521,6 +521,29 @@ pub fn make_options_util(ctx: *Context) Value {
521
521
  // make_point (gotcha #2: rbac PrePoint short-circuit)
522
522
  // ============================================================================
523
523
 
524
+ // How many path segments a point has.
525
+ fn point_parts_len(point: Value) i64 {
526
+ const parts = h.getp(point, "parts");
527
+ if (parts != .array) return 0;
528
+ return @intCast(parts.array.data.items.len);
529
+ }
530
+
531
+ // Does this point's path end in a parameter? A record route ends in the
532
+ // record's identifier (/boards/{id}); a cross-reference that also returns the
533
+ // entity ends in the relationship's name (/posts/{id}/author). That, then
534
+ // fewest segments, is what tells the entity's own route from a
535
+ // cross-reference. The same rule runs at generation time, in
536
+ // helpers/opShape.ts — both sides must move together.
537
+ fn point_terminal_param(point: Value) bool {
538
+ const parts = h.getp(point, "parts");
539
+ if (parts != .array) return false;
540
+ const items = parts.array.data.items;
541
+ if (0 == items.len) return false;
542
+ const last = items[items.len - 1];
543
+ if (last != .string) return false;
544
+ return 0 < last.string.len and '{' == last.string[0];
545
+ }
546
+
524
547
  pub fn make_point_util(ctx: *Context) E!Value {
525
548
  if (ctx.out_get("point")) |ov| {
526
549
  switch (ov) {
@@ -560,10 +583,11 @@ pub fn make_point_util(ctx: *Context) E!Value {
560
583
  const selector: Value = if (std.mem.eql(u8, op.input, "data")) ctx.data else ctx.mtch;
561
584
 
562
585
  var point: Value = h.vnull();
586
+ var matched = false;
563
587
  var i: i64 = 0;
564
588
  while (i < plen) : (i += 1) {
565
- point = h.get_elem(points, h.vnum(i), h.vnull());
566
- const select_def = h.to_map(h.getp(point, "select"));
589
+ const cand = h.get_elem(points, h.vnum(i), h.vnull());
590
+ const select_def = h.to_map(h.getp(cand, "select"));
567
591
  var found = true;
568
592
 
569
593
  if (!h.is_noval(selector) and !h.is_noval(select_def)) {
@@ -589,7 +613,46 @@ pub fn make_point_util(ctx: *Context) E!Value {
589
613
  if (!h.veq(req_action, select_action)) found = false;
590
614
  }
591
615
 
592
- if (found) break;
616
+ if (found) {
617
+ point = cand;
618
+ matched = true;
619
+ break;
620
+ }
621
+ }
622
+
623
+ // select.exist can list more than the params needed to pick a point
624
+ // (for /boards/{id} it is Trello's 17 optional query-includes), so a
625
+ // plain {id} call matches NOTHING. Fall back to the entity's own
626
+ // route rather than whichever point came last.
627
+ if (!matched) {
628
+ // A request naming an action reaches here only because that
629
+ // action's own point failed its exist test, so it is unbuildable
630
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the
631
+ // guard below compares the chosen point's $action and would wave
632
+ // the request through whenever the fallback lands on the action
633
+ // point itself.
634
+ const unmatched_action = h.getp(reqselector, "$action");
635
+ if (!h.is_noval(unmatched_action)) {
636
+ return ctx.fail("point_action_invalid", fmt("Operation \"{s}\" action \"{s}\" is not valid.", .{ op.name, h.stringify(unmatched_action) }));
637
+ }
638
+
639
+ // A terminal parameter marks a record route (/boards/{id}); a
640
+ // cross-reference ends in the relationship's name
641
+ // (/posts/{id}/author). Failing that, the shallower path wins.
642
+ // The same rule runs at generation time, in helpers/opShape.ts —
643
+ // both sides must move together.
644
+ point = h.get_elem(points, h.vnum(0), h.vnull());
645
+ var j: i64 = 0;
646
+ while (j < plen) : (j += 1) {
647
+ const cand = h.get_elem(points, h.vnum(j), h.vnull());
648
+ const cand_term = point_terminal_param(cand);
649
+ const best_term = point_terminal_param(point);
650
+ if (cand_term != best_term) {
651
+ if (cand_term) point = cand;
652
+ } else if (point_parts_len(cand) < point_parts_len(point)) {
653
+ point = cand;
654
+ }
655
+ }
593
656
  }
594
657
 
595
658
  const req_action = h.getp(reqselector, "$action");
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "3.5.1",
6
+ "version": "3.7.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -15,7 +15,6 @@
15
15
  "go",
16
16
  "go-cli",
17
17
  "go-mcp",
18
- "haskell",
19
18
  "java",
20
19
  "js",
21
20
  "kotlin",
@@ -80,6 +80,24 @@ package (other languages). Each target's guide documents its features.
80
80
  Content(`**Entities** (${entities.length}): `)
81
81
  Content(entities.map((e: any) => `\`${e.Name || e.name}\``).join(', ') + `.
82
82
 
83
+ `)
84
+ }
85
+
86
+ // Station paragraph (station design §9.4), only when the model
87
+ // carries the feature: an agent working on this repo should know
88
+ // the runtime story without leaving AGENTS.md.
89
+ if (features.some((f: any) => 'station' === f.name)) {
90
+ Content(`**Station**: this SDK is a
91
+ [voxgig/station](https://github.com/voxgig/station) plugin (the
92
+ \`station\` feature, off by default). Bound to an open \`Station\`,
93
+ the credential is resolved by sekreto under the plugin's secret name
94
+ and injected at the transport seam — \`options()\` and
95
+ \`prepare()\` output hold only a placeholder, so both are safe to
96
+ inspect and log. \`station.tap(...)\`/\`station.events()\` show live
97
+ traffic; \`station.plugins()\` lists descriptors. See the "Use with
98
+ Station" README section and \`src/feature/station/\` (or the
99
+ target's feature container) for the generated adapter.
100
+
83
101
  `)
84
102
  }
85
103
 
@@ -88,7 +88,7 @@ const ExternalTarget = cmp(function ExternalTarget(props: any) {
88
88
  names(target, target.name)
89
89
 
90
90
  if (phaseActive('entity')) {
91
- each(entity, (entity: any) => {
91
+ each(entity).filter((entity: any) => entity.active).map((entity: any) => {
92
92
  names(entity, entity.name)
93
93
  Entity({ target, entity })
94
94
  })
package/src/cmp/Readme.ts CHANGED
@@ -6,6 +6,7 @@ import { ReadmeIntro } from './ReadmeIntro'
6
6
  import { ReadmeInstall } from './ReadmeInstall'
7
7
  import { ReadmeQuick } from './ReadmeQuick'
8
8
  import { ReadmeErrors } from './ReadmeErrors'
9
+ import { ReadmeStation } from './ReadmeStation'
9
10
  import { ReadmeModel } from './ReadmeModel'
10
11
  import { ReadmeOptions } from './ReadmeOptions'
11
12
  import { ReadmeEntity } from './ReadmeEntity'
@@ -24,6 +25,7 @@ const Readme = cmp(function Readme(props: any) {
24
25
  ReadmeIntro({ target })
25
26
  ReadmeInstall({ target })
26
27
  ReadmeQuick({ target })
28
+ ReadmeStation({ target })
27
29
  ReadmeErrors({ target })
28
30
  ReadmeHowto({ target })
29
31
  ReadmeModel({ target })
@@ -0,0 +1,69 @@
1
+
2
+ import { cmp, Content } from 'jostraca'
3
+
4
+ import {
5
+ KIT,
6
+ getModelPath
7
+ } from '../types'
8
+
9
+ import { envName } from '../helpers/packageMeta'
10
+
11
+
12
+ // The "Use with Station" README section (station design §9.4): rendered
13
+ // ONLY when the project's model carries the station feature (installed
14
+ // via `package add @voxgig/sdkgen-station`) — a project without it sees
15
+ // nothing. Documents the binding forms and the secret name; store
16
+ // configuration is sekreto's documentation, deliberately not restated
17
+ // here (one canonical source).
18
+
19
+ // Targets where station.connect(SDK) is the idiomatic binding; everything
20
+ // else uses inverted binding through the SDK's own constructor.
21
+ const CONNECT_TARGETS = ['ts', 'js', 'py', 'rb', 'php', 'lua', 'perl']
22
+
23
+ const ReadmeStation = cmp(function ReadmeStation(props: any) {
24
+ const { target } = props
25
+ const { model } = props.ctx$
26
+
27
+ const features = getModelPath(model, `main.${KIT}.feature`,
28
+ { only_active: false, required: false }) || {}
29
+ const station = Object.values(features)
30
+ .find((f: any) => 'station' === f?.name)
31
+ if (null == station) { return }
32
+
33
+ const env = envName(model)
34
+ const secretname = env.toLowerCase() + '.apikey'
35
+ const connect = CONNECT_TARGETS.includes(target.name)
36
+
37
+ Content(`
38
+ ## Use with Station
39
+
40
+ This SDK ships as a [voxgig/station](https://github.com/voxgig/station)
41
+ plugin: bind it to a local \`Station\` and outbound configuration,
42
+ credentials, and observability move to one place. The feature is
43
+ present but **off by default** — nothing changes until you bind.
44
+
45
+ ${connect
46
+ ? `Bind by passing the SDK class to the station:
47
+
48
+ 1. \`station = Station.open()\` — profile, env, and proxy all defaulted.
49
+ 2. \`client = station.connect(${model.const.Name}SDK)\` — replaces direct
50
+ construction.`
51
+ : `Bind through the constructor this SDK already has (inverted
52
+ binding): open a station, then construct with station-built options —
53
+ \`station.options()\` merges the handle, the activation entry, and the
54
+ correct feature order into the plain options the constructor accepts.`}
55
+
56
+ The credential comes from [sekreto](https://github.com/voxgig/sekreto)
57
+ under the name \`${secretname}\` — by default the \`${env}_APIKEY\`
58
+ environment variable this README already documents, unchanged. Point a
59
+ profile in \`station.json\` at a vault later and application code does
60
+ not change; sekreto's own documentation covers the stores. The key
61
+ stays out of \`options()\` and \`prepare()\` output; \`station.tap(...)\`
62
+ shows live traffic.
63
+ `)
64
+ })
65
+
66
+
67
+ export {
68
+ ReadmeStation
69
+ }
@@ -112,17 +112,67 @@ function isRbCoreConstant(Name: string): boolean {
112
112
  }
113
113
 
114
114
 
115
+ // RUBY SDK-OWNED CONSTANTS — the OTHER half of "already taken".
116
+ //
117
+ // `RB_CORE_CONSTANTS` guards names the LANGUAGE owns. This guards names the
118
+ // GENERATED SCAFFOLDING claims for itself, which is a distinct hazard and the
119
+ // one that stayed open: an entity named `Runner` produced `class Runner` in
120
+ // `<Sdk>_types.rb`, and the generated harness then does
121
+ //
122
+ // Runner = ProjectNameTestRunner # tm/rb/test/runner.rb
123
+ //
124
+ // so inside the test process the entity TYPE silently *is* the test runner.
125
+ // Ruby warns (`already initialized constant Runner`) and carries on, which is
126
+ // why it survived: nothing in the suite exercises the type, so `rb` passed.
127
+ // Reported on gitlab-sdk, which has a `Runner` entity (issue #64).
128
+ //
129
+ // EVERYTHING SHARES ONE NAMESPACE HERE, unlike swift. Swift's Tests module is
130
+ // separate from the SDK module, so `SWIFT_SDK_TYPES` can exclude test-only
131
+ // declarations. Ruby has no such split — the types file and the test files are
132
+ // all required into one process when the suite runs, which is exactly the
133
+ // reported symptom — so test-declared names belong in this set.
134
+ //
135
+ // PREFIXED DECLARATIONS ARE DELIBERATELY ABSENT. `ProjectNameUtility` and
136
+ // friends substitute to `<Sdk>Utility`, and a generated entity type is the
137
+ // bare entity name, so those cannot collide. Only the UNPREFIXED declarations
138
+ // are reachable.
139
+ //
140
+ // `rb-sdk-constants.test.ts` re-derives this list from the templates AND the
141
+ // components and fails on drift — the same discipline as
142
+ // `swift-sdk-types.test.ts`, and for the same reason: the first cut of that
143
+ // list was collected by hand and missed three names, one of them declared by
144
+ // a component rather than a template.
145
+ const RB_SDK_CONSTANTS = new Set<string>([
146
+ // module-level aliases the test harness defines for convenience
147
+ 'Helpers', 'Runner', 'Vs',
148
+ // vendored struct library and its test scaffolding
149
+ 'StructRunner', 'StructTestClient', 'StructUtilityTest', 'VoxgigStruct',
150
+ 'STRUCT_TEST_JSON_FILE',
151
+ // the generated/templated test classes
152
+ 'ExistsTest', 'FeatureTest', 'NetsimTest', 'PipelineTest',
153
+ 'PrimaryUtilityTest', 'ReadmeExamplesTest', 'TestHookFeature',
154
+ 'TestInitFeature',
155
+ ])
156
+
157
+
158
+ // Does `Name` collide with a constant the generated Ruby SDK already declares?
159
+ function isRbSdkConstant(Name: string): boolean {
160
+ return RB_SDK_CONSTANTS.has(Name)
161
+ }
162
+
163
+
115
164
  // A top-level-safe Ruby constant name for a generated type: the name
116
- // unchanged, unless Ruby core already owns it, in which case `Type` is
117
- // appended (`File` -> `FileType`). Applied ONLY to the bare entity data type
118
- // the per-op type names always carry a suffix (`FileCreateData`,
119
- // `FileLoadMatch`), which no core constant matches.
165
+ // unchanged, unless it is ALREADY TAKEN — by Ruby core, or by the SDK's own
166
+ // scaffolding — in which case `Type` is appended (`File` -> `FileType`,
167
+ // `Runner` -> `RunnerType`). Applied ONLY to the bare entity data type: the
168
+ // per-op type names always carry a suffix (`FileCreateData`,
169
+ // `FileLoadMatch`), which nothing in either set matches.
120
170
  //
121
171
  // The entity ACCESSOR keeps its original name (`client.File(...)`): methods
122
172
  // and constants live in separate namespaces in Ruby, so the accessor never
123
173
  // collided and the public surface is unchanged.
124
174
  function rbSafeTypeName(Name: string): string {
125
- return isRbCoreConstant(Name) ? Name + 'Type' : Name
175
+ return isRbCoreConstant(Name) || isRbSdkConstant(Name) ? Name + 'Type' : Name
126
176
  }
127
177
 
128
178
 
@@ -166,6 +216,8 @@ const SWIFT_SDK_TYPES = new Set<string>([
166
216
  'OrderedDictionary', 'Sentinel', 'Value', 'VList', 'VMap', 'WalkApply',
167
217
  // function/callback aliases
168
218
  'FetcherFunc', 'Formatter', 'NativeCall0', 'NativeRef', 'SystemFetch',
219
+ // transport plumbing
220
+ 'ManualRedirectDelegate',
169
221
  // features
170
222
  'AuditFeature', 'BaseFeature', 'CacheFeature', 'ClienttrackFeature',
171
223
  'DebugFeature', 'IdempotencyFeature', 'LogFeature', 'MetricsBucket',
@@ -195,6 +247,116 @@ function swiftSafeTypeName(Name: string): string {
195
247
  }
196
248
 
197
249
 
250
+ // PHP RESERVED WORDS THAT CANNOT BE A CLASS NAME — a fifth hazard, and the
251
+ // only one of the five where the check must be CASE-INSENSITIVE.
252
+ //
253
+ // PHP's grammar refuses a reserved word where a class name is expected, so a
254
+ // spec with a `Namespace` entity emitted a file that does not parse at all:
255
+ //
256
+ // /** Namespace entity data model. */
257
+ // class Namespace
258
+ // {
259
+ // }
260
+ // -> PHP Parse error: syntax error, unexpected token "namespace",
261
+ // expecting identifier
262
+ //
263
+ // Found on gitlab-sdk (issue #64), and LATENT in the worst way: `types/` is on
264
+ // the composer classmap but nothing references the file, so PHP never loads it
265
+ // and the php lane passed for as long as the target has existed. It would
266
+ // fatal the moment anything required it, and the documented type is unusable
267
+ // either way.
268
+ //
269
+ // CASE-INSENSITIVITY IS THE WHOLE TRICK. Class and function names in PHP are
270
+ // case-insensitive, so `Namespace`, `NAMESPACE` and `namespace` are one
271
+ // identifier — and the generated name is PascalCase while the reserved word is
272
+ // lowercase. A case-sensitive `Set.has('Namespace')` against a lowercase list
273
+ // therefore matches NOTHING and reopens the bug silently. Entries are stored
274
+ // folded, and the lookup folds too.
275
+ //
276
+ // Taken from the PHP manual's reserved-words tables wholesale rather than
277
+ // grown one collision at a time: the list is fixed by the language, and
278
+ // picking from it by hand is how the second `Namespace` gets shipped.
279
+ const PHP_RESERVED_TYPES = new Set<string>([
280
+ // keywords
281
+ 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch',
282
+ 'class', 'clone', 'const', 'continue', 'declare', 'default', 'do', 'echo',
283
+ 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif',
284
+ 'endswitch', 'endwhile', 'enum', 'eval', 'exit', 'extends', 'final',
285
+ 'finally', 'fn', 'for', 'foreach', 'function', 'global', 'goto', 'if',
286
+ 'implements', 'include', 'include_once', 'instanceof', 'insteadof',
287
+ 'interface', 'isset', 'list', 'match', 'namespace', 'new', 'or', 'print',
288
+ 'private', 'protected', 'public', 'readonly', 'require', 'require_once',
289
+ 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use',
290
+ 'var', 'while', 'xor', 'yield',
291
+ // compile-time constants
292
+ '__class__', '__dir__', '__file__', '__function__', '__line__',
293
+ '__method__', '__namespace__', '__trait__',
294
+ // reserved as type names / cannot be declared as a class
295
+ 'bool', 'false', 'float', 'int', 'iterable', 'mixed', 'never', 'null',
296
+ 'numeric', 'object', 'parent', 'resource', 'self', 'string', 'true', 'void',
297
+ ])
298
+
299
+
300
+ // Does `Name` collide with a word PHP reserves, ignoring case as PHP does?
301
+ function isPhpReservedType(Name: string): boolean {
302
+ return PHP_RESERVED_TYPES.has(String(Name).toLowerCase())
303
+ }
304
+
305
+
306
+ // A declarable PHP class name for a generated type: unchanged, unless PHP
307
+ // reserves the word, in which case `Type` is appended (`Namespace` ->
308
+ // `NamespaceType`). Mirrors rbSafeTypeName and swiftSafeTypeName deliberately
309
+ // — same suffix, same "only rename on an actual collision" rule, so every SDK
310
+ // that does not collide is byte-identical to before.
311
+ //
312
+ // Applied ONLY to the bare entity data class. Per-op type names already carry
313
+ // their own suffix (`NamespaceLoadData`, `NamespaceCreateData`), which no
314
+ // reserved word matches, and the entity ACCESSOR is a method rather than a
315
+ // class — PHP resolves those separately — so the public surface is unchanged.
316
+ function phpSafeTypeName(Name: string): string {
317
+ return isPhpReservedType(Name) ? Name + 'Type' : Name
318
+ }
319
+
320
+
321
+ // TypeScript GLOBAL TYPE NAMES — a fourth hazard, same shape as the Ruby and
322
+ // Swift ones above but for TS/JS builtins, which are in scope everywhere
323
+ // with no import. An entity named `Record` emitted `export interface Record
324
+ // {...}` and canonToType's own generic-object mapping (`Record<string,
325
+ // any>`) then resolved to that flat interface INSIDE THE SAME FILE instead
326
+ // of the builtin: `error TS2315: Type 'Record' is not generic` — on a
327
+ // field of the `Record` entity itself.
328
+ //
329
+ // Utility types (`Record`, `Partial`, ...) and common global constructors
330
+ // (`Array`, `Map`, `Promise`, ...) that a REST entity name plausibly lands
331
+ // on. Mirrors RB_CORE_CONSTANTS / SWIFT_SDK_TYPES: only real builtins are
332
+ // listed, so a non-colliding SDK stays byte-identical to before.
333
+ const TS_RESERVED_TYPES = new Set<string>([
334
+ // utility types
335
+ 'Record', 'Partial', 'Required', 'Readonly', 'Pick', 'Omit', 'Exclude',
336
+ 'Extract', 'NonNullable', 'Parameters', 'ReturnType', 'InstanceType',
337
+ 'Awaited', 'ConstructorParameters',
338
+ // global constructors / builtins
339
+ 'Array', 'Object', 'Map', 'Set', 'WeakMap', 'WeakSet', 'Date', 'Error',
340
+ 'Function', 'Promise', 'RegExp', 'String', 'Number', 'Boolean', 'Symbol',
341
+ 'ArrayBuffer', 'Proxy', 'Reflect', 'JSON', 'Math',
342
+ ])
343
+
344
+
345
+ // Does `Name` collide with a TS/JS global type?
346
+ function isTsReservedType(Name: string): boolean {
347
+ return TS_RESERVED_TYPES.has(Name)
348
+ }
349
+
350
+
351
+ // A collision-free TS type name for a generated type: unchanged, unless it
352
+ // shadows a TS/JS global, in which case `Type` is appended (`Record` ->
353
+ // `RecordType`). Mirrors rbSafeTypeName / swiftSafeTypeName. Applied ONLY
354
+ // to the bare entity data type, for the same reason as both of those.
355
+ function tsSafeTypeName(Name: string): string {
356
+ return isTsReservedType(Name) ? Name + 'Type' : Name
357
+ }
358
+
359
+
198
360
  // Is `name` a reserved word (an illegal identifier) in the target language?
199
361
  function isReservedName(name: string, lang: string): boolean {
200
362
  const set = RESERVED[lang]
@@ -328,9 +490,14 @@ export {
328
490
  phpEntityAccessor,
329
491
  entityCacheField,
330
492
  isRbCoreConstant,
493
+ isRbSdkConstant,
331
494
  rbSafeTypeName,
332
495
  isSwiftSdkType,
333
496
  swiftSafeTypeName,
497
+ isPhpReservedType,
498
+ phpSafeTypeName,
499
+ isTsReservedType,
500
+ tsSafeTypeName,
334
501
  jsProp,
335
502
  jsOptProp,
336
503
  jsKey,
@@ -189,6 +189,72 @@ function entityPath(entity: any): string {
189
189
  }
190
190
 
191
191
 
192
+ // The entity's OWN endpoint among an op's points.
193
+ //
194
+ // The other notion of canonical above — "not a `$action` route" — separates a
195
+ // folded-in custom action from the real op. This one separates the entity's
196
+ // own route from a CROSS-REFERENCE: another resource's route that happens to
197
+ // return this entity (`/notifications/{id}/board` for a board). Both are
198
+ // plain GETs with no `$action`, so the path itself is all there is to go on.
199
+ //
200
+ // Two signals, in order:
201
+ //
202
+ // 1. A record route ends in the record's identifier (`/boards/{id}`,
203
+ // `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
204
+ // relationship's name (`/posts/{id}/author`). A terminal parameter is
205
+ // the stronger signal, and unlike depth alone it survives an entity
206
+ // nested more deeply than the route that points at it — depth by itself
207
+ // picked `/posts/{id}/author` over `/accounts/{account_id}/users/{id}`
208
+ // and then generated that op's required params from the wrong route.
209
+ // 2. Failing that, fewest path segments — the shallower route is the one
210
+ // the entity is named for, which is what settles a `list` op where
211
+ // neither route ends in an id (`/boards` over `/members/{id}/boards`).
212
+ //
213
+ // Ties keep the earlier point, so the sorted-key order of the model decides
214
+ // and the output stays byte-stable.
215
+ //
216
+ // The same rule runs at RUNTIME, in each language's makePoint template, when
217
+ // no point's `select.exist` matches. It cannot be shared with them — a
218
+ // template ships standalone, outside this package — so it is written twice on
219
+ // purpose, and both sides must move together.
220
+ function terminalParam(point: any): boolean {
221
+ const parts: any[] = (point && point.parts) || []
222
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
223
+ return 'string' === typeof last && last.startsWith('{')
224
+ }
225
+
226
+
227
+ function ownPoint(points: any[]): any {
228
+ let best = points[0]
229
+
230
+ for (const pt of points) {
231
+ if (null == pt || null == pt.parts || null == best || null == best.parts) {
232
+ continue
233
+ }
234
+
235
+ const ptterm = terminalParam(pt)
236
+ const bestterm = terminalParam(best)
237
+
238
+ if (ptterm !== bestterm ? ptterm : pt.parts.length < best.parts.length) {
239
+ best = pt
240
+ }
241
+ }
242
+
243
+ return best
244
+ }
245
+
246
+
247
+ // Do all these points describe the same route? Then they are alternative
248
+ // selectors on one endpoint (the same path, chosen by different query
249
+ // params), not cross-references to different resources.
250
+ function samePath(points: any[]): boolean {
251
+ const first = ((points[0] && points[0].parts) || []).join('/')
252
+
253
+ return points.every((pt: any) =>
254
+ first === ((pt && pt.parts) || []).join('/'))
255
+ }
256
+
257
+
192
258
  function opParams(op: any): any[] {
193
259
  let points: any[] = op && op.points ? each(op.points) : []
194
260
 
@@ -227,6 +293,25 @@ function opParams(op: any): any[] {
227
293
  p.reqd = true === requiredOnAll[p.name]
228
294
  })
229
295
 
296
+ // Unrelated cross-references share no param, so the intersection came out
297
+ // empty and NOTHING is required — which generates a `load({})` that drops
298
+ // the id. Fall back to the entity's own point and take its params whole.
299
+ //
300
+ // Two guards, and both are load-bearing:
301
+ //
302
+ // - only when the intersection produced nothing, so a genuine set of
303
+ // alternative routes with a shared param still merges (a `page_id` on
304
+ // every route stays required, per-route siblings stay optional);
305
+ // - only when the points describe DIFFERENT routes. Points on one path
306
+ // that differ solely by optional query params (`/users?email=` and
307
+ // `/users?name=`) legitimately require nothing, and are not
308
+ // cross-references — collapsing them to one point would drop the
309
+ // other's fields from the generated request type, so a caller could
310
+ // not express them at all.
311
+ if (1 < points.length && !out.some((p: any) => p.reqd) && !samePath(points)) {
312
+ return opParams({ points: [ownPoint(points)] })
313
+ }
314
+
230
315
  return out
231
316
  }
232
317