@voxgig/sdkgen 4.6.0 → 4.8.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 (144) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Feature.js +5 -0
  3. package/dist/cmp/Feature.js.map +1 -1
  4. package/dist/cmp/TestControl.d.ts +4 -0
  5. package/dist/cmp/TestControl.js +51 -0
  6. package/dist/cmp/TestControl.js.map +1 -0
  7. package/dist/helpers/featureSource.d.ts +3 -1
  8. package/dist/helpers/featureSource.js +105 -0
  9. package/dist/helpers/featureSource.js.map +1 -1
  10. package/dist/helpers/opShape.js +8 -11
  11. package/dist/helpers/opShape.js.map +1 -1
  12. package/dist/helpers/pointPath.d.ts +10 -0
  13. package/dist/helpers/pointPath.js +54 -0
  14. package/dist/helpers/pointPath.js.map +1 -0
  15. package/dist/sdkgen.d.ts +6 -3
  16. package/dist/sdkgen.js +14 -4
  17. package/dist/sdkgen.js.map +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/utility.d.ts +2 -1
  20. package/dist/utility.js +78 -1
  21. package/dist/utility.js.map +1 -1
  22. package/model/sdkgen.aon +58 -0
  23. package/package.json +2 -2
  24. package/project/.sdk/model/feature/secrets.aon +82 -0
  25. package/project/.sdk/src/cmp/c/Config_c.ts +7 -8
  26. package/project/.sdk/src/cmp/c/TestDirect_c.ts +4 -3
  27. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  28. package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +4 -3
  29. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  30. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  31. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +34 -9
  32. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  33. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  34. package/project/.sdk/src/cmp/dart/Config_dart.ts +7 -8
  35. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  36. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +28 -10
  37. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  38. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  39. package/project/.sdk/src/cmp/elixir/Config_elixir.ts +9 -8
  40. package/project/.sdk/src/cmp/go/Config_go.ts +1 -7
  41. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  42. package/project/.sdk/src/cmp/go/TestDirect_go.ts +5 -4
  43. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  44. package/project/.sdk/src/cmp/java/Config_java.ts +7 -7
  45. package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
  46. package/project/.sdk/src/cmp/java/TestDirect_java.ts +5 -4
  47. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  48. package/project/.sdk/src/cmp/js/Config_js.ts +7 -8
  49. package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
  50. package/project/.sdk/src/cmp/js/TestDirect_js.ts +34 -8
  51. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  52. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  53. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  54. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  55. package/project/.sdk/src/cmp/kotlin/Config_kotlin.ts +7 -7
  56. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  57. package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +5 -4
  58. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  59. package/project/.sdk/src/cmp/lean/Config_lean.ts +9 -8
  60. package/project/.sdk/src/cmp/lua/Config_lua.ts +1 -7
  61. package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
  62. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +32 -7
  63. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  64. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  65. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  66. package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
  67. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +29 -7
  68. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  69. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  70. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  71. package/project/.sdk/src/cmp/php/Config_php.ts +1 -7
  72. package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
  73. package/project/.sdk/src/cmp/php/TestDirect_php.ts +28 -8
  74. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  75. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  76. package/project/.sdk/src/cmp/py/Config_py.ts +1 -7
  77. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  78. package/project/.sdk/src/cmp/py/TestDirect_py.ts +5 -4
  79. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  80. package/project/.sdk/src/cmp/rb/Config_rb.ts +1 -7
  81. package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
  82. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +28 -8
  83. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  84. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  85. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  86. package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
  87. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +35 -7
  88. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  89. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  90. package/project/.sdk/src/cmp/scala/Config_scala.ts +9 -7
  91. package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
  92. package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +4 -3
  93. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  94. package/project/.sdk/src/cmp/seneca-provider/Extras_seneca-provider.ts +3 -1
  95. package/project/.sdk/src/cmp/seneca-provider/Main_seneca-provider.ts +9 -5
  96. package/project/.sdk/src/cmp/ts/Config_ts.ts +57 -16
  97. package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
  98. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -7
  99. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
  100. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  101. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  102. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  103. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  104. package/project/.sdk/tm/js/test/utility.js +91 -1
  105. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  106. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  107. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  108. package/project/.sdk/tm/php/test/Runner.php +36 -0
  109. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  110. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  111. package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
  112. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
  113. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
  114. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
  115. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  116. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  117. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  118. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  119. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  120. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  121. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  122. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  123. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  124. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  125. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  126. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  127. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  128. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  129. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  130. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  131. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
  132. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
  133. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
  134. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
  135. package/project/.sdk/tm/zig/core/utility.zig +1 -1
  136. package/project/sdkgen-package.json +1 -1
  137. package/src/cmp/Feature.ts +5 -0
  138. package/src/cmp/TestControl.ts +64 -0
  139. package/src/helpers/featureSource.ts +130 -0
  140. package/src/helpers/opShape.ts +9 -14
  141. package/src/helpers/pointPath.ts +88 -0
  142. package/src/sdkgen.ts +18 -0
  143. package/src/utility.ts +92 -1
  144. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
@@ -260,6 +260,134 @@ function srcFeatureExcludes(model: any): RegExp[] {
260
260
  }
261
261
 
262
262
 
263
+ // The same rule as srcFeatureExcludes, ONE LEVEL DEEPER: a feature's
264
+ // declared-but-inactive PLUGINS.
265
+ //
266
+ // A plugin is a feature's optional part — the `secrets` feature over
267
+ // sekreto has one per provider kind, and each carries its own platform
268
+ // cost (node:crypto for AWS request signing, fetch for seven HTTP vault
269
+ // clients). A project whose chain is `[dotenv, env]` should carry neither,
270
+ // and before the plugin trim it carried both, because every kind was
271
+ // reachable from a single import.
272
+ //
273
+ // Layout is `src/feature/<feature>/plugin/<plugin>/`, so a plugin's tree
274
+ // is excluded exactly as a feature's is. Only declared-but-inactive
275
+ // plugins are excluded: a directory the model never mentions is left
276
+ // alone, matching srcFeatureExcludes' treatment of `base`.
277
+ //
278
+ // Note this trims a plugin belonging to an ACTIVE feature. An inactive
279
+ // feature's whole tree — plugins included — is already gone via
280
+ // srcFeatureExcludes, so walking its plugins here would be redundant, and
281
+ // asking the model for them would fail on the `only_active` filter anyway.
282
+ // One feature's inactive-plugin patterns.
283
+ //
284
+ // Separate from pluginExcludes(model) because the two COPIES that need it
285
+ // see different things: Main copies the whole target tree and has the
286
+ // model, while the Feature component copies one feature's tree and has
287
+ // only that feature. Feature's copy is the one that actually matters —
288
+ // it runs for every ACTIVE feature, so without this an active feature's
289
+ // plugins were all copied regardless, and Main's exclude never saw them.
290
+ function pluginExcludesFor(model: any, fname: string): RegExp[] {
291
+ if (null == model || null == fname) {
292
+ return []
293
+ }
294
+
295
+ // `only_active: false`, and this is the whole subtlety. The feature
296
+ // object a component is handed has ALREADY been filtered, so its
297
+ // `plugin` map contains only the ACTIVE plugins — and a function looking
298
+ // there for something inactive finds nothing and excludes nothing. That
299
+ // is exactly the trap srcFeatureExcludes documents, hit again one level
300
+ // down: the first cut of this read the filtered feature and silently
301
+ // trimmed nothing while every test passed.
302
+ const plugins = getModelPath(model,
303
+ `main.${KIT}.feature.${fname}.plugin`,
304
+ { required: false, only_active: false }) || {}
305
+
306
+ const out: RegExp[] = []
307
+ const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
308
+
309
+ for (const pname of Object.keys(plugins).sort()) {
310
+ const plugin = plugins[pname]
311
+ if (false !== plugin?.active) continue
312
+
313
+ const declared = plugin.path || []
314
+
315
+ if (0 < declared.length) {
316
+ for (const one of declared) {
317
+ // FEATURE-RELATIVE, because jostraca tests a Copy's candidates
318
+ // against the path RELATIVE TO THAT COPY'S ROOT — `sekreto/
319
+ // provider/aws.ts`, not `src/feature/secrets/sekreto/provider/
320
+ // aws.ts`. The Feature component's Copy is rooted at
321
+ // `tm/<target>/src/feature/<feature>`, so a full declared path
322
+ // never matches and the trim silently does nothing.
323
+ //
324
+ // Worth stating because it is invisible: an exclude that matches
325
+ // nothing is indistinguishable from no exclude at all, and both
326
+ // the patterns and the model were correct while nothing was
327
+ // trimmed. Main's copy IS rooted at the target root, which is why
328
+ // pluginExcludes below keeps the full path.
329
+ const rel = String(one).replace(
330
+ new RegExp('^src/feature/' + esc(fname) + '/'), '')
331
+ const pat = esc(rel)
332
+ out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
333
+ (/\/$/.test(rel) ? '/' : '$')))
334
+ }
335
+ continue
336
+ }
337
+
338
+ out.push(new RegExp('(^|/)plugin/' + esc(pname) + '/'))
339
+ }
340
+
341
+ return out
342
+ }
343
+
344
+
345
+ function pluginExcludes(model: any): RegExp[] {
346
+ const active = getModelPath(model, `main.${KIT}.feature`,
347
+ { required: false }) || {}
348
+
349
+ const out: RegExp[] = []
350
+ const esc = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
351
+
352
+ for (const fname of Object.keys(active)) {
353
+ const all = getModelPath(model,
354
+ `main.${KIT}.feature.${fname}.plugin`,
355
+ { required: false, only_active: false }) || {}
356
+ const on = getModelPath(model,
357
+ `main.${KIT}.feature.${fname}.plugin`,
358
+ { required: false }) || {}
359
+
360
+ for (const pname of Object.keys(all)) {
361
+ if (null != on[pname]) continue
362
+
363
+ // DECLARED paths first. A plugin's files are often not free to
364
+ // move — sekreto's provider modules are vendored, and both the
365
+ // vendoring guard and their own relative imports pin them at
366
+ // upstream's directory depth — so a plugin says which paths it
367
+ // owns rather than being assumed to own a directory.
368
+ const declared = all[pname].path || []
369
+
370
+ if (0 < declared.length) {
371
+ for (const one of declared) {
372
+ const pat = esc(String(one))
373
+ // A trailing slash means a folder and everything under it;
374
+ // anything else matches that path exactly, as featureExcludes
375
+ // does for a file source.
376
+ out.push(new RegExp('(^|/)' + pat.replace(/\\\/$/, '') +
377
+ (/\/$/.test(String(one)) ? '/' : '$')))
378
+ }
379
+ continue
380
+ }
381
+
382
+ out.push(new RegExp(
383
+ '(^|/)src/feature/' + esc(fname) + '/plugin/' + esc(pname) + '/'))
384
+ }
385
+ }
386
+
387
+ return out
388
+ }
389
+
390
+
263
391
  export type {
264
392
  FeatureSource,
265
393
  FeatureEntry,
@@ -275,4 +403,6 @@ export {
275
403
  featureExcludes,
276
404
  fullsetExcludes,
277
405
  srcFeatureExcludes,
406
+ pluginExcludes,
407
+ pluginExcludesFor,
278
408
  }
@@ -29,6 +29,8 @@
29
29
  import { each, names } from 'jostraca'
30
30
  import { KIT, getModelPath } from '@voxgig/apidef'
31
31
 
32
+ import { pointSegments, pointTerminalParam, pointPathKey } from './pointPath'
33
+
32
34
 
33
35
  // THE entity collection for a model — resolved once, with a stable identity.
34
36
  //
@@ -217,25 +219,19 @@ function entityPath(entity: any): string {
217
219
  // no point's `select.exist` matches. It cannot be shared with them — a
218
220
  // template ships standalone, outside this package — so it is written twice on
219
221
  // purpose, and both sides must move together.
220
- function terminalParam(point: any): boolean {
221
- const parts: any[] = (point && point.parts) || []
222
- const last = 0 < parts.length ? parts[parts.length - 1] : ''
223
- return 'string' === typeof last && last.startsWith('{')
224
- }
225
-
226
-
227
222
  function ownPoint(points: any[]): any {
228
223
  let best = points[0]
229
224
 
230
225
  for (const pt of points) {
231
- if (null == pt || null == pt.parts || null == best || null == best.parts) {
226
+ if (null == pt || null == pt.segments || null == best || null == best.segments) {
232
227
  continue
233
228
  }
234
229
 
235
- const ptterm = terminalParam(pt)
236
- const bestterm = terminalParam(best)
230
+ const ptterm = pointTerminalParam(pt)
231
+ const bestterm = pointTerminalParam(best)
237
232
 
238
- if (ptterm !== bestterm ? ptterm : pt.parts.length < best.parts.length) {
233
+ if (ptterm !== bestterm ?
234
+ ptterm : pointSegments(pt).length < pointSegments(best).length) {
239
235
  best = pt
240
236
  }
241
237
  }
@@ -248,10 +244,9 @@ function ownPoint(points: any[]): any {
248
244
  // selectors on one endpoint (the same path, chosen by different query
249
245
  // params), not cross-references to different resources.
250
246
  function samePath(points: any[]): boolean {
251
- const first = ((points[0] && points[0].parts) || []).join('/')
247
+ const first = pointPathKey(points[0])
252
248
 
253
- return points.every((pt: any) =>
254
- first === ((pt && pt.parts) || []).join('/'))
249
+ return points.every((pt: any) => first === pointPathKey(pt))
255
250
  }
256
251
 
257
252
 
@@ -0,0 +1,88 @@
1
+ /* Copyright (c) 2024-2025 Voxgig, MIT License */
2
+
3
+ // A point's path, as apidef resolves it.
4
+ //
5
+ // apidef ADR-003: the model carries the path as a typed segment vector —
6
+ // `[{ lit: 'element' }, { var: 'id' }]` — and no brace-templated string. A
7
+ // `var` names one of the point's `args.params` directly, so a consumer walks
8
+ // the vector instead of parsing anything.
9
+ //
10
+ // The generated SDK runtimes still speak the older braced-string form
11
+ // (`['element', '{id}']`), which this file reconstructs in ONE place. Every
12
+ // generation-time consumer and the embedded config go through here, so when
13
+ // the runtimes move onto segments there is a single call site to retire —
14
+ // rather than the twenty-odd hand-written brace parsers ADR-003 is about.
15
+
16
+
17
+ type PathSegment = {
18
+ lit?: string
19
+ var?: string
20
+ }
21
+
22
+
23
+ // The segment vector, defensively. GraphQL points address the single endpoint
24
+ // and carry no path, so an empty vector is normal, not a fault.
25
+ function pointSegments(point: any): PathSegment[] {
26
+ const segments = point && point.segments
27
+ return Array.isArray(segments) ? segments : []
28
+ }
29
+
30
+
31
+ // The braced-string form the SDK runtimes still consume. A `lit` is emitted
32
+ // verbatim — including one that contains braces, which apidef leaves literal
33
+ // (a compound element like `{a}.{b}` names no single parameter). That is the
34
+ // lossiness ADR-003 removed from the model, and it survives here only because
35
+ // this is the OLD representation being reconstructed on the way out.
36
+ function pointParts(point: any): string[] {
37
+ return pointSegments(point).map((seg: PathSegment) =>
38
+ null == seg.var ? String(seg.lit ?? '') : '{' + seg.var + '}')
39
+ }
40
+
41
+
42
+ // Does the path end in a parameter?
43
+ //
44
+ // DELIBERATELY asked of the reconstructed part, not of the vector, even
45
+ // though the vector states it directly and more accurately.
46
+ //
47
+ // The same rule runs at RUNTIME, in all 21 languages' makePoint template
48
+ // (`0 === last.indexOf('{')` over `parts`), to pick a fallback route when no
49
+ // point's `select.exist` matches. Those templates ship standalone, outside
50
+ // this package, so the rule is written twice on purpose and BOTH SIDES MUST
51
+ // MOVE TOGETHER — see the note in opShape.ts and in each template.
52
+ //
53
+ // Reading the vector here would break that. A path ending in a LITERAL that
54
+ // contains braces (`/reports/{id}.json`) is not a terminal parameter by the
55
+ // vector, but every runtime still says it is, because from the reconstructed
56
+ // string it cannot tell. Generation-time `ownPoint` would then pick a
57
+ // different route than the SDK picks at request time — for the same model.
58
+ //
59
+ // So this stays bug-compatible with the runtimes until they move onto
60
+ // segments, at which point this becomes `null != last.var` and all 21 change
61
+ // with it.
62
+ function pointTerminalParam(point: any): boolean {
63
+ const parts = pointParts(point)
64
+ const last = 0 < parts.length ? parts[parts.length - 1] : ''
65
+ return 0 === last.indexOf('{')
66
+ }
67
+
68
+
69
+ // Do two points describe the same route? Compares the vectors, so a literal
70
+ // containing braces cannot be mistaken for a parameter of the same spelling.
71
+ function pointPathKey(point: any): string {
72
+ return pointSegments(point)
73
+ .map((seg: PathSegment) =>
74
+ null == seg.var ? 'l:' + String(seg.lit ?? '') : 'v:' + seg.var)
75
+ .join('/')
76
+ }
77
+
78
+
79
+ export type {
80
+ PathSegment,
81
+ }
82
+
83
+ export {
84
+ pointSegments,
85
+ pointParts,
86
+ pointTerminalParam,
87
+ pointPathKey,
88
+ }
package/src/sdkgen.ts CHANGED
@@ -40,6 +40,7 @@ import { License } from './cmp/License'
40
40
  import { Security } from './cmp/Security'
41
41
  import { Changelog } from './cmp/Changelog'
42
42
  import { Test } from './cmp/Test'
43
+ import { TestControl, TEST_CONTROL_EXCLUDE } from './cmp/TestControl'
43
44
  import { ReadmeInstall } from './cmp/ReadmeInstall'
44
45
  import { ReadmeQuick } from './cmp/ReadmeQuick'
45
46
  import { ReadmeErrors } from './cmp/ReadmeErrors'
@@ -66,6 +67,9 @@ import { serverVariables, hasServerVariables, serverVarEnv } from './helpers/ser
66
67
  import { primaryOpCall, idLiteral, matchArg, dataArg, litFor } from './helpers/opExample'
67
68
  import type { ExampleLang } from './helpers/opExample'
68
69
  import { liveStrict } from './helpers/testPolicy'
70
+ import { pointSegments, pointParts, pointTerminalParam, pointPathKey }
71
+ from './helpers/pointPath'
72
+ import type { PathSegment } from './helpers/pointPath'
69
73
  import {
70
74
  featureOf,
71
75
  availableFeatures,
@@ -73,6 +77,8 @@ import {
73
77
  featureExcludes,
74
78
  fullsetExcludes,
75
79
  srcFeatureExcludes,
80
+ pluginExcludes,
81
+ pluginExcludesFor,
76
82
  } from './helpers/featureSource'
77
83
  import type { FeatureSource } from './helpers/featureSource'
78
84
  import { stationLibrary } from './helpers/station'
@@ -1011,6 +1017,7 @@ export type {
1011
1017
  SdkGenOptions,
1012
1018
  ExampleLang,
1013
1019
  DepEntry,
1020
+ PathSegment,
1014
1021
  FeatureSource,
1015
1022
  DoctorReport,
1016
1023
  RegisterOptions,
@@ -1072,6 +1079,8 @@ export {
1072
1079
  Entity,
1073
1080
  Feature,
1074
1081
  Test,
1082
+ TestControl,
1083
+ TEST_CONTROL_EXCLUDE,
1075
1084
  Readme,
1076
1085
  ReadmeTop,
1077
1086
  AgentGuideTop,
@@ -1114,6 +1123,13 @@ export {
1114
1123
  buildIdNames,
1115
1124
  getMatchEntries,
1116
1125
  collectDeps,
1126
+
1127
+ // apidef ADR-003: the model carries typed path segments, not braced
1128
+ // strings. Scaffold components read the path through these.
1129
+ pointSegments,
1130
+ pointParts,
1131
+ pointTerminalParam,
1132
+ pointPathKey,
1117
1133
  canonToType,
1118
1134
  canonToDtype,
1119
1135
  canonKey,
@@ -1167,6 +1183,8 @@ export {
1167
1183
  featureExcludes,
1168
1184
  fullsetExcludes,
1169
1185
  srcFeatureExcludes,
1186
+ pluginExcludes,
1187
+ pluginExcludesFor,
1170
1188
  stationLibrary,
1171
1189
 
1172
1190
  featureApplies,
package/src/utility.ts CHANGED
@@ -8,6 +8,7 @@ import { KIT, getModelPath } from '@voxgig/apidef'
8
8
  import { targetFeatures } from './helpers/applicability'
9
9
 
10
10
  import { serverVariables } from './helpers/serverVars'
11
+ import { pointParts } from './helpers/pointPath'
11
12
  import { packageVersion } from './helpers/packageMeta'
12
13
 
13
14
 
@@ -82,6 +83,34 @@ function isHttpBasicAuth(model: any): boolean {
82
83
  }
83
84
 
84
85
 
86
+ // The API's ACCESS-TOKEN EXCHANGE, as apidef recorded it from the spec
87
+ // (main.kit.info.security.exchange): where the token endpoint lives, and the
88
+ // field names it sends and answers with. Null when the spec describes none,
89
+ // which is the common case.
90
+ //
91
+ // These are FACTS ABOUT THE API, so they belong in the generated config
92
+ // rather than in a project's hand-written model: an SDK whose spec says the
93
+ // exchange is at `auth/token` should not need to be told so again. A feature
94
+ // opts in to receiving them with `spec: { authexchange: '<options-key>' }`;
95
+ // configDefinition does the overlay.
96
+ //
97
+ // Deliberately NOT included: `active`. Whether a client performs the
98
+ // exchange is the project's decision (some resolve a static credential
99
+ // through the same feature on an API that also has a token endpoint), so
100
+ // apidef records only what the exchange IS, never that it should run.
101
+ function resolveAuthExchange(model: any): Record<string, any> | null {
102
+ const security = getModelPath(model, `main.${KIT}.info.security`,
103
+ { only_active: false, required: false })
104
+
105
+ const exchange = security?.exchange
106
+ if (null == exchange || 'object' !== typeof exchange) {
107
+ return null
108
+ }
109
+
110
+ return exchange
111
+ }
112
+
113
+
85
114
  function requirePath(ctx$: any, path: string, flags?: { ignore?: boolean }): any {
86
115
  const fullpath = resolvePath(ctx$, path)
87
116
  const ignore = null == flags?.ignore ? false : flags.ignore
@@ -118,6 +147,7 @@ export {
118
147
  requirePath,
119
148
  isAuthActive,
120
149
  resolveAuthPrefix,
150
+ resolveAuthExchange,
121
151
  isHttpBasicAuth,
122
152
  SdkGenError,
123
153
  CONFIG_DATA_THRESHOLD,
@@ -301,6 +331,15 @@ function rawStringLiteral(s: string): string {
301
331
  }
302
332
 
303
333
 
334
+ // The closed vocabulary of spec-derived facts a feature may ask for by
335
+ // declaring `spec: { <fact>: <options-key> }`. Closed, and resolved through
336
+ // one function each, so a feature cannot reach arbitrarily into the model
337
+ // and a fact's shape is defined in exactly one place.
338
+ const SPEC_FACTS: Record<string, (model: any) => any> = {
339
+ authexchange: resolveAuthExchange,
340
+ }
341
+
342
+
304
343
  // THE CANONICAL CONFIG OBJECT, and the JSON the threshold is measured on.
305
344
  //
306
345
  // Every target builds its config from this one function, so the literal a
@@ -312,6 +351,35 @@ function rawStringLiteral(s: string): string {
312
351
  //
313
352
  // Key order is `each`'s order, which is sorted, so the JSON is byte-stable
314
353
  // across runs exactly like the literal it replaces.
354
+ // The embedded config still speaks the braced-string path form
355
+ // (`['element', '{id}']`) that every generated runtime reads. apidef now
356
+ // emits the resolved vector instead (its ADR-003), so the old shape is
357
+ // reconstructed HERE — the single point at which a point reaches generated
358
+ // output — via the single reconstruction in helpers/pointPath.
359
+ //
360
+ // Both are written: `parts` for the runtimes as they stand, `segments`
361
+ // alongside it so a runtime can be moved over one language at a time. That
362
+ // duplication is deliberate and temporary, and it lives in GENERATED output,
363
+ // not in the model — which is what ADR-003 forbids. It ends when the last
364
+ // runtime reads segments and `parts` is dropped from this function.
365
+ function withPointParts(op: any): any {
366
+ if (null == op) {
367
+ return op
368
+ }
369
+
370
+ const out: any = {}
371
+ each(op, (o: any, opname: string) => {
372
+ out[opname] = null == o || null == o.points ? o : {
373
+ ...o,
374
+ points: each(o.points).map((pt: any) =>
375
+ null == pt ? pt : { ...pt, parts: pointParts(pt) }),
376
+ }
377
+ })
378
+
379
+ return out
380
+ }
381
+
382
+
315
383
  function configDefinition(model: any, targetname?: string): { def: any, json: string } {
316
384
  const entity = getModelPath(model, `main.${KIT}.entity`)
317
385
 
@@ -339,7 +407,7 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
339
407
  entityDefs[e.name] = clean({
340
408
  fields: e.fields,
341
409
  name: e.name,
342
- op: e.op,
410
+ op: withPointParts(e.op),
343
411
  relations: e.relations,
344
412
  }, true)
345
413
  entityStubs[e.name] = {}
@@ -361,6 +429,29 @@ function configDefinition(model: any, targetname?: string): { def: any, json: st
361
429
  if (null != f.transport && '' !== f.transport) {
362
430
  fdef.transport = String(f.transport)
363
431
  }
432
+
433
+ // SPEC-DERIVED OPTIONS. A feature declares `spec: { <fact>: <options
434
+ // key> }` to receive facts apidef recorded from the OpenAPI spec — the
435
+ // same declare-never-infer rule `needs` and `transport` follow, and for
436
+ // the same reason: the alternative is this function knowing feature
437
+ // names, which is exactly the coupling the generic feature loop exists
438
+ // to avoid.
439
+ //
440
+ // The fact overlays the feature's DECLARED DEFAULTS, and rightly: a
441
+ // default like `path: 'auth/token'` is a generic guess, while the spec
442
+ // states where this API's endpoint actually is. A project that needs
443
+ // something else still overrides at runtime through `options.feature`,
444
+ // which beats the embedded config either way.
445
+ for (const factname of Object.keys(f.spec || {}).sort()) {
446
+ const optkey = f.spec[factname]
447
+ const fact = SPEC_FACTS[factname]?.(model)
448
+ if (null == fact || null == optkey || '' === optkey) {
449
+ continue
450
+ }
451
+ fdef.options = { ...(fdef.options || {}) }
452
+ fdef.options[optkey] = { ...(fdef.options[optkey] || {}), ...fact }
453
+ }
454
+
364
455
  featureDefs[f.name] = fdef
365
456
  })
366
457