@voxgig/sdkgen 4.23.0 → 4.24.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 (163) 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/cmp/Deploy.js +9 -6
  15. package/dist/cmp/Deploy.js.map +1 -1
  16. package/dist/helpers/semver.js +3 -1
  17. package/dist/helpers/semver.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/package.json +2 -2
  20. package/project/.sdk/tm/c/core/context.c +3 -1
  21. package/project/.sdk/tm/c/feature/paging.c +37 -3
  22. package/project/.sdk/tm/c/feature/test.c +51 -27
  23. package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
  24. package/project/.sdk/tm/c/utility/transform_request.c +25 -3
  25. package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
  26. package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
  27. package/project/.sdk/tm/cpp/core/types.hpp +1 -1
  28. package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
  29. package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
  30. package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
  31. package/project/.sdk/tm/csharp/core/Context.cs +1 -1
  32. package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
  33. package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
  34. package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
  35. package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
  36. package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
  37. package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
  38. package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
  39. package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
  40. package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
  41. package/project/.sdk/tm/go/core/context.go +13 -13
  42. package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
  43. package/project/.sdk/tm/go/feature/test_feature.go +3 -15
  44. package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
  45. package/project/.sdk/tm/go/test/feature_test.go +54 -0
  46. package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
  47. package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
  48. package/project/.sdk/tm/go/utility/fetcher.go +41 -20
  49. package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
  50. package/project/.sdk/tm/go/utility/transform_request.go +22 -3
  51. package/project/.sdk/tm/java/core/Context.java +2 -1
  52. package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
  53. package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
  54. package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
  55. package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
  56. package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
  57. package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
  58. package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
  59. package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
  60. package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
  61. package/project/.sdk/tm/js/src/Context.js +7 -2
  62. package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
  63. package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
  64. package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
  65. package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
  66. package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
  67. package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
  68. package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
  69. package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
  70. package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
  71. package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
  72. package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
  73. package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
  74. package/project/.sdk/tm/lua/core/context.lua +8 -1
  75. package/project/.sdk/tm/lua/core/control.lua +3 -0
  76. package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
  77. package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
  78. package/project/.sdk/tm/lua/test/omni.lua +8 -1
  79. package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
  80. package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
  81. package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
  82. package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
  83. package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
  84. package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
  85. package/project/.sdk/tm/perl/core/context.pm +2 -1
  86. package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
  87. package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
  88. package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
  89. package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
  90. package/project/.sdk/tm/perl/t/omni.pm +7 -1
  91. package/project/.sdk/tm/perl/t/pipeline.t +124 -27
  92. package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
  93. package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
  94. package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
  95. package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
  96. package/project/.sdk/tm/php/core/Context.php +5 -1
  97. package/project/.sdk/tm/php/core/Control.php +5 -0
  98. package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
  99. package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
  100. package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
  101. package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
  102. package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
  103. package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
  104. package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
  105. package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
  106. package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
  107. package/project/.sdk/tm/py/pkg/core/context.py +2 -1
  108. package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
  109. package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
  110. package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
  111. package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
  112. package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
  113. package/project/.sdk/tm/py/test/test_feature.py +27 -0
  114. package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
  115. package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
  116. package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
  117. package/project/.sdk/tm/rb/core/context.rb +1 -1
  118. package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
  119. package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
  120. package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
  121. package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
  122. package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
  123. package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
  124. package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
  125. package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
  126. package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
  127. package/project/.sdk/tm/rust/core/context.rs +2 -6
  128. package/project/.sdk/tm/rust/feature/paging.rs +44 -3
  129. package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
  130. package/project/.sdk/tm/rust/feature/test.rs +66 -33
  131. package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
  132. package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
  133. package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
  134. package/project/.sdk/tm/scala/core/Context.scala +2 -1
  135. package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
  136. package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
  137. package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
  138. package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
  139. package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
  140. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
  141. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
  142. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
  143. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
  144. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
  145. package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
  146. package/project/.sdk/tm/ts/src/Context.ts +7 -2
  147. package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
  148. package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
  149. package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
  150. package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
  151. package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
  152. package/project/.sdk/tm/zig/core/context.zig +1 -1
  153. package/project/.sdk/tm/zig/core/utility.zig +19 -5
  154. package/project/.sdk/tm/zig/feature/paging.zig +34 -4
  155. package/project/.sdk/tm/zig/feature/test.zig +54 -26
  156. package/project/sdkgen-package.json +1 -1
  157. package/src/action/doctor.ts +12 -9
  158. package/src/action/edition.ts +8 -0
  159. package/src/action/feature.ts +7 -1
  160. package/src/action/remove.ts +492 -0
  161. package/src/action/target.ts +8 -1
  162. package/src/cmp/Deploy.ts +11 -6
  163. package/src/helpers/semver.ts +3 -1
@@ -317,6 +317,11 @@ describe('pipeline:feature order', () => {
317
317
  })
318
318
 
319
319
 
320
+ // A cookie credential as prepareAuth writes it: `<scheme>=K` for the probe
321
+ // key, with no scheme prefix and nothing else in the bag.
322
+ const COOKIE_PAIR = /^[^=;]+=K$/
323
+
324
+
320
325
  describe('pipeline:prepareAuth', () => {
321
326
 
322
327
  function authCtx(options: any, spec: any) {
@@ -327,19 +332,42 @@ describe('pipeline:prepareAuth', () => {
327
332
  return { headers: {} as any, query: {} as any }
328
333
  }
329
334
 
335
+ // `basic: false` is explicit: an HTTP Basic API's generated config carries
336
+ // `auth.basic: true`, and a client that merges it in takes a branch needing
337
+ // a secret as well. With none supplied that branch writes nothing, which
338
+ // the probe below would then read as a public API.
339
+ function auth(prefix: string): any {
340
+ return { prefix, basic: false }
341
+ }
342
+
330
343
  // Run prepareAuth with both containers present and see which one the
331
344
  // generated utility writes to, and under what name. Null means this SDK
332
- // places no credential at all — a public API — which is a legitimate
333
- // shape, and the tests below assert exactly that instead.
334
- const CRED = (() => {
335
- const ctx = authCtx({ apikey: 'K', auth: { prefix: 'Bearer' } }, bags())
345
+ // places no credential at all — a public API — which the cases below then
346
+ // assert instead.
347
+
348
+ // `pair` is the `<scheme>=` lead-in of a COOKIE credential, which rides the
349
+ // header bag under the key `cookie` rather than taking a header of its own.
350
+ function probe(options: any) {
351
+ const ctx = authCtx(options, bags())
336
352
  ;(stdutil as any).prepareAuth(ctx)
337
353
  for (const where of ['headers', 'query'] as const) {
338
354
  const name = Object.keys(ctx.spec[where] as any)[0]
339
- if (null != name) return { where, name, value: ctx.spec[where][name] }
355
+ if (null == name) continue
356
+ const value = ctx.spec[where][name]
357
+ const pair = COOKIE_PAIR.test(String(value)) && 'cookie' === name && 'headers' === where
358
+ ? String(value).slice(0, -1)
359
+ : ''
360
+ return { where, name, value, pair }
340
361
  }
341
362
  return null
342
- })()
363
+ }
364
+
365
+ const CRED = probe({ apikey: 'K', auth: auth('Bearer') })
366
+
367
+ // Every credential this SDK could possibly place: both credentials and
368
+ // Basic switched on, so whichever branch the API has, something lands
369
+ // unless the API is public.
370
+ const ANY = probe({ apikey: 'K', secret: 'S', auth: { prefix: 'Bearer', basic: true } })
343
371
 
344
372
  function placed(options: any, seed?: any) {
345
373
  const spec: any = bags()
@@ -350,27 +378,40 @@ describe('pipeline:prepareAuth', () => {
350
378
  }
351
379
 
352
380
  test('guards a missing spec', () => {
353
- strictEqual((stdutil as any).prepareAuth(authCtx({ auth: { prefix: '' }, apikey: 'K' }, null)).code, 'auth_no_spec')
381
+ strictEqual((stdutil as any).prepareAuth(authCtx({ auth: auth(''), apikey: 'K' }, null)).code, 'auth_no_spec')
382
+ })
383
+
384
+ // Without this the cases below cannot fail for an SDK whose credential the
385
+ // probe misses: every one of them takes the public-API path instead.
386
+ test('the probe finds the credential this SDK places', () => {
387
+ strictEqual(null != CRED, null != ANY)
354
388
  })
355
389
 
356
390
  test('the apikey is placed where this API puts it', () => {
357
391
  if (null == CRED) {
358
392
  // A public API places nothing, and that is the whole assertion.
359
- strictEqual(placed({ apikey: 'K', auth: { prefix: 'Bearer' } }), undefined)
393
+ strictEqual(placed({ apikey: 'K', auth: auth('Bearer') }), undefined)
360
394
  return
361
395
  }
362
396
  ok('headers' === CRED.where || 'query' === CRED.where)
397
+ if ('' !== CRED.pair) {
398
+ // A cookie credential is a `<scheme>=<key>` pair, and the scheme name
399
+ // leaves no room for the option's prefix.
400
+ ok(COOKIE_PAIR.test(String(CRED.value)), String(CRED.value))
401
+ return
402
+ }
363
403
  // A header credential is prefix-joined; a query credential is the raw
364
404
  // key, because a query parameter has nowhere to put a scheme name.
365
405
  strictEqual(CRED.value, 'headers' === CRED.where ? 'Bearer K' : 'K')
366
406
  })
367
407
 
368
408
  test('a raw apikey (empty prefix) goes in as-is', () => {
369
- strictEqual(placed({ apikey: 'K', auth: { prefix: '' } }), null == CRED ? undefined : 'K')
409
+ const raw = null == CRED ? undefined : CRED.pair + 'K'
410
+ strictEqual(placed({ apikey: 'K', auth: auth('') }), raw)
370
411
  })
371
412
 
372
413
  test('an empty apikey drops the credential', () => {
373
- strictEqual(placed({ apikey: '', auth: { prefix: 'Bearer' } }, 'stale'), undefined)
414
+ strictEqual(placed({ apikey: '', auth: auth('Bearer') }, 'stale'), undefined)
374
415
  })
375
416
 
376
417
  test('a public API (no auth block) drops the credential', () => {
@@ -378,7 +419,7 @@ describe('pipeline:prepareAuth', () => {
378
419
  })
379
420
 
380
421
  test('a missing apikey option drops the credential', () => {
381
- strictEqual(placed({ auth: { prefix: 'Bearer' } }, 'stale'), undefined)
422
+ strictEqual(placed({ auth: auth('Bearer') }, 'stale'), undefined)
382
423
  })
383
424
  })
384
425
 
@@ -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.1",
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