@voxgig/sdkgen 4.1.0 → 4.2.1

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 (131) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/helpers/canonType.d.ts +2 -1
  3. package/dist/helpers/canonType.js +36 -0
  4. package/dist/helpers/canonType.js.map +1 -1
  5. package/dist/sdkgen.d.ts +2 -2
  6. package/dist/sdkgen.js +3 -2
  7. package/dist/sdkgen.js.map +1 -1
  8. package/dist/tsconfig.tsbuildinfo +1 -1
  9. package/package.json +1 -1
  10. package/project/.sdk/src/cmp/c/ReadmeEntity_c.ts +2 -2
  11. package/project/.sdk/src/cmp/c/ReadmeHowto_c.ts +2 -2
  12. package/project/.sdk/src/cmp/c/ReadmeQuick_c.ts +2 -2
  13. package/project/.sdk/src/cmp/c/ReadmeRef_c.ts +2 -2
  14. package/project/.sdk/src/cmp/c/ReadmeTopQuick_c.ts +2 -2
  15. package/project/.sdk/src/cmp/c/ReadmeTopTest_c.ts +2 -2
  16. package/project/.sdk/src/cmp/clojure/ReadmeEntity_clojure.ts +2 -2
  17. package/project/.sdk/src/cmp/clojure/ReadmeHowto_clojure.ts +2 -2
  18. package/project/.sdk/src/cmp/clojure/ReadmeQuick_clojure.ts +2 -2
  19. package/project/.sdk/src/cmp/clojure/ReadmeRef_clojure.ts +2 -2
  20. package/project/.sdk/src/cmp/clojure/ReadmeTopQuick_clojure.ts +2 -2
  21. package/project/.sdk/src/cmp/clojure/ReadmeTopTest_clojure.ts +2 -2
  22. package/project/.sdk/src/cmp/cpp/ReadmeEntity_cpp.ts +2 -2
  23. package/project/.sdk/src/cmp/cpp/ReadmeHowto_cpp.ts +2 -2
  24. package/project/.sdk/src/cmp/cpp/ReadmeQuick_cpp.ts +2 -2
  25. package/project/.sdk/src/cmp/cpp/ReadmeRef_cpp.ts +2 -2
  26. package/project/.sdk/src/cmp/cpp/ReadmeTopQuick_cpp.ts +2 -2
  27. package/project/.sdk/src/cmp/cpp/ReadmeTopTest_cpp.ts +2 -2
  28. package/project/.sdk/src/cmp/csharp/ReadmeEntity_csharp.ts +2 -2
  29. package/project/.sdk/src/cmp/csharp/ReadmeHowto_csharp.ts +2 -2
  30. package/project/.sdk/src/cmp/csharp/ReadmeQuick_csharp.ts +2 -2
  31. package/project/.sdk/src/cmp/csharp/ReadmeRef_csharp.ts +2 -2
  32. package/project/.sdk/src/cmp/csharp/ReadmeTopQuick_csharp.ts +2 -2
  33. package/project/.sdk/src/cmp/csharp/ReadmeTopTest_csharp.ts +2 -2
  34. package/project/.sdk/src/cmp/dart/ReadmeEntity_dart.ts +2 -2
  35. package/project/.sdk/src/cmp/dart/ReadmeHowto_dart.ts +2 -2
  36. package/project/.sdk/src/cmp/dart/ReadmeQuick_dart.ts +2 -2
  37. package/project/.sdk/src/cmp/dart/ReadmeRef_dart.ts +2 -2
  38. package/project/.sdk/src/cmp/dart/ReadmeTopQuick_dart.ts +2 -2
  39. package/project/.sdk/src/cmp/dart/ReadmeTopTest_dart.ts +2 -2
  40. package/project/.sdk/src/cmp/elixir/utility_elixir.ts +2 -1
  41. package/project/.sdk/src/cmp/go/ReadmeHowto_go.ts +2 -2
  42. package/project/.sdk/src/cmp/go/ReadmeTopTest_go.ts +2 -2
  43. package/project/.sdk/src/cmp/go/utility_go.ts +10 -1
  44. package/project/.sdk/src/cmp/java/ReadmeEntity_java.ts +2 -2
  45. package/project/.sdk/src/cmp/java/ReadmeHowto_java.ts +2 -2
  46. package/project/.sdk/src/cmp/java/ReadmeQuick_java.ts +2 -2
  47. package/project/.sdk/src/cmp/java/ReadmeRef_java.ts +2 -2
  48. package/project/.sdk/src/cmp/java/ReadmeTopQuick_java.ts +2 -2
  49. package/project/.sdk/src/cmp/java/ReadmeTopTest_java.ts +2 -2
  50. package/project/.sdk/src/cmp/js/utility_js.ts +10 -1
  51. package/project/.sdk/src/cmp/kotlin/ReadmeEntity_kotlin.ts +2 -2
  52. package/project/.sdk/src/cmp/kotlin/ReadmeHowto_kotlin.ts +2 -2
  53. package/project/.sdk/src/cmp/kotlin/ReadmeQuick_kotlin.ts +2 -2
  54. package/project/.sdk/src/cmp/kotlin/ReadmeRef_kotlin.ts +2 -2
  55. package/project/.sdk/src/cmp/kotlin/ReadmeTopQuick_kotlin.ts +2 -2
  56. package/project/.sdk/src/cmp/kotlin/ReadmeTopTest_kotlin.ts +2 -2
  57. package/project/.sdk/src/cmp/lua/ReadmeEntity_lua.ts +2 -2
  58. package/project/.sdk/src/cmp/lua/ReadmeHowto_lua.ts +2 -2
  59. package/project/.sdk/src/cmp/lua/ReadmeQuick_lua.ts +2 -2
  60. package/project/.sdk/src/cmp/lua/ReadmeRef_lua.ts +2 -2
  61. package/project/.sdk/src/cmp/lua/ReadmeTopQuick_lua.ts +2 -2
  62. package/project/.sdk/src/cmp/lua/ReadmeTopTest_lua.ts +2 -2
  63. package/project/.sdk/src/cmp/ocaml/ReadmeEntity_ocaml.ts +2 -2
  64. package/project/.sdk/src/cmp/ocaml/ReadmeHowto_ocaml.ts +2 -2
  65. package/project/.sdk/src/cmp/ocaml/ReadmeQuick_ocaml.ts +2 -2
  66. package/project/.sdk/src/cmp/ocaml/ReadmeRef_ocaml.ts +2 -2
  67. package/project/.sdk/src/cmp/ocaml/ReadmeTopQuick_ocaml.ts +2 -2
  68. package/project/.sdk/src/cmp/ocaml/ReadmeTopTest_ocaml.ts +2 -2
  69. package/project/.sdk/src/cmp/perl/ReadmeEntity_perl.ts +2 -2
  70. package/project/.sdk/src/cmp/perl/ReadmeHowto_perl.ts +2 -2
  71. package/project/.sdk/src/cmp/perl/ReadmeQuick_perl.ts +2 -2
  72. package/project/.sdk/src/cmp/perl/ReadmeRef_perl.ts +2 -2
  73. package/project/.sdk/src/cmp/perl/ReadmeTopQuick_perl.ts +2 -2
  74. package/project/.sdk/src/cmp/perl/ReadmeTopTest_perl.ts +2 -2
  75. package/project/.sdk/src/cmp/php/ReadmeEntity_php.ts +2 -2
  76. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +2 -2
  77. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +2 -2
  78. package/project/.sdk/src/cmp/php/ReadmeRef_php.ts +2 -2
  79. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +2 -2
  80. package/project/.sdk/src/cmp/php/ReadmeTopTest_php.ts +2 -2
  81. package/project/.sdk/src/cmp/py/ReadmeEntity_py.ts +2 -2
  82. package/project/.sdk/src/cmp/py/ReadmeHowto_py.ts +2 -2
  83. package/project/.sdk/src/cmp/py/ReadmeQuick_py.ts +2 -2
  84. package/project/.sdk/src/cmp/py/ReadmeRef_py.ts +2 -2
  85. package/project/.sdk/src/cmp/py/ReadmeTopQuick_py.ts +2 -2
  86. package/project/.sdk/src/cmp/py/ReadmeTopTest_py.ts +2 -2
  87. package/project/.sdk/src/cmp/rb/ReadmeEntity_rb.ts +2 -2
  88. package/project/.sdk/src/cmp/rb/ReadmeHowto_rb.ts +2 -2
  89. package/project/.sdk/src/cmp/rb/ReadmeQuick_rb.ts +2 -2
  90. package/project/.sdk/src/cmp/rb/ReadmeRef_rb.ts +2 -2
  91. package/project/.sdk/src/cmp/rb/ReadmeTopQuick_rb.ts +2 -2
  92. package/project/.sdk/src/cmp/rb/ReadmeTopTest_rb.ts +2 -2
  93. package/project/.sdk/src/cmp/rust/ReadmeEntity_rust.ts +2 -2
  94. package/project/.sdk/src/cmp/rust/ReadmeHowto_rust.ts +2 -2
  95. package/project/.sdk/src/cmp/rust/ReadmeQuick_rust.ts +2 -2
  96. package/project/.sdk/src/cmp/rust/ReadmeRef_rust.ts +2 -2
  97. package/project/.sdk/src/cmp/rust/ReadmeTopQuick_rust.ts +2 -2
  98. package/project/.sdk/src/cmp/rust/ReadmeTopTest_rust.ts +2 -2
  99. package/project/.sdk/src/cmp/scala/ReadmeEntity_scala.ts +2 -2
  100. package/project/.sdk/src/cmp/scala/ReadmeHowto_scala.ts +2 -2
  101. package/project/.sdk/src/cmp/scala/ReadmeQuick_scala.ts +2 -2
  102. package/project/.sdk/src/cmp/scala/ReadmeRef_scala.ts +2 -2
  103. package/project/.sdk/src/cmp/scala/ReadmeTopQuick_scala.ts +2 -2
  104. package/project/.sdk/src/cmp/scala/ReadmeTopTest_scala.ts +2 -2
  105. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +1 -1
  106. package/project/.sdk/src/cmp/scala/TestEntity_scala.ts +1 -1
  107. package/project/.sdk/src/cmp/swift/ReadmeEntity_swift.ts +2 -2
  108. package/project/.sdk/src/cmp/swift/ReadmeHowto_swift.ts +2 -2
  109. package/project/.sdk/src/cmp/swift/ReadmeQuick_swift.ts +2 -2
  110. package/project/.sdk/src/cmp/swift/ReadmeRef_swift.ts +2 -2
  111. package/project/.sdk/src/cmp/swift/ReadmeTopQuick_swift.ts +2 -2
  112. package/project/.sdk/src/cmp/swift/ReadmeTopTest_swift.ts +2 -2
  113. package/project/.sdk/src/cmp/ts/utility_ts.ts +10 -1
  114. package/project/.sdk/src/cmp/zig/ReadmeEntity_zig.ts +2 -2
  115. package/project/.sdk/src/cmp/zig/ReadmeHowto_zig.ts +2 -2
  116. package/project/.sdk/src/cmp/zig/ReadmeQuick_zig.ts +2 -2
  117. package/project/.sdk/src/cmp/zig/ReadmeRef_zig.ts +2 -2
  118. package/project/.sdk/src/cmp/zig/ReadmeTopQuick_zig.ts +2 -2
  119. package/project/.sdk/src/cmp/zig/ReadmeTopTest_zig.ts +2 -2
  120. package/project/.sdk/tm/clojure/src/sdk/features.clj +21 -3
  121. package/project/.sdk/tm/cpp/Makefile +11 -1
  122. package/project/.sdk/tm/dart/lib/feature/test/TestFeature.dart +38 -10
  123. package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +1 -1
  124. package/project/.sdk/tm/lean/src/SdkRuntime.lean +19 -1
  125. package/project/.sdk/tm/lean/src/SdkUtility.lean +2 -2
  126. package/project/.sdk/tm/ocaml/sdk_features.ml +31 -12
  127. package/project/.sdk/tm/py-data/test/conftest.py +24 -0
  128. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +10 -10
  129. package/project/sdkgen-package.json +1 -1
  130. package/src/helpers/canonType.ts +38 -0
  131. package/src/sdkgen.ts +2 -1
@@ -38,12 +38,40 @@ class TestFeature extends BaseFeature {
38
38
 
39
39
  final self = this;
40
40
 
41
- dynamic respond(int status, [dynamic data, dynamic res]) {
41
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
42
+ // `transform.res: `body.item`` describes an API that answers
43
+ // {"item": {...}}, and the response transform unwraps that key on the way
44
+ // back. Returning the bare payload means the transform unwraps a property
45
+ // that is not there and the caller gets nothing. Mirrors the go/ts/lua/php
46
+ // mocks.
47
+ dynamic envelope(dynamic fctx, dynamic data) {
48
+ if (null == data || null == fctx || null == fctx.point) {
49
+ return data;
50
+ }
51
+ // dart wraps the point in a Point OBJECT (Context._aspoint), so its
52
+ // fields are typed properties, not map keys — vs.getprop on it returns
53
+ // null and the envelope silently never applies.
54
+ final tm = fctx.point.transform;
55
+ final spec = vs.getprop(tm, 'res');
56
+ if (spec is! String) {
57
+ return data;
58
+ }
59
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
60
+ // synthesise, so it is left alone rather than guessed at.
61
+ final m = RegExp(r'^`body\.([^`.]+)`$').firstMatch(spec);
62
+ if (null == m) {
63
+ return data;
64
+ }
65
+ return <String, dynamic>{m.group(1)!: data};
66
+ }
67
+
68
+ dynamic respond(dynamic fctx, int status, [dynamic data, dynamic res]) {
69
+ final payload = envelope(fctx, data);
42
70
  final out = vs.merge([
43
71
  <String, dynamic>{
44
72
  'status': status,
45
73
  'statusText': 'OK',
46
- 'json': () => data,
74
+ 'json': () => payload,
47
75
  'body': 'not-used',
48
76
  },
49
77
  vs.getdef(res, {}),
@@ -66,30 +94,30 @@ class TestFeature extends BaseFeature {
66
94
  final found = vs.select(entmap, args);
67
95
  final ent = vs.getelem(found, 0);
68
96
  if (null == ent) {
69
- return respond(404, null, {'statusText': S_NOT_FOUND});
97
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
70
98
  } else {
71
99
  vs.delprop(ent, r'$KEY');
72
100
  final out = vs.clone(ent);
73
- return respond(200, out);
101
+ return respond(fctx, 200, out);
74
102
  }
75
103
  } else if ('list' == op.name) {
76
104
  final args = self.buildArgs(fctx, op, fctx.reqmatch);
77
105
  final found = vs.select(entmap, args);
78
106
  if (null == found) {
79
- return respond(404, null, {'statusText': S_NOT_FOUND});
107
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
80
108
  } else {
81
109
  for (final ent in found) {
82
110
  vs.delprop(ent, r'$KEY');
83
111
  }
84
112
  final out = vs.clone(found);
85
- return respond(200, out);
113
+ return respond(fctx, 200, out);
86
114
  }
87
115
  } else if ('update' == op.name) {
88
116
  final args = self.buildArgs(fctx, op, fctx.reqdata);
89
117
  final found = vs.select(entmap, args);
90
118
  final ent = vs.getelem(found, 0);
91
119
  if (null == ent) {
92
- return respond(404, null, {'statusText': S_NOT_FOUND});
120
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
93
121
  } else {
94
122
  // Dart's single null stands in for the donor's undefined: merge
95
123
  // must not overwrite stored values with absent ones.
@@ -104,7 +132,7 @@ class TestFeature extends BaseFeature {
104
132
  vs.merge([ent, upddata]);
105
133
  vs.delprop(ent, r'$KEY');
106
134
  final out = vs.clone(ent);
107
- return respond(200, out);
135
+ return respond(fctx, 200, out);
108
136
  }
109
137
  } else if ('remove' == op.name) {
110
138
  final args = self.buildArgs(fctx, op, fctx.reqmatch);
@@ -115,7 +143,7 @@ class TestFeature extends BaseFeature {
115
143
  if (null != ent) {
116
144
  vs.delprop(entmap, vs.getprop(ent, 'id'));
117
145
  }
118
- return respond(200);
146
+ return respond(fctx, 200);
119
147
  } else if ('create' == op.name) {
120
148
  self.buildArgs(fctx, op, fctx.reqdata);
121
149
  dynamic id = param(fctx, 'id');
@@ -130,7 +158,7 @@ class TestFeature extends BaseFeature {
130
158
  vs.setprop(entmap, id, ent);
131
159
  vs.delprop(ent, r'$KEY');
132
160
  final out = vs.clone(ent);
133
- return respond(200, out);
161
+ return respond(fctx, 200, out);
134
162
  }
135
163
 
136
164
  return null;
@@ -285,7 +285,7 @@ defmodule ProjectName.Feature.Test do
285
285
  netsleep.(pick_latency(net))
286
286
  status = H.or_(S.getprop(net, "failStatus"), 503)
287
287
 
288
- respond(status, nil,
288
+ respond(fctx, status, nil,
289
289
  S.jm(["statusText", "Simulated Failure", "headers", S.jm([])]))
290
290
 
291
291
  true ->
@@ -205,6 +205,23 @@ def liveFetcher : SdkFeature.Fetcher := fun _ctx url fetchdef => do
205
205
  ("body", json), ("headers", ← emptyMap)]
206
206
  pure (resp, none)
207
207
 
208
+ /-- THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
209
+ `transform.res: `body.item`` describes an API that answers {"item": {...}}
210
+ and the response transform unwraps that key on the way back. Returning the
211
+ bare payload means the transform unwraps a property that is not there and
212
+ the caller gets nothing. Mirrors the go/ts/lua/php mocks. -/
213
+ def mockEnvelope (ctx : Value) (data : Value) : SIO Value := do
214
+ if isNv data then pure data else do
215
+ let tm ← gp (← gp ctx "point") "transform"
216
+ let spec := asStr (← gp tm "res")
217
+ -- Exactly `body.<key>`; a deeper path is not an envelope this mock can
218
+ -- synthesise, so it is left alone rather than guessed at.
219
+ if spec.startsWith "`body." && spec.endsWith "`" && spec.length > 7 then
220
+ let inner := ((spec.drop 6).dropRight 1).toString
221
+ if inner.isEmpty || inner.contains '.' then pure data
222
+ else newMap #[(inner, data)]
223
+ else pure data
224
+
208
225
  /-- The base transport in test mode: answer from the seeded store. -/
209
226
  def testFetcher : SdkFeature.Fetcher := fun ctx _url _fetchdef => do
210
227
  let client ← gp ctx "client"
@@ -213,8 +230,9 @@ def testFetcher : SdkFeature.Fetcher := fun ctx _url _fetchdef => do
213
230
  let matchV ← gp ctx "reqmatch"
214
231
  let dataV ← gp ctx "reqdata"
215
232
  let out ← mockOp client entityName opName matchV dataV
233
+ let payload ← mockEnvelope ctx out
216
234
  let resp ← newMap #[("status", .num 200.0), ("statusText", .str "OK"),
217
- ("body", out), ("headers", ← emptyMap)]
235
+ ("body", payload), ("headers", ← emptyMap)]
218
236
  pure (resp, none)
219
237
 
220
238
  /-- Merge config.feature and options.feature into the client's feature options. -/
@@ -595,8 +595,6 @@ def makeResult (ctx : Value) : SIO Value := do
595
595
  sp ctx "result" res
596
596
  pure res
597
597
 
598
- /-- Select the endpoint for this operation: the single point, else the first
599
- whose `select.exist` keys are all present and whose `$action` agrees. -/
600
598
  /-- How many path segments a point has, and whether its path ends in a
601
599
  parameter. A record route ends in the record's identifier (/boards/{id});
602
600
  a cross-reference that also returns the entity ends in the relationship's
@@ -611,6 +609,8 @@ def pointShape (pt : Value) : SIO (Nat × Bool) := do
611
609
  else pure (items.size, (vs items[items.size - 1]!).startsWith "{")
612
610
  | _ => pure (0, false)
613
611
 
612
+ /-- Select the endpoint for this operation: the single point, else the first
613
+ whose `select.exist` keys are all present and whose `$action` agrees. -/
614
614
  def makePoint (ctx : Value) : SIO Value := do
615
615
  let op ← gp ctx "op"
616
616
  let matchV ← gp ctx "reqmatch"
@@ -980,8 +980,27 @@ let netsim_feature () : feature =
980
980
  let test_feature () : feature =
981
981
  let f = { f_name = "test"; f_version = "0.0.1"; f_active = true; f_options = Noval;
982
982
  f_init = (fun _ _ -> ()); f_hook = (fun _ _ -> ()) } in
983
- let respond status data extra =
984
- let out = jo [("status", vint_of status); ("statusText", Str "OK"); ("json", json_thunk data); ("body", Str "not-used")] in
983
+ (* THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
984
+ `transform.res: `body.item`` describes an API that answers {"item": {...}}
985
+ and the response transform unwraps that key on the way back. Returning the
986
+ bare payload means the transform unwraps a property that is not there and
987
+ the caller gets nothing. Mirrors the go/ts/lua/php mocks. *)
988
+ let envelope ctx data =
989
+ if is_nullish data then data
990
+ else
991
+ match getp (getp ctx.c_point "transform") "res" with
992
+ | Str spec ->
993
+ let n = String.length spec in
994
+ (* Exactly `body.<key>`; a deeper path is not an envelope this mock
995
+ can synthesise, so it is left alone rather than guessed at. *)
996
+ if n > 7 && String.sub spec 0 6 = "`body." && spec.[n - 1] = '`' then
997
+ let inner = String.sub spec 6 (n - 7) in
998
+ if String.length inner = 0 || String.contains inner '.' then data
999
+ else jo [(inner, data)]
1000
+ else data
1001
+ | _ -> data in
1002
+ let respond ctx status data extra =
1003
+ let out = jo [("status", vint_of status); ("statusText", Str "OK"); ("json", json_thunk (envelope ctx data)); ("body", Str "not-used")] in
985
1004
  (match extra with Some (Map _ as e) -> List.iter (fun k -> setp out k (getp e k)) (keysof e) | _ -> ());
986
1005
  (out, None) in
987
1006
  let build_args ctx (op : operation) args =
@@ -1027,15 +1046,15 @@ let test_feature () : feature =
1027
1046
  | "load" ->
1028
1047
  let args = build_args fctx op (resolve_match fctx fctx.c_reqmatch) in
1029
1048
  let ent = getelem (select entmap args) (Num 0.0) in
1030
- if is_nullish ent then respond 404 Noval (Some (jo [("statusText", Str "Not found")]))
1031
- else (ignore (delprop ent (Str "$KEY")); respond 200 (clone ent) None)
1049
+ if is_nullish ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
1050
+ else (ignore (delprop ent (Str "$KEY")); respond fctx 200 (clone ent) None)
1032
1051
  | "list" ->
1033
1052
  let args = build_args fctx op fctx.c_reqmatch in
1034
1053
  let found = select entmap args in
1035
- if is_nullish found then respond 404 Noval (Some (jo [("statusText", Str "Not found")]))
1054
+ if is_nullish found then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
1036
1055
  else begin
1037
1056
  (match found with List r -> List.iter (fun item -> ignore (delprop item (Str "$KEY"))) !r | _ -> ());
1038
- respond 200 (clone found) None
1057
+ respond fctx 200 (clone found) None
1039
1058
  end
1040
1059
  | "update" ->
1041
1060
  let update_match = empty_map () in
@@ -1046,17 +1065,17 @@ let test_feature () : feature =
1046
1065
  (if is_nullish !ent then match entmap with
1047
1066
  | Map m -> (try (match List.find (fun (_, v) -> match v with Map _ -> true | _ -> false) m.entries with (_, v) -> ent := v) with Not_found -> ())
1048
1067
  | _ -> ());
1049
- if is_nullish !ent then respond 404 Noval (Some (jo [("statusText", Str "Not found")]))
1068
+ if is_nullish !ent then respond fctx 404 Noval (Some (jo [("statusText", Str "Not found")]))
1050
1069
  else begin
1051
1070
  (match !ent with Map _ -> (match fctx.c_reqdata with Map _ -> List.iter (fun k -> setp !ent k (getp fctx.c_reqdata k)) (keysof fctx.c_reqdata) | _ -> ()) | _ -> ());
1052
1071
  ignore (delprop !ent (Str "$KEY"));
1053
- respond 200 (clone !ent) None
1072
+ respond fctx 200 (clone !ent) None
1054
1073
  end
1055
1074
  | "remove" ->
1056
1075
  let args = build_args fctx op (resolve_match fctx fctx.c_reqmatch) in
1057
1076
  let ent = getelem (select entmap args) (Num 0.0) in
1058
1077
  (match ent with Map _ -> ignore (delprop entmap (getp ent "id")) | _ -> ());
1059
- respond 200 Noval None
1078
+ respond fctx 200 Noval None
1060
1079
  | "create" ->
1061
1080
  ignore (build_args fctx op fctx.c_reqdata);
1062
1081
  let eid = let v = (cu fctx).u_param fctx (Str "id") in if is_nullish v then Str (random_id16 ()) else v in
@@ -1066,9 +1085,9 @@ let test_feature () : feature =
1066
1085
  setp ent "id" eid;
1067
1086
  (match eid with Str s -> setp entmap s ent | _ -> ());
1068
1087
  ignore (delprop ent (Str "$KEY"));
1069
- respond 200 (clone ent) None
1070
- | _ -> respond 200 ent None)
1071
- | _ -> respond 404 Noval (Some (jo [("statusText", Str "Unknown operation")]))) in
1088
+ respond fctx 200 (clone ent) None
1089
+ | _ -> respond fctx 200 ent None)
1090
+ | _ -> respond fctx 404 Noval (Some (jo [("statusText", Str "Unknown operation")]))) in
1072
1091
  let make_netsim net inner =
1073
1092
  let netcalls = ref 0 in
1074
1093
  let pick_latency () =
@@ -0,0 +1,24 @@
1
+ # Make the suite runnable with a bare `make test`, the way every other
2
+ # target's is.
3
+ #
4
+ # py-data is the one target that CONSUMES a sibling: its package imports the
5
+ # ProjectName SDK generated into ../py of the same repo. The Makefile's `dev`
6
+ # target pip-installs both editable, but `test` does not depend on it, so
7
+ # `make test` on a fresh clone died in collection with
8
+ # "ModuleNotFoundError: No module named 'projectname_sdk'" — before a single
9
+ # assertion ran.
10
+ #
11
+ # Requiring an install step would also make this target the only one that
12
+ # cannot be tested from a clean checkout. Putting the two source roots on
13
+ # sys.path keeps it self-contained; an installed copy still wins, because
14
+ # these entries are appended only when the import is not already satisfiable.
15
+ from __future__ import annotations
16
+
17
+ import sys
18
+ from pathlib import Path
19
+
20
+ _here = Path(__file__).resolve().parent
21
+ for _root in (_here.parent, _here.parent.parent / "py"):
22
+ _p = str(_root)
23
+ if _root.is_dir() and _p not in sys.path:
24
+ sys.path.insert(0, _p)
@@ -172,22 +172,22 @@ public final class TestFeature: BaseFeature {
172
172
  if isNil(ent) {
173
173
  let extra = VMap()
174
174
  extra.entries["statusText"] = .string("Not found")
175
- return testRespond(ctx, 404, .noval, extra)
175
+ return testRespond(ctx2, 404, .noval, extra)
176
176
  }
177
177
  delprop(ent, .string("$KEY"))
178
- return testRespond(ctx, 200, clone(ent), nil)
178
+ return testRespond(ctx2, 200, clone(ent), nil)
179
179
  } else if op.name == "list" {
180
180
  let args = testBuildArgs(ctx2, op, ctx2.reqmatch)
181
181
  let found = select(.map(entmap), args)
182
182
  if isNil(found) {
183
183
  let extra = VMap()
184
184
  extra.entries["statusText"] = .string("Not found")
185
- return testRespond(ctx, 404, .noval, extra)
185
+ return testRespond(ctx2, 404, .noval, extra)
186
186
  }
187
187
  if let fl = found.asList {
188
188
  for item in fl.items { delprop(item, .string("$KEY")) }
189
189
  }
190
- return testRespond(ctx, 200, clone(found), nil)
190
+ return testRespond(ctx2, 200, clone(found), nil)
191
191
  } else if op.name == "update" {
192
192
  var updateMatch = VMap()
193
193
  if let idv = ctx2.reqdata.entries["id"] {
@@ -209,13 +209,13 @@ public final class TestFeature: BaseFeature {
209
209
  if isNil(ent) {
210
210
  let extra = VMap()
211
211
  extra.entries["statusText"] = .string("Not found")
212
- return testRespond(ctx, 404, .noval, extra)
212
+ return testRespond(ctx2, 404, .noval, extra)
213
213
  }
214
214
  if let entm = ent.asMap {
215
215
  for (k, v) in ctx2.reqdata.entries { entm.entries[k] = v }
216
216
  }
217
217
  delprop(ent, .string("$KEY"))
218
- return testRespond(ctx, 200, clone(ent), nil)
218
+ return testRespond(ctx2, 200, clone(ent), nil)
219
219
  } else if op.name == "remove" {
220
220
  let args = testBuildArgs(ctx2, op, testResolveMatch(ctx2, ctx2.reqmatch))
221
221
  let found = select(.map(entmap), args)
@@ -224,7 +224,7 @@ public final class TestFeature: BaseFeature {
224
224
  let id = gp(entm2, "id")
225
225
  delprop(.map(entmap), id)
226
226
  }
227
- return testRespond(ctx, 200, .noval, nil)
227
+ return testRespond(ctx2, 200, .noval, nil)
228
228
  } else if op.name == "create" {
229
229
  _ = testBuildArgs(ctx2, op, ctx2.reqdata)
230
230
  var id = ctx2.utility!.param(ctx2, .string("id"))
@@ -239,14 +239,14 @@ public final class TestFeature: BaseFeature {
239
239
  entm.entries["id"] = id
240
240
  if let idStr = id.asString { entmap.entries[idStr] = .map(entm) }
241
241
  delprop(.map(entm), .string("$KEY"))
242
- return testRespond(ctx, 200, clone(.map(entm)), nil)
242
+ return testRespond(ctx2, 200, clone(.map(entm)), nil)
243
243
  }
244
- return testRespond(ctx, 200, ent, nil)
244
+ return testRespond(ctx2, 200, ent, nil)
245
245
  }
246
246
 
247
247
  let extra = VMap()
248
248
  extra.entries["statusText"] = .string("Unknown operation")
249
- return testRespond(ctx, 404, .noval, extra)
249
+ return testRespond(ctx2, 404, .noval, extra)
250
250
  }
251
251
 
252
252
  // Optional network behaviour simulation over the mock transport.
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.1.0",
6
+ "version": "4.2.1",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -182,6 +182,43 @@ function canonKey(sentinel: unknown): string {
182
182
  }
183
183
 
184
184
 
185
+ // The bare key of the sentinel a VALUE of this type must satisfy, resolving
186
+ // the union sentinel `['`$ONE`', [member, ...]]` to its first non-NULL member
187
+ // (recursively — a member may itself be a union).
188
+ //
189
+ // canonKey alone cannot do this: given an array it stringifies it, so a
190
+ // nullable `['`$ONE`', ['`$NUMBER`','`$NULL`']]` field yields neither NUMBER
191
+ // nor anything else recognizable and every caller falls through to its
192
+ // "unknown, use a string" branch. canonToType meanwhile renders that same
193
+ // sentinel as `number | null`, so the doc example said `'..._id'` while the
194
+ // generated type said `number` — a TS2322 in the SDK's own README, which is
195
+ // exactly the disagreement the example helpers exist to prevent.
196
+ //
197
+ // A union of nothing but NULL resolves to NULL; an unrecognized array shape,
198
+ // or an empty member list, resolves to '' (unknown) like any missing
199
+ // sentinel.
200
+ function canonScalarKey(sentinel: unknown): string {
201
+ if (Array.isArray(sentinel)) {
202
+ if ('ONE' !== canonKey(sentinel[0]) || !Array.isArray(sentinel[1])) {
203
+ return ''
204
+ }
205
+ let sawNull = false
206
+ for (const member of sentinel[1]) {
207
+ const key = canonScalarKey(member)
208
+ if ('NULL' === key) {
209
+ sawNull = true
210
+ continue
211
+ }
212
+ if ('' !== key) {
213
+ return key
214
+ }
215
+ }
216
+ return sawNull ? 'NULL' : ''
217
+ }
218
+ return canonKey(sentinel)
219
+ }
220
+
221
+
185
222
  // Map a field/param type sentinel to a target-language primitive type.
186
223
  // Unknown or missing sentinel -> that language's "any" (never throws).
187
224
  //
@@ -220,6 +257,7 @@ export {
220
257
  canonToType,
221
258
  canonToDtype,
222
259
  canonKey,
260
+ canonScalarKey,
223
261
  CANON_TYPE,
224
262
  CANON_ANY,
225
263
  CANON_UNION_JOIN,
package/src/sdkgen.ts CHANGED
@@ -57,7 +57,7 @@ import { buildIdNames } from './helpers/buildIdNames'
57
57
  import { getMatchEntries } from './helpers/getMatchEntries'
58
58
  import { collectDeps } from './helpers/collectDeps'
59
59
  import type { DepEntry } from './helpers/collectDeps'
60
- import { canonToType, canonToDtype, canonKey } from './helpers/canonType'
60
+ import { canonToType, canonToDtype, canonKey, canonScalarKey } from './helpers/canonType'
61
61
  import { OP_SUFFIX, opTypeName, opParams, ownPoint, opActions, entityActions, entityPath, opRequestShape, entityIdField, entityDataIdField, entityOps, entityPrimaryOp, pickExampleEntity, entityClassName, entityTypeCollisions, warnEntityTypeCollisions, deriveEntityNames, entityCollection } from './helpers/opShape'
62
62
  import { isReservedName, safeVarName, exampleVarName, phpEntityAccessor, entityCacheField, isRbCoreConstant, isRbSdkConstant, rbSafeTypeName, isSwiftSdkType, swiftSafeTypeName, isPhpReservedType, phpSafeTypeName, isTsReservedType, tsSafeTypeName, jsProp, jsOptProp, jsKey } from './helpers/naming'
63
63
  import { serverVariables, hasServerVariables } from './helpers/serverVars'
@@ -1059,6 +1059,7 @@ export {
1059
1059
  canonToType,
1060
1060
  canonToDtype,
1061
1061
  canonKey,
1062
+ canonScalarKey,
1062
1063
 
1063
1064
  OP_SUFFIX,
1064
1065
  opTypeName,