@voxgig/sdkgen 4.23.0 → 4.24.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 (138) hide show
  1. package/bin/voxgig-sdkgen +18 -2
  2. package/dist/action/doctor.d.ts +1 -1
  3. package/dist/action/doctor.js +12 -10
  4. package/dist/action/doctor.js.map +1 -1
  5. package/dist/action/edition.js +4 -0
  6. package/dist/action/edition.js.map +1 -1
  7. package/dist/action/feature.js +5 -1
  8. package/dist/action/feature.js.map +1 -1
  9. package/dist/action/remove.d.ts +16 -0
  10. package/dist/action/remove.js +345 -0
  11. package/dist/action/remove.js.map +1 -0
  12. package/dist/action/target.js +6 -1
  13. package/dist/action/target.js.map +1 -1
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/package.json +1 -1
  16. package/project/.sdk/tm/c/core/context.c +3 -1
  17. package/project/.sdk/tm/c/feature/paging.c +37 -3
  18. package/project/.sdk/tm/c/feature/test.c +51 -27
  19. package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
  20. package/project/.sdk/tm/c/utility/transform_request.c +25 -3
  21. package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
  22. package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
  23. package/project/.sdk/tm/cpp/core/types.hpp +1 -1
  24. package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
  25. package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
  26. package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
  27. package/project/.sdk/tm/csharp/core/Context.cs +1 -1
  28. package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
  29. package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
  30. package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
  31. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
  32. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
  33. package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
  34. package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
  35. package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
  36. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
  37. package/project/.sdk/tm/go/core/context.go +13 -13
  38. package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
  39. package/project/.sdk/tm/go/feature/test_feature.go +3 -15
  40. package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
  41. package/project/.sdk/tm/go/test/feature_test.go +54 -0
  42. package/project/.sdk/tm/go/utility/fetcher.go +41 -20
  43. package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
  44. package/project/.sdk/tm/go/utility/transform_request.go +22 -3
  45. package/project/.sdk/tm/java/core/Context.java +2 -1
  46. package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
  47. package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
  48. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
  49. package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
  50. package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
  51. package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
  52. package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
  53. package/project/.sdk/tm/js/src/Context.js +7 -2
  54. package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
  55. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
  56. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
  57. package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
  58. package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
  59. package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
  60. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
  61. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
  62. package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
  63. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
  64. package/project/.sdk/tm/lua/core/context.lua +8 -1
  65. package/project/.sdk/tm/lua/core/control.lua +3 -0
  66. package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
  67. package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
  68. package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
  69. package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
  70. package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
  71. package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
  72. package/project/.sdk/tm/perl/core/context.pm +2 -1
  73. package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
  74. package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
  75. package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
  76. package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
  77. package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
  78. package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
  79. package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
  80. package/project/.sdk/tm/php/core/Context.php +5 -1
  81. package/project/.sdk/tm/php/core/Control.php +5 -0
  82. package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
  83. package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
  84. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
  85. package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
  86. package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
  87. package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
  88. package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
  89. package/project/.sdk/tm/py/pkg/core/context.py +2 -1
  90. package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
  91. package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
  92. package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
  93. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
  94. package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
  95. package/project/.sdk/tm/py/test/test_feature.py +27 -0
  96. package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
  97. package/project/.sdk/tm/rb/core/context.rb +1 -1
  98. package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
  99. package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
  100. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
  101. package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
  102. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
  103. package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
  104. package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
  105. package/project/.sdk/tm/rust/core/context.rs +2 -6
  106. package/project/.sdk/tm/rust/feature/paging.rs +44 -3
  107. package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
  108. package/project/.sdk/tm/rust/feature/test.rs +66 -33
  109. package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
  110. package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
  111. package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
  112. package/project/.sdk/tm/scala/core/Context.scala +2 -1
  113. package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
  114. package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
  115. package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
  116. package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
  117. package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
  118. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
  119. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
  120. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
  121. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
  122. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
  123. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
  124. package/project/.sdk/tm/ts/src/Context.ts +7 -2
  125. package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
  126. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
  127. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
  128. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
  129. package/project/.sdk/tm/zig/core/context.zig +1 -1
  130. package/project/.sdk/tm/zig/core/utility.zig +19 -5
  131. package/project/.sdk/tm/zig/feature/paging.zig +34 -4
  132. package/project/.sdk/tm/zig/feature/test.zig +54 -26
  133. package/project/sdkgen-package.json +1 -1
  134. package/src/action/doctor.ts +12 -9
  135. package/src/action/edition.ts +8 -0
  136. package/src/action/feature.ts +7 -1
  137. package/src/action/remove.ts +492 -0
  138. package/src/action/target.ts +8 -1
@@ -22,7 +22,7 @@ class PagingFeature extends BaseFeature {
22
22
 
23
23
 
24
24
  PreRequest(this: any, ctx: any) {
25
- if (!this._isList(ctx)) {
25
+ if (!this.active || !this._isList(ctx)) {
26
26
  return
27
27
  }
28
28
  const spec = ctx.spec
@@ -51,7 +51,10 @@ class PagingFeature extends BaseFeature {
51
51
  spec.query[cursorParam] = paging.cursor
52
52
  }
53
53
  else if (null == spec.query[pageParam]) {
54
- spec.query[pageParam] = null != paging.page ? paging.page : (this._options.startPage || 1)
54
+ // A record written back by PreResult holds the page just fetched as
55
+ // `page` and the one to fetch as `nextPage`, so nextPage wins.
56
+ spec.query[pageParam] = null != paging.nextPage ? paging.nextPage :
57
+ null != paging.page ? paging.page : (this._options.startPage || 1)
55
58
  }
56
59
 
57
60
  if (null != this._options.limit && null == spec.query[limitParam]) {
@@ -61,7 +64,7 @@ class PagingFeature extends BaseFeature {
61
64
 
62
65
 
63
66
  PreResult(this: any, ctx: any) {
64
- if (!this._isList(ctx)) {
67
+ if (!this.active || !this._isList(ctx)) {
65
68
  return
66
69
  }
67
70
  const result = ctx.result
@@ -118,8 +121,17 @@ class PagingFeature extends BaseFeature {
118
121
 
119
122
  if (body && 'object' === typeof body) {
120
123
  if (null != body.next) { paging.next = paging.next || body.next }
124
+ if (null != body.next_cursor) { paging.cursor = body.next_cursor }
121
125
  if (null != body.cursor) { paging.cursor = body.cursor }
122
126
  if (null != body.nextCursor) { paging.cursor = body.nextCursor }
127
+ if (null == paging.nextPage) {
128
+ const np = null != body.nextPage ? body.nextPage : body.next_page
129
+ if ('number' === typeof np || 'string' === typeof np) { paging.nextPage = np }
130
+ }
131
+ if ('boolean' === typeof body.has_more) {
132
+ paging.hasMore = body.has_more
133
+ explicitMore = true
134
+ }
123
135
  if ('boolean' === typeof body.hasMore) {
124
136
  paging.hasMore = body.hasMore
125
137
  explicitMore = true
@@ -138,6 +150,10 @@ class PagingFeature extends BaseFeature {
138
150
 
139
151
  result.paging = paging
140
152
 
153
+ if (null != ctx.ctrl) {
154
+ ctx.ctrl.paging = paging
155
+ }
156
+
141
157
  const client: any = this._client
142
158
  client._paging = { last: paging }
143
159
  }
@@ -193,6 +193,7 @@ class TestFeature extends BaseFeature {
193
193
  const found = select(entmap, args)
194
194
  const ent = getelem(found, 0)
195
195
  if (null == ent) {
196
+ // update miss: 404, never another record
196
197
  return respond(404, undefined, { statusText: S_NOT_FOUND })
197
198
  }
198
199
  else {
@@ -237,6 +238,8 @@ class TestFeature extends BaseFeature {
237
238
  const out = clone(ent)
238
239
  return respond(200, out)
239
240
  }
241
+
242
+ return respond(404, undefined, { statusText: 'Unknown operation' })
240
243
  }
241
244
 
242
245
  // Optional network behaviour simulation over the mock transport. Enable
@@ -16,7 +16,7 @@ function prepareQuery(ctx: Context) {
16
16
 
17
17
  const out: any = {}
18
18
  for (let [key, val] of items(reqmatch)) {
19
- if (null != val && !params.includes(key)) {
19
+ if (null != val && '$action' !== key && !params.includes(key)) {
20
20
  out[key] = val
21
21
  }
22
22
  }
@@ -206,6 +206,7 @@ describe('FeatureCorpus', () => {
206
206
 
207
207
  const probe: any = makeClient({})
208
208
  if (!probe._rootctx.config.hasFeature(name)) {
209
+ t.diagnostic(`feature.${name}: inert (this SDK does not generate the feature)`)
209
210
  return t.skip(`this SDK was generated without the ${name} feature`)
210
211
  }
211
212
 
@@ -100,7 +100,7 @@ pub const Context = struct {
100
100
  ctrl = c;
101
101
  } else if (ctxspec.ctrl_obj) |co| {
102
102
  ctrl = co;
103
- } else if (basectx) |b| {
103
+ } else if (if (ctxspec.opname == null) basectx else null) |b| {
104
104
  ctrl = b.ctrl;
105
105
  } else {
106
106
  ctrl = Control.make();
@@ -1120,7 +1120,7 @@ pub fn prepare_query_util(ctx: *Context) Value {
1120
1120
  }
1121
1121
  }
1122
1122
  }
1123
- if (!h.is_noval(val) and !contained) h.setp(out, key, val);
1123
+ if (!h.is_noval(val) and !std.mem.eql(u8, key, "$action") and !contained) h.setp(out, key, val);
1124
1124
  }
1125
1125
  }
1126
1126
  return out;
@@ -1375,6 +1375,20 @@ pub fn result_body_util(ctx: *Context) ?*SdkResult {
1375
1375
  return result;
1376
1376
  }
1377
1377
 
1378
+ // `$action` selects the point (see make_point_util); it is never an API
1379
+ // field, so the body is a copy without it. The caller's map is left untouched.
1380
+ fn strip_action(reqdata: Value) Value {
1381
+ if (reqdata != .object) return reqdata;
1382
+ if (reqdata.object.get("$action") == null) return reqdata;
1383
+ const body = h.omap();
1384
+ var it = reqdata.object.iterator();
1385
+ while (it.next()) |kv| {
1386
+ const key = kv.key_ptr.*;
1387
+ if (!std.mem.eql(u8, key, "$action")) h.setp(body, key, kv.value_ptr.*);
1388
+ }
1389
+ return body;
1390
+ }
1391
+
1378
1392
  pub fn transform_request_util(ctx: *Context) Value {
1379
1393
  const spec = ctx.spec;
1380
1394
  const point = ctx.point;
@@ -1382,16 +1396,16 @@ pub fn transform_request_util(ctx: *Context) Value {
1382
1396
  if (spec) |sp| sp.step = "reqform";
1383
1397
 
1384
1398
  const transform = h.to_map(h.getp(point, "transform"));
1385
- if (h.is_noval(transform)) return ctx.reqdata;
1399
+ if (h.is_noval(transform)) return strip_action(ctx.reqdata);
1386
1400
 
1387
1401
  const reqform = h.getp(transform, "req");
1388
- if (h.is_noval(reqform)) return ctx.reqdata;
1402
+ if (h.is_noval(reqform)) return strip_action(ctx.reqdata);
1389
1403
 
1390
1404
  const store = h.jo(&.{.{ "reqdata", ctx.reqdata }});
1391
1405
  // transform now reports collected injection errors beside the value; .out
1392
1406
  // is what it used to return on its own, errors or not.
1393
- const tres = vs.transform(h.A(), store, reqform) catch return ctx.reqdata;
1394
- return tres.out;
1407
+ const tres = vs.transform(h.A(), store, reqform) catch return strip_action(ctx.reqdata);
1408
+ return strip_action(tres.out);
1395
1409
  }
1396
1410
 
1397
1411
  pub fn transform_response_util(ctx: *Context) Value {
@@ -3,8 +3,8 @@
3
3
  // (or a cursor) into the request query; on the way back (PreResult) it reads
4
4
  // the server's pagination signals — a `Link: rel="next"` header,
5
5
  // `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
6
- // `next`/`cursor`/`nextCursor`/`hasMore` fields in the body — and records them
7
- // on `result.paging`. A per-call cursor/page from ctrl takes priority (used by
6
+ // `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case forms
7
+ // too) — and records them on `result.paging` and the caller's ctrl paging. A per-call cursor/page from ctrl takes priority (used by
8
8
  // auto-iteration). Parameter names (`pageParam`, `limitParam`, `cursorParam`),
9
9
  // the page size (`limit`) and the start page (`startPage`, default 1) are
10
10
  // configurable.
@@ -114,7 +114,10 @@ pub const PagingFeature = struct {
114
114
  if (!h.is_noval(cursor)) {
115
115
  h.setp(query, cursor_param, cursor);
116
116
  } else if (h.is_noval(h.getp(query, page_param))) {
117
- const page = h.getp(paging, "page");
117
+ // A record written back by pre_result holds the page just fetched
118
+ // as "page" and the one to fetch as "nextPage", so nextPage wins.
119
+ var page = h.getp(paging, "nextPage");
120
+ if (h.is_noval(page)) page = h.getp(paging, "page");
118
121
  if (!h.is_noval(page)) {
119
122
  h.setp(query, page_param, page);
120
123
  } else {
@@ -257,12 +260,16 @@ pub const PagingFeature = struct {
257
260
  }
258
261
  }
259
262
 
260
- // Body-level cursors.
263
+ // Body-level signals; snake_case forms are read first so camelCase wins.
261
264
  if (body == .object) {
262
265
  const next = h.getp(body, "next");
263
266
  if (!h.is_noval(next) and h.is_noval(h.getp(paging, "next"))) {
264
267
  h.setp(paging, "next", next);
265
268
  }
269
+ const snake_cursor = h.getp(body, "next_cursor");
270
+ if (!h.is_noval(snake_cursor)) {
271
+ h.setp(paging, "cursor", snake_cursor);
272
+ }
266
273
  const cursor = h.getp(body, "cursor");
267
274
  if (!h.is_noval(cursor)) {
268
275
  h.setp(paging, "cursor", cursor);
@@ -271,6 +278,18 @@ pub const PagingFeature = struct {
271
278
  if (!h.is_noval(next_cursor)) {
272
279
  h.setp(paging, "cursor", next_cursor);
273
280
  }
281
+ if (h.is_noval(h.getp(paging, "nextPage"))) {
282
+ var np = h.getp(body, "nextPage");
283
+ if (h.is_noval(np)) np = h.getp(body, "next_page");
284
+ switch (np) {
285
+ .integer, .float, .string => h.setp(paging, "nextPage", np),
286
+ else => {},
287
+ }
288
+ }
289
+ if (h.get_bool(body, "has_more")) |has_more| {
290
+ h.setp(paging, "hasMore", h.vbool(has_more));
291
+ explicit_more = true;
292
+ }
274
293
  if (h.get_bool(body, "hasMore")) |has_more| {
275
294
  h.setp(paging, "hasMore", h.vbool(has_more));
276
295
  explicit_more = true;
@@ -292,6 +311,17 @@ pub const PagingFeature = struct {
292
311
 
293
312
  result.paging = paging;
294
313
  self.last = paging;
314
+
315
+ // `.object` is a *MapRef, so the caller's map is refilled in place; a
316
+ // fresh map on ctrl never reaches the caller.
317
+ if (ctx.ctrl.paging == .object) {
318
+ const shared = ctx.ctrl.paging.object;
319
+ shared.data.clearRetainingCapacity();
320
+ var it = paging.object.iterator();
321
+ while (it.next()) |kv| h.setp(ctx.ctrl.paging, kv.key_ptr.*, kv.value_ptr.*);
322
+ } else {
323
+ ctx.ctrl.paging = paging;
324
+ }
295
325
  }
296
326
 
297
327
  fn vname(p: *anyopaque) []const u8 {
@@ -150,26 +150,62 @@ fn resolve_match(ctx: *Context, explicit: Value) Value {
150
150
  return h.omap();
151
151
  }
152
152
 
153
+ fn point_terminal(p: Value) bool {
154
+ const parts = h.getp(p, "parts");
155
+ if (parts != .array) return false;
156
+ const items = parts.array.data.items;
157
+ if (items.len == 0) return false;
158
+ const last = items[items.len - 1];
159
+ return last == .string and std.mem.startsWith(u8, last.string, "{");
160
+ }
161
+
162
+ fn point_depth(p: Value) usize {
163
+ const parts = h.getp(p, "parts");
164
+ return if (parts == .array) parts.array.data.items.len else 0;
165
+ }
166
+
167
+ // The entity's own endpoint: a terminal `{param}` marks a record route, and
168
+ // among equals the shallower path wins (the same rule as make_point).
169
+ fn pick_point(points: Value) Value {
170
+ if (points != .array) return h.vnull();
171
+ const items = points.array.data.items;
172
+ if (items.len == 0) return h.vnull();
173
+ var point = items[0];
174
+ for (items[1..]) |cand| {
175
+ if (point_terminal(cand) != point_terminal(point)) {
176
+ if (point_terminal(cand)) point = cand;
177
+ } else if (point_depth(cand) < point_depth(point)) {
178
+ point = cand;
179
+ }
180
+ }
181
+ return point;
182
+ }
183
+
184
+ fn reqd_names(point: Value, kind: []const u8) Value {
185
+ const args_path = h.getpath(&.{ "args", kind }, point);
186
+ const reqd_args = vs.select(h.A(), args_path, h.jo(&.{.{ "reqd", h.vbool(true) }})) catch return h.olist();
187
+ // transform reports collected injection errors beside the value; .out is
188
+ // the value itself, errors or not.
189
+ const tres = vs.transform(h.A(), reqd_args, h.ja(&.{
190
+ h.vstr("`$EACH`"),
191
+ h.vstr(""),
192
+ h.vstr("`$KEY.name`"),
193
+ })) catch return h.olist();
194
+ return tres.out;
195
+ }
196
+
153
197
  fn build_args(ctx: *Context, args: Value) Value {
154
198
  const op = ctx.op;
155
199
  const opname = op.name;
156
200
  const entname: []const u8 = if (ctx.entity) |e| e.get_name() else op.entity;
157
201
 
158
202
  const points = h.getpath(&.{ "entity", entname, "op", opname, "points" }, ctx.config);
159
- const point = h.get_elem(points, h.vnum(-1), h.vnull());
160
-
161
- const params_path = h.getpath(&.{ "args", "params" }, point);
162
- const reqd_params = vs.select(h.A(), params_path, h.jo(&.{.{ "reqd", h.vbool(true) }})) catch h.olist();
163
- const reqd = blk: {
164
- // transform now reports collected injection errors beside the value;
165
- // .out is what it used to return on its own, errors or not.
166
- const tres = vs.transform(h.A(), reqd_params, h.ja(&.{
167
- h.vstr("`$EACH`"),
168
- h.vstr(""),
169
- h.vstr("`$KEY.name`"),
170
- })) catch break :blk h.olist();
171
- break :blk tres.out;
172
- };
203
+ const point = pick_point(points);
204
+
205
+ // Path AND query: a path-only read misses a query-addressed record
206
+ // (e.g. GET /result?trace_id=), which has no path param at all.
207
+ const reqd_params = reqd_names(point, "params");
208
+ const reqd_query = reqd_names(point, "query");
173
209
 
174
210
  const qand = h.olist();
175
211
  const q = h.jo(&.{.{ "`$AND`", qand }});
@@ -178,8 +214,8 @@ fn build_args(ctx: *Context, args: Value) Value {
178
214
  const keys = h.keysof_vec(args);
179
215
  for (keys) |key| {
180
216
  const is_id = std.mem.eql(u8, key, "id");
181
- const selected = vs.select(h.A(), reqd, h.vstr(key)) catch h.olist();
182
- const is_reqd = !h.is_empty(selected);
217
+ const is_reqd = !h.is_empty(vs.select(h.A(), reqd_params, h.vstr(key)) catch h.olist()) or
218
+ !h.is_empty(vs.select(h.A(), reqd_query, h.vstr(key)) catch h.olist());
183
219
 
184
220
  if (is_id or is_reqd) {
185
221
  const v = ctx.util().param(ctx, h.vstr(key));
@@ -251,20 +287,12 @@ fn test_fetch(entity: Value, ctx: *Context, _: []const u8, _: Value) err.E!Value
251
287
  const args = build_args(ctx, update_match);
252
288
  const found = vs.select(h.A(), entmap, args) catch h.olist();
253
289
  var ent = h.get_elem(found, h.vnum(0), h.vnull());
254
- if (h.is_noval(ent) and h.sizeOf(entmap) > 0) {
255
- for (h.items_vec(entmap)) |kv| {
256
- if (kv.v == .object) {
257
- ent = kv.v;
258
- break;
259
- }
260
- }
261
- }
262
290
  if (h.is_noval(ent)) {
291
+ // update miss: 404, never another record
263
292
  return respond(ctx, 404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
264
293
  }
265
294
  if (ent == .object and reqdata == .object) {
266
- var it = reqdata.object.iterator();
267
- while (it.next()) |kv| h.setp(ent, kv.key_ptr.*, kv.value_ptr.*);
295
+ ent = h.merge(h.ja(&.{ ent, reqdata }));
268
296
  }
269
297
  h.del_prop(ent, h.vstr("$KEY"));
270
298
  return respond(ctx, 200, h.clone(ent), &.{});
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.23.0",
6
+ "version": "4.24.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",
@@ -230,7 +230,7 @@ function orphanModelFiles(actx: ActionContext): string[] {
230
230
 
231
231
 
232
232
  async function doctor(
233
- actx: ActionContext, scope?: DoctorScope,
233
+ actx: ActionContext, scope?: DoctorScope, selected?: string[],
234
234
  ): Promise<ActionResult> {
235
235
  const log = actx.log
236
236
  const fs = actx.fs()
@@ -283,17 +283,18 @@ async function doctor(
283
283
  }
284
284
 
285
285
  if ('target' === kind) {
286
- checkTarget(actx, source, report)
286
+ checkTarget(actx, source, report, selected)
287
287
  }
288
288
 
289
289
  if ('edition' === kind) {
290
290
  checkEdition(actx, source, report)
291
291
  }
292
292
 
293
- // Only an ACTIVE feature has source copied out; what an inactive one
294
- // left behind is stale, and the target walk reports it as such.
293
+ // Only an ACTIVE feature has source copied out; an inactive one's
294
+ // leftovers are stale. `selected` overrides that for the caller's own.
295
295
  if ('feature' === kind &&
296
- false !== (model as any)?.main?.[KIT]?.feature?.[name]?.active) {
296
+ (false !== (model as any)?.main?.[KIT]?.feature?.[name]?.active ||
297
+ true === selected?.includes(name))) {
297
298
  checkFeatureSource(actx, source, targets, report)
298
299
  }
299
300
 
@@ -409,6 +410,7 @@ function resolveDeclared(
409
410
 
410
411
  function checkTarget(
411
412
  actx: ActionContext, resolved: Source, report: DoctorReport,
413
+ selected?: string[],
412
414
  ) {
413
415
  const tname = resolved.name
414
416
  const tfolder = resolved.folder
@@ -442,14 +444,15 @@ function checkTarget(
442
444
  },
443
445
  ]
444
446
 
445
- // The feature set `target add` would select right now. A project that
446
- // added its targets before feature trimming existed carries source for
447
- // features its model never declared — expected here as STALE, which is
448
- // exactly what it is.
447
+ // The feature set `target add` would select right now, plus any the CALLER
448
+ // is acting on (`selected`, see COMMENT-NOTES.md). A project that added its
449
+ // targets before feature trimming existed carries source for features its
450
+ // model never declared — expected here as STALE, which is what it is.
449
451
  const featuremodel: any = model?.main?.[KIT]?.feature ?? {}
450
452
  const features = Array.from(new Set([
451
453
  'test',
452
454
  ...Object.keys(featuremodel).filter((n: string) => false !== featuremodel[n]?.active),
455
+ ...(selected ?? []),
453
456
  ]))
454
457
 
455
458
  // `folder` and `model` matter: the trim catalogue is resolved consumer-side
@@ -39,6 +39,7 @@ function cmpBase(): string {
39
39
 
40
40
  const CMD_MAP: any = Object.assign(Object.create(null), {
41
41
  add: cmd_edition_add,
42
+ remove: cmd_edition_remove,
42
43
  })
43
44
 
44
45
 
@@ -65,6 +66,13 @@ async function cmd_edition_add(
65
66
  }
66
67
 
67
68
 
69
+ async function cmd_edition_remove(
70
+ args: string[], actx: ActionContext,
71
+ ): Promise<ActionResult> {
72
+ return require('./remove').kind_remove('edition', parseAddNames(args), actx)
73
+ }
74
+
75
+
68
76
  async function edition_add(
69
77
  edition: string[], actx: ActionContext,
70
78
  ): Promise<ActionResult> {
@@ -42,7 +42,8 @@ import {
42
42
 
43
43
 
44
44
  const CMD_MAP: any = {
45
- add: cmd_feature_add
45
+ add: cmd_feature_add,
46
+ remove: cmd_feature_remove,
46
47
  }
47
48
 
48
49
  const BASE = 'node_modules/@voxgig/sdkgen'
@@ -74,6 +75,11 @@ async function cmd_feature_add(args: string[], actx: ActionContext): Promise<Act
74
75
  }
75
76
 
76
77
 
78
+ async function cmd_feature_remove(args: string[], actx: ActionContext): Promise<ActionResult> {
79
+ return require('./remove').kind_remove('feature', parseAddNames(args), actx)
80
+ }
81
+
82
+
77
83
  async function feature_add(features: string[], actx: ActionContext): Promise<ActionResult> {
78
84
 
79
85
  // Reuse the caller's Jostraca instance so feature generation honours the