@voxgig/sdkgen 3.5.0 → 3.6.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 (171) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/ExternalTarget.js +1 -1
  3. package/dist/cmp/ExternalTarget.js.map +1 -1
  4. package/dist/helpers/naming.d.ts +4 -1
  5. package/dist/helpers/naming.js +119 -5
  6. package/dist/helpers/naming.js.map +1 -1
  7. package/dist/helpers/opShape.js +72 -0
  8. package/dist/helpers/opShape.js.map +1 -1
  9. package/dist/helpers/packageMeta.d.ts +2 -1
  10. package/dist/helpers/packageMeta.js +57 -20
  11. package/dist/helpers/packageMeta.js.map +1 -1
  12. package/dist/sdkgen.d.ts +3 -3
  13. package/dist/sdkgen.js +61 -18
  14. package/dist/sdkgen.js.map +1 -1
  15. package/dist/testkit.d.ts +39 -0
  16. package/dist/testkit.js +663 -0
  17. package/dist/testkit.js.map +1 -0
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/package.json +32 -8
  20. package/project/.sdk/src/cmp/c/Test_c.ts +5 -7
  21. package/project/.sdk/src/cmp/clojure/Test_clojure.ts +3 -7
  22. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -7
  23. package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
  24. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -7
  25. package/project/.sdk/src/cmp/dart/Test_dart.ts +3 -4
  26. package/project/.sdk/src/cmp/elixir/Package_elixir.ts +1 -1
  27. package/project/.sdk/src/cmp/elixir/Test_elixir.ts +5 -6
  28. package/project/.sdk/src/cmp/go/EntityTypes_go.ts +1 -1
  29. package/project/.sdk/src/cmp/go/Entity_go.ts +1 -1
  30. package/project/.sdk/src/cmp/go/Gitignore_go.ts +6 -0
  31. package/project/.sdk/src/cmp/go/Main_go.ts +2 -2
  32. package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
  33. package/project/.sdk/src/cmp/go/ReadmeExamplesTest_go.ts +59 -6
  34. package/project/.sdk/src/cmp/go/ReadmeExplanation_go.ts +1 -1
  35. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +1 -1
  36. package/project/.sdk/src/cmp/go/ReadmeInstall_go.ts +1 -1
  37. package/project/.sdk/src/cmp/go/ReadmeModel_go.ts +1 -1
  38. package/project/.sdk/src/cmp/go/ReadmeQuick_go.ts +1 -1
  39. package/project/.sdk/src/cmp/go/ReadmeTopQuick_go.ts +1 -1
  40. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +1 -1
  41. package/project/.sdk/src/cmp/go/Test_go.ts +6 -8
  42. package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +6 -2
  43. package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +6 -2
  44. package/project/.sdk/src/cmp/java/Test_java.ts +4 -7
  45. package/project/.sdk/src/cmp/js/Package_js.ts +1 -1
  46. package/project/.sdk/src/cmp/js/Test_js.ts +4 -7
  47. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +4 -7
  48. package/project/.sdk/src/cmp/lean/Test_lean.ts +3 -3
  49. package/project/.sdk/src/cmp/lua/Package_lua.ts +2 -2
  50. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -7
  51. package/project/.sdk/src/cmp/ocaml/Test_ocaml.ts +3 -7
  52. package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
  53. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -7
  54. package/project/.sdk/src/cmp/php/EntityTypes_php.ts +13 -2
  55. package/project/.sdk/src/cmp/php/Package_php.ts +2 -2
  56. package/project/.sdk/src/cmp/php/Test_php.ts +5 -7
  57. package/project/.sdk/src/cmp/py/Package_py.ts +2 -2
  58. package/project/.sdk/src/cmp/py/Test_py.ts +5 -7
  59. package/project/.sdk/src/cmp/py-data/Main_py-data.ts +14 -19
  60. package/project/.sdk/src/cmp/rb/Package_rb.ts +3 -3
  61. package/project/.sdk/src/cmp/rb/ReadmeInstall_rb.ts +1 -1
  62. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -7
  63. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -7
  64. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -2
  65. package/project/.sdk/src/cmp/seneca-provider/Gitignore_seneca-provider.ts +161 -0
  66. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +20 -11
  67. package/project/.sdk/src/cmp/swift/Test_swift.ts +5 -7
  68. package/project/.sdk/src/cmp/ts/Package_ts.ts +1 -1
  69. package/project/.sdk/src/cmp/ts/Test_ts.ts +8 -7
  70. package/project/.sdk/src/cmp/zig/Test_zig.ts +3 -7
  71. package/project/.sdk/tm/c/utility/make_point.c +64 -3
  72. package/project/.sdk/tm/clojure/src/sdk/core.clj +49 -14
  73. package/project/.sdk/tm/cpp/utility/pipeline.hpp +55 -3
  74. package/project/.sdk/tm/csharp/feature/TestFeature.cs +36 -2
  75. package/project/.sdk/tm/csharp/utility/MakePoint.cs +65 -2
  76. package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +34 -4
  77. package/project/.sdk/tm/dart/lib/utility/MakePointUtility.dart +48 -2
  78. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +70 -10
  79. package/project/.sdk/tm/go/feature/test_feature.go +34 -2
  80. package/project/.sdk/tm/go/utility/make_point.go +66 -2
  81. package/project/.sdk/tm/java/feature/TestFeature.java +42 -2
  82. package/project/.sdk/tm/java/utility/MakePoint.java +61 -2
  83. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +22 -3
  84. package/project/.sdk/tm/js/src/utility/MakePointUtility.js +72 -2
  85. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +31 -2
  86. package/project/.sdk/tm/kotlin/utility/MakePoint.kt +58 -2
  87. package/project/.sdk/tm/lean/src/SdkUtility.lean +28 -0
  88. package/project/.sdk/tm/lua/utility/make_point.lua +65 -2
  89. package/project/.sdk/tm/ocaml/sdk_runtime.ml +40 -2
  90. package/project/.sdk/tm/perl/utility/make_point.pm +55 -2
  91. package/project/.sdk/tm/php/feature/TestFeature.php +30 -1
  92. package/project/.sdk/tm/php/utility/MakePoint.php +49 -1
  93. package/project/.sdk/tm/py/pkg/utility/make_point.py +56 -2
  94. package/project/.sdk/tm/rb/utility/make_point.rb +42 -2
  95. package/project/.sdk/tm/rust/utility/make_point.rs +69 -2
  96. package/project/.sdk/tm/scala/feature/TestFeature.scala +36 -1
  97. package/project/.sdk/tm/scala/utility/Make.scala +57 -3
  98. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +29 -1
  99. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +51 -2
  100. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +22 -3
  101. package/project/.sdk/tm/ts/src/utility/MakePointUtility.ts +72 -2
  102. package/project/.sdk/tm/zig/core/utility.zig +66 -3
  103. package/project/sdkgen-package.json +1 -2
  104. package/src/cmp/ExternalTarget.ts +1 -1
  105. package/src/helpers/naming.ts +129 -5
  106. package/src/helpers/opShape.ts +85 -0
  107. package/src/helpers/packageMeta.ts +59 -20
  108. package/src/sdkgen.ts +68 -20
  109. package/src/testkit.ts +863 -0
  110. package/testkit.d.ts +3 -0
  111. package/testkit.js +12 -0
  112. package/project/.sdk/model/target/haskell.aontu +0 -72
  113. package/project/.sdk/src/cmp/haskell/Config_haskell.ts +0 -98
  114. package/project/.sdk/src/cmp/haskell/Entity_haskell.ts +0 -13
  115. package/project/.sdk/src/cmp/haskell/Gitignore_haskell.ts +0 -27
  116. package/project/.sdk/src/cmp/haskell/Main_haskell.ts +0 -123
  117. package/project/.sdk/src/cmp/haskell/Package_haskell.ts +0 -60
  118. package/project/.sdk/src/cmp/haskell/ReadmeEntity_haskell.ts +0 -186
  119. package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +0 -135
  120. package/project/.sdk/src/cmp/haskell/ReadmeExplanation_haskell.ts +0 -48
  121. package/project/.sdk/src/cmp/haskell/ReadmeHowto_haskell.ts +0 -168
  122. package/project/.sdk/src/cmp/haskell/ReadmeInstall_haskell.ts +0 -51
  123. package/project/.sdk/src/cmp/haskell/ReadmeIntro_haskell.ts +0 -67
  124. package/project/.sdk/src/cmp/haskell/ReadmeModel_haskell.ts +0 -162
  125. package/project/.sdk/src/cmp/haskell/ReadmeOptions_haskell.ts +0 -74
  126. package/project/.sdk/src/cmp/haskell/ReadmeQuick_haskell.ts +0 -241
  127. package/project/.sdk/src/cmp/haskell/ReadmeRef_haskell.ts +0 -408
  128. package/project/.sdk/src/cmp/haskell/ReadmeTopHowto_haskell.ts +0 -31
  129. package/project/.sdk/src/cmp/haskell/ReadmeTopQuick_haskell.ts +0 -104
  130. package/project/.sdk/src/cmp/haskell/ReadmeTopTest_haskell.ts +0 -80
  131. package/project/.sdk/src/cmp/haskell/Test_haskell.ts +0 -300
  132. package/project/.sdk/src/cmp/haskell/tsconfig.json +0 -15
  133. package/project/.sdk/src/cmp/haskell/utility_haskell.ts +0 -166
  134. package/project/.sdk/tm/haskell/LICENSE +0 -21
  135. package/project/.sdk/tm/haskell/Makefile +0 -22
  136. package/project/.sdk/tm/haskell/VERSION +0 -1
  137. package/project/.sdk/tm/haskell/src/SdkFeatures.hs +0 -1440
  138. package/project/.sdk/tm/haskell/src/SdkHelpers.hs +0 -337
  139. package/project/.sdk/tm/haskell/src/SdkJson.hs +0 -111
  140. package/project/.sdk/tm/haskell/src/SdkRuntime.hs +0 -1219
  141. package/project/.sdk/tm/haskell/src/SdkTypes.hs +0 -195
  142. package/project/.sdk/tm/haskell/src/VoxgigStruct.hs +0 -2299
  143. package/project/.sdk/tm/haskell/src/Vregex.hs +0 -237
  144. package/project/.sdk/tm/haskell/src/feature/README.md +0 -5
  145. package/project/.sdk/tm/haskell/src/feature/audit/.gitkeep +0 -0
  146. package/project/.sdk/tm/haskell/src/feature/base/.gitkeep +0 -0
  147. package/project/.sdk/tm/haskell/src/feature/cache/.gitkeep +0 -0
  148. package/project/.sdk/tm/haskell/src/feature/clienttrack/.gitkeep +0 -0
  149. package/project/.sdk/tm/haskell/src/feature/debug/.gitkeep +0 -0
  150. package/project/.sdk/tm/haskell/src/feature/idempotency/.gitkeep +0 -0
  151. package/project/.sdk/tm/haskell/src/feature/log/.gitkeep +0 -0
  152. package/project/.sdk/tm/haskell/src/feature/metrics/.gitkeep +0 -0
  153. package/project/.sdk/tm/haskell/src/feature/netsim/.gitkeep +0 -0
  154. package/project/.sdk/tm/haskell/src/feature/paging/.gitkeep +0 -0
  155. package/project/.sdk/tm/haskell/src/feature/proxy/.gitkeep +0 -0
  156. package/project/.sdk/tm/haskell/src/feature/ratelimit/.gitkeep +0 -0
  157. package/project/.sdk/tm/haskell/src/feature/rbac/.gitkeep +0 -0
  158. package/project/.sdk/tm/haskell/src/feature/retry/.gitkeep +0 -0
  159. package/project/.sdk/tm/haskell/src/feature/streaming/.gitkeep +0 -0
  160. package/project/.sdk/tm/haskell/src/feature/telemetry/.gitkeep +0 -0
  161. package/project/.sdk/tm/haskell/src/feature/test/.gitkeep +0 -0
  162. package/project/.sdk/tm/haskell/src/feature/timeout/.gitkeep +0 -0
  163. package/project/.sdk/tm/haskell/test/Harness.hs +0 -281
  164. package/project/.sdk/tm/haskell/test/Runner.hs +0 -47
  165. package/project/.sdk/tm/haskell/test/StructCorpus.hs +0 -449
  166. package/project/.sdk/tm/haskell/test/TCustomUtility.hs +0 -38
  167. package/project/.sdk/tm/haskell/test/TFeature.hs +0 -794
  168. package/project/.sdk/tm/haskell/test/TNetsim.hs +0 -50
  169. package/project/.sdk/tm/haskell/test/TPipeline.hs +0 -334
  170. package/project/.sdk/tm/haskell/test/TPrimaryUtility.hs +0 -386
  171. package/project/.sdk/tm/haskell/test/Testutil.hs +0 -59
@@ -7,6 +7,33 @@ use crate::core::types::OutVal;
7
7
  use crate::utility::voxgigstruct as vs;
8
8
  use crate::utility::voxgigstruct::Value;
9
9
 
10
+ // How many path segments a point has.
11
+ fn parts_len(point: &Value) -> usize {
12
+ match getp(point, "parts") {
13
+ Value::List(l) => l.borrow().len(),
14
+ _ => 0,
15
+ }
16
+ }
17
+
18
+ // Does this point's path end in a parameter? A record route ends in the
19
+ // record's identifier (/boards/{id}); a cross-reference that also returns
20
+ // the entity ends in the relationship's name (/posts/{id}/author). That,
21
+ // then fewest segments, is what tells the entity's own route from a
22
+ // cross-reference. The same rule runs at generation time, in
23
+ // helpers/opShape.ts — both sides must move together.
24
+ fn terminal_param(point: &Value) -> bool {
25
+ match getp(point, "parts") {
26
+ Value::List(l) => {
27
+ let parts = l.borrow();
28
+ match parts.last() {
29
+ Some(Value::Str(s)) => s.starts_with('{'),
30
+ _ => false,
31
+ }
32
+ }
33
+ _ => false,
34
+ }
35
+ }
36
+
10
37
  pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
11
38
  match ctx.out_get("point") {
12
39
  // A PrePoint feature hook (e.g. rbac) may short-circuit the
@@ -60,9 +87,10 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
60
87
  };
61
88
 
62
89
  let mut point = Value::Noval;
90
+ let mut matched = false;
63
91
  for i in 0..plen {
64
- point = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
65
- let select_def = to_map(&getp(&point, "select"));
92
+ let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
93
+ let select_def = to_map(&getp(&cand, "select"));
66
94
  let mut found = true;
67
95
 
68
96
  if !selector.is_noval() && !select_def.is_noval() {
@@ -89,10 +117,49 @@ pub fn make_point_util(ctx: &Rc<Context>) -> Result<Value, ProjectNameError> {
89
117
  }
90
118
 
91
119
  if found {
120
+ point = cand;
121
+ matched = true;
92
122
  break;
93
123
  }
94
124
  }
95
125
 
126
+ // select.exist can list more than the params needed to pick a point,
127
+ // so nothing matches — fall back to the entity's own route rather
128
+ // than whichever point came last.
129
+ if !matched {
130
+ // A request naming an action reaches here only because that
131
+ // action's own point failed its exist test, so it is unbuildable
132
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the
133
+ // guard below compares the chosen point's $action and would wave
134
+ // the request through whenever the fallback lands on the action
135
+ // point itself.
136
+ let unmatched_action = getp(&reqselector, "$action");
137
+ if !unmatched_action.is_noval() {
138
+ return Err(ctx.make_error(
139
+ "point_action_invalid",
140
+ &format!(
141
+ "Operation \"{}\" action \"{}\" is not valid.",
142
+ op.name,
143
+ vs::stringify(&unmatched_action, None, false)
144
+ ),
145
+ ));
146
+ }
147
+
148
+ point = vs::get_elem(&points, &Value::Num(0.0), Value::Noval);
149
+ for i in 0..plen {
150
+ let cand = vs::get_elem(&points, &Value::Num(i as f64), Value::Noval);
151
+ let cand_term = terminal_param(&cand);
152
+ let best_term = terminal_param(&point);
153
+ if cand_term != best_term {
154
+ if cand_term {
155
+ point = cand;
156
+ }
157
+ } else if parts_len(&cand) < parts_len(&point) {
158
+ point = cand;
159
+ }
160
+ }
161
+ }
162
+
96
163
  let req_action = getp(&reqselector, "$action");
97
164
  if !req_action.is_noval() && !point.is_noval() {
98
165
  let point_select = to_map(&getp(&point, "select"));
@@ -215,9 +215,44 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
215
215
  private def buildArgs(ctx: Context, op: Operation, args: JMap[String, Object]): Object = {
216
216
  val opname = op.name
217
217
 
218
+ // Pick the entity's own endpoint from config, not a cross-reference from
219
+ // another resource that also returns it — the same rule makePoint falls
220
+ // back to, so the seed-data query is built from the endpoint the request
221
+ // will actually be sent to: a terminal `{id}` marks a record route, and
222
+ // failing that the shallower path wins.
218
223
  val points = Struct.getpath(ctx.config,
219
224
  java.util.List.of("entity", if (ctx.entity == null) "" else ctx.entity.getName(), "op", opname, "points"))
220
- val point = Struct.getelem(points, java.lang.Integer.valueOf(-1))
225
+ var point = Struct.getelem(points, java.lang.Integer.valueOf(0))
226
+ points match {
227
+ case plist: java.util.List[_] =>
228
+ def pointPartsLen(p: Object): Int =
229
+ Struct.getprop(p, "parts") match {
230
+ case parts: java.util.List[_] => parts.size()
231
+ case _ => 0
232
+ }
233
+ def pointTerminalParam(p: Object): Boolean =
234
+ Struct.getprop(p, "parts") match {
235
+ case parts: java.util.List[_] if parts.size() > 0 =>
236
+ parts.get(parts.size() - 1) match {
237
+ case s: String => s.startsWith("{")
238
+ case _ => false
239
+ }
240
+ case _ => false
241
+ }
242
+ var k = 0
243
+ while (k < plist.size()) {
244
+ val cand = plist.get(k).asInstanceOf[Object]
245
+ val candTerm = pointTerminalParam(cand)
246
+ val bestTerm = pointTerminalParam(point)
247
+ if (candTerm != bestTerm) {
248
+ if (candTerm) point = cand
249
+ } else if (pointPartsLen(cand) < pointPartsLen(point)) {
250
+ point = cand
251
+ }
252
+ k += 1
253
+ }
254
+ case _ =>
255
+ }
221
256
 
222
257
  val paramsPath = Struct.getpath(point, java.util.List.of("args", "params"))
223
258
  val reqdParams = Struct.select(paramsPath, Struct.jm("reqd", java.lang.Boolean.TRUE))
@@ -94,11 +94,12 @@ object MakePoint {
94
94
  val selector: JMap[String, Object] = if ("data" == op.input) ctx.data else ctx.matchData
95
95
 
96
96
  var point: JMap[String, Object] = null
97
+ var matched = false
97
98
  var i = 0
98
99
  var break = false
99
100
  while (i < op.points.size() && !break) {
100
- point = op.points.get(i)
101
- val selectDef = Helpers.toMapAny(Struct.getprop(point, "select"))
101
+ val cand = op.points.get(i)
102
+ val selectDef = Helpers.toMapAny(Struct.getprop(cand, "select"))
102
103
  var found = true
103
104
 
104
105
  if (selector != null && selectDef != null) {
@@ -122,7 +123,60 @@ object MakePoint {
122
123
  if (!Objects.equals(reqAction, selectAction)) found = false
123
124
  }
124
125
 
125
- if (found) break = true else i += 1
126
+ if (found) { point = cand; matched = true; break = true } else i += 1
127
+ }
128
+
129
+ // select.exist can list more than the params needed to pick a point
130
+ // (for /boards/{id} it is Trello's 17 optional query-includes), so a
131
+ // plain {id} call matches NOTHING. Fall back to the entity's own route
132
+ // rather than whichever point came last.
133
+ if (!matched) {
134
+ // A request naming an action reaches here only because that action's
135
+ // own point failed its exist test, so it is unbuildable whatever we
136
+ // pick. Refuse it BEFORE choosing a fallback: the guard below
137
+ // compares the chosen point's $action and would wave the request
138
+ // through whenever the fallback lands on the action point itself.
139
+ val unmatchedAction =
140
+ if (reqselector != null) Struct.getprop(reqselector, "$action", null) else null
141
+ if (unmatchedAction != null) {
142
+ throw ctx.makeError("point_action_invalid",
143
+ "Operation \"" + op.name + "\" action \"" +
144
+ Struct.stringify(unmatchedAction) + "\" is not valid.")
145
+ }
146
+
147
+ // A terminal parameter marks a record route (/boards/{id}); a
148
+ // cross-reference ends in the relationship's name
149
+ // (/posts/{id}/author). Failing that, the shallower path wins. The
150
+ // same rule runs at generation time, in helpers/opShape.ts — both
151
+ // sides must move together.
152
+ def partsLen(p: JMap[String, Object]): Int =
153
+ Struct.getprop(p, "parts") match {
154
+ case parts: JList[_] => parts.size()
155
+ case _ => 0
156
+ }
157
+ def terminalParam(p: JMap[String, Object]): Boolean =
158
+ Struct.getprop(p, "parts") match {
159
+ case parts: JList[_] if parts.size() > 0 =>
160
+ parts.get(parts.size() - 1) match {
161
+ case s: String => s.startsWith("{")
162
+ case _ => false
163
+ }
164
+ case _ => false
165
+ }
166
+
167
+ point = op.points.get(0)
168
+ var j = 0
169
+ while (j < op.points.size()) {
170
+ val cand = op.points.get(j)
171
+ val candTerm = terminalParam(cand)
172
+ val bestTerm = terminalParam(point)
173
+ if (candTerm != bestTerm) {
174
+ if (candTerm) point = cand
175
+ } else if (partsLen(cand) < partsLen(point)) {
176
+ point = cand
177
+ }
178
+ j += 1
179
+ }
126
180
  }
127
181
 
128
182
  if (reqselector != null) {
@@ -38,7 +38,35 @@ private func testBuildArgs(_ ctx: Context, _ op: Operation, _ args: VMap?) -> Va
38
38
 
39
39
  let cfg: Value = ctx.config == nil ? .noval : .map(ctx.config!)
40
40
  let points = getpath(cfg, jtp("entity", ctx.entity!.getName(), "op", opname, "points"))
41
- let point = getelem(points, .int(-1))
41
+
42
+ // Pick the entity's own endpoint from config, not a cross-reference from
43
+ // another resource that also returns it — the same rule makePoint falls
44
+ // back to, so the seed-data query is built from the endpoint the request
45
+ // will actually be sent to: a terminal `{id}` marks a record route, and
46
+ // failing that the shallower path wins.
47
+ func pointPartsLen(_ p: Value) -> Int {
48
+ return getprop(p, .string("parts")).asList?.items.count ?? 0
49
+ }
50
+ func pointTerminalParam(_ p: Value) -> Bool {
51
+ guard let items = getprop(p, .string("parts")).asList?.items, let last = items.last else {
52
+ return false
53
+ }
54
+ return (last.asString ?? "").hasPrefix("{")
55
+ }
56
+
57
+ var point = getelem(points, .int(0))
58
+ if let candidates = points.asList?.items {
59
+ for cand in candidates {
60
+ let candTerm = pointTerminalParam(cand)
61
+ let bestTerm = pointTerminalParam(point)
62
+ if candTerm != bestTerm {
63
+ if candTerm { point = cand }
64
+ }
65
+ else if pointPartsLen(cand) < pointPartsLen(point) {
66
+ point = cand
67
+ }
68
+ }
69
+ }
42
70
 
43
71
  let paramsPath = getpath(point, jtp("args", "params"))
44
72
  let reqdParams = select(paramsPath, .map(vm(("reqd", .bool(true)))))
@@ -40,8 +40,8 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
40
40
  }
41
41
 
42
42
  var point: VMap? = nil
43
+ var matched = false
43
44
  for candidate in op.points {
44
- point = candidate
45
45
  let selectDef = gp(candidate, "select").asMap
46
46
  var found = true
47
47
 
@@ -62,7 +62,56 @@ func makePointUtil(_ ctx: Context) throws -> VMap? {
62
62
  if reqAction != selectAction { found = false }
63
63
  }
64
64
 
65
- if found { break }
65
+ if found {
66
+ point = candidate
67
+ matched = true
68
+ break
69
+ }
70
+ }
71
+
72
+ // select.exist can list more than the params needed to pick a point (for
73
+ // /boards/{id} it is Trello's 17 optional query-includes), so a plain
74
+ // {id} call matches NOTHING. Fall back to the entity's own route rather
75
+ // than whichever point came last.
76
+ if !matched {
77
+ // A request naming an action reaches here only because that action's
78
+ // own point failed its exist test, so it is unbuildable whatever we
79
+ // pick. Refuse it BEFORE choosing a fallback: the guard below compares
80
+ // the chosen point's $action and would wave the request through
81
+ // whenever the fallback lands on the action point itself.
82
+ let unmatchedAction = gp(reqselector, "$action")
83
+ if !isNil(unmatchedAction) {
84
+ throw ctx.makeError("point_action_invalid",
85
+ "Operation \"\(op.name)\" action \"\(stringify(unmatchedAction))\" is not valid.")
86
+ }
87
+
88
+ // A terminal parameter marks a record route (/boards/{id}); a
89
+ // cross-reference ends in the relationship's name (/posts/{id}/author).
90
+ // Failing that, the shallower path wins. The same rule runs at
91
+ // generation time, in helpers/opShape.ts — both sides must move
92
+ // together.
93
+ func partsLen(_ p: VMap) -> Int {
94
+ return gp(p, "parts").asList?.items.count ?? 0
95
+ }
96
+ func terminalParam(_ p: VMap) -> Bool {
97
+ guard let items = gp(p, "parts").asList?.items, let last = items.last else {
98
+ return false
99
+ }
100
+ return (last.asString ?? "").hasPrefix("{")
101
+ }
102
+
103
+ point = op.points.first
104
+ for candidate in op.points {
105
+ guard let best = point else { break }
106
+ let candTerm = terminalParam(candidate)
107
+ let bestTerm = terminalParam(best)
108
+ if candTerm != bestTerm {
109
+ if candTerm { point = candidate }
110
+ }
111
+ else if partsLen(candidate) < partsLen(best) {
112
+ point = candidate
113
+ }
114
+ }
66
115
  }
67
116
 
68
117
  let reqAction = gp(reqselector, "$action")
@@ -253,9 +253,28 @@ class TestFeature extends BaseFeature {
253
253
  const isempty = struct.isempty
254
254
 
255
255
  const opname = getprop(op, 'name')
256
- const point =
257
- getelem(getpath(ctx.config, [
258
- 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']), -1)
256
+ const points = getpath(ctx.config, [
257
+ 'entity', getprop(ctx.entity, 'name'), 'op', opname, 'points']) || []
258
+
259
+ // Pick the entity's own endpoint, not a cross-reference from another
260
+ // resource that also returns it — the same rule makePoint falls back to,
261
+ // so the seed-data query is built from the endpoint the request will
262
+ // actually be sent to: a terminal `{id}` marks a record route, and
263
+ // failing that the shallower path wins.
264
+ const isterm = (pt: any) => {
265
+ const parts = pt.parts
266
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
267
+ return 'string' === typeof last && 0 === last.indexOf('{')
268
+ }
269
+
270
+ let point = getelem(points, 0)
271
+ for (let i = 1; i < points.length; i++) {
272
+ const cand = getelem(points, i)
273
+ if (isterm(cand) !== isterm(point) ? isterm(cand) :
274
+ cand.parts.length < point.parts.length) {
275
+ point = cand
276
+ }
277
+ }
259
278
 
260
279
  const reqd = transform(
261
280
  select(getpath(point, ['args', 'params']), { reqd: true }),
@@ -2,6 +2,52 @@
2
2
  import { Context, Point } from '../types'
3
3
 
4
4
 
5
+ // The entity's OWN route among an op's points, as opposed to a
6
+ // CROSS-REFERENCE: another resource's route that happens to return this
7
+ // entity (`/notifications/{id}/board` for a board). Both are plain GETs
8
+ // with no `$action`, so the path itself is all there is to go on.
9
+ //
10
+ // Two signals, in order:
11
+ //
12
+ // 1. A record route ends in the record's identifier (`/boards/{id}`,
13
+ // `/accounts/{account_id}/users/{id}`); a cross-reference ends in the
14
+ // relationship's name (`/posts/{id}/author`). A terminal parameter is
15
+ // therefore the stronger signal, and unlike depth it survives an entity
16
+ // nested more deeply than the route pointing at it.
17
+ // 2. Failing that, fewest path segments — the shallower route is the one
18
+ // the entity is named for.
19
+ //
20
+ // Ties keep the earlier point, so the sorted-key order of the model decides
21
+ // and the output stays byte-stable.
22
+ //
23
+ // The same rule runs at GENERATION time, in `shortestPoint`/`ownPoint` in
24
+ // helpers/opShape.ts. It cannot be shared with this file — a template ships
25
+ // standalone, outside that package — so it is written twice on purpose, and
26
+ // both sides must move together.
27
+ function terminalParam(point: any): boolean {
28
+ const parts = point.parts
29
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
30
+ return 'string' === typeof last && 0 === last.indexOf('{')
31
+ }
32
+
33
+
34
+ function ownPoint(points: any[]): any {
35
+ let best = points[0]
36
+
37
+ for (const cand of points) {
38
+ const candterm = terminalParam(cand)
39
+ const bestterm = terminalParam(best)
40
+
41
+ if (candterm !== bestterm ? candterm :
42
+ cand.parts.length < best.parts.length) {
43
+ best = cand
44
+ }
45
+ }
46
+
47
+ return best
48
+ }
49
+
50
+
5
51
  function makePoint(ctx: Context): Point | Error {
6
52
  if (ctx.out.point) {
7
53
  return ctx.point = ctx.out.point
@@ -31,9 +77,10 @@ function makePoint(ctx: Context): Point | Error {
31
77
  const selector = getprop(ctx, op.input)
32
78
 
33
79
  let point
80
+ let matched = false
34
81
  for (let i = 0; i < op.points.length; i++) {
35
- point = op.points[i]
36
- const select = point.select
82
+ const cand = op.points[i]
83
+ const select = cand.select
37
84
  let found = true
38
85
 
39
86
  if (selector && select.exist) {
@@ -56,10 +103,33 @@ function makePoint(ctx: Context): Point | Error {
56
103
  }
57
104
 
58
105
  if (found) {
106
+ point = cand
107
+ matched = true
59
108
  break
60
109
  }
61
110
  }
62
111
 
112
+ // select.exist can list more than the params needed to pick a point (for
113
+ // /boards/{id} it is Trello's 17 optional query-includes), so a plain
114
+ // {id} call matches NOTHING. Fall back to the entity's OWN route rather
115
+ // than whichever point came last.
116
+ if (!matched) {
117
+ // A request naming an action reaches here only because that action's
118
+ // own point failed its exist test, so the request is unbuildable
119
+ // whatever we pick. Refuse it BEFORE choosing a fallback: the guard
120
+ // below compares the chosen point's $action, and would wave the
121
+ // request through whenever the action point is itself the one the
122
+ // fallback selects (a short action route folded into an entity whose
123
+ // canonical route is longer). An unbuildable action request is an
124
+ // error, never a silent request to some other endpoint.
125
+ if (null != reqselector.$action) {
126
+ return ctx.error('point_action_invalid', 'Operation "' + op.name +
127
+ '" action "' + reqselector.$action + '" is not valid.')
128
+ }
129
+
130
+ point = ownPoint(op.points)
131
+ }
132
+
63
133
  if (
64
134
  null != reqselector.$action &&
65
135
  null != point &&
@@ -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.0",
6
+ "version": "3.6.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",
@@ -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
  })